Nektar++
Public Member Functions | Static Public Attributes | Private Member Functions | Private Attributes | List of all members
CellMLToNektar.optimize.LinearityAnalyser Class Reference
Inheritance diagram for CellMLToNektar.optimize.LinearityAnalyser:
[legend]

Public Member Functions

def analyse_for_jacobian (self, doc, V=None)
 
def find_linear_odes (self, state_vars, V, free_var)
 
def rearrange_linear_odes (self, doc)
 
def show (self, d)
 

Static Public Attributes

 LINEAR_KINDS = Enum('None', 'Linear', 'Nonlinear')
 

Private Member Functions

def _get_rhs (self, expr)
 
def _check_expr (self, expr, state_var, bad_vars)
 
def _clone (self, expr)
 
def _make_apply (self, operator, ghs, i, filter_none=True, preserve=False)
 
def _transfer_lut (self, expr, gh, var)
 
def _rearrange_expr (self, expr, var)
 

Private Attributes

 __expr
 

Detailed Description

Analyse linearity aspects of a model.

This class performs analyses to determine which ODEs have a linear
dependence on their state variable, discounting the presence of
the transmembrane potential.

This can be used to decouple the ODEs for more efficient solution,
especially in a multi-cellular context.

analyse_for_jacobian(doc) must be called before
rearrange_linear_odes(doc).

Definition at line 903 of file optimize.py.

Member Function Documentation

◆ _check_expr()

def CellMLToNektar.optimize.LinearityAnalyser._check_expr (   self,
  expr,
  state_var,
  bad_vars 
)
private
The actual linearity checking function.

Recursively determine the type of dependence expr has on
state_var.  The presence of any members of bad_vars indicates
a non-linear dependency.

Return a member of the self.LINEAR_KINDS enum.

Definition at line 959 of file optimize.py.

959 def _check_expr(self, expr, state_var, bad_vars):
960 """The actual linearity checking function.
961
962 Recursively determine the type of dependence expr has on
963 state_var. The presence of any members of bad_vars indicates
964 a non-linear dependency.
965
966 Return a member of the self.LINEAR_KINDS enum.
967 """
968 kind = self.LINEAR_KINDS
969 result = None
970 if isinstance(expr, mathml_ci):
971 var = expr.variable.get_source_variable(recurse=True)
972 if var is state_var:
973 result = kind.Linear
974 elif var in bad_vars:
975 result = kind.Nonlinear
976 elif var.get_type(follow_maps=True) == VarTypes.Computed:
977 # Recurse into defining expression
978 src_var = var.get_source_variable(recurse=True)
979 src_expr = self._get_rhs(src_var.get_dependencies()[0])
980 DEBUG('find-linear-deps', "--recurse for", src_var.name,
981 "to", src_expr)
982 result = self._check_expr(src_expr, state_var, bad_vars)
983 else:
984 result = kind.None
985 # Record the kind of this variable, for later use when
986 # rearranging linear ODEs
987 var._cml_linear_kind = result
988 elif isinstance(expr, mathml_cn):
989 result = kind.None
990 elif isinstance(expr, mathml_piecewise):
991 # If any conditions have a dependence, then we're
992 # nonlinear. Otherwise, all the pieces must be the same
993 # (and that's what we are) or we're nonlinear.
994 pieces = getattr(expr, u'piece', [])
995 conds = map(lambda p: child_i(p, 2), pieces)
996 chld_exprs = map(lambda p: child_i(p, 1), pieces)
997 if hasattr(expr, u'otherwise'):
998 chld_exprs.append(child_i(expr.otherwise, 1))
999 for cond in conds:
1000 if self._check_expr(cond, state_var, bad_vars) != kind.None:
1001 result = kind.Nonlinear
1002 break
1003 else:
1004 # Conditions all OK
1005 for e in chld_exprs:
1006 res = self._check_expr(e, state_var, bad_vars)
1007 if result is not None and res != result:
1008 # We have a difference
1009 result = kind.Nonlinear
1010 break
1011 result = res
1012 elif isinstance(expr, mathml_apply):
1013 # Behaviour depends on the operator
1014 operator = expr.operator().localName
1015 operands = expr.operands()
1016 if operator in ['plus', 'minus']:
1017 # Linear if any operand linear, and none non-linear
1018 op_kinds = map(lambda op: self._check_expr(op, state_var,
1019 bad_vars),
1020 operands)
1021 result = max(op_kinds)
1022 elif operator == 'divide':
1023 # Linear iff only numerator linear
1024 numer = operands.next()
1025 denom = operands.next()
1026 if self._check_expr(denom, state_var, bad_vars) != kind.None:
1027 result = kind.Nonlinear
1028 else:
1029 result = self._check_expr(numer, state_var, bad_vars)
1030 elif operator == 'times':
1031 # Linear iff only 1 linear operand
1032 op_kinds = map(lambda op: self._check_expr(op, state_var,
1033 bad_vars),
1034 operands)
1035 lin, nonlin = 0, 0
1036 for res in op_kinds:
1037 if res == kind.Linear: lin += 1
1038 elif res == kind.Nonlinear: nonlin += 1
1039 if nonlin > 0 or lin > 1:
1040 result = kind.Nonlinear
1041 elif lin == 1:
1042 result = kind.Linear
1043 else:
1044 result = kind.None
1045 else:
1046 # Cannot be linear; may be no dependence at all
1047 result = max(map(lambda op: self._check_expr(op, state_var,
1048 bad_vars),
1049 operands))
1050 if result == kind.Linear:
1051 result = kind.Nonlinear
1052 else:
1053 # Either a straightforward container element
1054 try:
1055 child = child_i(expr, 1)
1056 except ValueError:
1057 # Assume it's just a constant
1058 result = kind.None
1059 else:
1060 result = self._check_expr(child, state_var, bad_vars)
1061 DEBUG('find-linear-deps', "Expression", expr, "gives result", result)
1062 return result
1063
def child_i(elt, i)
Definition: pycml.py:3449
def DEBUG(facility, *args)
Definition: utilities.py:95

