def update_abstract_code(self): # Update the not_refractory variable for the refractory period mechanism ref = self.group._refractory if ref is None: # No refractoriness self.abstract_code = '' elif isinstance(ref, Quantity): self.abstract_code = 'not_refractory = 1*((t - lastspike) > %f)\n' % ref else: namespace = self.group.namespace unit = parse_expression_unit(str(ref), namespace, self.group.variables) if have_same_dimensions(unit, second): self.abstract_code = 'not_refractory = 1*((t - lastspike) > %s)\n' % ref elif have_same_dimensions(unit, Unit(1)): if not is_boolean_expression(str(ref), namespace, self.group.variables): raise TypeError(('Refractory expression is dimensionless ' 'but not a boolean value. It needs to ' 'either evaluate to a timespan or to a ' 'boolean value.')) # boolean condition # we have to be a bit careful here, we can't just use the given # condition as it is, because we only want to *leave* # refractoriness, based on the condition self.abstract_code = 'not_refractory = 1*(not_refractory or not (%s))\n' % ref else: raise TypeError(('Refractory expression has to evaluate to a ' 'timespan or a boolean value, expression' '"%s" has units %s instead') % (ref, unit)) self.abstract_code += self.method(self.group.equations, self.group.variables)
def spatialneuron_segment(neuron, item): ''' Selects a segment from `SpatialNeuron` neuron, where item is a slice of either compartment indexes or distances. Note a: segment is not a `SpatialNeuron`, only a `Group`. ''' if isinstance(item, slice) and isinstance(item.start, Quantity): if item.step is not None: raise ValueError('Cannot specify a step size for slicing based' 'on length.') start, stop = item.start, item.stop if (not have_same_dimensions(start, meter) or not have_same_dimensions(stop, meter)): raise DimensionMismatchError('Start and stop should have units ' 'of meter', start, stop) # Convert to integers (compartment numbers) indices = neuron.morphology.indices[item] start, stop = indices[0], indices[-1] + 1 elif not isinstance(item, slice) and hasattr(item, 'indices'): start, stop = to_start_stop(item.indices[:], neuron._N) else: start, stop = to_start_stop(item, neuron._N) if start >= stop: raise IndexError('Illegal start/end values for subgroup, %d>=%d' % (start, stop)) return Subgroup(neuron, start, stop)
def _get_refractory_code(self, run_namespace, level=0): ref = self.group._refractory if ref is False: # No refractoriness abstract_code = '' elif isinstance(ref, Quantity): abstract_code = 'not_refractory = (t - lastspike) > %f\n' % ref else: identifiers = get_identifiers(ref) variables = self.group.resolve_all(identifiers, identifiers, run_namespace=run_namespace, level=level + 1) unit = parse_expression_unit(str(ref), variables) if have_same_dimensions(unit, second): abstract_code = 'not_refractory = (t - lastspike) > %s\n' % ref elif have_same_dimensions(unit, Unit(1)): if not is_boolean_expression(str(ref), variables): raise TypeError(('Refractory expression is dimensionless ' 'but not a boolean value. It needs to ' 'either evaluate to a timespan or to a ' 'boolean value.')) # boolean condition # we have to be a bit careful here, we can't just use the given # condition as it is, because we only want to *leave* # refractoriness, based on the condition abstract_code = 'not_refractory = not_refractory or not (%s)\n' % ref else: raise TypeError(('Refractory expression has to evaluate to a ' 'timespan or a boolean value, expression' '"%s" has units %s instead') % (ref, unit)) return abstract_code
def spatialneuron_segment(neuron, item): """ Selects a segment from `SpatialNeuron` neuron, where item is a slice of either compartment indexes or distances. Note a: segment is not a `SpatialNeuron`, only a `Group`. """ if isinstance(item, slice) and isinstance(item.start, Quantity): if item.step is not None: raise ValueError("Cannot specify a step size for slicing based" "on length.") start, stop = item.start, item.stop if (not have_same_dimensions(start, meter) or not have_same_dimensions(stop, meter)): raise DimensionMismatchError("Start and stop should have units " "of meter", start, stop) # Convert to integers (compartment numbers) indices = neuron.morphology.indices[item] start, stop = indices[0], indices[-1] + 1 elif not isinstance(item, slice) and hasattr(item, 'indices'): start, stop = to_start_stop(item.indices[:], neuron._N) else: start, stop = to_start_stop(item, neuron._N) if isinstance(neuron, SpatialSubgroup): start += neuron.start stop += neuron.start if start >= stop: raise IndexError(f'Illegal start/end values for subgroup, {int(start)}>={int(stop)}') if isinstance(neuron, SpatialSubgroup): # Note that the start/stop values calculated above are always # absolute values, even for subgroups neuron = neuron.source return Subgroup(neuron, start, stop)
def _get_refractory_code(self, run_namespace, level=0): ref = self.group._refractory if ref is False: # No refractoriness abstract_code = '' elif isinstance(ref, Quantity): abstract_code = 'not_refractory = (t - lastspike) > %f\n' % ref else: identifiers = get_identifiers(ref) variables = self.group.resolve_all(identifiers, identifiers, run_namespace=run_namespace, level=level+1) unit = parse_expression_unit(str(ref), variables) if have_same_dimensions(unit, second): abstract_code = 'not_refractory = (t - lastspike) > %s\n' % ref elif have_same_dimensions(unit, Unit(1)): if not is_boolean_expression(str(ref), variables): raise TypeError(('Refractory expression is dimensionless ' 'but not a boolean value. It needs to ' 'either evaluate to a timespan or to a ' 'boolean value.')) # boolean condition # we have to be a bit careful here, we can't just use the given # condition as it is, because we only want to *leave* # refractoriness, based on the condition abstract_code = 'not_refractory = not_refractory or not (%s)\n' % ref else: raise TypeError(('Refractory expression has to evaluate to a ' 'timespan or a boolean value, expression' '"%s" has units %s instead') % (ref, unit)) return abstract_code
def spatialneuron_segment(neuron, item): ''' Selects a segment from `SpatialNeuron` neuron, where item is a slice of either compartment indexes or distances. Note a: segment is not a `SpatialNeuron`, only a `Group`. ''' if not isinstance(item, slice): raise TypeError( 'Subgroups can only be constructed using slicing syntax') start, stop, step = item.start, item.stop, item.step if step is None: step = 1 if step != 1: raise IndexError('Subgroups have to be contiguous') if isinstance(start, Quantity): if not have_same_dimensions( start, meter) or not have_same_dimensions(stop, meter): raise DimensionMismatchError( 'Start and stop should have units of meter', start, stop) # Convert to integers (compartment numbers) indices = neuron.morphology.indices[item] start, stop = indices[0], indices[-1] + 1 if start >= stop: raise IndexError('Illegal start/end values for subgroup, %d>=%d' % (start, stop)) return Subgroup(neuron, start, stop)
def assert_quantity(q, values, unit): assert isinstance(q, Quantity) or (isinstance(q, np.ndarray) and have_same_dimensions(unit, 1)) assert_equal(np.asarray(q), values) assert have_same_dimensions( q, unit), ('Dimension mismatch: (%s) (%s)' % (get_dimensions(q), get_dimensions(unit)))
def spatialneuron_segment(neuron, item): ''' Selects a segment from `SpatialNeuron` neuron, where item is a slice of either compartment indexes or distances. Note a: segment is not a `SpatialNeuron`, only a `Group`. ''' if not isinstance(item, slice): raise TypeError( 'Subgroups can only be constructed using slicing syntax') start, stop, step = item.start, item.stop, item.step if step is None: step = 1 if step != 1: raise IndexError('Subgroups have to be contiguous') if isinstance(start, Quantity): if not have_same_dimensions(start, meter) or not have_same_dimensions(stop, meter): raise DimensionMismatchError('Start and stop should have units of meter', start, stop) # Convert to integers (compartment numbers) indices = neuron.morphology.indices[item] start, stop = indices[0], indices[-1] + 1 if start >= stop: raise IndexError('Illegal start/end values for subgroup, %d>=%d' % (start, stop)) return Subgroup(neuron, start, stop)
def get_refractory_code(group): ref = group._refractory if ref is False: # No refractoriness abstract_code = '' elif isinstance(ref, Quantity): abstract_code = 'not_refractory = 1*((t - lastspike) > %f)\n' % ref else: namespace = group.namespace unit = parse_expression_unit(str(ref), namespace, group.variables) if have_same_dimensions(unit, second): abstract_code = 'not_refractory = 1*((t - lastspike) > %s)\n' % ref elif have_same_dimensions(unit, Unit(1)): if not is_boolean_expression(str(ref), namespace, group.variables): raise TypeError(('Refractory expression is dimensionless ' 'but not a boolean value. It needs to ' 'either evaluate to a timespan or to a ' 'boolean value.')) # boolean condition # we have to be a bit careful here, we can't just use the given # condition as it is, because we only want to *leave* # refractoriness, based on the condition abstract_code = 'not_refractory = 1*(not_refractory or not (%s))\n' % ref else: raise TypeError(('Refractory expression has to evaluate to a ' 'timespan or a boolean value, expression' '"%s" has units %s instead') % (ref, unit)) return abstract_code
def assert_quantity(q, values, unit): assert isinstance(q, Quantity) or (have_same_dimensions(unit, 1) and (values.shape == () or isinstance(q, np.ndarray))), q assert_allclose(np.asarray(q), values) assert have_same_dimensions(q, unit), ('Dimension mismatch: (%s) (%s)' % (get_dimensions(q), get_dimensions(unit)))
def assert_quantity(q, values, unit): assert isinstance( q, Quantity) or (have_same_dimensions(unit, 1) and (values.shape == () or isinstance(q, np.ndarray))), q assert_allclose(np.asarray(q), values) assert have_same_dimensions( q, unit), ('Dimension mismatch: (%s) (%s)' % (get_dimensions(q), get_dimensions(unit)))
def __call__(self, value): if not isinstance(value, self.value.__class__): return False if isinstance(self.value, Quantity): if not have_same_dimensions(self.value, value): return False return True
def __getattr__(self, item): # We do this because __setattr__ and __getattr__ are not active until # _group_attribute_access_active attribute is set, and if it is set, # then __getattr__ will not be called. Therefore, if getattr is called # with this name, it is because it hasn't been set yet and so this # method should raise an AttributeError to agree that it hasn't been # called yet. if item == '_group_attribute_access_active': raise AttributeError if not hasattr(self, '_group_attribute_access_active'): raise AttributeError # TODO: Decide about the interface if item == 't': return Quantity(self._t.data.copy(), dim=second.dim) elif item == 't_': return self._t.data.copy() elif item in self.record_variables: unit = self.variables[item].unit if have_same_dimensions(unit, 1): return self._values[item].data.T.copy() else: return Quantity(self._values[item].data.T.copy(), dim=unit.dim) elif item.endswith('_') and item[:-1] in self.record_variables: return self._values[item[:-1]].data.T.copy() else: raise AttributeError('Unknown attribute %s' % item)
def __init__(self, type, varname, unit, var_type=FLOAT, expr=None, flags=None): self.type = type self.varname = varname self.unit = unit self.var_type = var_type if not have_same_dimensions(unit, 1): if var_type == BOOLEAN: raise TypeError( 'Boolean variables are necessarily dimensionless.') elif var_type == INTEGER: raise TypeError( 'Integer variables are necessarily dimensionless.') if type == DIFFERENTIAL_EQUATION: if var_type != FLOAT: raise TypeError( 'Differential equations can only define floating point variables' ) self.expr = expr if flags is None: self.flags = [] else: self.flags = flags # will be set later in the sort_subexpressions method of Equations self.update_order = -1
def __init__(self, name, unit, dtype=None, scalar=False, constant=False, read_only=False): #: The variable's unit. self.unit = unit #: The variable's name. self.name = name #: The dtype used for storing the variable. self.dtype = dtype if dtype is None: self.dtype = brian_prefs.core.default_float_dtype if self.is_boolean: if not have_same_dimensions(unit, 1): raise ValueError('Boolean variables can only be dimensionless') #: Whether the variable is a scalar self.scalar = scalar #: Whether the variable is constant during a run self.constant = constant #: Whether the variable is read-only self.read_only = read_only
def __init__(self, name, unit, dtype=None, scalar=False, constant=False, is_bool=False, read_only=False): #: The variable's unit. self.unit = unit #: The variable's name. self.name = name #: The dtype used for storing the variable. self.dtype = default_dtype(dtype, is_bool) #: Whether this variable is a boolean self.is_bool = is_bool if is_bool: if not have_same_dimensions(unit, 1): raise ValueError('Boolean variables can only be dimensionless') #: Whether the variable is a scalar self.scalar = scalar #: Whether the variable is constant during a run self.constant = constant #: Whether the variable is read-only self.read_only = read_only
def assert_allclose(actual, desired, rtol=4.5e8, atol=0, **kwds): ''' Thin wrapper around numpy's `~numpy.testing.utils.assert_allclose` function. The tolerance depends on the floating point precision as defined by the `core.default_float_dtype` preference. Parameters ---------- actual : `numpy.ndarray` The results to check. desired : `numpy.ndarray` The expected results. rtol : float, optional The relative tolerance which will be multiplied with the machine epsilon of the type set as `core.default_float_type`. atol : float, optional The absolute tolerance which will be multiplied with the machine epsilon of the type set as `core.default_float_type`. ''' assert have_same_dimensions(actual, desired) eps = np.finfo(prefs['core.default_float_dtype']).eps rtol = eps * rtol atol = eps * atol numpy_allclose(np.asarray(actual), np.asarray(desired), rtol=rtol, atol=atol, **kwds)
def test_state_variable_access(): for codeobj_class in codeobj_classes: G = NeuronGroup(10, 'v:volt', codeobj_class=codeobj_class) G.v = np.arange(10) * volt assert_equal(np.asarray(G.v[:]), np.arange(10)) assert have_same_dimensions(G.v[:], volt) assert_equal(np.asarray(G.v[:]), G.v_[:]) # Accessing single elements, slices and arrays assert G.v[5] == 5 * volt assert G.v_[5] == 5 assert_equal(G.v[:5], np.arange(5) * volt) assert_equal(G.v_[:5], np.arange(5)) assert_equal(G.v[[0, 5]], [0, 5] * volt) assert_equal(G.v_[[0, 5]], np.array([0, 5])) # Illegal indexing assert_raises(IndexError, lambda: G.v[0, 0]) assert_raises(IndexError, lambda: G.v_[0, 0]) assert_raises(TypeError, lambda: G.v[object()]) assert_raises(TypeError, lambda: G.v_[object()]) # A string representation should not raise any error assert len(str(G.v)) assert len(repr(G.v)) assert len(str(G.v_)) assert len(repr(G.v_))
def test_state_variable_access(): G = NeuronGroup(10, 'v:volt') G.v = np.arange(10) * volt assert_equal(np.asarray(G.v[:]), np.arange(10)) assert have_same_dimensions(G.v[:], volt) assert_equal(np.asarray(G.v[:]), G.v_[:]) # Accessing single elements, slices and arrays assert G.v[5] == 5 * volt assert G.v_[5] == 5 assert_equal(G.v[:5], np.arange(5) * volt) assert_equal(G.v_[:5], np.arange(5)) assert_equal(G.v[[0, 5]], [0, 5] * volt) assert_equal(G.v_[[0, 5]], np.array([0, 5])) # Illegal indexing assert_raises(IndexError, lambda: G.v[0, 0]) assert_raises(IndexError, lambda: G.v_[0, 0]) assert_raises(TypeError, lambda: G.v[object()]) assert_raises(TypeError, lambda: G.v_[object()]) # Indexing with strings assert G.v['i==2'] == G.v[2] assert G.v_['i==2'] == G.v_[2] assert_equal(G.v['v >= 3*volt'], G.v[3:]) assert_equal(G.v_['v >= 3*volt'], G.v_[3:]) # Should also check for units assert_raises(DimensionMismatchError, lambda: G.v['v >= 3']) assert_raises(DimensionMismatchError, lambda: G.v['v >= 3*second'])
def _get_refractory_code(self, run_namespace): ref = self.group._refractory if ref is False: # No refractoriness abstract_code = "" elif isinstance(ref, Quantity): fail_for_dimension_mismatch( ref, second, ("Refractory period has to " "be specified in units " "of seconds but got " "{value}"), value=ref, ) abstract_code = "not_refractory = (t - lastspike) > %f\n" % ref else: identifiers = get_identifiers(ref) variables = self.group.resolve_all(identifiers, identifiers, run_namespace=run_namespace) unit = parse_expression_unit(str(ref), variables) if have_same_dimensions(unit, second): abstract_code = "not_refractory = (t - lastspike) > %s\n" % ref elif have_same_dimensions(unit, Unit(1)): if not is_boolean_expression(str(ref), variables): raise TypeError( ( "Refractory expression is dimensionless " "but not a boolean value. It needs to " "either evaluate to a timespan or to a " "boolean value." ) ) # boolean condition # we have to be a bit careful here, we can't just use the given # condition as it is, because we only want to *leave* # refractoriness, based on the condition abstract_code = "not_refractory = not_refractory or not (%s)\n" % ref else: raise TypeError( ( "Refractory expression has to evaluate to a " "timespan or a boolean value, expression" '"%s" has units %s instead' ) % (ref, unit) ) return abstract_code
def test_properties(): ''' Test accessing the various properties of equation objects ''' tau = 10 * ms eqs = Equations('''dv/dt = -(v + I)/ tau : volt I = sin(2 * 22/7. * f * t)* volt : volt f = freq * Hz: Hz freq : 1''') assert (len(eqs.diff_eq_expressions) == 1 and eqs.diff_eq_expressions[0][0] == 'v' and isinstance(eqs.diff_eq_expressions[0][1], Expression)) assert eqs.diff_eq_names == {'v'} assert (len(eqs.eq_expressions) == 3 and set([name for name, _ in eqs.eq_expressions]) == {'v', 'I', 'f'} and all((isinstance(expr, Expression) for _, expr in eqs.eq_expressions))) assert len(eqs.eq_names) == 3 and eqs.eq_names == {'v', 'I', 'f'} assert set(eqs.keys()) == {'v', 'I', 'f', 'freq'} # test that the equations object is iterable itself assert all((isinstance(eq, SingleEquation) for eq in eqs.itervalues())) assert all((isinstance(eq, basestring) for eq in eqs)) assert (len(eqs.ordered) == 4 and all( (isinstance(eq, SingleEquation) for eq in eqs.ordered)) and [eq.varname for eq in eqs.ordered] == ['f', 'I', 'v', 'freq']) assert eqs.names == {'v', 'I', 'f', 'freq'} assert eqs.parameter_names == {'freq'} assert eqs.subexpr_names == {'I', 'f'} units = eqs.units assert set(units.keys()) == {'v', 'I', 'f', 'freq'} assert units['v'] == volt assert units['I'] == volt assert units['f'] == Hz assert have_same_dimensions(units['freq'], 1) assert eqs.names == set(eqs.units.keys()) assert eqs.identifiers == {'tau', 'volt', 'Hz', 'sin', 't'} # stochastic equations assert len(eqs.stochastic_variables) == 0 assert eqs.stochastic_type is None eqs = Equations('''dv/dt = -v / tau + 0.1*second**-.5*xi : 1''') assert eqs.stochastic_variables == {'xi'} assert eqs.stochastic_type == 'additive' eqs = Equations( '''dv/dt = -v / tau + 0.1*second**-.5*xi_1 + 0.1*second**-.5*xi_2: 1''' ) assert eqs.stochastic_variables == {'xi_1', 'xi_2'} assert eqs.stochastic_type == 'additive' eqs = Equations('''dv/dt = -v / tau + 0.1*second**-1.5*xi*t : 1''') assert eqs.stochastic_type == 'multiplicative' eqs = Equations('''dv/dt = -v / tau + 0.1*second**-1.5*xi*v : 1''') assert eqs.stochastic_type == 'multiplicative'
def test_list(): ''' Test converting to and from a list. ''' values = [3 * mV, np.array([1, 2]) * mV, np.arange(12).reshape(4, 3) * mV] for value in values: l = value.tolist() from_list = Quantity(l) assert have_same_dimensions(from_list, value) assert_equal(from_list, value)
def __init__(self, target, target_var, N, rate, weight, when='synapses', order=0): if target_var not in target.variables: raise KeyError('%s is not a variable of %s' % (target_var, target.name)) self._weight = weight self._target_var = target_var if isinstance(weight, str): weight = '(%s)' % weight else: weight_dims = get_dimensions(weight) target_dims = target.variables[target_var].dim # This will be checked automatically in the abstract code as well # but doing an explicit check here allows for a clearer error # message if not have_same_dimensions(weight_dims, target_dims): raise DimensionMismatchError( ('The provided weight does not ' 'have the same unit as the ' 'target variable "%s"') % target_var, weight_dims, target_dims) weight = repr(weight) self._N = N self._rate = rate binomial_sampling = BinomialFunction(N, rate * target.clock.dt, name='poissoninput_binomial*') code = '{targetvar} += {binomial}()*{weight}'.format( targetvar=target_var, binomial=binomial_sampling.name, weight=weight) self._stored_dt = target.dt_[:] # make a copy # FIXME: we need an explicit reference here for on-the-fly subgroups # For example: PoissonInput(group[:N], ...) self._group = target CodeRunner.__init__(self, group=target, template='stateupdate', code=code, user_code='', when=when, order=order, name='poissoninput*', clock=target.clock) self.variables = Variables(self) self.variables._add_variable(binomial_sampling.name, binomial_sampling)
def test_pickling(): ''' Test pickling of units. ''' for q in [500 * mV, 500 * mV/mV, np.arange(10) * mV, np.arange(12).reshape(4, 3) * mV/ms]: pickled = pickle.dumps(q) unpickled = pickle.loads(pickled) assert isinstance(unpickled, type(q)) assert have_same_dimensions(unpickled, q) assert_equal(unpickled, q)
def __getitem__(self, i): variable = self.variable if variable.scalar: if not (i == slice(None) or i == 0 or (hasattr(i, '__len__') and len(i) == 0)): raise IndexError('Variable is a scalar variable.') indices = 0 else: indices = self.group.indices[self.group.variable_indices[self.name]][i] if self.unit is None or have_same_dimensions(self.unit, Unit(1)): return variable.get_value()[indices] else: return Quantity(variable.get_value()[indices], self.unit.dimensions)
def variableview_set_with_index_array(self, variableview, item, value, check_units): if isinstance(item, slice) and item == slice(None): item = 'True' value = Quantity(value) if (isinstance(item, int) or (isinstance(item, np.ndarray) and item.shape == ())) and value.size == 1: array_name = self.get_array_name(variableview.variable, access_data=False) # For a single assignment, generate a code line instead of storing the array self.main_queue.append( ('set_by_single_value', (array_name, item, float(value)))) elif (value.size == 1 and item == 'True' and variableview.index_var_name == '_idx'): # set the whole array to a scalar value if have_same_dimensions(value, 1): # Avoid a representation as "Quantity(...)" or "array(...)" value = float(value) variableview.set_with_expression_conditional( cond=item, code=repr(value), check_units=check_units) # Simple case where we don't have to do any indexing elif (item == 'True' and variableview.index_var == '_idx'): self.fill_with_array(variableview.variable, value) else: # We have to calculate indices. This will not work for synaptic # variables try: indices = np.asarray( variableview.indexing(item, index_var=variableview.index_var)) except NotImplementedError: raise NotImplementedError( ('Cannot set variable "%s" this way in ' 'standalone, try using string ' 'expressions.') % variableview.name) # Using the std::vector instead of a pointer to the underlying # data for dynamic arrays is fast enough here and it saves us some # additional work to set up the pointer arrayname = self.get_array_name(variableview.variable, access_data=False) staticarrayname_index = self.static_array('_index_' + arrayname, indices) if (indices.shape != () and (value.shape == () or (value.size == 1 and indices.size > 1))): value = np.repeat(value, indices.size) staticarrayname_value = self.static_array('_value_' + arrayname, value) self.main_queue.append( ('set_array_by_array', (arrayname, staticarrayname_index, staticarrayname_value)))
def test_properties(): ''' Test accessing the various properties of equation objects ''' tau = 10 * ms eqs = Equations('''dv/dt = -(v + I)/ tau : volt I = sin(2 * 22/7. * f * t)* volt : volt f = freq * Hz: Hz freq : 1''') assert (len(eqs.diff_eq_expressions) == 1 and eqs.diff_eq_expressions[0][0] == 'v' and isinstance(eqs.diff_eq_expressions[0][1], Expression)) assert eqs.diff_eq_names == {'v'} assert (len(eqs.eq_expressions) == 3 and set([name for name, _ in eqs.eq_expressions]) == {'v', 'I', 'f'} and all((isinstance(expr, Expression) for _, expr in eqs.eq_expressions))) assert len(eqs.eq_names) == 3 and eqs.eq_names == {'v', 'I', 'f'} assert set(eqs.keys()) == {'v', 'I', 'f', 'freq'} # test that the equations object is iterable itself assert all((isinstance(eq, SingleEquation) for eq in eqs.itervalues())) assert all((isinstance(eq, basestring) for eq in eqs)) assert (len(eqs.ordered) == 4 and all((isinstance(eq, SingleEquation) for eq in eqs.ordered)) and [eq.varname for eq in eqs.ordered] == ['f', 'I', 'v', 'freq']) assert eqs.names == {'v', 'I', 'f', 'freq'} assert eqs.parameter_names == {'freq'} assert eqs.subexpr_names == {'I', 'f'} units = eqs.units assert set(units.keys()) == {'v', 'I', 'f', 'freq'} assert units['v'] == volt assert units['I'] == volt assert units['f'] == Hz assert have_same_dimensions(units['freq'], 1) assert eqs.names == set(eqs.units.keys()) assert eqs.identifiers == {'tau', 'volt', 'Hz', 'sin', 't'} # stochastic equations assert len(eqs.stochastic_variables) == 0 assert eqs.stochastic_type is None eqs = Equations('''dv/dt = -v / tau + 0.1*second**-.5*xi : 1''') assert eqs.stochastic_variables == {'xi'} assert eqs.stochastic_type == 'additive' eqs = Equations('''dv/dt = -v / tau + 0.1*second**-.5*xi_1 + 0.1*second**-.5*xi_2: 1''') assert eqs.stochastic_variables == {'xi_1', 'xi_2'} assert eqs.stochastic_type == 'additive' eqs = Equations('''dv/dt = -v / tau + 0.1*second**-1.5*xi*t : 1''') assert eqs.stochastic_type == 'multiplicative' eqs = Equations('''dv/dt = -v / tau + 0.1*second**-1.5*xi*v : 1''') assert eqs.stochastic_type == 'multiplicative'
def variableview_set_with_index_array(self, variableview, item, value, check_units): if isinstance(item, slice) and item == slice(None): item = 'True' value = Quantity(value) if (isinstance(item, int) or (isinstance(item, np.ndarray) and item.shape==())) and value.size == 1: array_name = self.get_array_name(variableview.variable, access_data=False) # For a single assignment, generate a code line instead of storing the array self.main_queue.append(('set_by_single_value', (array_name, item, float(value)))) elif (value.size == 1 and item == 'True' and variableview.index_var_name == '_idx'): # set the whole array to a scalar value if have_same_dimensions(value, 1): # Avoid a representation as "Quantity(...)" or "array(...)" value = float(value) variableview.set_with_expression_conditional(cond=item, code=repr(value), check_units=check_units) # Simple case where we don't have to do any indexing elif (item == 'True' and variableview.index_var == '_idx'): self.fill_with_array(variableview.variable, value) else: # We have to calculate indices. This will not work for synaptic # variables try: indices = np.asarray(variableview.indexing(item, index_var=variableview.index_var)) except NotImplementedError: raise NotImplementedError(('Cannot set variable "%s" this way in ' 'standalone, try using string ' 'expressions.') % variableview.name) # Using the std::vector instead of a pointer to the underlying # data for dynamic arrays is fast enough here and it saves us some # additional work to set up the pointer arrayname = self.get_array_name(variableview.variable, access_data=False) staticarrayname_index = self.static_array('_index_'+arrayname, indices) if (indices.shape != () and (value.shape == () or (value.size == 1 and indices.size > 1))): value = np.repeat(value, indices.size) staticarrayname_value = self.static_array('_value_'+arrayname, value) self.main_queue.append(('set_array_by_array', (arrayname, staticarrayname_index, staticarrayname_value)))
def test_switching_off_unit_checks(): ''' Check switching off unit checks (used for external functions). ''' import brian2.units.fundamentalunits as fundamentalunits x = 3 * second y = 5 * volt assert_raises(DimensionMismatchError, lambda: x + y) fundamentalunits.unit_checking = False # Now it should work assert np.asarray(x + y) == np.array(8) assert have_same_dimensions(x, y) assert x.has_same_dimensions(y) fundamentalunits.unit_checking = True
def __init__(self, type, varname, unit, is_bool=False, expr=None, flags=None): self.type = type self.varname = varname self.unit = unit self.is_bool = is_bool if is_bool and not have_same_dimensions(unit, 1): raise ValueError('Boolean variables are necessarily dimensionless.') self.expr = expr if flags is None: self.flags = [] else: self.flags = flags # will be set later in the sort_static_equations method of Equations self.update_order = -1
def __call__(self, value): if not isinstance(value, self.value.__class__): try: valstr = str(value) except: valstr = '(undefined)' raise TypeError('Parameter {self.name} should be of class ' '{self.value.__class__.__name__}, value {valstr} ' 'has class {value.__class__.__name__}'.format( self=self, value=value, valstr=valstr)) if isinstance(self.value, Quantity): if not have_same_dimensions(self.value, value): raise DimensionMismatchError('Parameter {self.name}, current ' 'value = {self.value}, tried to ' 'change to {value}'.format( self=self, value=value))
def __init__(self, target, target_var, N, rate, weight, when='synapses', order=0): if target_var not in target.variables: raise KeyError('%s is not a variable of %s' % (target_var, target.name)) if isinstance(weight, basestring): weight = '(%s)' % weight else: weight_unit = get_unit(weight) weight = repr(weight) target_unit = target.variables[target_var].unit # This will be checked automatically in the abstract code as well # but doing an explicit check here allows for a clearer error # message if not have_same_dimensions(weight_unit, target_unit): raise DimensionMismatchError(('The provided weight does not ' 'have the same unit as the ' 'target variable "%s"') % target_var, weight_unit.dim, target_unit.dim) binomial_sampling = BinomialFunction(N, rate*target.clock.dt, name='poissoninput_binomial*') code = '{targetvar} += {binomial}()*{weight}'.format(targetvar=target_var, binomial=binomial_sampling.name, weight=weight) self._stored_dt = target.dt_[:] # make a copy # FIXME: we need an explicit reference here for on-the-fly subgroups # For example: PoissonInput(group[:N], ...) self._group = target CodeRunner.__init__(self, group=target, template='stateupdate', code=code, user_code='', when=when, order=order, name='poissoninput*', clock=target.clock ) self.variables = Variables(self) self.variables._add_variable(binomial_sampling.name, binomial_sampling)
def __init__(self, unit, value=None, dtype=None, scalar=None, constant=False, is_bool=None): #: The variable's unit. self.unit = unit #: reference to a value of type `dtype` self.value = value if dtype is None: self.dtype = get_dtype(value) else: value_dtype = get_dtype(value) if value is not None and value_dtype != dtype: raise TypeError(('Conflicting dtype information: ' 'referred value has dtype %r, not ' '%r.') % (value_dtype, dtype)) #: The dtype used for storing the variable. self.dtype = dtype if is_bool is None: if value is None: self.is_bool = False self.is_bool = value is True or value is False else: #: Whether this variable is a boolean self.is_bool = is_bool if is_bool: if not have_same_dimensions(unit, 1): raise ValueError('Boolean variables can only be dimensionless') if scalar is None: if value is None: self.scalar = True self.scalar = is_scalar_type(value) else: #: Whether the variable is a scalar self.scalar = scalar #: Whether the variable is constant during a run self.constant = constant
def __init__(self, type, varname, unit, var_type=FLOAT, expr=None, flags=None): self.type = type self.varname = varname self.unit = unit self.var_type = var_type if not have_same_dimensions(unit, 1): if var_type == BOOLEAN: raise TypeError("Boolean variables are necessarily dimensionless.") elif var_type == INTEGER: raise TypeError("Integer variables are necessarily dimensionless.") if type == DIFFERENTIAL_EQUATION: if var_type != FLOAT: raise TypeError("Differential equations can only define floating point variables") self.expr = expr if flags is None: self.flags = [] else: self.flags = flags # will be set later in the sort_subexpressions method of Equations self.update_order = -1
def __init__(self, type, varname, unit, is_bool=False, expr=None, flags=None): self.type = type self.varname = varname self.unit = unit self.is_bool = is_bool if is_bool and not have_same_dimensions(unit, 1): raise ValueError( 'Boolean variables are necessarily dimensionless.') self.expr = expr if flags is None: self.flags = [] else: self.flags = flags # will be set later in the sort_static_equations method of Equations self.update_order = -1
def assert_allclose(actual, desired, rtol=4.5e8, atol=0, **kwds): ''' Thin wrapper around numpy's `~numpy.testing.utils.assert_allclose` function. The tolerance depends on the floating point precision as defined by the `core.default_float_dtype` preference. Parameters ---------- actual : `numpy.ndarray` The results to check. desired : `numpy.ndarray` The expected results. rtol : float, optional The relative tolerance which will be multiplied with the machine epsilon of the type set as `core.default_float_type`. atol : float, optional The absolute tolerance which will be multiplied with the machine epsilon of the type set as `core.default_float_type`. ''' assert have_same_dimensions(actual, desired) eps = np.finfo(prefs['core.default_float_dtype']).eps rtol = eps*rtol atol = eps*atol numpy_allclose(np.asarray(actual), np.asarray(desired), rtol=rtol, atol=atol, **kwds)
def test_properties(): ''' Test accessing the various properties of equation objects ''' tau = 10 * ms eqs = Equations('''dv/dt = -(v + I)/ tau : volt I = sin(2 * 22/7. * f * t)* volt : volt f = freq * Hz: Hz freq : 1''') assert (len(eqs.diff_eq_expressions) == 1 and eqs.diff_eq_expressions[0][0] == 'v' and isinstance(eqs.diff_eq_expressions[0][1], Expression)) assert eqs.diff_eq_names == ['v'] assert (len(eqs.eq_expressions) == 3 and set([name for name, _ in eqs.eq_expressions]) == set(['v', 'I', 'f']) and all((isinstance(expr, Expression) for _, expr in eqs.eq_expressions))) assert len(eqs.eq_names) == 3 and set(eqs.eq_names) == set(['v', 'I', 'f']) assert set(eqs.equations.keys()) == set(['v', 'I', 'f', 'freq']) assert all((isinstance(eq, SingleEquation) for eq in eqs.equations.itervalues())) # test that the equations object is iterable itself assert all((isinstance(eq, SingleEquation) for (_, eq) in eqs)) assert (len(eqs.equations_ordered) == 4 and all((isinstance(eq, SingleEquation) for eq in eqs.equations_ordered)) and [eq.varname for eq in eqs.equations_ordered] == ['f', 'I', 'v', 'freq']) assert set(eqs.names) == set(['v', 'I', 'f', 'freq']) assert set(eqs.parameter_names) == set(['freq']) assert set(eqs.static_eq_names) == set(['I', 'f']) units = eqs.units assert set(units.keys()) == set(['v', 'I', 'f', 'freq', 't', 'dt', 'xi']) assert units['v'] == volt assert units['I'] == volt assert units['f'] == Hz assert have_same_dimensions(units['freq'], 1) assert units['t'] == second assert units['dt'] == second assert units['xi'] == second**-0.5 assert set(eqs.variables) == set(eqs.units.keys())
def group_set_with_index_array(self, group, variable_name, variable, item, value, check_units): if isinstance(item, slice) and item == slice(None): item = 'True' value = Quantity(value) if value.size == 1 and item == 'True': # set the whole array to a scalar value if have_same_dimensions(value, 1): # Avoid a representation as "Quantity(...)" or "array(...)" value = float(value) group.set_with_expression_conditional(variable_name, variable, cond=item, code=repr(value), check_units=check_units) # Simple case where we don't have to do any indexing elif item == 'True' and group.variables.indices[variable_name] == '_idx': self.fill_with_array(variable, value) else: # We have to calculate indices. This will not work for synaptic # variables try: indices = group.calc_indices(item) except NotImplementedError: raise NotImplementedError(('Cannot set variable "%s" this way in ' 'standalone, try using string ' 'expressions.') % variable_name) # Using the std::vector instead of a pointer to the underlying # data for dynamic arrays is fast enough here and it saves us some # additional work to set up the pointer arrayname = self.get_array_name(variable, access_data=False) staticarrayname_index = self.static_array('_index_'+arrayname, indices) staticarrayname_value = self.static_array('_value_'+arrayname, value) self.main_queue.append(('set_array_by_array', (arrayname, staticarrayname_index, staticarrayname_value)))
def __init__(self, filterbank, targetvar, *args, **kwds): # Make sure we're not in standalone mode (which won't work) if not isinstance(get_device(), RuntimeDevice): raise RuntimeError("Cannot use standalone mode with brian2hears") self.targetvar = targetvar self.filterbank = filterbank filterbank.buffer_init() # Sanitize the clock - does it have the right dt value? if 'clock' in kwds: if int(1/kwds['clock'].dt)!=int(filterbank.samplerate): raise ValueError('Clock should have 1/dt=samplerate') elif 'dt' in kwds: if int(1 / kwds['dt']) != int(filterbank.samplerate): raise ValueError('Require 1/dt=samplerate') else: kwds['dt'] = 1/filterbank.samplerate buffersize = kwds.pop('buffersize', 32) if not isinstance(buffersize, int): if not have_same_dimensions(buffersize, second): raise DimensionMismatchError("buffersize argument should be an integer or in seconds") buffersize = int(buffersize*filterbank.samplerate) self.buffersize = buffersize self.apply_filterbank = ApplyFilterbank(self, targetvar, filterbank, buffersize) NeuronGroup.__init__(self, filterbank.nchannels, *args, **kwds) if self.variables[targetvar].dim is not DIMENSIONLESS: raise DimensionMismatchError("Target variable must be dimensionless") apply_filterbank_output = NetworkOperation(self.apply_filterbank.__call__, when='start', clock=self.clock) self.contained_objects.append(apply_filterbank_output)
def parse_expression_dimensions(expr, variables): ''' Returns the unit value of an expression, and checks its validity Parameters ---------- expr : str The expression to check. variables : dict Dictionary of all variables used in the `expr` (including `Constant` objects for external variables) Returns ------- unit : Quantity The output unit of the expression Raises ------ SyntaxError If the expression cannot be parsed, or if it uses ``a**b`` for ``b`` anything other than a constant number. DimensionMismatchError If any part of the expression is dimensionally inconsistent. ''' # If we are working on a string, convert to the top level node if isinstance(expr, basestring): mod = ast.parse(expr, mode='eval') expr = mod.body if expr.__class__ is getattr(ast, 'NameConstant', None): # new class for True, False, None in Python 3.4 value = expr.value if value is True or value is False: return DIMENSIONLESS else: raise ValueError('Do not know how to handle value %s' % value) if expr.__class__ is ast.Name: name = expr.id # Raise an error if a function is called as if it were a variable # (most of the time this happens for a TimedArray) if name in variables and isinstance(variables[name], Function): raise SyntaxError( '%s was used like a variable/constant, but it is ' 'a function.' % name) if name in variables: return variables[name].dim elif name in ['True', 'False']: return DIMENSIONLESS else: raise KeyError('Unknown identifier %s' % name) elif (expr.__class__ is ast.Num or expr.__class__ is getattr(ast, 'Constant', None)): # Python 3.8 return DIMENSIONLESS elif expr.__class__ is ast.BoolOp: # check that the units are valid in each subexpression for node in expr.values: parse_expression_dimensions(node, variables) # but the result is a bool, so we just return 1 as the unit return DIMENSIONLESS elif expr.__class__ is ast.Compare: # check that the units are consistent in each subexpression subexprs = [expr.left] + expr.comparators subunits = [] for node in subexprs: subunits.append(parse_expression_dimensions(node, variables)) for left_dim, right_dim in zip(subunits[:-1], subunits[1:]): if not have_same_dimensions(left_dim, right_dim): msg = ( 'Comparison of expressions with different units. Expression ' '"{}" has unit ({}), while expression "{}" has units ({})' ).format(NodeRenderer().render_node(expr.left), get_dimensions(left_dim), NodeRenderer().render_node(expr.comparators[0]), get_dimensions(right_dim)) raise DimensionMismatchError(msg) # but the result is a bool, so we just return 1 as the unit return DIMENSIONLESS elif expr.__class__ is ast.Call: if len(expr.keywords): raise ValueError("Keyword arguments not supported.") elif getattr(expr, 'starargs', None) is not None: raise ValueError("Variable number of arguments not supported") elif getattr(expr, 'kwargs', None) is not None: raise ValueError("Keyword arguments not supported") func = variables.get(expr.func.id, None) if func is None: raise SyntaxError('Unknown function %s' % expr.func.id) if not hasattr(func, '_arg_units') or not hasattr( func, '_return_unit'): raise ValueError(('Function %s does not specify how it ' 'deals with units.') % expr.func.id) if len(func._arg_units) != len(expr.args): raise SyntaxError( 'Function %s was called with %d parameters, ' 'needs %d.' % (expr.func.id, len(expr.args), len(func._arg_units))) for idx, (arg, expected_unit) in enumerate(zip(expr.args, func._arg_units)): # A "None" in func._arg_units means: No matter what unit if expected_unit is None: continue elif expected_unit == bool: if not is_boolean_expression(arg, variables): raise TypeError( ('Argument number %d for function %s was ' 'expected to be a boolean value, but is ' '"%s".') % (idx + 1, expr.func.id, NodeRenderer().render_node(arg))) else: arg_unit = parse_expression_dimensions(arg, variables) if not have_same_dimensions(arg_unit, expected_unit): msg = ( 'Argument number {} for function {} does not have the ' 'correct units. Expression "{}" has units ({}), but ' 'should be ({}).').format( idx + 1, expr.func.id, NodeRenderer().render_node(arg), get_dimensions(arg_unit), get_dimensions(expected_unit)) raise DimensionMismatchError(msg) if func._return_unit == bool: return DIMENSIONLESS elif isinstance(func._return_unit, (Unit, int)): # Function always returns the same unit return getattr(func._return_unit, 'dim', DIMENSIONLESS) else: # Function returns a unit that depends on the arguments arg_units = [ parse_expression_dimensions(arg, variables) for arg in expr.args ] return func._return_unit(*arg_units).dim elif expr.__class__ is ast.BinOp: op = expr.op.__class__.__name__ left_dim = parse_expression_dimensions(expr.left, variables) right_dim = parse_expression_dimensions(expr.right, variables) if op in ['Add', 'Sub', 'Mod']: # dimensions should be the same if left_dim is not right_dim: op_symbol = {'Add': '+', 'Sub': '-', 'Mod': '%'}.get(op) left_str = NodeRenderer().render_node(expr.left) right_str = NodeRenderer().render_node(expr.right) left_unit = get_unit_for_display(left_dim) right_unit = get_unit_for_display(right_dim) error_msg = ('Expression "{left} {op} {right}" uses ' 'inconsistent units ("{left}" has unit ' '{left_unit}; "{right}" ' 'has unit {right_unit})').format( left=left_str, right=right_str, op=op_symbol, left_unit=left_unit, right_unit=right_unit) raise DimensionMismatchError(error_msg) u = left_dim elif op == 'Mult': u = left_dim * right_dim elif op == 'Div': u = left_dim / right_dim elif op == 'FloorDiv': if not (left_dim is DIMENSIONLESS and right_dim is DIMENSIONLESS): raise SyntaxError('Floor division can only be used on ' 'dimensionless values.') u = DIMENSIONLESS elif op == 'Pow': if left_dim is DIMENSIONLESS and right_dim is DIMENSIONLESS: return DIMENSIONLESS n = _get_value_from_expression(expr.right, variables) u = left_dim**n else: raise SyntaxError("Unsupported operation " + op) return u elif expr.__class__ is ast.UnaryOp: op = expr.op.__class__.__name__ # check validity of operand and get its unit u = parse_expression_dimensions(expr.operand, variables) if op == 'Not': return DIMENSIONLESS else: return u else: raise SyntaxError('Unsupported operation ' + str(expr.__class__))
def parse_expression_unit(expr, namespace, variables): ''' Returns the unit value of an expression, and checks its validity Parameters ---------- expr : str The expression to check. namespace : dict-like The namespace of external variables. variables : dict of `Variable` objects The information about the internal variables Returns ------- unit : Quantity The output unit of the expression Raises ------ SyntaxError If the expression cannot be parsed, or if it uses ``a**b`` for ``b`` anything other than a constant number. DimensionMismatchError If any part of the expression is dimensionally inconsistent. Notes ----- Currently, functions do not work, see comments in function. ''' # If we are working on a string, convert to the top level node if isinstance(expr, basestring): mod = ast.parse(expr, mode='eval') expr = mod.body if expr.__class__ is ast.Name: name = expr.id if name in variables: return variables[name].unit elif name in namespace: return get_unit_fast(namespace[name]) elif name in ['True', 'False']: return Unit(1) else: raise ValueError('Unknown identifier %s' % name) elif expr.__class__ is ast.Num: return get_unit_fast(1) elif expr.__class__ is ast.BoolOp: # check that the units are valid in each subexpression for node in expr.values: parse_expression_unit(node, namespace, variables) # but the result is a bool, so we just return 1 as the unit return get_unit_fast(1) elif expr.__class__ is ast.Compare: # check that the units are consistent in each subexpression subexprs = [expr.left] + expr.comparators subunits = [] for node in subexprs: subunits.append(parse_expression_unit(node, namespace, variables)) for left, right in zip(subunits[:-1], subunits[1:]): if not have_same_dimensions(left, right): raise DimensionMismatchError( "Comparison of expressions with different units", *[getattr(u, 'dim', 1) for u in subunits]) # but the result is a bool, so we just return 1 as the unit return get_unit_fast(1) elif expr.__class__ is ast.Call: if len(expr.keywords): raise ValueError("Keyword arguments not supported.") elif expr.starargs is not None: raise ValueError("Variable number of arguments not supported") elif expr.kwargs is not None: raise ValueError("Keyword arguments not supported") arg_units = [ parse_expression_unit(arg, namespace, variables) for arg in expr.args ] func = namespace.get(expr.func.id, variables.get(expr.func, None)) if func is None: raise SyntaxError('Unknown function %s' % expr.func.id) if not hasattr(func, '_arg_units') or not hasattr( func, '_return_unit'): raise ValueError(('Function %s does not specify how it ' 'deals with units.') % expr.func.id) for idx, arg_unit in enumerate(arg_units): # A "None" in func._arg_units means: No matter what unit if (func._arg_units[idx] is not None and not have_same_dimensions(arg_unit, func._arg_units[idx])): raise DimensionMismatchError( ('Argument number %d for function ' '%s does not have the correct ' 'units' % (idx + 1, expr.func.id)), arg_unit, func._arg_units[idx]) if isinstance(func._return_unit, (Unit, int)): # Function always returns the same unit return get_unit_fast(func._return_unit) else: # Function returns a unit that depends on the arguments return func._return_unit(*arg_units) elif expr.__class__ is ast.BinOp: op = expr.op.__class__.__name__ left = parse_expression_unit(expr.left, namespace, variables) right = parse_expression_unit(expr.right, namespace, variables) if op == 'Add' or op == 'Sub': u = left + right elif op == 'Mult': u = left * right elif op == 'Div': u = left / right elif op == 'Pow': if have_same_dimensions(left, 1) and have_same_dimensions( right, 1): return get_unit_fast(1) n = _get_value_from_expression(expr.right, namespace, variables) u = left**n elif op == 'Mod': u = left % right else: raise SyntaxError("Unsupported operation " + op) return u elif expr.__class__ is ast.UnaryOp: op = expr.op.__class__.__name__ # check validity of operand and get its unit u = parse_expression_unit(expr.operand, namespace, variables) if op == 'Not': return get_unit_fast(1) else: return u else: raise SyntaxError('Unsupported operation ' + str(expr.__class__))
def parse_expression_unit(expr, variables): ''' Returns the unit value of an expression, and checks its validity Parameters ---------- expr : str The expression to check. variables : dict Dictionary of all variables used in the `expr` (including `Constant` objects for external variables) Returns ------- unit : Quantity The output unit of the expression Raises ------ SyntaxError If the expression cannot be parsed, or if it uses ``a**b`` for ``b`` anything other than a constant number. DimensionMismatchError If any part of the expression is dimensionally inconsistent. ''' # If we are working on a string, convert to the top level node if isinstance(expr, basestring): mod = ast.parse(expr, mode='eval') expr = mod.body if expr.__class__ is getattr(ast, 'NameConstant', None): # new class for True, False, None in Python 3.4 value = expr.value if value is True or value is False: return Unit(1) else: raise ValueError('Do not know how to handle value %s' % value) if expr.__class__ is ast.Name: name = expr.id if name in variables: return variables[name].unit elif name in ['True', 'False']: return Unit(1) else: raise KeyError('Unknown identifier %s' % name) elif expr.__class__ is ast.Num: return get_unit_fast(1) elif expr.__class__ is ast.BoolOp: # check that the units are valid in each subexpression for node in expr.values: parse_expression_unit(node, variables) # but the result is a bool, so we just return 1 as the unit return get_unit_fast(1) elif expr.__class__ is ast.Compare: # check that the units are consistent in each subexpression subexprs = [expr.left]+expr.comparators subunits = [] for node in subexprs: subunits.append(parse_expression_unit(node, variables)) for left, right in zip(subunits[:-1], subunits[1:]): if not have_same_dimensions(left, right): raise DimensionMismatchError("Comparison of expressions with different units", *[getattr(u, 'dim', 1) for u in subunits]) # but the result is a bool, so we just return 1 as the unit return get_unit_fast(1) elif expr.__class__ is ast.Call: if len(expr.keywords): raise ValueError("Keyword arguments not supported.") elif expr.starargs is not None: raise ValueError("Variable number of arguments not supported") elif expr.kwargs is not None: raise ValueError("Keyword arguments not supported") arg_units = [parse_expression_unit(arg, variables) for arg in expr.args] func = variables.get(expr.func.id, None) if func is None: raise SyntaxError('Unknown function %s' % expr.func.id) if not hasattr(func, '_arg_units') or not hasattr(func, '_return_unit'): raise ValueError(('Function %s does not specify how it ' 'deals with units.') % expr.func.id) for idx, arg_unit in enumerate(arg_units): # A "None" in func._arg_units means: No matter what unit if (func._arg_units[idx] is not None and not have_same_dimensions(arg_unit, func._arg_units[idx])): raise DimensionMismatchError(('Argument number %d for function ' '%s does not have the correct ' 'units' % (idx + 1, expr.func.id)), arg_unit, func._arg_units[idx]) if isinstance(func._return_unit, (Unit, int)): # Function always returns the same unit return get_unit_fast(func._return_unit) else: # Function returns a unit that depends on the arguments return func._return_unit(*arg_units) elif expr.__class__ is ast.BinOp: op = expr.op.__class__.__name__ left = parse_expression_unit(expr.left, variables) right = parse_expression_unit(expr.right, variables) if op=='Add' or op=='Sub': u = left+right elif op=='Mult': u = left*right elif op=='Div': u = left/right elif op=='Pow': if have_same_dimensions(left, 1) and have_same_dimensions(right, 1): return get_unit_fast(1) n = _get_value_from_expression(expr.right, variables) u = left**n elif op=='Mod': u = left % right else: raise SyntaxError("Unsupported operation "+op) return u elif expr.__class__ is ast.UnaryOp: op = expr.op.__class__.__name__ # check validity of operand and get its unit u = parse_expression_unit(expr.operand, variables) if op=='Not': return get_unit_fast(1) else: return u else: raise SyntaxError('Unsupported operation ' + str(expr.__class__))
def __getitem__(self, x): """ Returns the subtree named x. Ex.: ```neuron['axon']``` or ```neuron['11213']``` ```neuron[10*um:20*um]``` returns the subbranch from 10 um to 20 um. ```neuron[10*um]``` returns one compartment. ```neuron[5]``` returns compartment number 5. """ if isinstance(x, slice): # neuron[10*um:20*um] or neuron[1:3] using_lengths = all([arg is None or have_same_dimensions(arg, meter) for arg in [x.start, x.stop]]) using_ints = all([arg is None or int(arg) == float(arg) for arg in [x.start, x.stop]]) if not (using_lengths or using_ints): raise TypeError('Index slice has to use lengths or integers') morpho = self._branch() if using_lengths: if x.step is not None: raise TypeError(('Cannot provide a step argument when ' 'slicing with lengths')) l = cumsum(array(morpho.length)) # coordinate on the branch if x.start is None: i = 0 else: i = searchsorted(l, float(x.start)) if x.stop is None: j = len(l) else: j = searchsorted(l, float(x.stop)) else: # integers i, j, step = x.indices(len(morpho)) if step != 1: raise TypeError('Can only slice a contiguous segment') elif isinstance(x, Quantity) and have_same_dimensions(x, meter): # neuron[10*um] morpho = self._branch() l = cumsum(array(morpho.length)) i = searchsorted(l, x) j = i + 1 elif isinstance(x, numbers.Integral): # int: returns one compartment morpho = self._branch() if x < 0: # allows e.g. to use -1 to get the last compartment x += len(morpho) if x >= len(morpho): raise IndexError(('Invalid index %d ' 'for %d compartments') % (x, len(morpho))) i = x j = i + 1 elif x == 'main': return self._branch() elif isinstance(x, basestring): x = str(x) # convert int to string if (len(x) > 1) and all([c in 'LR123456789' for c in x]): # binary string of the form LLLRLR or 1213 (or mixed) return self._namedkid[x[0]][x[1:]] elif x in self._namedkid: return self._namedkid[x] else: raise AttributeError, "The subtree " + x + " does not exist" else: raise TypeError('Index of type %s not understood' % type(x)) # Return the sub-morphology morpho.diameter = morpho.diameter[i:j] morpho.length = morpho.length[i:j] morpho.area = morpho.area[i:j] morpho.x = morpho.x[i:j] morpho.y = morpho.y[i:j] morpho.z = morpho.z[i:j] morpho.distance = morpho.distance[i:j] if hasattr(morpho, '_origin'): morpho._origin += i return morpho
def test_special_case_numpy_functions(): ''' Test a couple of functions/methods that need special treatment. ''' from brian2.units.unitsafefunctions import ravel, diagonal, trace, dot, where quadratic_matrix = np.reshape(np.arange(9), (3, 3)) * mV # Temporarily suppress warnings related to the matplotlib 1.3 bug with warnings.catch_warnings(): warnings.simplefilter("ignore") # Check that function and method do the same thing assert_equal(ravel(quadratic_matrix), quadratic_matrix.ravel()) # Check that function gives the same result as on unitless arrays assert_equal(np.asarray(ravel(quadratic_matrix)), ravel(np.asarray(quadratic_matrix))) # Check that the function gives the same results as the original numpy # function assert_equal(np.ravel(np.asarray(quadratic_matrix)), ravel(np.asarray(quadratic_matrix))) # Do the same checks for diagonal, trace and dot assert_equal(diagonal(quadratic_matrix), quadratic_matrix.diagonal()) assert_equal(np.asarray(diagonal(quadratic_matrix)), diagonal(np.asarray(quadratic_matrix))) assert_equal(np.diagonal(np.asarray(quadratic_matrix)), diagonal(np.asarray(quadratic_matrix))) assert_equal(trace(quadratic_matrix), quadratic_matrix.trace()) assert_equal(np.asarray(trace(quadratic_matrix)), trace(np.asarray(quadratic_matrix))) assert_equal(np.trace(np.asarray(quadratic_matrix)), trace(np.asarray(quadratic_matrix))) assert_equal(dot(quadratic_matrix, quadratic_matrix), quadratic_matrix.dot(quadratic_matrix)) assert_equal( np.asarray(dot(quadratic_matrix, quadratic_matrix)), dot(np.asarray(quadratic_matrix), np.asarray(quadratic_matrix))) assert_equal( np.dot(np.asarray(quadratic_matrix), np.asarray(quadratic_matrix)), dot(np.asarray(quadratic_matrix), np.asarray(quadratic_matrix))) assert_equal(np.asarray(quadratic_matrix.prod()), np.asarray(quadratic_matrix).prod()) assert_equal(np.asarray(quadratic_matrix.prod(axis=0)), np.asarray(quadratic_matrix).prod(axis=0)) # Check for correct units if use_matplotlib_units_fix: assert have_same_dimensions(1, ravel(quadratic_matrix)) else: assert have_same_dimensions(quadratic_matrix, ravel(quadratic_matrix)) assert have_same_dimensions(quadratic_matrix, trace(quadratic_matrix)) assert have_same_dimensions(quadratic_matrix, diagonal(quadratic_matrix)) assert have_same_dimensions(quadratic_matrix[0]**2, dot(quadratic_matrix, quadratic_matrix)) assert have_same_dimensions(quadratic_matrix.prod(axis=0), quadratic_matrix[0]**quadratic_matrix.shape[0]) # check the where function # pure numpy array cond = [True, False, False] ar1 = np.array([1, 2, 3]) ar2 = np.array([4, 5, 6]) assert_equal(np.where(cond), where(cond)) assert_equal(np.where(cond, ar1, ar2), where(cond, ar1, ar2)) # dimensionless quantity assert_equal(np.where(cond, ar1, ar2), np.asarray(where(cond, ar1 * mV / mV, ar2 * mV / mV))) # quantity with dimensions ar1 = ar1 * mV ar2 = ar2 * mV assert_equal(np.where(cond, np.asarray(ar1), np.asarray(ar2)), np.asarray(where(cond, ar1, ar2))) # Check some error cases with pytest.raises(ValueError): where(cond, ar1) with pytest.raises(TypeError): where(cond, ar1, ar1, ar2) with pytest.raises(DimensionMismatchError): where(cond, ar1, ar1 / ms) # Check setasflat (for numpy < 1.7) if hasattr(Quantity, 'setasflat'): a = np.arange(10) * mV b = np.ones(10).reshape(5, 2) * volt c = np.ones(10).reshape(5, 2) * second with pytest.raises(DimensionMismatchError): a.setasflat(c) a.setasflat(b) assert_equal(a.flatten(), b.flatten()) # Check cumprod a = np.arange(1, 10) * mV / mV assert_equal(a.cumprod(), np.asarray(a).cumprod()) with pytest.raises(TypeError): (np.arange(1, 5) * mV).cumprod()
def test_special_case_numpy_functions(): ''' Test a couple of functions/methods that need special treatment. ''' from brian2.units.unitsafefunctions import ravel, diagonal, trace, dot, where quadratic_matrix = np.reshape(np.arange(9), (3, 3)) * mV # Temporarily suppress warnings related to the matplotlib 1.3 bug with warnings.catch_warnings(): warnings.simplefilter("ignore") # Check that function and method do the same thing assert_equal(ravel(quadratic_matrix), quadratic_matrix.ravel()) # Check that function gives the same result as on unitless arrays assert_equal(np.asarray(ravel(quadratic_matrix)), ravel(np.asarray(quadratic_matrix))) # Check that the function gives the same results as the original numpy # function assert_equal(np.ravel(np.asarray(quadratic_matrix)), ravel(np.asarray(quadratic_matrix))) # Do the same checks for diagonal, trace and dot assert_equal(diagonal(quadratic_matrix), quadratic_matrix.diagonal()) assert_equal(np.asarray(diagonal(quadratic_matrix)), diagonal(np.asarray(quadratic_matrix))) assert_equal(np.diagonal(np.asarray(quadratic_matrix)), diagonal(np.asarray(quadratic_matrix))) assert_equal(trace(quadratic_matrix), quadratic_matrix.trace()) assert_equal(np.asarray(trace(quadratic_matrix)), trace(np.asarray(quadratic_matrix))) assert_equal(np.trace(np.asarray(quadratic_matrix)), trace(np.asarray(quadratic_matrix))) assert_equal(dot(quadratic_matrix, quadratic_matrix), quadratic_matrix.dot(quadratic_matrix)) assert_equal(np.asarray(dot(quadratic_matrix, quadratic_matrix)), dot(np.asarray(quadratic_matrix), np.asarray(quadratic_matrix))) assert_equal(np.dot(np.asarray(quadratic_matrix), np.asarray(quadratic_matrix)), dot(np.asarray(quadratic_matrix), np.asarray(quadratic_matrix))) assert_equal(np.asarray(quadratic_matrix.prod()), np.asarray(quadratic_matrix).prod()) assert_equal(np.asarray(quadratic_matrix.prod(axis=0)), np.asarray(quadratic_matrix).prod(axis=0)) # Check for correct units if use_matplotlib_units_fix: assert have_same_dimensions(1, ravel(quadratic_matrix)) else: assert have_same_dimensions(quadratic_matrix, ravel(quadratic_matrix)) assert have_same_dimensions(quadratic_matrix, trace(quadratic_matrix)) assert have_same_dimensions(quadratic_matrix, diagonal(quadratic_matrix)) assert have_same_dimensions(quadratic_matrix[0] ** 2, dot(quadratic_matrix, quadratic_matrix)) assert have_same_dimensions(quadratic_matrix.prod(axis=0), quadratic_matrix[0] ** quadratic_matrix.shape[0]) # check the where function # pure numpy array cond = [True, False, False] ar1 = np.array([1, 2, 3]) ar2 = np.array([4, 5, 6]) assert_equal(np.where(cond), where(cond)) assert_equal(np.where(cond, ar1, ar2), where(cond, ar1, ar2)) # dimensionless quantity assert_equal(np.where(cond, ar1, ar2), np.asarray(where(cond, ar1 * mV/mV, ar2 * mV/mV))) # quantity with dimensions ar1 = ar1 * mV ar2 = ar2 * mV assert_equal(np.where(cond, np.asarray(ar1), np.asarray(ar2)), np.asarray(where(cond, ar1, ar2))) # Check some error cases assert_raises(ValueError, lambda: where(cond, ar1)) assert_raises(TypeError, lambda: where(cond, ar1, ar1, ar2)) assert_raises(DimensionMismatchError, lambda: where(cond, ar1, ar1 / ms)) # Check setasflat (for numpy < 1.7) if hasattr(Quantity, 'setasflat'): a = np.arange(10) * mV b = np.ones(10).reshape(5, 2) * volt c = np.ones(10).reshape(5, 2) * second assert_raises(DimensionMismatchError, lambda: a.setasflat(c)) a.setasflat(b) assert_equal(a.flatten(), b.flatten()) # Check cumprod a = np.arange(1, 10) * mV/mV assert_equal(a.cumprod(), np.asarray(a).cumprod()) assert_raises(TypeError, lambda: (np.arange(1, 5)*mV).cumprod())
def __getitem__(self, x): """ Returns the subtree named x. Ex.: ```neuron['axon']``` or ```neuron['11213']``` ```neuron[10*um:20*um]``` returns the subbranch from 10 um to 20 um. ```neuron[10*um]``` returns one compartment. ```neuron[5]``` returns compartment number 5. """ if isinstance(x, slice): # neuron[10*um:20*um] or neuron[1:3] using_lengths = all([ arg is None or have_same_dimensions(arg, meter) for arg in [x.start, x.stop] ]) using_ints = all([ arg is None or int(arg) == float(arg) for arg in [x.start, x.stop] ]) if not (using_lengths or using_ints): raise TypeError('Index slice has to use lengths or integers') morpho = self._branch() if using_lengths: if x.step is not None: raise TypeError(('Cannot provide a step argument when ' 'slicing with lengths')) l = cumsum(array(morpho.length)) # coordinate on the branch if x.start is None: i = 0 else: i = searchsorted(l, float(x.start)) if x.stop is None: j = len(l) else: j = searchsorted(l, float(x.stop)) else: # integers i, j, step = x.indices(len(morpho)) if step != 1: raise TypeError('Can only slice a contiguous segment') elif isinstance(x, Quantity) and have_same_dimensions( x, meter): # neuron[10*um] morpho = self._branch() l = cumsum(array(morpho.length)) i = searchsorted(l, x) j = i + 1 elif isinstance(x, numbers.Integral): # int: returns one compartment morpho = self._branch() if x < 0: # allows e.g. to use -1 to get the last compartment x += len(morpho) if x >= len(morpho): raise IndexError(('Invalid index %d ' 'for %d compartments') % (x, len(morpho))) i = x j = i + 1 elif x == 'main': return self._branch() elif isinstance(x, basestring): x = str(x) # convert int to string if (len(x) > 1) and all([ c in 'LR123456789' for c in x ]): # binary string of the form LLLRLR or 1213 (or mixed) return self._namedkid[x[0]][x[1:]] elif x in self._namedkid: return self._namedkid[x] else: raise AttributeError, "The subtree " + x + " does not exist" else: raise TypeError('Index of type %s not understood' % type(x)) # Return the sub-morphology morpho.diameter = morpho.diameter[i:j] morpho.length = morpho.length[i:j] morpho.area = morpho.area[i:j] morpho.x = morpho.x[i:j] morpho.y = morpho.y[i:j] morpho.z = morpho.z[i:j] morpho.distance = morpho.distance[i:j] if hasattr(morpho, '_origin'): morpho._origin += i return morpho
def parse_expression_unit(expr, variables): ''' Returns the unit value of an expression, and checks its validity Parameters ---------- expr : str The expression to check. variables : dict Dictionary of all variables used in the `expr` (including `Constant` objects for external variables) Returns ------- unit : Quantity The output unit of the expression Raises ------ SyntaxError If the expression cannot be parsed, or if it uses ``a**b`` for ``b`` anything other than a constant number. DimensionMismatchError If any part of the expression is dimensionally inconsistent. ''' # If we are working on a string, convert to the top level node if isinstance(expr, basestring): mod = ast.parse(expr, mode='eval') expr = mod.body if expr.__class__ is getattr(ast, 'NameConstant', None): # new class for True, False, None in Python 3.4 value = expr.value if value is True or value is False: return Unit(1) else: raise ValueError('Do not know how to handle value %s' % value) if expr.__class__ is ast.Name: name = expr.id # Raise an error if a function is called as if it were a variable # (most of the time this happens for a TimedArray) if name in variables and isinstance(variables[name], Function): raise SyntaxError( '%s was used like a variable/constant, but it is ' 'a function.' % name) if name in variables: return variables[name].unit elif name in ['True', 'False']: return Unit(1) else: raise KeyError('Unknown identifier %s' % name) elif expr.__class__ is ast.Num: return get_unit_fast(1) elif expr.__class__ is ast.BoolOp: # check that the units are valid in each subexpression for node in expr.values: parse_expression_unit(node, variables) # but the result is a bool, so we just return 1 as the unit return get_unit_fast(1) elif expr.__class__ is ast.Compare: # check that the units are consistent in each subexpression subexprs = [expr.left] + expr.comparators subunits = [] for node in subexprs: subunits.append(parse_expression_unit(node, variables)) for left, right in zip(subunits[:-1], subunits[1:]): if not have_same_dimensions(left, right): raise DimensionMismatchError( "Comparison of expressions with different units", *[getattr(u, 'dim', 1) for u in subunits]) # but the result is a bool, so we just return 1 as the unit return get_unit_fast(1) elif expr.__class__ is ast.Call: if len(expr.keywords): raise ValueError("Keyword arguments not supported.") elif getattr(expr, 'starargs', None) is not None: raise ValueError("Variable number of arguments not supported") elif getattr(expr, 'kwargs', None) is not None: raise ValueError("Keyword arguments not supported") arg_units = [ parse_expression_unit(arg, variables) for arg in expr.args ] func = variables.get(expr.func.id, None) if func is None: raise SyntaxError('Unknown function %s' % expr.func.id) if not hasattr(func, '_arg_units') or not hasattr( func, '_return_unit'): raise ValueError(('Function %s does not specify how it ' 'deals with units.') % expr.func.id) if len(func._arg_units) != len(arg_units): raise SyntaxError( 'Function %s was called with %d parameters, ' 'needs %d.' % (expr.func.id, len(arg_units), len(func._arg_units))) for idx, arg_unit in enumerate(arg_units): # A "None" in func._arg_units means: No matter what unit if (func._arg_units[idx] is not None and not have_same_dimensions(arg_unit, func._arg_units[idx])): raise DimensionMismatchError( ('Argument number %d for function ' '%s does not have the correct ' 'units' % (idx + 1, expr.func.id)), arg_unit, func._arg_units[idx]) if isinstance(func._return_unit, (Unit, int)): # Function always returns the same unit return get_unit_fast(func._return_unit) else: # Function returns a unit that depends on the arguments return func._return_unit(*arg_units) elif expr.__class__ is ast.BinOp: op = expr.op.__class__.__name__ left = parse_expression_unit(expr.left, variables) right = parse_expression_unit(expr.right, variables) if op == 'Add' or op == 'Sub': u = left + right elif op == 'Mult': u = left * right elif op == 'Div': u = left / right elif op == 'Pow': if have_same_dimensions(left, 1) and have_same_dimensions( right, 1): return get_unit_fast(1) n = _get_value_from_expression(expr.right, variables) u = left**n elif op == 'Mod': u = left % right else: raise SyntaxError("Unsupported operation " + op) return u elif expr.__class__ is ast.UnaryOp: op = expr.op.__class__.__name__ # check validity of operand and get its unit u = parse_expression_unit(expr.operand, variables) if op == 'Not': return get_unit_fast(1) else: return u else: raise SyntaxError('Unsupported operation ' + str(expr.__class__))
def _get_value_from_expression(expr, namespace, variables): ''' Returns the scalar value of an expression, and checks its validity. Parameters ---------- expr : str or `ast.Expression` The expression to check. namespace : dict-like The namespace of external variables. variables : dict of `Variable` objects The information about the internal variables Returns ------- value : float The value of the expression Raises ------ SyntaxError If the expression cannot be evaluated to a scalar value DimensionMismatchError If any part of the expression is dimensionally inconsistent. ''' # If we are working on a string, convert to the top level node if isinstance(expr, basestring): mod = ast.parse(expr, mode='eval') expr = mod.body if expr.__class__ is ast.Name: name = expr.id if name in variables: if not getattr(variables[name], 'constant', False): raise SyntaxError('Value %s is not constant' % name) if not getattr(variables[name], 'scalar', False): raise SyntaxError('Value %s is not scalar' % name) return variables[name].get_value() elif name in namespace: if not have_same_dimensions(namespace[name], 1): raise SyntaxError('Variable %s is not dimensionless' % name) try: val = float(namespace[name]) except (TypeError, ValueError): raise SyntaxError('Cannot convert %s to a scalar value' % name) return val elif name in ['True', 'False']: return 1.0 if name == 'True' else 0.0 else: raise ValueError('Unknown identifier %s' % name) elif expr.__class__ is ast.Num: return expr.n elif expr.__class__ is ast.BoolOp: raise SyntaxError( 'Cannot determine the numerical value for a boolean operation.') elif expr.__class__ is ast.Compare: raise SyntaxError( 'Cannot determine the numerical value for a boolean operation.') elif expr.__class__ is ast.Call: raise SyntaxError( 'Cannot determine the numerical value for a function call.') elif expr.__class__ is ast.BinOp: op = expr.op.__class__.__name__ left = _get_value_from_expression(expr.left, namespace, variables) right = _get_value_from_expression(expr.right, namespace, variables) if op == 'Add' or op == 'Sub': v = left + right elif op == 'Mult': v = left * right elif op == 'Div': v = left / right elif op == 'Pow': v = left**right elif op == 'Mod': v = left % right else: raise SyntaxError("Unsupported operation " + op) return v elif expr.__class__ is ast.UnaryOp: op = expr.op.__class__.__name__ # check validity of operand and get its unit v = _get_value_from_expression(expr.operand, namespace, variables) if op == 'Not': raise SyntaxError(('Cannot determine the numerical value ' 'for a boolean operation.')) if op == 'USub': return -v else: raise SyntaxError('Unknown unary operation ' + op) else: raise SyntaxError('Unsupported operation ' + str(expr.__class__))
def test_special_case_numpy_functions(): ''' Test a couple of functions/methods that need special treatment. ''' from brian2.units.unitsafefunctions import ravel, diagonal, trace, dot, where quadratic_matrix = np.reshape(np.arange(9), (3, 3)) * mV # Check that function and method do the same thing assert_equal(ravel(quadratic_matrix), quadratic_matrix.ravel()) # Check that function gives the same result as on unitless arrays assert_equal(np.asarray(ravel(quadratic_matrix)), ravel(np.asarray(quadratic_matrix))) # Check that the function gives the same results as the original numpy # function assert_equal(np.ravel(np.asarray(quadratic_matrix)), ravel(np.asarray(quadratic_matrix))) # Do the same checks for diagonal, trace and dot assert_equal(diagonal(quadratic_matrix), quadratic_matrix.diagonal()) assert_equal(np.asarray(diagonal(quadratic_matrix)), diagonal(np.asarray(quadratic_matrix))) assert_equal(np.diagonal(np.asarray(quadratic_matrix)), diagonal(np.asarray(quadratic_matrix))) assert_equal(trace(quadratic_matrix), quadratic_matrix.trace()) assert_equal(np.asarray(trace(quadratic_matrix)), trace(np.asarray(quadratic_matrix))) assert_equal(np.trace(np.asarray(quadratic_matrix)), trace(np.asarray(quadratic_matrix))) assert_equal(dot(quadratic_matrix, quadratic_matrix), quadratic_matrix.dot(quadratic_matrix)) assert_equal(np.asarray(dot(quadratic_matrix, quadratic_matrix)), dot(np.asarray(quadratic_matrix), np.asarray(quadratic_matrix))) assert_equal(np.dot(np.asarray(quadratic_matrix), np.asarray(quadratic_matrix)), dot(np.asarray(quadratic_matrix), np.asarray(quadratic_matrix))) assert_equal(np.asarray(quadratic_matrix.prod()), np.asarray(quadratic_matrix).prod()) assert_equal(np.asarray(quadratic_matrix.prod(axis=0)), np.asarray(quadratic_matrix).prod(axis=0)) # Check for correct units assert have_same_dimensions(quadratic_matrix, ravel(quadratic_matrix)) assert have_same_dimensions(quadratic_matrix, trace(quadratic_matrix)) assert have_same_dimensions(quadratic_matrix, diagonal(quadratic_matrix)) assert have_same_dimensions(quadratic_matrix[0] ** 2, dot(quadratic_matrix, quadratic_matrix)) assert have_same_dimensions(quadratic_matrix.prod(axis=0), quadratic_matrix[0] ** quadratic_matrix.shape[0]) # check the where function # pure numpy array cond = [True, False, False] ar1 = np.array([1, 2, 3]) ar2 = np.array([4, 5, 6]) assert_equal(np.where(cond), where(cond)) assert_equal(np.where(cond, ar1, ar2), where(cond, ar1, ar2)) # dimensionless quantity assert_equal(np.where(cond, ar1, ar2), np.asarray(where(cond, ar1 * mV/mV, ar2 * mV/mV))) # quantity with dimensions ar1 = ar1 * mV ar2 = ar2 * mV assert_equal(np.where(cond, np.asarray(ar1), np.asarray(ar2)), np.asarray(where(cond, ar1, ar2))) # Check some error cases assert_raises(ValueError, lambda: where(cond, ar1)) assert_raises(TypeError, lambda: where(cond, ar1, ar1, ar2)) assert_raises(DimensionMismatchError, lambda: where(cond, ar1, ar1 / ms))
def assert_quantity(q, values, unit): assert isinstance(q, Quantity) or (isinstance(q, np.ndarray) and have_same_dimensions(unit, 1)) assert_equal(np.asarray(q), values) assert have_same_dimensions(q, unit), ('Dimension mismatch: (%s) (%s)' % (get_dimensions(q), get_dimensions(unit)))