sign_crn.conditions

Conditions for CBE

We consider conditions for existence and uniqueness of positive complex-balanced equilibria (CBE) of (chemical) reaction networks. These conditions also describe injectivity and bijectivity of polynomial and exponential maps as seen in [MHR19].

In both cases, we apply the conditions to a pair of matrices.

Robustness of existence and uniqueness

We consider the following matrices:

sage: from sign_crn.conditions import *
sage: P = matrix([[1, 0, 1, 0], [0, 0, 0, 1]])
sage: P
[1 0 1 0]
[0 0 0 1]
sage: Pt = matrix([[1, 0, 1, 1], [0, 1, 0, -1]])
sage: Pt
[ 1  0  1  1]
[ 0  1  0 -1]

To study robustness, we consider a condition involving maximal minors:

sage: closure_condition(P, Pt)
True

There is also an equivalent condition using sign vectors:

sage: from sign_crn.conditions import closure_condition_sign_vectors
sage: closure_condition_sign_vectors(P, Pt)
True

Now, we consider an example involving parameters:

sage: var("a, b, c")
(a, b, c)
sage: P = matrix([[c, 1, c]])
sage: P
[c 1 c]
sage: Pt = matrix([[a, b, -1]])
sage: Pt
[ a  b -1]

We obtain the following condition on the variables:

sage: closure_condition(P, Pt) # random
[{-b > 0, c == 0},
 {-b < 0, c == 0},
 {-b > 0, c > 0, -a*c > 0},
 {-b < 0, c < 0, -a*c < 0}]

Thus, there are four possibilities to set the variables: From the first two sets of conditions, we see that the closure condition is satisfied if \(c\) is zero and \(b\) is nonzero. The closure condition is also satisfied if \(a\) and \(b\) are negative and \(c\) is positive or if \(a\) and \(b\) are positive and \(c\) is negative.

Uniqueness

We define the following matrices:

sage: P = matrix([[1, 1, 1]])
sage: Pt = matrix([[1, 0, 1]])

The package uses maximal minors to study uniqueness:

sage: uniqueness_condition(P, Pt)
True

Instead, we can consider the oriented matroids determined by these matrices:

sage: from sign_crn.conditions import uniqueness_condition_sign_vectors
sage: uniqueness_condition_sign_vectors(P, Pt)
True

Now, we consider another example:

sage: P = matrix([[1, 1, 1]])
sage: Pt = matrix([[1, -1, 1]])

The condition is violated:

sage: uniqueness_condition(P, Pt)
False

Finally, we consider an example with parameters \(a, b \in \mathbb{R}\):

sage: var("a, b")
(a, b)
sage: P = matrix([[1, 1, 1]])
sage: P
[1 1 1]
sage: Pt = matrix([[a, b, -1]])
sage: Pt
[ a  b -1]

Here, the function returns a system of inequalities:

sage: uniqueness_condition(P, Pt) # random order
[{-a >= 0, -b >= 0}]

Hence, the condition holds if and only if \(a, b \leq 0\).

Note that we cannot apply uniqueness_condition_sign_vectors() because of the parameters.

Existence and uniqueness

We consider the following matrices:

sage: P = matrix([[1, 0, 1, 0], [0, 1, 0, 1]])
sage: P
[1 0 1 0]
[0 1 0 1]
sage: Pt = matrix([[1, 0, 0, -1], [0, 1, 1, 1]])
sage: Pt
[ 1  0  0 -1]
[ 0  1  1  1]

The uniqueness condition is satisfied:

sage: uniqueness_condition(P, Pt)
True

We check the face condition:

sage: face_condition(P, Pt)
True

Finally, the nondegeneracy condition delivers:

sage: nondegeneracy_condition(P, Pt)
True

Let us consider another example by swapping the matrices. The uniqueness condition is symmetric in the two matrices and thus holds again:

sage: uniqueness_condition(Pt, P)
True

However, the face condition is violated:

sage: face_condition(Pt, P)
False

Now, we consider a map involving a parameter. (Compare with Example 20 of [MHR19]):

sage: var("a")
a
sage: assume(a > 0)
sage: P = matrix([[0, 0, 1, 1, -1, 0], [1, -1, 0, 0, 0, -1], [0, 0, 1, -1, 0, 0]])
sage: P
[ 0  0  1  1 -1  0]
[ 1 -1  0  0  0 -1]
[ 0  0  1 -1  0  0]
sage: Pt = matrix([[1, 1, 0, 0, -1, a], [1, -1, 0, 0, 0, 0], [0, 0, 1, -1, 0, 0]])
sage: Pt
[ 1  1  0  0 -1  a]
[ 1 -1  0  0  0  0]
[ 0  0  1 -1  0  0]