References CellMLToNektar.optimize.LinearityAnalyser._check_expr(), CellMLToNektar.optimize.LinearityAnalyser._get_rhs(), CellMLToNektar.pycml.child_i(), CellMLToNektar.utilities.DEBUG(), and CellMLToNektar.optimize.LinearityAnalyser.LINEAR_KINDS.

Referenced by CellMLToNektar.optimize.LinearityAnalyser._check_expr(), and CellMLToNektar.optimize.LinearityAnalyser.find_linear_odes().

◆ _clone()

def CellMLToNektar.optimize.LinearityAnalyser._clone (   self,
  expr 
)
private
Properly clone a MathML sub-expression.

Definition at line 1084 of file optimize.py.

1084 def _clone(self, expr):
1085 """Properly clone a MathML sub-expression."""
1086 if isinstance(expr, mathml):
1087 clone = expr.clone_self(register=True)
1088 else:
1089 clone = mathml.clone(expr)
1090 return clone
1091

Referenced by CellMLToNektar.optimize.LinearityAnalyser._make_apply(), and CellMLToNektar.optimize.LinearityAnalyser._rearrange_expr().

◆ _get_rhs()

def CellMLToNektar.optimize.LinearityAnalyser._get_rhs (   self,
  expr 
)
private
Return the RHS of an assignment expression.

Definition at line 953 of file optimize.py.

953 def _get_rhs(self, expr):
954 """Return the RHS of an assignment expression."""
955 ops = expr.operands()
956 ops.next()
957 return ops.next()
958

Referenced by CellMLToNektar.optimize.LinearityAnalyser._check_expr(), CellMLToNektar.optimize.LinearityAnalyser._rearrange_expr(), CellMLToNektar.optimize.LinearityAnalyser.find_linear_odes(), and CellMLToNektar.optimize.LinearityAnalyser.rearrange_linear_odes().

◆ _make_apply()

def CellMLToNektar.optimize.LinearityAnalyser._make_apply (   self,
  operator,
  ghs,
  i,
  filter_none = True,
  preserve = False 
)
private
Construct a new apply expression for g or h.

ghs is an iterable of (g,h) pairs for operands.

i indicates whether to construct g (0) or h (1).

filter_none indicates the behaviour of 0 under this operator.
If True, it's an additive zero, otherwise it's a
multiplicative zero.

Definition at line 1092 of file optimize.py.

