-
Notifications
You must be signed in to change notification settings - Fork 1
/
invariants.py
executable file
·175 lines (154 loc) · 6.31 KB
/
invariants.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import numpy as np
import math
class Invariants(object):
"""
Abstract base class for calculating invariants.
Instance Variables:
- _petri_net_data
- p_invariants
- t_invariants
Interface Method:
- calculate_invariants: private, accessed by PTInvariants object.
Private Methods:
- get_new_line
- remove_lines
- check_new_line
- minimal_only
- factorize
"""
##############################################
## Instance Variables: Getters and Setters
##############################################
def set_petri_net(self, p):
""" Set petri net as PetriNetDat object that is passed. """
self._petri_net_data = p
self._stoichiometry_matrix = self._petri_net_data.stoichiometry.stoichiometry_matrix
@property
def p_invariants(self):
""" Return P invariants (?xP array). """
return self._p_invariants
@property
def t_invariants(self):
""" Return T invariants (?xT array). """
return self._t_invariants
##############################################
## Interface Method
##############################################
def _calculate_invariants(self, a_matrix):
""" Calculate invariants of a given input matrix A. """
# Concatenate stoichiometry / incidence matrix A with an identity matrix.
# This new matrix is called Q
a_height = a_matrix.shape[0]
a_width = a_matrix.shape[1]
i_matrix = np.identity(a_height, int)
q_matrix = np.concatenate((a_matrix, i_matrix), axis = 1)
# For each column i in matrix Q from A carry out simple row operations
for i in range(0, a_width):
# Add to matrix Q as many lines l as there are linear combinations
# of two lines, with positive integer coefficients, such that
# element[l, i] is zero.
q_height = q_matrix.shape[0]
for j in range(0, q_height-1):
j_val = q_matrix[j, i]
for k in range(j+1, q_height):
k_val = q_matrix[k, i]
if (j_val < 0 and k_val > 0) or (j_val > 0 and k_val < 0):
new_line = Invariants.__get_new_line(self,
q_matrix, j, k, j_val, k_val)
q_matrix = np.vstack((q_matrix, new_line))
# Eliminate from matrix Q all the lines l whose element [l, i] is
# not zero.
q_matrix = Invariants.__remove_lines(self, q_matrix, i)
if q_matrix.shape[0] == 0:
break
# Remove A' from Q
q_matrix = q_matrix[:, a_width:]
if q_matrix.shape[0] != 0:
# Remove zero lines from Q
q_matrix = Invariants.__check_empty_line(self, q_matrix)
# Eliminate non-minimal invariants from Q
q_matrix = Invariants.__minimal_only(self, q_matrix)
# Simplify invariants if possible
q_matrix = Invariants.__factorize(self, q_matrix)
return q_matrix
##############################################
## Private Methods
##############################################
def __get_new_line(self, matrix, j, k, j_val, k_val):
""" Produce new line from j and k such that ith element is zero. """
if j_val + k_val == 0:
j_coef = 1
k_coef = 1
elif j_val > 0:
j_coef = -k_val
k_coef = j_val
elif j_val < 0:
j_coef = k_val
k_coef = -j_val
j_line = matrix[j,:]
k_line = matrix[k,:]
new_line = j_coef * j_line + k_coef * k_line
return new_line
def __remove_lines(self, matrix, column):
""" Remove all lines j where element [j, i] is not zero. """
height = matrix.shape[0]
del_list = np.array([], dtype = np.int)
for j in range(0, height):
if matrix[j, column] != 0:
del_list = np.append(del_list, [j])
matrix = np.delete(matrix, del_list, 0)
return matrix
def __check_empty_line(self, matrix):
""" Remove all-zero lines. """
height = matrix.shape[0]
del_list = np.array([], dtype = np.int)
for i in range(0, height):
line = matrix[i,:]
line_check = np.equal(line, 0)
if line_check.all():
del_list = np.append(del_list, [i])
matrix = np.delete(matrix, del_list, 0)
return matrix
def __minimal_only(self, matrix):
"""
For pairs of lines j, k, if the support of line k is a superset
of the support of j, line k is removed.
"""
height = matrix.shape[0]
del_list = np.array([], dtype = np.int)
for j in range(0, height):
if j not in del_list:
j_line = matrix[j,:]
j_support = np.not_equal(j_line, 0)
for k in range(0, height):
if j != k and k not in del_list:
k_line = matrix[k,:]
k_support = np.not_equal(k_line, 0)
support_overlap = np.logical_and(j_support, k_support)
if np.equal(j_support, support_overlap).all():
del_list = np.append(del_list, [k])
matrix = np.delete(matrix, del_list, 0)
return matrix
def __factorize(self, matrix):
"""
For invariants whose smallest non-zero element c is >1, the line
is simplified if possibe by testing for divisibility through factors
of c.
"""
height = matrix.shape[0]
masked_matrix = np.ma.masked_equal(matrix, 0, copy=False)
for i in range(0, height):
line = matrix[i,:]
min_val = masked_matrix[i,:].min()
if min_val != 1:
max_factor = int(math.floor(math.sqrt(min_val) + 1))
factors = reduce(list.__add__, ([i, min_val//i] \
for i in range(1, max_factor) if min_val % i == 0))
factors.sort(reverse=True)
factors.pop()
for j in range(0, len(factors)):
if np.equal(line % factors[j], 0).all():
line = line // factors[j]
matrix[i,:] = line
break
return matrix