The first two conditions depend on the sign vectors corresponding to the rows of these matrices which are independent of the specific value for \(a\):

sage: uniqueness_condition(P, Pt)
True
sage: face_condition(P, Pt)
True

The nondegeneracy condition is satisfied for \(a \in (0, 1) \cup (1, 2)\). We demonstrate this for some values:

sage: nondegeneracy_condition(P, Pt(a=1/2))
True
sage: nondegeneracy_condition(P, Pt(a=3/2))
True

On the other hand, this condition does not hold if \(a \in \{1\} \cup [2, \infty)\):

sage: nondegeneracy_condition(P, Pt(a=1))
False
sage: nondegeneracy_condition(P, Pt(a=2))
False
sage: nondegeneracy_condition(P, Pt(a=3))
False

To certify the result, we call:

sage: nondegeneracy_condition(P, Pt(a=1), certify=True)
(False, (1, 1, 0, 0, -1, 1))

Hence, the positive support of the vector v = (1, 1, 0, 0, -1, 1) in row(Pt) can be covered by a sign vector (++000+) corresponding to ker(P).

Functions

closure_condition(matrix1, matrix2)

Closure condition using maximal minors.

closure_condition_sign_vectors(matrix1, matrix2)

Closure condition using sign vectors.

face_condition(kernel_matrix1, kernel_matrix2)

Face condition using nonnegative cocircuits.

nondegeneracy_condition(kernel_matrix1, ...)

Nondegeneracy condition.

uniqueness_condition(matrix1, matrix2)

Uniqueness condition using maximal minors.

uniqueness_condition_sign_vectors(matrix1, ...)

Uniqueness condition using sign vectors.

Classes

ConditionsCRN()

Class for conditions for chemical reaction networks.

class sign_crn.conditions.ConditionsCRN

Class for conditions for chemical reaction networks.

are_kernel_matrices_defined() bool

Return whether the kernel matrices are defined.

are_reduced_matrices_defined() bool

Return whether the reduced matrices are defined.

closure_condition() bool | list[set]

Closure condition using maximal minors.

OUTPUT: If the result depends on variables, a list of sets is returned. The condition holds if the inequalities in (at least) one of these sets are satisfied.

face_condition() bool

Face condition using nonnegative cocircuits.

This condition is about nonnegative cocircuits covering other nonnegative cocircuits.

static from_kernel_matrices(stoichiometric_kernel_matrix: matrix, kinetic_order_kernel_matrix: matrix) ConditionsCRN

Construct the object from kernel matrices.

The kernel matrices are the right kernel matrices of the stoichiometric matrix and the kinetic order matrix.

static from_reduced_matrices(reduced_stoichiometric_matrix: matrix, reduced_kinetic_order_matrix: matrix) ConditionsCRN

Construct the object from the stoichiometric matrix and the kinetic order matrix.

property kinetic_order_kernel_matrix: matrix

Return the kernel matrix of the kinetic order matrix.

nondegeneracy_condition(certify: bool = False) bool

Nondegeneracy condition.

This condition is about whether all positive equal components of a vector can be covered by nonnegative cocircuits.

If certify is true, a list is returned to certify the result. (See the examples.)

property reduced_kinetic_order_matrix: matrix

Return the reduced kinetic order matrix.

property reduced_stoichiometric_matrix: matrix

Return the reduced stoichiometric matrix.

property stoichiometric_kernel_matrix: matrix

Return the kernel matrix of the stoichiometric matrix.

uniqueness_condition() bool | list[set]

Uniqueness condition using maximal minors.

OUTPUT: If the result depends on variables, a list of sets is returned. The condition holds if the inequalities in exactly one of these sets are satisfied.

sign_crn.conditions.closure_condition(matrix1: matrix, matrix2: matrix) bool | list[set]

Closure condition using maximal minors.

OUTPUT: If the result depends on variables, a list of sets is returned. The condition holds if the inequalities in (at least) one of these sets are satisfied.

Note

The matrices need to have maximal rank and the same dimensions. Otherwise, a ValueError is raised.

sign_crn.conditions.closure_condition_sign_vectors(matrix1: matrix, matrix2: matrix) bool

Closure condition using sign vectors.

This condition is about whether a set of sign vectors is contained in the closure of another set of sign vectors, or equivalently, whether covectors are covered by topes.

Note

This implementation is inefficient and should not be used for large examples. Instead, use closure_condition().

sign_crn.conditions.face_condition(kernel_matrix1: matrix, kernel_matrix2: matrix) bool