1093 preserve=False):
1094 """Construct a new apply expression for g or h.
1095
1096 ghs is an iterable of (g,h) pairs for operands.
1097
1098 i indicates whether to construct g (0) or h (1).
1099
1100 filter_none indicates the behaviour of 0 under this operator.
1101 If True, it's an additive zero, otherwise it's a
1102 multiplicative zero.
1103 """
1104 # Find g or h operands
1105 ghs_i = map(lambda gh: gh[i], ghs)
1106 if not filter_none and None in ghs_i:
1107 # Whole expr is None
1108 new_expr = None
1109 else:
1110 # Filter out None subexprs
1111 if operator == u'minus':
1112 # Do we need to retain a unary minus?
1113 if len(ghs_i) == 1 or ghs_i[0] is None:
1114 # Original was -a or 0-a
1115 retain_unary_minus = True
1116 else:
1117 # Original was a-0 or a-b
1118 retain_unary_minus = False
1119 else:
1120 # Only retain if we're told to preserve as-is
1121 retain_unary_minus = preserve
1122 ghs_i = filter(None, ghs_i)
1123 if ghs_i:
1124 if len(ghs_i) > 1 or retain_unary_minus:
1125 new_expr = mathml_apply.create_new(
1126 self.__expr, operator, ghs_i)
1127 else:
1128 new_expr = self._clone(ghs_i[0]) # Clone may be unneeded
1129 else:
1130 new_expr = None
1131 return new_expr
1132

References CellMLToNektar.optimize.LinearityAnalyser.__expr, and CellMLToNektar.optimize.LinearityAnalyser._clone().

◆ _rearrange_expr()

def CellMLToNektar.optimize.LinearityAnalyser._rearrange_expr (   self,
  expr,
  var 
)
private
Rearrange an expression into the form g + h*var.

Performs a post-order traversal of this expression's tree,
and returns a pair (g, h)

Definition at line 1156 of file optimize.py.

