def test_regime_aliases(self): a = Dynamics( name='a', aliases=[Alias('A', '4/t')], regimes=[ Regime('dX/dt=1/t + A', name='r1', transitions=On('X>X1', do=['X=X0'], to='r2')), Regime('dX/dt=1/t + A', name='r2', transitions=On('X>X1', do=['X=X0'], to='r1'), aliases=[Alias('A', '8 / t')])]) self.assertEqual(a.regime('r2').alias('A'), Alias('A', '8 / t')) self.assertRaises( NineMLUsageError, Dynamics, name='a', regimes=[ Regime('dX/dt=1/t + A', name='r1', transitions=On('X>X1', do=['X=X0'], to='r2')), Regime('dX/dt=1/t + A', name='r2', transitions=On('X>X1', do=['X=X0'], to='r1'), aliases=[Alias('A', '8 / t')])]) document = Document() a_xml = a.serialize(format='xml', version=1, document=document) b = Dynamics.unserialize(a_xml, format='xml', version=1, document=Document(un.dimensionless.clone())) self.assertEqual(a, b, "Dynamics with regime-specific alias failed xml " "roundtrip:\n{}".format(a.find_mismatch(b)))
def test_is_alias(self): # Signature: name(cls, alias_string) # Returns True if the string could be an alias for expr_str, _ in Aliases: self.assertTrue(Alias.is_alias_str(expr_str)) for expr_str, _ in TimeDerivatives: self.assertFalse(Alias.is_alias_str(expr_str)) for expr_str, _ in Assignments: self.assertFalse(Alias.is_alias_str(expr_str))
def test_sympify(self): a = sympy.Symbol('a') self.assertEqual(a, sympy.sympify(Alias('a', 'b + c'))) self.assertEqual(a, sympy.sympify(AnalogReceivePort('a'))) self.assertEqual(a, sympy.sympify(AnalogReducePort('a'))) self.assertEqual(a, sympy.sympify(Constant('a', 1.0, units=un.unitless)))
def setUp(self): self.parameters = ['P4', 'P1', 'P3', 'P5', 'P2'] self.state_variables = ['SV3', 'SV5', 'SV4', 'SV2', 'SV1'] self.regimes = ['R2', 'R3', 'R1'] self.time_derivatives = { 'R1': ['SV5', 'SV1', 'SV4', 'SV3', 'SV2'], 'R2': ['SV2', 'SV4'], 'R3': ['SV4', 'SV2', 'SV1'] } self.aliases = ['A4', 'A3', 'A1', 'A2'] # Create a dynamics object with elements in a particular order self.d = Dynamics( name='d', parameters=[Parameter(p) for p in self.parameters], state_variables=[StateVariable(sv) for sv in self.state_variables], regimes=[ Regime(name=r, time_derivatives=[ TimeDerivative(td, '{}/t'.format(td)) for td in self.time_derivatives[r] ], transitions=[ OnCondition('SV1 > P5', target_regime_name=self.regimes[ self.regimes.index(r) - 1]) ]) for r in self.regimes ], aliases=[ Alias(a, 'P{}'.format(i + 1)) for i, a in enumerate(self.aliases) ])
def test_get_rhs_substituted(self): # Signature: name(cls, expr_obj, namemap) # from nineml.abstraction.component.util import MathUtil e = Alias.from_str('a := b*c + b1 + e_*exp(-12*g) + ' 'd/(e*sin(f + g/e))') rhs_sub = e.rhs_substituted({'b': 'B', 'e': 'E'}) self.assertEqual( rhs_sub, sympy.sympify('B*c + b1 + e_*exp(-12*g) + d/(E*sin(f + g/E))', locals={'E': sympy.Symbol('E')}))
def test_get_prefixed_rhs_string(self): # Signature: name(cls, expr_obj, prefix='', exclude=None) # No Docstring # from nineml.abstraction.component.util import MathUtil e = Alias.from_str('a := b*c + d/(e_*sin(f+g/e_)) + b1 + ' 'e_ / exp(12*g)') rhs_sub = e.rhs_suffixed(suffix='', prefix='U_', excludes=['c', 'e_']) self.assertEqual( rhs_sub, sympy.sympify('U_b*c + U_d/(e_*sin(U_f+U_g/e_)) + U_b1 +' ' e_ / exp(12*U_g)'))
def test_get_prefixed_rhs_string(self): # Signature: name(cls, expr_obj, prefix='', exclude=None) # No Docstring # from nineml.abstraction.component.util import MathUtil e = Alias.from_str('a := b*c + d/(e_*sin(f+g/e_)) + b1 + ' 'e_ / exp(12*g)') rhs_sub = e.rhs_suffixed(suffix='', prefix='U_', excludes=['c', 'e_']) self.assertEqual( rhs_sub, sympy.sympify('U_b*c + U_d/(e_*sin(U_f+U_g/e_)) + U_b1 +' ' e_ / exp(12*U_g)') )
def setUp(self): self.a = Dynamics(name='A', aliases=['A1:=P1', 'A2 := ARP2', 'A3 := SV1'], regimes=[ Regime('dSV1/dt = -SV1 / (P2*t)', 'dSV2/dt = A2/t + A3/t + ARP1/t', name='R1', transitions=On('input', 'SV1 = SV1 + 1')) ], analog_ports=[ AnalogReceivePort('ARP1'), AnalogReceivePort('ARP2'), AnalogSendPort('A1'), AnalogSendPort('A2') ], parameters=['P1', 'P2']) self.b = Dynamics(name='B', aliases=['A1:=P1', 'A2 := ARP1 + SV2', 'A3 := SV1'], regimes=[ Regime( 'dSV1/dt = -SV1 / (P2*t)', 'dSV2/dt = SV1 / (ARP1*t) + SV2 / (P1*t)', transitions=[ On('SV1 > P1', do=[OutputEvent('emit')]), On('spikein', do=[OutputEvent('emit')]) ], aliases=[Alias('A1', 'P1 * 2')], name='R1', ), Regime(name='R2', transitions=On( 'SV1 > 1', 'SV1 = SV1 * random.normal()', to='R1')) ], analog_ports=[ AnalogReceivePort('ARP1'), AnalogReceivePort('ARP2'), AnalogSendPort('A1'), AnalogSendPort('A2') ], parameters=['P1', 'P2'])
def test_remove(self): # Copy templates a = copy(self.a) b = copy(self.b) # Add missing items b.remove(b.alias('A4')) b.remove(b.state_variable('SV3')) b.regime('R1').remove(b.regime('R1').time_derivative('SV3')) b.regime('R1').on_event('spikein').remove( b.regime('R1').on_event('spikein').state_assignment('SV1')) b.regime('R2').remove(b.regime('R2').on_condition('SV3 < 0.001')) b.remove(b.analog_send_port('SV3')) b.remove(b.parameter('P3')) b.validate() self.assertEqual( a, b, "Did not transform 'b' into 'a':\n {}".format(a.find_mismatch(b))) b.add(Alias('A5', 'P1 + P2')) self.assertNotEqual(a, b, "Added Alias was not detected")
def test_equals_with_annotations_ns(self): a = Dynamics(name='D', parameters=[Parameter('P', dimension=un.dimensionless)], aliases=[Alias('A', 'P')]) b = a.clone() c = a.clone() d = a.clone() e = a.clone() a.parameter('P').annotations.set(('annot1', 'dummy_ns'), 'val1', 1.0) b.parameter('P').annotations.set(('annot1', 'dummy_ns'), 'val1', 1.0) c.parameter('P').annotations.set(('annot1', 'dummy_ns'), 'val1', 2.0) e.parameter('P').annotations.set(('annot1', 'dummy_ns2'), 'val1', 1.0) self.assertTrue(a.equals(b, annotations_ns=['dummy_ns'])) self.assertTrue(a.equals(c)) self.assertFalse(a.equals(c, annotations_ns=['dummy_ns'])) self.assertTrue(a.equals(d)) self.assertFalse(a.equals(d, annotations_ns=['dummy_ns'])) self.assertTrue(a.equals(e)) self.assertFalse(a.equals(e, annotations_ns=['dummy_ns']))
def test_add(self): # Copy templates a = self.a.clone() b = self.b.clone() # Add missing items a.add(Alias('A4', 'SV1^3 + SV2^-3')) a.add(StateVariable('SV3')) a.regime('R1').add(TimeDerivative('SV3', '-SV3/t + P3/t')) a.regime('R1').on_event('spikein').add(StateAssignment('SV1', 'P1')) a.regime('R2').add( OnCondition('SV3 < 0.001', target_regime_name='R2', state_assignments=[StateAssignment('SV3', 1)])) a.add(Parameter('P3')) a.add(AnalogSendPort('SV3')) a.bind() a.validate() self.assertEqual( b, a, "Did not transform 'a' into 'b':\n {}".format(b.find_mismatch(a)))
def setUp(self): self.a = Dynamics( name='A', aliases=['A1:=P1 / P2', 'A2 := ARP2 + P3', 'A3 := P4 * P5'], regimes=[ Regime('dSV1/dt = -A1 / A2', aliases=[Alias('A1', 'P1 / P2 * 2')], name='R1') ], analog_ports=[ AnalogReceivePort('ARP1', dimension=un.resistance), AnalogReceivePort('ARP2', dimension=un.charge) ], parameters=[ Parameter('P1', dimension=un.voltage), Parameter('P2', dimension=un.resistance), Parameter('P3', dimension=un.charge), Parameter('P4', dimension=old_div(un.length, un.current**2)), Parameter('P5', dimension=old_div(un.current**2, un.length)) ])
def test_alias(self): for expr_str, (exp_lhs, exp_rhs) in Aliases: alias = Alias.from_str(expr_str) self.assertEqual(alias.lhs, exp_lhs) self.assertEqual(str(alias.rhs), exp_rhs)
def transform_for_build(self, name, component_class, **kwargs): """ Copies and transforms the component class to match the format of the simulator (overridden in derived class) Parameters ---------- name : str The name of the transformed component class component_class : nineml.Dynamics The component class to be transformed """ self._set_build_props(component_class, **kwargs) if not isinstance(component_class, WithSynapses): raise Pype9RuntimeError( "'component_class' must be a DynamicsWithSynapses object") # --------------------------------------------------------------------- # Clone original component class # --------------------------------------------------------------------- trfrm = component_class.dynamics.flatten() # --------------------------------------------------------------------- # Get the membrane voltage and convert it to 'v' # --------------------------------------------------------------------- try: name = kwargs['membrane_voltage'] try: orig_v = component_class.element( name, nineml_children=Dynamics.nineml_children) except KeyError: raise Pype9BuildError( "Could not find specified membrane voltage '{}'" .format(name)) except KeyError: # Guess voltage from its dimension if not supplied candidate_vs = [cv for cv in component_class.state_variables if cv.dimension == un.voltage] if len(candidate_vs) == 0: candidate_vs = [ cv for cv in component_class.analog_receive_ports if cv.dimension == un.voltage] if len(candidate_vs) == 1: orig_v = candidate_vs[0] logger.info("Guessing that '{}' is the membrane voltage" .format(orig_v)) elif len(candidate_vs) > 1: try: orig_v = next(c for c in candidate_vs if c.name == 'v') logger.info("Guessing that '{}' is the membrane voltage" .format(orig_v)) except StopIteration: raise Pype9BuildError( "Could not guess the membrane voltage, candidates: " "'{}'" .format("', '".join(v.name for v in candidate_vs))) else: orig_v = None logger.info( "Can't find candidate for the membrane voltage in " "state_variables '{}' or analog_receive_ports '{}', " "treating '{}' as an \"artificial cell\"".format( "', '".join( sv.name for sv in component_class.state_variables), "', '".join( p.name for p in component_class.analog_receive_ports), component_class.name)) if orig_v is not None: # Map voltage to hard-coded 'v' symbol if orig_v.name != 'v': trfrm.rename_symbol(orig_v.name, 'v') v = trfrm.state_variable('v') v.annotations.set((BUILD_TRANS, PYPE9_NS), TRANSFORM_SRC, orig_v) else: v = trfrm.state_variable('v') # Add annotations to the original and build models component_class.annotations.set((BUILD_TRANS, PYPE9_NS), MEMBRANE_VOLTAGE, orig_v.name) # @IgnorePep8 trfrm.annotations.set((BUILD_TRANS, PYPE9_NS), MEMBRANE_VOLTAGE, 'v') # Remove associated analog send port if present try: trfrm.remove(trfrm.analog_send_port('v')) except KeyError: pass # Need to convert to AnalogReceivePort if v is a StateVariable if isinstance(v, StateVariable): self._transform_full_component(trfrm, component_class, v, **kwargs) trfrm.annotations.set((BUILD_TRANS, PYPE9_NS), MECH_TYPE, FULL_CELL_MECH) else: raise NotImplementedError( "Build sub-components is not supported in PyPe9 v0.1") else: trfrm.annotations.set((BUILD_TRANS, PYPE9_NS), MECH_TYPE, ARTIFICIAL_CELL_MECH) # ----------------------------------------------------------------- # Insert dummy aliases for parameters (such as capacitance) that # now do not show up in the inferred interface for the transformed # class (i.e. that were only # present in the voltage time derivative) # ----------------------------------------------------------------- # Infer required parameters inferred = DynamicsInterfaceInferer(trfrm) for parameter in list(trfrm.parameters): if parameter.name not in inferred.parameter_names: trfrm.add(Alias(parameter.name + '___dummy', parameter.name)) # ----------------------------------------------------------------- # Validate the transformed component class and construct prototype # ----------------------------------------------------------------- trfrm.validate() trfrm_with_syn = DynamicsWithSynapses( name, trfrm, component_class.synapses, component_class.connection_parameter_sets) # Retun a prototype of the transformed class return trfrm_with_syn
def _transform_full_component(self, trfrm, component_class, v, **kwargs): # ----------------------------------------------------------------- # Remove all analog send ports with 'current' dimension so they # don't get confused with the converted voltage time derivative # expression # ----------------------------------------------------------------- for port in list(trfrm.analog_send_ports): if port.dimension == un.current: trfrm.remove(port) # ----------------------------------------------------------------- # Insert membrane capacitance if not present # ----------------------------------------------------------------- # Get or guess the location of the membrane capacitance try: name = kwargs['membrane_capacitance'] try: orig_cm = component_class.parameter(name) except KeyError: raise Pype9BuildError( "Could not find specified membrane capacitance '{}'" .format(name)) cm = trfrm.parameter(orig_cm.name) except KeyError: # 'membrane_capacitance' was not specified candidate_cms = [ccm for ccm in component_class.parameters if ccm.dimension == un.capacitance] if len(candidate_cms) == 1: orig_cm = candidate_cms[0] cm = trfrm.parameter(orig_cm.name) logger.info("Guessing that '{}' is the membrane capacitance" .format(orig_cm)) elif len(candidate_cms) > 1: raise Pype9BuildError( "Could not guess the membrane capacitance, candidates:" " '{}'".format("', '".join(candidate_cms))) else: cm = Parameter("cm___pype9", dimension=un.capacitance) trfrm.add(cm) cm.annotations.set((BUILD_TRANS, PYPE9_NS), TRANSFORM_SRC, None) trfrm.annotations.set((BUILD_TRANS, PYPE9_NS), MEMBRANE_CAPACITANCE, cm.name) # ----------------------------------------------------------------- # Replace membrane voltage equation with membrane current # ----------------------------------------------------------------- # Determine the regimes in which each state variables has a time # derivative in has_td = defaultdict(list) # List which regimes need to be clamped to their last voltage # (as it has no time derivative) clamped_regimes = [] # The voltage clamp equation where v_clamp is the last voltage # value and g_clamp_ is a large conductance clamp_i = sympy.sympify('g_clamp___pype9 * (v - v_clamp___pype9)') memb_is = [] for regime in trfrm.regimes: # Add an appropriate membrane current try: # Convert the voltage time derivative into a membrane # current dvdt = regime.time_derivative(v.name) regime.remove(dvdt) i = -dvdt.rhs * cm memb_is.append(i) except KeyError: i = clamp_i clamped_regimes.append(regime) regime.add(Alias('i___pype9', i)) # Record state vars that have a time deriv. in this regime for var in regime.time_derivative_variables: if var != 'v': has_td[var].append(regime) # Pick the most popular membrane current to be the alias in # the global scope assert memb_is, "No regimes contain voltage time derivatives" memb_i = Alias('i___pype9', max(memb_is, key=memb_is.count)) # Add membrane current along with a analog send port trfrm.add(memb_i) i_port = AnalogSendPort('i___pype9', dimension=un.current) i_port.annotations.set((BUILD_TRANS, PYPE9_NS), ION_SPECIES, NONSPECIFIC_CURRENT) trfrm.add(i_port) # Remove membrane currents that match the membrane current in the # outer scope for regime in trfrm.regimes: if regime.alias('i___pype9') == memb_i: regime.remove(regime.alias('i___pype9')) # If there are clamped regimes add extra parameters and set the # voltage to clamp to in the regimes that trfrmition to them if clamped_regimes: trfrm.add(StateVariable('v_clamp___pype9', un.voltage)) trfrm.add(Constant('g_clamp___pype9', 1e8, un.uS)) for trans in trfrm.transitions: if trans.target_regime in clamped_regimes: # Assign v_clamp_ to the value try: v_clamp_rhs = trans.state_assignment('v').rhs except KeyError: v_clamp_rhs = 'v' trans.add(StateAssignment('v_clamp___pype9', v_clamp_rhs)) # ----------------------------------------------------------------- trfrm.annotations.set( (BUILD_TRANS, PYPE9_NS), NO_TIME_DERIVS, ','.join(['v'] + [sv for sv in trfrm.state_variable_names if sv not in has_td])) trfrm.annotations.set((BUILD_TRANS, PYPE9_NS), NUM_TIME_DERIVS, len(has_td)) # ----------------------------------------------------------------- # Remove the external input currents # ----------------------------------------------------------------- # Analog receive or reduce ports that are of dimension current and # are purely additive to the membrane current and nothing else # (actually subtractive as it is outward current) try: ext_is = [] for i_name in kwargs['external_currents']: try: ext_i = trfrm.analog_receive_port(i_name) except KeyError: try: ext_i = trfrm.analog_reduce_port(i_name) except KeyError: raise Pype9BuildError( "Did not find specified external current port " "'{}'".format(i_name)) if ext_i.dimension != un.current: raise Pype9BuildError( "Analog receive port matching specified external " "current '{}' does not have 'current' dimension " "({})".format(ext_i.name, ext_i.dimension)) ext_is.append(ext_i) except KeyError: ext_is = [] for port in chain(component_class.analog_receive_ports, component_class.analog_reduce_ports): # Check to see if the receive/reduce port has current dimension if port.dimension != un.current: continue # Check to see if the current appears in the membrane current # expression # FIXME: This test should check to to see if the port is # additive to the membrane current and substitute all # aliases. if port.name not in memb_i.rhs_symbol_names: continue # Get the number of expressions the receive port appears in # an expression if len([e for e in component_class.all_expressions if port.symbol in e.free_symbols]) > 1: continue # If all those conditions are met guess that port is a external # current that can be removed (ports that don't meet these # conditions will have to be specified separately) ext_is.append(port) if ext_is: logger.info("Guessing '{}' are external currents to be removed" .format(ext_is)) trfrm.annotations.set((BUILD_TRANS, PYPE9_NS), EXTERNAL_CURRENTS, ','.join(p.name for p in ext_is)) # Remove external input current ports (as NEURON handles them) for ext_i in ext_is: trfrm.remove(ext_i) for expr in chain(trfrm.aliases, trfrm.all_time_derivatives()): expr.subs(ext_i, 0) expr.simplify()
event_send_ports=[EventSendPort('ESP1')], event_receive_ports=[EventReceivePort('ERP1')], parameters=['P1', 'P2', 'P3']) dynC = Dynamics( name='dynC', aliases=['A1:=P1', 'A2 := ARP1 + SV2', 'A3 := SV1'], regimes=[ Regime( 'dSV1/dt = -SV1 / (P2*t)', 'dSV2/dt = SV1 / (ARP1*t) + SV2 / (P1*t)', on_conditions=[ OnCondition('SV1 > P1', output_events=[OutputEvent('ESP1')]) ], on_events=[OnEvent('ERP1', output_events=[OutputEvent('ESP1')])], aliases=[Alias('A1', 'P1 * 2')], name='R1', ), Regime(name='R2', transitions=On('SV1 > 1', to='R1')) ], ports=[ AnalogReceivePort('ARP1'), AnalogReceivePort('ARP2'), AnalogSendPort('A1'), AnalogSendPort('A2') ], parameters=['P1', 'P2']) dynD = Dynamics( name='dynD', state_variables=[StateVariable('SV1', dimension=un.voltage)],
def setUp(self): self.a = Dynamics( name='A', aliases=[ 'A1:=P1 / P2', 'A2 := ARP2 + P3', 'A3 := A4 * P4 * P5', 'A4:=P6 ** 2 + ADP1', 'A5:=SV1 * SV2 * P8', 'A6:=SV1 * P1 / P8', 'A7:=A1 / P8' ], regimes=[ Regime('dSV1/dt = -A1 / A2', 'dSV2/dt = -ADP1 / P7', 'dSV3/dt = -A1 * A3 / (A2 * C1)', transitions=[ OnCondition('SV1 > 10', target_regime_name='R2') ], aliases=[ Alias('A1', 'P1 / P2 * 2'), Alias('A5', 'SV1 * SV2 * P8 * 2') ], name='R1'), Regime('dSV1/dt = -A1 / A2', 'dSV3/dt = -A1 / A2 * A4', transitions=[ OnCondition('C2 > A6', state_assignments=[ StateAssignment('SV1', 'SV1 - A7') ], target_regime_name='R1') ], name='R2') ], analog_ports=[ AnalogReceivePort('ARP1', dimension=un.resistance), AnalogReceivePort('ARP2', dimension=un.charge), AnalogReducePort('ADP1', dimension=un.dimensionless), AnalogSendPort('A5', dimension=un.current) ], parameters=[ Parameter('P1', dimension=un.voltage), Parameter('P2', dimension=un.resistance), Parameter('P3', dimension=un.charge), Parameter('P4', dimension=old_div(un.length, un.current**2)), Parameter('P5', dimension=old_div(un.current**2, un.length)), Parameter('P6', dimension=un.dimensionless), Parameter('P7', dimension=un.time), Parameter('P8', dimension=un.current) ], constants=[ Constant('C1', value=10.0, units=un.unitless), Constant('C2', value=1.0, units=un.ohm) ]) self.ref_substituted_a = Dynamics( name='substituted_A', aliases=['A5:=SV1 * SV2 * P8'], regimes=[ Regime('dSV1/dt = -2 * (P1 / P2) / (ARP2 + P3)', 'dSV2/dt = -ADP1 / P7', ('dSV3/dt = -2 * (P1 / P2) * ((P6 ** 2 + ADP1) * P4 * ' 'P5) / ((ARP2 + P3) * C1)'), transitions=[ OnCondition('SV1 > 10', target_regime_name='R2') ], aliases=[Alias('A5', 'SV1 * SV2 * P8 * 2')], name='R1'), Regime('dSV1/dt = -(P1 / P2) / (ARP2 + P3)', 'dSV3/dt = -(P1 / P2) / (ARP2 + P3) * (P6 ** 2 + ADP1)', transitions=[ OnCondition('C2 > (SV1 * P1 / P8)', state_assignments=[ StateAssignment( 'SV1', 'SV1 - (P1 / P2) / P8') ], target_regime_name='R1') ], name='R2') ], analog_ports=[ AnalogReceivePort('ARP1', dimension=un.resistance), AnalogReceivePort('ARP2', dimension=un.charge), AnalogReducePort('ADP1', dimension=un.dimensionless), AnalogSendPort('A5', dimension=un.current) ], parameters=[ Parameter('P1', dimension=un.voltage), Parameter('P2', dimension=un.resistance), Parameter('P3', dimension=un.charge), Parameter('P4', dimension=old_div(un.length, un.current**2)), Parameter('P5', dimension=old_div(un.current**2, un.length)), Parameter('P6', dimension=un.dimensionless), Parameter('P7', dimension=un.time), Parameter('P8', dimension=un.current) ], constants=[ Constant('C1', value=10.0, units=un.unitless), Constant('C2', value=1.0, units=un.ohm) ])
def test_aliases(self): # Signature: name # Forwarding function to self.dynamics.aliases # No Aliases: self.assertEqual( list(Dynamics(name='C1').aliases), [] ) # 2 Aliases C = Dynamics(name='C1', aliases=['G:= 0', 'H:=1']) self.assertEqual(len(list((C.aliases))), 2) self.assertEqual( set(C.alias_names), set(['G', 'H']) ) C = Dynamics(name='C1', aliases=['G:= 0', 'H:=1', Alias('I', '3')]) self.assertEqual(len(list((C.aliases))), 3) self.assertEqual( set(C.alias_names), set(['G', 'H', 'I']) ) # Using DynamicsBlock Parameter: C = Dynamics(name='C1', aliases=['G:= 0', 'H:=1']) self.assertEqual(len(list((C.aliases))), 2) self.assertEqual( set(C.alias_names), set(['G', 'H']) ) C = Dynamics(name='C1', aliases=['G:= 0', 'H:=1', Alias('I', '3')]) self.assertEqual(len(list((C.aliases))), 3) self.assertEqual( set(C.alias_names), set(['G', 'H', 'I']) ) # Invalid Construction: # Invalid Valid String: self.assertRaises( NineMLUsageError, Dynamics, name='C1', aliases=['H=0'] ) # Duplicate Alias Names: Dynamics(name='C1', aliases=['H:=0', 'G:=1']) self.assertRaises( NineMLUsageError, Dynamics, name='C1', aliases=['H:=0', 'H:=1'] ) self.assertRaises( NineMLUsageError, Dynamics, name='C1', aliases=['H:=0', Alias('H', '1')] ) # Self referential aliases: self.assertRaises( NineMLUsageError, Dynamics, name='C1', aliases=['H := H +1'], ) self.assertRaises( NineMLUsageError, Dynamics, name='C1', aliases=['H := G + 1', 'G := H + 1'], ) # Referencing none existent symbols: self.assertRaises( NineMLUsageError, Dynamics, name='C1', aliases=['H := G + I'], parameters=['P1'], ) # Invalid Names: self.assertRaises( NineMLUsageError, Dynamics, name='C1', aliases=['H.2 := 0'], ) self.assertRaises( NineMLUsageError, Dynamics, name='C1', aliases=['2H := 0'], ) self.assertRaises( NineMLUsageError, Dynamics, name='C1', aliases=['E(H) := 0'], ) self.assertRaises( NineMLUsageError, Dynamics, name='C1', aliases=['tanh := 0'], ) self.assertRaises( NineMLUsageError, Dynamics, name='C1', aliases=['t := 0'], )