Face condition using nonnegative cocircuits.

This condition is about nonnegative cocircuits covering other nonnegative cocircuits.

sign_crn.conditions.nondegeneracy_condition(kernel_matrix1: matrix, kernel_matrix2: matrix, certify: bool = False) bool

Nondegeneracy condition.

This condition is about whether all positive equal components of a vector can be covered by nonnegative cocircuits.

If certify is true, a list is returned to certify the result. (See the examples.)

EXAMPLES:

We consider the following matrices:

sage: from sign_crn import *
sage: P = matrix([[1, 0, 1, 0], [0, 1, 0, 1]])
sage: Pt = matrix([[1, 0, 0, -1], [0, 1, 0, -1]])
sage: nondegeneracy_condition(P, Pt)
True

Next, we certify the result. The corresponding subspaces are trivially nondegenerate since there are no nonnegative covectors in the kernel of P:

sage: nondegeneracy_condition(P, Pt, certify=True)
(True, 'no nonnegative covectors')

Now, we consider an example of degenerate subspaces:

sage: P = matrix([[0, 0, 1]])
sage: Pt = matrix([[1, 1, 0]])
sage: nondegeneracy_condition(P, Pt, certify=True)
(False, (1, 1, 0))

The resulting vector lies in the row space of Pt. The nonnegative covector (++0) in the kernel of P covers the first two equal components.

In the next example, there exists a partial cover:

sage: P = matrix([[1, 1, 0, 0], [0, 0, 1, -1]])
sage: Pt = matrix([[1, 1, 0, -1], [0, 0, 1, 0]])
sage: nondegeneracy_condition(P, Pt, certify=True)
(True, ([], [[[2, 3]]], [[[[2, 3]], [(--++)]]]))

In fact, a vector in the row space of Pt with equal positive components on [2, 3] corresponding to (--++) can be fully covered by covectors. However, this vector would not satisfy the condition on the support.

sign_crn.conditions.uniqueness_condition(matrix1: matrix, matrix2: matrix) bool | list[set]

Uniqueness condition using maximal minors.

OUTPUT: Return whether there exists at most one equilibrium. If the result depends on variables, a list of sets is returned. The condition holds if the inequalities in exactly one of these sets are satisfied.

Note

The matrices need to have maximal rank and the same dimensions. Otherwise, a ValueError is raised.

TESTS:

sage: from sign_crn import *
sage: var("a, b")
(a, b)
sage: P = matrix([[1, 0, -1], [0, 1, -1]])
sage: P
[ 1  0 -1]
[ 0  1 -1]
sage: Pt = matrix([[1, 0, a], [0, 1, b]])
sage: Pt
[1 0 a]
[0 1 b]
sage: uniqueness_condition(P, Pt) # random order
[{-a >= 0, -b >= 0}]
sage: conditions = uniqueness_condition(P, Pt)[0]
sage: conditions # random order
sage: (-a >= 0) in conditions and (-b >= 0) in conditions
True
sage: P = matrix([[a, 0, 1, 0], [0, 1, -1, 0], [0, 0, 0, 1]])
sage: Pt = matrix([[1, 0, 0, -1], [0, b, 1, 1], [0, 0, a, 1]])
sage: uniqueness_condition(P, Pt) # random order
[{(a - 1)*a >= 0, a*b >= 0}, {(a - 1)*a <= 0, a*b <= 0}]
sage: len(_), len(_[0])
(2, 2)
sign_crn.conditions.uniqueness_condition_sign_vectors(matrix1: matrix, matrix2: matrix) bool

Uniqueness condition using sign vectors.

Note

This implementation is inefficient and should not be used for large examples. Instead, use uniqueness_condition().

EXAMPLES:

sage: from sign_crn.conditions import uniqueness_condition_sign_vectors
sage: P = matrix([[1, 1, 1]])
sage: P
[1 1 1]
sage: Pt = matrix([[1, 0, 1]])
sage: Pt
[1 0 1]
sage: uniqueness_condition_sign_vectors(P, Pt)
True
sage: P = matrix([[1, 0, -1], [0, 1, -1]])
sage: P
[ 1  0 -1]
[ 0  1 -1]
sage: Pt = matrix([[1, 0, -1], [0, 1, 1]])
sage: Pt
[ 1  0 -1]
[ 0  1  1]
sage: uniqueness_condition_sign_vectors(P, Pt)
False

TESTS:

sage: from sign_crn.conditions import uniqueness_condition_sign_vectors
sage: A = identity_matrix(3)
sage: uniqueness_condition_sign_vectors(A, A)
True