1156 def _rearrange_expr(self, expr, var):
1157 """Rearrange an expression into the form g + h*var.
1158
1159 Performs a post-order traversal of this expression's tree,
1160 and returns a pair (g, h)
1161 """
1162# import inspect
1163# depth = len(inspect.stack())
1164# print ' '*depth, "_rearrange_expr", prid(expr, True), var.name, expr
1165 gh = None
1166 if isinstance(expr, mathml_ci):
1167 # Variable
1168 ci_var = expr.variable.get_source_variable(recurse=True)
1169 if var is ci_var:
1170 gh = (None, mathml_cn.create_new(expr,
1171 u'1', u'dimensionless'))
1172 else:
1173 if ci_var._cml_linear_kind == self.LINEAR_KINDS.None:
1174 # Just put the <ci> in g, but with full name
1175 gh = (mathml_ci.create_new(expr, ci_var.fullname()), None)
1176 gh[0]._set_variable_obj(ci_var)
1177 else:
1178 # ci_var is a linear function of var, so rearrange
1179 # its definition
1180 if not hasattr(ci_var, '_cml_linear_split'):
1181 ci_defn = ci_var.get_dependencies()[0]
1182 ci_var._cml_linear_split = self._rearrange_expr(
1183 self._get_rhs(ci_defn), var)
1184 gh = ci_var._cml_linear_split
1185 elif isinstance(expr, mathml_piecewise):
1186 # The tests have to move into both components of gh:
1187 # "if C1 then (a1,b1) elif C2 then (a2,b2) else (a0,b0)"
1188 # maps to "(if C1 then a1 elif C2 then a2 else a0,
1189 # if C1 then b1 elif C2 then b2 else b0)"
1190 # Note that no test is a function of var.
1191 # First rearrange child expressions
1192 pieces = getattr(expr, u'piece', [])
1193 cases = map(lambda p: child_i(p, 1), pieces)
1194 cases_ghs = map(lambda c: self._rearrange_expr(c, var), cases)
1195 if hasattr(expr, u'otherwise'):
1196 ow_gh = self._rearrange_expr(child_i(expr.otherwise, 1), var)
1197 else:
1198 ow_gh = (None, None)
1199 # Now construct the new expression
1200 conds = map(lambda p: self._clone(child_i(p, 2)), pieces)
1201 def piecewise_branch(i):
1202 pieces_i = zip(map(lambda gh: gh[i], cases_ghs),
1203 conds)
1204 pieces_i = filter(lambda p: p[0] is not None,
1205 pieces_i) # Remove cases that are None
1206 ow = ow_gh[i]
1207 if pieces_i:
1208 new_expr = mathml_piecewise.create_new(
1209 expr, pieces_i, ow)
1210 elif ow:
1211 new_expr = ow
1212 else:
1213 new_expr = None
1214 return new_expr
1215 gh = (piecewise_branch(0), piecewise_branch(1))
1216 self._transfer_lut(expr, gh, var)
1217 elif isinstance(expr, mathml_apply):
1218 # Behaviour depends on the operator
1219 operator = expr.operator().localName
1220 operands = expr.operands()
1221 self.__expr = expr # For self._make_apply
1222 if operator in ['plus', 'minus']:
1223 # Just split the operation into each component
1224 operand_ghs = map(lambda op: self._rearrange_expr(op, var),
1225 operands)
1226 g = self._make_apply(operator, operand_ghs, 0)
1227 h = self._make_apply(operator, operand_ghs, 1)
1228 gh = (g, h)
1229 elif operator == 'divide':
1230 # (a, b) / (c, 0) = (a/c, b/c)
1231 numer = self._rearrange_expr(operands.next(), var)
1232 denom = self._rearrange_expr(operands.next(), var)
1233 assert denom[1] is None
1234 denom_g = denom[0]
1235 g = h = None
1236 if numer[0]:
1237 g = mathml_apply.create_new(expr, operator,
1238 [numer[0], denom_g])
1239 if numer[1]:
1240 if g:
1241 denom_g = self._clone(denom_g)
1242 h = mathml_apply.create_new(expr, operator,
1243 [numer[1], denom_g])
1244 gh = (g, h)
1245 elif operator == 'times':
1246 # (a1,b1)*(a2,b2) = (a1*a2, b1*a2 or a1*b2 or None)
1247 # Similarly for the nary case - at most one b_i is not None
1248 operand_ghs = map(lambda op: self._rearrange_expr(op, var),
1249 operands)
1250 g = self._make_apply(operator, operand_ghs, 0,
1251 filter_none=False)
1252 # Find non-None b_i, if any
1253 for i, ab in enumerate(operand_ghs):
1254 if ab[1] is not None:
1255 operand_ghs[i] = (ab[1], None)
1256 # Clone the a_i to avoid objects having 2 parents
1257 for j, ab in enumerate(operand_ghs):
1258 if j != i:
1259 operand_ghs[j] = (self._clone(operand_ghs[j][0]), None)
1260 h = self._make_apply(operator, operand_ghs, 0, filter_none=False)
1261 break
1262 else:
1263 h = None
1264 gh = (g, h)
1265 else:
1266 # (a, None) op (b, None) = (a op b, None)
1267 operand_ghs = map(lambda op: self._rearrange_expr(op, var),
1268 operands)
1269 g = self._make_apply(operator, operand_ghs, 0, preserve=True)
1270 gh = (g, None)
1271 self._transfer_lut(expr, gh, var)
1272 else:
1273 # Since this expression is linear, there can't be any
1274 # occurrence of var in it; all possible such cases are covered
1275 # above. So just clone it into g.
1276 gh = (self._clone(expr), None)
1277# print ' '*depth, "Re-arranged", prid(expr, True), "to", prid(gh[0], True), ",", prid(gh[1], True)
1278 return gh
1279

References CellMLToNektar.optimize.LinearityAnalyser._clone(), CellMLToNektar.optimize.LinearityAnalyser._get_rhs(), CellMLToNektar.optimize.LinearityAnalyser._rearrange_expr(), CellMLToNektar.optimize.LinearityAnalyser._transfer_lut(), CellMLToNektar.pycml.child_i(), and CellMLToNektar.optimize.LinearityAnalyser.LINEAR_KINDS.

Referenced by CellMLToNektar.optimize.LinearityAnalyser._rearrange_expr(), and CellMLToNektar.optimize.LinearityAnalyser.rearrange_linear_odes().

◆ _transfer_lut()

def CellMLToNektar.optimize.LinearityAnalyser._transfer_lut (   self,
  expr,
  gh,
  var 
)
private
Transfer lookup table annotations from expr to gh.

gh is a pair (g, h) s.t. expr = g + h*var.

