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.

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().

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

◆ _clone()

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

Definition at line 1084 of file optimize.py.

References CellMLToNektar.optimize.LinearityAnalyser._make_apply().

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

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 

◆ _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.

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().

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 

◆ _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 1093 of file optimize.py.

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

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

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 

◆ _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.

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().

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 
def child_i(elt, i)
Definition: pycml.py:3449

◆ _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.

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

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 

◆ 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.

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

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 
def DEBUG(facility, args)
Definition: utilities.py:95

◆ 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.

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().

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 

◆ 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.

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

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 

◆ 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 

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