def connect(self, sender, send_port_name, receive_port_name, delay=0.0 * un.ms, properties=None): """ Connects a port of the cell to a matching port on the 'other' cell Parameters ---------- sender : pype9.simulator.neuron.cells.Cell The sending cell to connect the from send_port_name : str Name of the port in the sending cell to connect to receive_port_name : str Name of the receive port in the current cell to connect from delay : nineml.Quantity (time) The delay of the connection properties : list(nineml.Property) The connection properties of the event port """ send_port = sender.component_class.send_port(send_port_name) receive_port = self.component_class.receive_port(receive_port_name) delay = float(delay.in_units(un.ms)) if properties is None: properties = [] if send_port.communicates != receive_port.communicates: raise Pype9UsageError( "Cannot connect {} send port, '{}', to {} receive port, '{}'". format(send_port.communicates, send_port_name, receive_port.communicates, receive_port_name)) if receive_port.communicates == 'event': if len(list(self.component_class.event_receive_ports)) > 1: raise Pype9Unsupported9MLException( "Multiple event receive ports ('{}') are not currently " "supported".format("', '".join([ p.name for p in self.component_class.event_receive_ports ]))) netcon = h.NetCon(sender._hoc, self._hoc, sec=self._sec) if delay: netcon.delay = delay self._check_connection_properties(receive_port_name, properties) if len(properties) > 1: raise Pype9Unsupported9MLException( "Cannot handle more than one connection property per port") elif properties: netcon.weight[0] = self.unit_handler.scale_value( properties[0].quantity) self._input_auxs.append(netcon) elif receive_port.communicates == 'analog': raise Pype9UsageError( "Cannot individually 'connect' analog ports. Simulate the " "sending cell in a separate simulation then play the analog " "signal in the port") else: raise Pype9UsageError( "Unrecognised port communication '{}'".format( receive_port.communicates))
def _prepare(self, **kwargs): "Reset the simulation and prepare it for creating new cells/networks" if self._min_delay is None: if self.num_threads() == 1: min_delay = self.device_delay else: raise Pype9UsageError( "Min delay needs to be set for NEST simulator if using " "more than one thread") else: min_delay = self._min_delay if self._max_delay is None: if self.num_threads() == 1: max_delay = self.DEFAULT_MAX_DELAY else: raise Pype9UsageError( "Max delay needs to be set for NEST simulator if using " "more than one thread") else: max_delay = self._max_delay pyNN_setup(timestep=float(self.dt.in_units(un.ms)), min_delay=float(min_delay.in_units(un.ms)), max_delay=float(max_delay.in_units(un.ms)), grng_seed=self.global_seed, rng_seeds=self.all_dynamics_seeds, **kwargs)
def parse_units(unit_str): # TODO: Should donate this function to the nineml.units module try: unit_expr = sympy.sympify(unit_str) except: raise Pype9UsageError( "Unit expression '{}' is not a valid expression".format(unit_str)) try: return _parse_subexpr(unit_expr) except Pype9UnitStrError: raise Pype9UsageError( "Unit expression '{}' contains operators other than " "multiplication and division".format(unit_str))
def register_cell(self, cell): if cell.code_generator != self.code_generator: raise Pype9UsageError( "Equivlent code generators must be provided to both the " "CellMetaClass and Simulation objects ({} and {})".format( cell.code_generator, self.code_generator)) self._registered_cells.append(cell)
def register_array(self, array): cell_code_gen = array.celltype.model.code_generator if cell_code_gen != self.code_generator: raise Pype9UsageError( "Equivlent code generators must be provided to both the " "Network and Simulation objects ({} and {})".format( cell_code_gen, self.code_generator)) self._registered_arrays.append(array)
def _check_units(self, varname, val, dimension, allow_none=False): if not (val is None and allow_none): try: assert val.units.dimension == dimension except (AssertionError, AttributeError): raise Pype9UsageError( "Provided value to {} ({}) is not a valid '{}' " "quantity".format(varname, val, dimension.name))
def play(self, port_name, signal, properties=[]): """ Injects current into the segment Parameters ---------- port_name : str The name of the receive port to play the signal into signal : neo.AnalogSignal (current) | neo.SpikeTrain Signal to play into the port properties : list(nineml.Property) The connection properties of the event port """ ext_is = self.build_component_class.annotations.get( (BUILD_TRANS, PYPE9_NS), EXTERNAL_CURRENTS).split(',') port = self.component_class.port(port_name) if isinstance(port, EventPort): if len(list(self.component_class.event_receive_ports)) > 1: raise Pype9Unsupported9MLException( "Multiple event receive ports ('{}') are not currently " "supported".format("', '".join([ p.name for p in self.component_class.event_receive_ports ]))) if signal.t_start < 1.0: raise Pype9UsageError( "Signal must start at or after 1 ms to handle delay in " "neuron ({})".format(signal.t_start)) times = numpy.asarray(signal.times.rescale(pq.ms)) - 1.0 vstim = h.VecStim() vstim_times = h.Vector(times) vstim.play(vstim_times) vstim_con = h.NetCon(vstim, self._hoc, sec=self._sec) self._check_connection_properties(port_name, properties) if len(properties) > 1: raise NotImplementedError( "Cannot handle more than one connection property per port") elif properties: vstim_con.weight[0] = self.unit_handler.scale_value( properties[0].quantity) self._inputs['vstim'] = vstim self._input_auxs.extend((vstim_times, vstim_con)) else: if port_name not in ext_is: raise Pype9Unsupported9MLException( "Can only play into external current ports ('{}'), not " "'{}' port.".format("', '".join(ext_is), port_name)) iclamp = h.IClamp(0.5, sec=self._sec) iclamp.delay = 0.0 iclamp.dur = 1e12 iclamp.amp = 0.0 iclamp_amps = h.Vector(pq.Quantity(signal, 'nA')) iclamp_times = h.Vector(signal.times.rescale(pq.ms)) iclamp_amps.play(iclamp._ref_amp, iclamp_times) self._inputs['iclamp'] = iclamp self._input_auxs.extend((iclamp_amps, iclamp_times))
def activate(self): if self.__class__._active is not None: raise Pype9UsageError( "Cannot enter context of multiple {} simulations at the same " "time".format(self.__class__.name)) self._set_seeds() self._running = False self._prepare() self._registered_cells = [] self._registered_arrays = [] self.__class__._active = self
def _get(self, varname): varname = self._escaped_name(varname) try: return getattr(self._hoc, varname) except AttributeError: try: return getattr(self._sec, varname) except AttributeError: raise Pype9UsageError( "'{}' doesn't have an attribute '{}'".format( self.name, varname))
def __init__(self, dt, t_start=0.0 * un.s, seed=None, properties_seed=None, min_delay=1 * un.ms, max_delay=10 * un.ms, code_generator=None, build_base_dir=None, **options): self._check_units('dt', dt, un.time) self._check_units('t_start', dt, un.time) self._check_units('min_delay', dt, un.time, allow_none=True) self._check_units('max_delay', dt, un.time, allow_none=True) self._dt = dt self._t_start = t_start self._t = t_start self._min_delay = min_delay if min_delay > dt else dt self._max_delay = max_delay if max_delay > dt else dt self._options = options self._registered_cells = None self._registered_arrays = None if seed is not None and (seed < 0 or seed > self.max_seed): raise Pype9UsageError( "Provided seed {} is out of range, must be between (0 and {})". format(seed, self.max_seed)) self._base_seed = seed if properties_seed is not None and (properties_seed < 0 or properties_seed > self.max_seed): raise Pype9UsageError( "Provided structure seed {} is out of range, must be between " "(0 and {})".format(seed, self.max_seed)) self._base_properties_seed = properties_seed if code_generator is None: code_generator = self.CodeGenerator(base_dir=build_base_dir) elif build_base_dir is not None: raise Pype9UsageError( "Cannot provide both code generator and 'build_base_dir' " "options to Simulation __init__") self._code_generator = code_generator
def _trim_analog_signal(self, signal, t_start, interval): sim_start = self.unit_handler.to_pq_quantity(self._t_start) offset = (t_start - sim_start) if offset > 0.0 * pq.s: offset_index = offset / interval if round(offset_index) != offset_index: raise Pype9UsageError( "Difference between recording start time ({}) needs to" "and simulation start time ({}) must be an integer " "multiple of the sampling interval ({})".format( t_start, sim_start, interval)) signal = signal[int(offset_index):] return signal
def _parse_subexpr(expr): """parse_units helper function""" try: return standard_units[str(expr)] except KeyError: if isinstance(expr, sympy.Symbol): raise Pype9UsageError("Unrecognised unit '{}'".format(expr)) elif isinstance(expr, (sympy.Mul, sympy.Pow)): op = operator.mul if isinstance(expr, sympy.Mul) else operator.pow return reduce(op, (_parse_subexpr(a) for a in expr.args)) elif isinstance(expr, sympy.Integer): return int(expr) else: raise Pype9UnitStrError
def set_regime(self, regime): if regime not in self.component_class.regime_names: raise Pype9UsageError( "'{}' is not a name of a regime in '{} cells " "(regimes are '{}')".format( regime, self.name, "', '".join(self.component_class.regime_names))) try: # If regime is an integer (as it will be when passed from PyNN) index = int(regime) except ValueError: # If the regime is the regime name index = self.regime_index(regime) super(Cell, self).__setattr__('_regime_index', index) self._set_regime()
def run(argv): import neo from pype9.exceptions import Pype9UsageError args = argparser().parse_args(argv) if args.hide: import matplotlib # @IgnorePep8 matplotlib.use('Agg') # Set to use Agg so DISPLAY is not required from pype9.plot import plot # @IgnorePep8 segments = neo.PickleIO(args.filename).read() if len(segments) > 1: raise Pype9UsageError( "Expected only a single recording segment in file '{}', found {}." .format(args.filename, len(segments))) seg = segments[0] plot(seg, dims=args.dims, show=not args.hide, resolution=args.resolution, save=args.save)
def _get_port_details(self, port_name): """ Return the communication type of the corresponding port and its fully qualified name in the cell-synapse namespace (e.g. the 'spike_output' port in the cell namespace will be 'spike_output__cell') Parameters ---------- port_name : str Name of the port or state variable Returns ------- communicates : str Either 'event' or 'analog' depending on the type of port port_name corresponds to record_name : str Name of the port fully qualified in the joint cell-synapse namespace """ # TODO: Need to add a check that the port was recorded component_class = self.celltype.model.component_class port = None for name in (port_name, port_name + '__cell'): try: port = component_class.send_port(name) except NineMLNameError: try: port = component_class.state_variable(name) except NineMLNameError: pass if port is None: raise Pype9UsageError( "Unknown port or state-variable '{}' for '{}' " "component array (available '{}').".format( port_name, self.name, "', '".join(chain( component_class.send_port_names, component_class.sub_component( 'cell').send_port_names)))) if isinstance(port, StateVariable): communicates = 'analog' else: communicates = port.communicates return communicates, port.name
def play(self, port_name, signal, properties=[]): """ Injects current into the segment Parameters ---------- port_name : str The name of the receive port to play the signal into signal : neo.AnalogSignal (current) | neo.SpikeTrain Signal to play into the port properties : list(nineml.Property) The connection properties of the event port """ port = self.component_class.receive_port(port_name) if port.nineml_type in ('EventReceivePort', 'EventReceivePortExposure'): # Shift the signal times to account for the minimum delay and # match the NEURON implementation spike_times = (numpy.asarray(signal.rescale(pq.ms)) - self.device_delay_ms) if any(spike_times <= 0.0): raise Pype9UsageError( "Some spike times are less than device delay and so " "can't be played into cell ({})".format( ', '.join(spike_times < self.device_delay_ms))) self._inputs[port_name] = nest.Create( 'spike_generator', 1, {'spike_times': list(spike_times)}) syn_spec = { 'receptor_type': self._receive_ports[port_name], 'delay': self.device_delay_ms } self._check_connection_properties(port_name, properties) if len(properties) > 1: raise NotImplementedError( "Cannot handle more than one connection property per port") elif properties: syn_spec['weight'] = self.unit_handler.scale_value( properties[0].quantity) nest.Connect(self._inputs[port_name], self._cell, syn_spec=syn_spec) elif port.nineml_type in ('AnalogReceivePort', 'AnalogReducePort', 'AnalogReceivePortExposure', 'AnalogReducePortExposure'): # Signals are played into NEST cells include a delay (set to be the # minimum), which is is subtracted from the start of the signal so # that the effect of the signal aligns with other simulators t_start = (float(signal.t_start.rescale(pq.ms)) - self.device_delay_ms) if t_start <= 0.0: raise Pype9UsageError( "Start time of signal played into port '{}' ({}) must " "be greater than device delay ({})".format( port_name, signal.t_start, self.device_delay)) step_current_params = { 'amplitude_values': list(numpy.ravel(pq.Quantity(signal, 'pA'))), 'amplitude_times': list( numpy.ravel(numpy.asarray(signal.times.rescale(pq.ms))) - self.device_delay_ms), 'start': t_start, 'stop': float(signal.t_stop.rescale(pq.ms)) } self._inputs[port_name] = nest.Create('step_current_generator', 1, step_current_params) nest.Connect(self._inputs[port_name], self._cell, syn_spec={ "receptor_type": self._receive_ports[port_name], 'delay': self.device_delay_ms }) else: raise Pype9UsageError( "Unrecognised port type '{}' to play signal into".format(port))
def get_v_threshold(self, dynamics_properties, port_name): """ Returns the voltage threshold at which an event is emitted from the given event port Parameters ---------- component_class : nineml.Dynamics The component class of the cell to find the v-threshold for port_name : str Name of the event send port to threshold """ comp_class = ( dynamics_properties.component_class._dynamics.substitute_aliases()) transitions = OuputEventTransitionsFinder( comp_class, comp_class.event_send_port(port_name)).transitions assert transitions try: triggers = set(t.trigger for t in transitions) except AttributeError: raise Pype9UsageError( "Cannot get threshold for event port '{}' in {} as it is " "emitted from at least OnEvent transition ({})".format( port_name, comp_class, ', '.join(str(t) for t in transitions))) if len(triggers) > 1: raise Pype9UsageError( "Cannot get threshold for event port '{}' in {} as it is the " "condition from at least OnEvent transition ({})".format( port_name, comp_class, ', '.join(transitions))) expr = next(iter(triggers)).rhs v = sympy.Symbol( self.component_class.annotations.get((BUILD_TRANS, PYPE9_NS), MEMBRANE_VOLTAGE)) if v not in expr.free_symbols: raise Pype9UsageError( "Trigger expression for '{}' port in {} is not an expression " "of membrane voltage '{}'".format(port_name, comp_class, v)) solution = None if isinstance(expr, (sympy.StrictGreaterThan, sympy.StrictLessThan)): # Get the equation for the transition between true and false equality = sympy.Eq(*expr.args) solutions = sympy.solvers.solve(equality, v) if len(solutions) == 1: solution = solutions[0] if solution is None: raise Pype9UsageError( "Cannot solve threshold equation for trigger expression '{}' " "for '{}' (to find condition where spikes are emitted from " "'{}' event send port) in {} as it is not a simple inequality". format(expr, v, port_name, comp_class)) values = {} for sym in solution.free_symbols: sym_str = str(sym) try: prop = dynamics_properties.property(sym_str) except NineMLNameError: try: prop = comp_class.constant(sym_str) except NineMLNameError: raise Pype9UsageError( "Cannot calculated fixed value for voltage threshold " "as it contains reference to {}".format( comp_class.element(sym_str))) values[sym] = self.unit_handler.scale_value(prop.quantity) threshold = solution.subs(values) return float(threshold)
def __init__(self, *args, **kwargs): self._in_array = kwargs.pop('_in_array', False) # Flag to determine whether the cell has been initialized or not # (it makes a difference to how the state of the cell is updated, # either saved until the 'initialze' method is called or directly # set to the state) sim = self.Simulation.active() self._t_start = sim.t_start self._t_stop = None if self.in_array: for k, v in kwargs.items(): self._set(k, v) # Values should be in the right units. self._regime_index = None else: # These position arguments are a little more complex to retrieve # due to Python 2's restriction of **kwargs only following # *args. # Get prototype argument if len(args) >= 1: prototype = args[0] if 'prototype_' in kwargs: raise Pype9UsageError( "Cannot provide prototype as (1st) argument ({}) and " "keyword arg ({})".format(prototype, kwargs['prototype_'])) else: prototype = kwargs.pop('prototype_', self.component_class) # Get regime argument if len(args) >= 2: regime = args[1] if 'regime_' in kwargs: raise Pype9UsageError( "Cannot provide regime as (2nd) argument ({}) and " "keyword arg ({})".format(regime, kwargs['regime_'])) else: try: regime = kwargs.pop('regime_') except KeyError: regime = None if regime is None: if self.component_class.num_regimes == 1: regime = next(self.component_class.regime_names) else: raise Pype9UsageError( "Need to specify initial regime using 'regime_' " "keyword arg for component class with multiple " "regimes ('{}')".format( self.component_class.regime_names)) if len(args) > 2: raise Pype9UsageError( "Only two non-keyword arguments ('prototype_' and " "'regime_' permitted in Cell __init__ (provided: {})" .format(', '.join(args))) self.set_regime(regime) properties = [] initial_values = [] for name, qty in kwargs.items(): if isinstance(qty, pq.Quantity): qty = self.unit_handler.from_pq_quantity(qty) if name in self.component_class.state_variable_names: initial_values.append(nineml.Initial(name, qty)) else: properties.append(nineml.Property(name, qty)) self._nineml = nineml.DynamicsProperties( name=self.name + '_properties', definition=prototype, properties=properties, initial_values=initial_values, check_initial_values=True) # Set up references from parameter names to internal variables and # set parameters for p in chain(self.properties, self.initial_values): qty = p.quantity if qty.value.nineml_type != 'SingleValue': raise Pype9UsageError( "Only SingleValue quantities can be used to initiate " "individual cell classes ({})".format(p)) self._set(p.name, float(self.unit_handler.scale_value(qty))) sim.register_cell(self)
def write(self, file, **kwargs): # @ReservedAssignment if self.in_array: raise Pype9UsageError( "Can only write cell properties when they are not in an " "array") self._nineml.write(file, **kwargs)
def run(argv): """ Runs the simulation script from the provided arguments """ import nineml from pype9.exceptions import Pype9UsageError import neo.io args = argparser().parse_args(argv) time = args.time * un.ms timestep = args.timestep * un.ms if args.simulator == 'neuron': from pype9.simulate.neuron import Network, CellMetaClass, Simulation # @UnusedImport @IgnorePep8 elif args.simulator == 'nest': from pype9.simulate.nest import Network, CellMetaClass, Simulation # @Reimport @IgnorePep8 else: assert False if not args.record: raise Pype9UsageError( "No recorders set, please specify at least one with the '--record'" " option") min_delay = (float(args.min_delay[0]) * parse_units(args.min_delay[1]) if args.min_delay is not None else 1.0 * un.ms) device_delay = (float(args.device_delay[0]) * parse_units(args.device_delay[1]) if args.device_delay is not None else None) # Parse record specs record_specs = [] for rec in args.record: if len(rec) == 4: rec_t_start = pq.Quantity(float(rec[2]), rec[3]) elif len(rec) == 2: rec_t_start = None else: raise Pype9UsageError( "Record options can be passed either have 2 or 4 (provided {})" ": PORT/STATE-VARIABLE FILENAME [T_START T_START_UNITS]") record_specs.append(RecordSpec(rec[0], rec[1], rec_t_start)) # Check for clashing record paths record_paths = [r.fname for r in record_specs] for pth in record_paths: if record_paths.count(pth) > 1: raise Pype9UsageError( "Duplicate record paths '{}' given to separate '--record' " "options".format(pth)) # For convenience model = args.model if isinstance(model, nineml.Network) and not model.num_projections: raise Pype9UsageError( "Provided network model '{}' (may have been implicitly created " "from complete document) does not contain any projections".format( model)) if isinstance(model, nineml.Network): with Simulation(dt=timestep, seed=args.seed, properties_seed=args.properties_seed, device_delay=device_delay, **model.delay_limits()) as sim: # Construct the network logger.info("Constructing network") network = Network(model, build_mode=args.build_mode, build_base_dir=args.build_dir) logger.info("Finished constructing the '{}' network".format( model.name)) for rspec in record_specs: pop_name, port_name = rspec.port.split('.') network.component_array(pop_name).record(port_name) logger.info("Running the simulation") sim.run(time) logger.info("Writing recorded data to file") for rspec in record_specs: pop_name, port_name = rspec.port.split('.') pop = network.component_array(pop_name) neo.PickleIO(rspec.fname).write( pop.recording(port_name, t_start=rspec.t_start)) else: assert isinstance(model, (nineml.DynamicsProperties, nineml.Dynamics)) # Override properties passed as options if args.prop: props_dict = dict((parm, float(val) * parse_units(unts)) for parm, val, unts in args.prop) props = nineml.DynamicsProperties(model.name + '_props', model, props_dict) component_class = model elif isinstance(model, nineml.DynamicsProperties): props = model component_class = model.component_class else: raise Pype9UsageError( "Specified model {} is not a dynamics properties object and " "no properties supplied to simulate command via --prop option". format(model)) # Get the initial state init_state = dict((sv, float(val) * parse_units(units)) for sv, val, units in args.init_value) # Get the init_regime init_regime = args.init_regime if init_regime is None: if component_class.num_regimes == 1: # If there is only one regime it doesn't need to be specified init_regime = next(component_class.regimes).name else: raise Pype9UsageError( "Need to specify initial regime as dynamics has more than " "one '{}'".format("', '".join( r.name for r in component_class.regimes))) # FIXME: A bit of a hack until better detection of input currents is # implemented in neuron code gen. external_currents = [] for port_name, _ in args.play: if component_class.port(port_name).dimension == un.current: external_currents.append(port_name) # Build cell class Cell = CellMetaClass(component_class, build_mode=args.build_mode, external_currents=external_currents, build_version=args.build_version, build_base_dir=args.build_dir) record_regime = False with Simulation(dt=timestep, seed=args.seed, min_delay=min_delay, device_delay=device_delay, build_base_dir=args.build_dir) as sim: # Create cell cell = Cell(props, regime_=init_regime, **init_state) # Play inputs for port_name, fname in args.play: port = component_class.receive_port(port_name) seg = neo.io.PickleIO(filename=fname).read()[0] if port.communicates == 'event': signal = seg.spiketrains[0] else: signal = seg.analogsignals[0] logger.info("Playing signal (t_start: {}, t_stop: {}, dt: {}) " "into port '{}".format(signal.t_start, signal.t_stop, signal.sampling_period, port_name)) # Input is an event train or analog signal cell.play(port_name, signal) # Set up recorders for rspec in record_specs: if (component_class.num_regimes > 1 and component_class.port( rspec.port).communicates == 'analog'): record_regime = True cell.record(rspec.port, t_start=rspec.t_start) if record_regime: cell.record_regime() # Run simulation sim.run(time) # Collect data into Neo Segments fnames = set(r.fname for r in record_specs) data_segs = {} for fname in fnames: data_segs[fname] = neo.Segment( description="Simulation of '{}' cell".format(model.name)) for rspec in record_specs: data = cell.recording(rspec.port, t_start=rspec.t_start) if isinstance(data, neo.AnalogSignal): data_segs[rspec.fname].analogsignals.append(data) else: data_segs[rspec.fname].spiketrains.append(data) if record_regime: data_segs[rspec.fname].epochs.append(cell.regime_epochs()) # Write data to file for fname, data_seg in data_segs.items(): neo.io.PickleIO(fname).write(data_seg) logger.info("Finished simulation of '{}' for {}".format(model.name, time))
def properties_rng(self): if self._properties_rng is None: raise Pype9UsageError( "Can only access rng inside simulation context") return self._properties_rng
def connect(self, sender, send_port_name, receive_port_name, delay=None, properties=None): """ Connects a port of the cell to a matching port on the 'other' cell Parameters ---------- sender : pype9.simulator.nest.cells.Cell The sending cell to connect the from send_port_name : str Name of the port in the sending cell to connect to receive_port_name : str Name of the receive port in the current cell to connect from delay : nineml.Quantity (time) The delay of the connection properties : list(nineml.Property) The connection properties of the event port """ if delay is None: delay = self.device_delay if properties is None: properties = [] delay = float(delay.in_units(un.ms)) send_port = sender.component_class.send_port(send_port_name) receive_port = self.component_class.receive_port(receive_port_name) if send_port.communicates != receive_port.communicates: raise Pype9UsageError( "Cannot connect {} send port, '{}', to {} receive port, '{}'". format(send_port.communicates, send_port_name, receive_port.communicates, receive_port_name)) if receive_port.communicates == 'event': if self.component_class.num_event_send_ports > 1: raise Pype9Unsupported9MLException( "Cannot currently differentiate between multiple event " "send ports in NEST implementation ('{}')".format( "', '".join( self.component_class.event_send_port_names))) syn_spec = { 'receptor_type': self._receive_ports[receive_port_name], 'delay': delay } if len(properties) > 1: raise Pype9Unsupported9MLException( "Cannot handle more than one connection property per port") elif properties: self._check_connection_properties(receive_port_name, properties) syn_spec['weight'] = self.unit_handler.scale_value( properties[0].quantity) nest.Connect(sender._cell, self._cell, syn_spec=syn_spec) elif receive_port.communicates == 'analog': raise Pype9UsageError( "Cannot individually 'connect' analog ports. Simulate the " "sending cell in a separate simulation then play the analog " "signal in the port") else: raise Pype9UsageError( "Unrecognised port communication '{}'".format( receive_port.communicates))