If expr can be represented by a lookup table, then the lookup
variable cannot be var, since if it were, then expr would have
a non-linear dependence on var.  Hence h must be 0, since
otherwise expr would contain a (state) variable other than the
lookup variable, and hence not be a candidate for a table.
Thus expr=g, so we transfer the annotations to g.

Definition at line 1133 of file optimize.py.

1133 def _transfer_lut(self, expr, gh, var):
1134 """Transfer lookup table annotations from expr to gh.
1135
1136 gh is a pair (g, h) s.t. expr = g + h*var.
1137
1138 If expr can be represented by a lookup table, then the lookup
1139 variable cannot be var, since if it were, then expr would have
1140 a non-linear dependence on var. Hence h must be 0, since
1141 otherwise expr would contain a (state) variable other than the
1142 lookup variable, and hence not be a candidate for a table.
1143 Thus expr=g, so we transfer the annotations to g.
1144 """
1145 if expr.getAttributeNS(NSS['lut'], u'possible', '') != u'yes':
1146 return
1147 # Paranoia check that our reasoning is correct
1148 g, h = gh
1149 assert h is None
1150 # Transfer the annotations into g
1151 LookupTableAnalyser.copy_lut_annotations(expr, g)
1152 # Make sure g has a reference to its component, for use by code generation.
1153 g._cml_component = expr.component
1154 return
1155

Referenced by CellMLToNektar.optimize.LinearityAnalyser._rearrange_expr().

◆ analyse_for_jacobian()

def CellMLToNektar.optimize.LinearityAnalyser.analyse_for_jacobian (   self,
  doc,
  V = None 
)
Analyse the model for computing a symbolic Jacobian.

Determines automatically which variables will need to be solved
for using Newton's method, and stores their names in
doc.model._cml_nonlinear_system_variables, as a list of variable
objects.

Also stores doc.model._cml_linear_vars, doc.model._cml_free_var,
doc.model._cml_transmembrane_potential.

Definition at line 918 of file optimize.py.

918 def analyse_for_jacobian(self, doc, V=None):
919 """Analyse the model for computing a symbolic Jacobian.
920
921 Determines automatically which variables will need to be solved
922 for using Newton's method, and stores their names in
923 doc.model._cml_nonlinear_system_variables, as a list of variable
924 objects.
925
926 Also stores doc.model._cml_linear_vars, doc.model._cml_free_var,
927 doc.model._cml_transmembrane_potential.
928 """
929 # TODO: Add error checking and tidy
930 stvs = doc.model.find_state_vars()
931 if V is None:
932 Vcname, Vvname = 'membrane', 'V'
933 V = doc.model.get_variable_by_name(Vcname, Vvname)
934 V = V.get_source_variable(recurse=True)
935 doc.model._cml_transmembrane_potential = V
936 free_var = doc.model.find_free_vars()[0]
937 lvs = self.find_linear_odes(stvs, V, free_var)
938 # Next 3 lines for benefit of rearrange_linear_odes(doc)
939 lvs.sort(key=lambda v: v.fullname())
940 doc.model._cml_linear_vars = lvs
941 doc.model._cml_free_var = free_var
942 # Store nonlinear vars in canonical order
943 nonlinear_vars = list(set(stvs) - set([V]) - set(lvs))
944 nonlinear_vars.sort(key=lambda v: v.fullname())
945 doc.model._cml_nonlinear_system_variables = nonlinear_vars
946 # Debugging
947 f = lambda var: var.fullname()
948 DEBUG('linearity-analyser', 'V=', V.fullname(), '; free var=',
949 free_var.fullname(), '; linear vars=', map(f, lvs),
950 '; nonlinear vars=', map(f, nonlinear_vars))
951 return
952

References CellMLToNektar.utilities.DEBUG(), and CellMLToNektar.optimize.LinearityAnalyser.find_linear_odes().

◆ find_linear_odes()

def CellMLToNektar.optimize.LinearityAnalyser.find_linear_odes (   self,
  state_vars,
  V,
  free_var 
)
Identify linear ODEs.

For each ODE (except that for V), determine whether it has a
linear dependence on the dependent variable, and thus can be
updated directly, without using Newton's method.

We also require it to not depend on any other state variable,
except for V.

Definition at line 1064 of file optimize.py.

1064 def find_linear_odes(self, state_vars, V, free_var):
1065 """Identify linear ODEs.
1066
1067 For each ODE (except that for V), determine whether it has a
1068 linear dependence on the dependent variable, and thus can be
1069 updated directly, without using Newton's method.
1070
1071 We also require it to not depend on any other state variable,
1072 except for V.
1073 """
1074 kind = self.LINEAR_KINDS
1075 candidates = set(state_vars) - set([V])
1076 linear_vars = []
1077 for var in candidates:
1078 ode_expr = var.get_ode_dependency(free_var)
1079 if self._check_expr(self._get_rhs(ode_expr), var,
1080 candidates - set([var])) == kind.Linear:
1081 linear_vars.append(var)
1082 return linear_vars
1083

References CellMLToNektar.optimize.LinearityAnalyser._check_expr(), CellMLToNektar.optimize.LinearityAnalyser._get_rhs(), and CellMLToNektar.optimize.LinearityAnalyser.LINEAR_KINDS.

Referenced by CellMLToNektar.optimize.LinearityAnalyser.analyse_for_jacobian().

◆ rearrange_linear_odes()

def CellMLToNektar.optimize.LinearityAnalyser.rearrange_linear_odes (   self,
  doc 
)
Rearrange the linear ODEs so they can be updated directly
on solving.

Each ODE du/dt = f(u, t) can be written in the form
    du/dt = g(t) + h(t)u.
A backward Euler update step is then as simple as
    u_n = (u_{n-1} + g(t)dt) / (1 - h(t)dt)
(assuming that the transmembrane potential has already been
solved for at t_n.

Stores the results in doc.model._cml_linear_update_exprs, a
mapping from variable object u to pair (g, h).

Definition at line 1280 of file optimize.py.

1280 def rearrange_linear_odes(self, doc):
1281 """Rearrange the linear ODEs so they can be updated directly
1282 on solving.
1283
1284 Each ODE du/dt = f(u, t) can be written in the form
1285 du/dt = g(t) + h(t)u.
1286 A backward Euler update step is then as simple as
1287 u_n = (u_{n-1} + g(t)dt) / (1 - h(t)dt)
1288 (assuming that the transmembrane potential has already been
1289 solved for at t_n.
1290
1291 Stores the results in doc.model._cml_linear_update_exprs, a
1292 mapping from variable object u to pair (g, h).
1293 """
1294
1295 odes = map(lambda v: v.get_ode_dependency(doc.model._cml_free_var),
1296 doc.model._cml_linear_vars)
1297 result = {}
1298 for var, ode in itertools.izip(doc.model._cml_linear_vars, odes):
1299 # Do the re-arrangement for this variable.
1300 # We do this by a post-order traversal of its defining ODE,
1301 # constructing a pair (g, h) for each subexpression recursively.
1302 # First, get the RHS of the ODE
1303 rhs = self._get_rhs(ode)
1304 # And traverse
1305 result[var] = self._rearrange_expr(rhs, var)
1306 # Store result in model
1307 doc.model._cml_linear_update_exprs = result
1308 return result
1309

References CellMLToNektar.optimize.LinearityAnalyser._get_rhs(), and CellMLToNektar.optimize.LinearityAnalyser._rearrange_expr().

Referenced by CellMLToNektar.optimize.LinearityAnalyser.show().

◆ show()

def CellMLToNektar.optimize.LinearityAnalyser.show (   self,
  d 
)
Print out a more readable report on a rearrangement,
as given by self.rearrange_linear_odes.

Definition at line 1310 of file optimize.py.

1310 def show(self, d):
1311 """Print out a more readable report on a rearrangement,
1312 as given by self.rearrange_linear_odes."""
1313 for var, expr in d.iteritems():
1314 print var.fullname()
1315 print "G:", expr[0].xml()
1316 print "H:", expr[1].xml()
1317 print "ODE:", var.get_ode_dependency(
1318 var.model._cml_free_var).xml()
1319 print
1320
1321

References CellMLToNektar.optimize.LinearityAnalyser.rearrange_linear_odes().

Member Data Documentation

◆ __expr

CellMLToNektar.optimize.LinearityAnalyser.__expr
private

Definition at line 1221 of file optimize.py.

Referenced by CellMLToNektar.optimize.LinearityAnalyser._make_apply().

◆ LINEAR_KINDS

CellMLToNektar.optimize.LinearityAnalyser.LINEAR_KINDS = Enum('None', 'Linear', 'Nonlinear')
static