def test_remove_sinkless_signals(): """Removing sinkless signals will modify the connection map to remove any signals which no-longer have any sinks. """ # Construct a connection map containing two signals. # Objects to act as sources and sinks obj_a = mock.Mock(name="A") obj_b = mock.Mock(name="B") # Add the connections cm = model.ConnectionMap() cm.add_connection(obj_a, None, model.SignalParameters(weight=1), None, obj_b, None, None) cm.add_connection(obj_b, None, model.SignalParameters(weight=2), None, obj_a, None, None) # Remove the sinks from one of the connections for _, sinks in cm._connections[obj_b][None]: for sink in sinks: sinks.remove(sink) # Remove all the sinkless signals model.remove_sinkless_signals(cm) # Assert that only one signal remains assert len(cm.get_signals_from_object(obj_a)[None]) == 1 assert len(cm.get_signals_from_object(obj_b)) == 0 assert len(list(cm.get_signals())) == 1
def test_eq(self): # Create several SignalParameters and ensure that they only # report equal when they are actually equal. ks = BitField() ks.add_field("x") params = ((False, 5, ks(x=2)), (True, 5, ks(x=2)), (False, 4, ks(x=2)), (False, 5, ks(x=3)), ) tps = tuple(model.SignalParameters(*args) for args in params) # None of these transmission parameters should test as equivalent. for a in tps: for b in tps: if a is not b: assert a != b # Create a whole new set of transmission parameters using the same set # of parameters and ensure that they all test equivalent to their # counterparts in the original list. for a, b in zip(tps, tuple(model.SignalParameters(*args) for args in params)): assert a is not b assert a == b
def test_combine_sig_pars_keyspaces_all_none(self): """Check that the signal parameters are combined correctly.""" # The keyspace should be None if all antecedents are None sps1 = model.SignalParameters() sps2 = model.SignalParameters() combined_sps = sps1.concat(sps2) assert combined_sps.keyspace is None
def test_combine_sig_pars_latching(self, latching_a, latching_b): """Check that the signal parameters are combined correctly.""" # The signal should be latching if any of its antecedents was latching. sps1 = model.SignalParameters(latching=latching_a) sps2 = model.SignalParameters(latching=latching_b) combined_sps = sps1.concat(sps2) assert combined_sps.latching is (latching_a or latching_b)
def test_combine_sig_pars_last_weight(self): """Check that the signal parameters are combined correctly.""" # The last weight should be used sps1 = model.SignalParameters(weight=1) sps2 = model.SignalParameters(weight=5) combined_sps = sps1.concat(sps2) assert combined_sps.weight == sps2.weight
def test_combine_sig_pars_keyspaces_one_specified(self, first): """Check that the signal parameters are combined correctly.""" # If ONE keyspace is specified it should be used for the entire trace ks = object() sps1 = model.SignalParameters(keyspace=ks if first else None) sps2 = model.SignalParameters(keyspace=None if first else ks) combined_sps = sps1.concat(sps2) assert combined_sps.keyspace is ks
def test_get_signals(self): # Construct some connections and ensure that these result in # appropriate signals being returned. # Objects to act as sources and sinks obj_a = mock.Mock(name="A") obj_b = mock.Mock(name="B") obj_c = mock.Mock(name="C") # Keyspaces ks_abc = mock.Mock(name="Keyspace A -> B,C") ks_cb = mock.Mock(name="Keyspace C -> B") # Add the connections cm = model.ConnectionMap() tp_a = mock.Mock(name="Transmission Parameters A") tp_b = mock.Mock(name="Transmission Parameters B") cm.add_connection( obj_a, None, model.SignalParameters(weight=3, keyspace=ks_abc), tp_a, obj_b, None, None ) cm.add_connection( obj_a, None, model.SignalParameters(weight=3, keyspace=ks_abc), tp_a, obj_c, None, None ) cm.add_connection( obj_c, None, model.SignalParameters(weight=5, keyspace=ks_cb), tp_b, obj_b, None, None ) # Get the signals, this should be a list of two signals signals = list(cm.get_signals()) assert len(signals) == 2 for signal, transmission_params in signals: if signal.source is obj_a: # Assert the sinks are correct assert len(signal.sinks) == 2 assert obj_b in signal.sinks assert obj_c in signal.sinks # Assert the keyspace is correct assert signal.keyspace is ks_abc # Assert the weight is correct assert signal.weight == 3 # Assert the correct paired transmission parameters are used. assert transmission_params is tp_a else: # Source should be C, sink B assert signal.source is obj_c assert signal.sinks == [obj_b] assert signal.keyspace is ks_cb assert signal.weight == 5 # Assert the correct paired transmission parameters are used. assert transmission_params is tp_b
def test_combine_sig_pars_keyspaces_two_specified(self): """Check that the signal parameters are combined correctly.""" # An error should be raised if multiple keyspaces are found ks = object() sps1 = model.SignalParameters(keyspace=ks) sps2 = model.SignalParameters(keyspace=ks) with pytest.raises(Exception) as exc: sps1.concat(sps2) assert "keyspace" in str(exc.value)
def test_insert_interposers_simple(self): """Test that interposers are inserted correctly.""" cm = model.ConnectionMap() # Add a connection from a node to a passthrough node to the model nodes = [object(), object()] ptn = model.PassthroughNode() for node in nodes: cm.add_connection( node, OutputPort.standard, model.SignalParameters(weight=1), NodeTransmissionParameters(Transform(1, 1, 1)), ptn, InputPort.standard, model.ReceptionParameters(None, 1, None) ) # Add a connection from the passthrough node to another node sink = object() sink_port = object() cm.add_connection( ptn, OutputPort.standard, model.SignalParameters(weight=70), PassthroughNodeTransmissionParameters( Transform(1, 70, np.ones((70, 1))) ), sink, sink_port, model.ReceptionParameters(None, 70, None) ) # Insert interposers, getting a list of interposers and a new # connection map. interposers, new_cm = cm.insert_interposers() assert len(interposers) == 1 # Should insert 1 interposer interposer = interposers[0] # Check that each of the nodes connects to the interposer for node in nodes: from_node = new_cm._connections[node][OutputPort.standard] assert len(from_node) == 1 for sinks in itervalues(from_node): assert len(sinks) == 1 for s in sinks: assert s.sink_object is interposer assert s.port is InputPort.standard # Check that the interposer connects to the sink from_interposer = new_cm._connections[interposer][OutputPort.standard] assert len(from_interposer) == 1 for sinks in itervalues(from_interposer): assert len(sinks) == 1 for s in sinks: assert s.sink_object is sink assert s.port is sink_port
def test_get_signals_to_all_objects(self): # Create two ports sp_1 = mock.Mock(name="Sink Port 1") sp_2 = mock.Mock(name="Sink Port 2") # Create some connections to an object sink_a = mock.Mock(name="Sink A") tp1 = model.SignalParameters() tp2 = model.SignalParameters(True) tp3 = model.SignalParameters(weight=1) rp1 = mock.Mock(name="Reception Parameters 1") rp2 = mock.Mock(name="Reception Parameters 2") # Add the connections cm = model.ConnectionMap() conns_a = ( (tp1, sink_a, sp_1, rp1), (tp1, sink_a, sp_1, rp2), (tp2, sink_a, sp_2, rp1), (tp3, sink_a, sp_2, rp1), ) for tp, sink, sink_port, rp in conns_a: cm.add_connection(None, None, tp, None, sink, sink_port, rp) # Add another connection to another object sink_b = mock.Mock(name="Sink B") cm.add_connection(None, None, tp1, None, sink_b, sp_1, rp) # Get the signals to sink_a, check that they are as expected sigs_a = cm.get_signals_to_all_objects()[sink_a] assert len(sigs_a[sp_1]) == 2 seen_rps = [] for spec in sigs_a[sp_1]: assert spec.signal_parameters is tp1 seen_rps.append(spec.reception_parameters) assert rp1 in seen_rps and rp2 in seen_rps assert len(sigs_a[sp_2]) == 2 seen_tps = [] for spec in sigs_a[sp_2]: assert spec.reception_parameters is rp1 seen_tps.append(spec.signal_parameters) assert tp2 in seen_tps and tp3 in seen_tps
def test_add_connection_different_port(self): # Create parameters for the first connection source1 = mock.Mock(name="Source 1") soport1 = mock.Mock(name="Source Port 1") sp1 = model.SignalParameters(False) tp1 = mock.Mock(name="Transmission Parameters") sink1 = mock.Mock(name="Sink 1") siport1 = mock.Mock(name="Sink Port 1") rp1 = mock.Mock(name="Reception Parameters") # Create the connection map and add the connection cm = model.ConnectionMap() cm.add_connection(source1, soport1, sp1, tp1, sink1, siport1, rp1) # Create and add the second connection with a different port soport2 = mock.Mock(name="Source Port 2") cm.add_connection(source1, soport2, sp1, tp1, sink1, siport1, rp1) # Assert the map is still correct assert (cm._connections[source1][soport1][(sp1, tp1)] == [(sink1, siport1, rp1)]) assert (cm._connections[source1][soport2][(sp1, tp1)] == [(sink1, siport1, rp1)])
def test_insert_interposers_removes_passthrough_node(self): """Test that passthrough nodes are removed while inserting interposers. """ cm = model.ConnectionMap() # Add a connection from a node to a passthrough node to the model node = object() ptn = model.PassthroughNode() cm.add_connection( node, OutputPort.standard, model.SignalParameters(weight=1), NodeTransmissionParameters(Transform(1, 1, 1)), ptn, InputPort.standard, model.ReceptionParameters(None, 1, None) ) # Add a connection from the passthrough node to another node sink = object() cm.add_connection( ptn, OutputPort.standard, model.SignalParameters(weight=1), PassthroughNodeTransmissionParameters(Transform(1, 1, 1)), sink, InputPort.standard, model.ReceptionParameters(None, 1, None) ) # Insert interposers, getting a list of interposers (empty) and a new # connection map. interposers, new_cm = cm.insert_interposers() assert len(interposers) == 0 # No interposers expected # Check that there is now just one connection from the node to the sink from_node = new_cm._connections[node] assert list(from_node) == [OutputPort.standard] for (signal_pars, transmission_pars), sinks in \ iteritems(from_node[OutputPort.standard]): # Check the transmission parameters assert transmission_pars == NodeTransmissionParameters( Transform(1, 1, 1) ) # Check that the sink is correct assert len(sinks) == 1 for s in sinks: assert s.sink_object is sink
def test_insert_interposers_ignore_sinkless(self): """Test that interposers are inserted correctly, ignoring those that don't connect to anything. """ cm = model.ConnectionMap() # Add a connection from a node to a passthrough node to the model nodes = [object(), object()] ptn = model.PassthroughNode() for node in nodes: cm.add_connection( node, OutputPort.standard, model.SignalParameters(weight=1), NodeTransmissionParameters(Transform(1, 1, 1)), ptn, InputPort.standard, model.ReceptionParameters(None, 1, None) ) # Connect the passthrough node to another passthrough node ptn2 = model.PassthroughNode() cm.add_connection(ptn, OutputPort.standard, model.SignalParameters(), PassthroughNodeTransmissionParameters( Transform(1, 70, transform=np.ones((70, 1)))), ptn2, InputPort.standard, model.ReceptionParameters(None, 70, None)) # Connect the passthrough node to another passthrough node ptn3 = model.PassthroughNode() cm.add_connection(ptn2, OutputPort.standard, model.SignalParameters(), PassthroughNodeTransmissionParameters( Transform(70, 70, 1)), ptn3, InputPort.standard, model.ReceptionParameters(None, 70, None)) # Insert interposers, getting a list of interposers and a new # connection map. interposers, new_cm = cm.insert_interposers() assert len(interposers) == 0 # No interposers # No signals at all assert len(list(new_cm.get_signals())) == 0
def test_get_signals_from_object(self): # Create two ports sp_1 = mock.Mock(name="Source Port 1") sp_2 = mock.Mock(name="Source Port 2") # Create some connections from two objects source_a = mock.Mock(name="Source A") source_b = mock.Mock(name="Source B") sp1 = model.SignalParameters() sp2 = model.SignalParameters(True) sp3 = model.SignalParameters(weight=1) tp1 = mock.Mock(name="Transmission Parameters 1") tp2 = mock.Mock(name="Transmission Parameters 2") tp3 = mock.Mock(name="Transmission Parameters 3") # Add the connections cm = model.ConnectionMap() conns_a = ((sp_1, sp1, tp1), (sp_1, sp2, tp2), (sp_2, sp3, tp3)) for port, sp, tp in conns_a: cm.add_connection(source_a, port, sp, tp, None, None, None) conns_b = ((sp_2, sp1, tp2), (sp_2, sp2, tp2), (sp_1, sp3, tp3)) for port, sp, tp in conns_b: cm.add_connection(source_b, port, sp, tp, None, None, None) # Get the signals from source_a, check that they are as expected sigs_a = cm.get_signals_from_object(source_a) assert (sp1, tp1) in sigs_a[sp_1] assert (sp2, tp2) in sigs_a[sp_1] assert sigs_a[sp_2] == [(sp3, tp3)] # Get the signals from source_b, check that they are as expected sigs_b = cm.get_signals_from_object(source_b) assert sigs_b[sp_1] == [(sp3, tp3)] assert (sp1, tp2) in sigs_b[sp_2] assert (sp2, tp2) in sigs_b[sp_2]
def test_remove_sinkless_objects(): """Filter objects with no outgoing connections (hence sinks) can safely be removed from the network. This process should be called repeatedly as long as there are filters which can be removed. We construct the following network and then expect that all items apart from A and G are removed from the connection map. /--> C --\ / \ A --> B E --> F | \ / | \--> D --/ \-> G The result should be a reduced connection map and a list of all objects apart from A and G. """ class G(object): """Separate class to indicate the type filtering works as expected.""" # Construct the network objects a = mock.Mock(name="A") b = mock.Mock(name="B") c = mock.Mock(name="C") d = mock.Mock(name="D") e = mock.Mock(name="E") f = mock.Mock(name="F") g = G() # Make the connection map cm = model.ConnectionMap() cm.add_connection(a, None, model.SignalParameters(), None, b, None, None) cm.add_connection(a, None, model.SignalParameters(), None, g, None, None) cm.add_connection(b, None, model.SignalParameters(), None, c, None, None) cm.add_connection(b, None, model.SignalParameters(), None, d, None, None) cm.add_connection(c, None, model.SignalParameters(), None, e, None, None) cm.add_connection(d, None, model.SignalParameters(), None, e, None, None) cm.add_connection(e, None, model.SignalParameters(), None, f, None, None) cm._connections[f][None] = list() # Remove the sinkless filters removed = model.remove_sinkless_objects(cm, mock.Mock) # Check that the expected connection A->G still exists. assert len(cm.get_signals_from_object(a)[None]) == 1 assert len(list(cm.get_signals())) == 1 # Assert that the removed objects list is correct expected_removed = set((b, c, d, e, f)) for x in expected_removed: assert len(cm.get_signals_from_object(x)) == 0 assert removed == expected_removed
def test_stack_interposers_no_interposer(self): # Create a network consisting of two ensembles targeting a sink MockEnsemble = collections.namedtuple("MockEnsemble", "size_in") source_1 = EnsembleLIF(MockEnsemble(1)) source_2 = EnsembleLIF(MockEnsemble(1)) sink = object() # Create a connection map and add the connections cm = model.ConnectionMap() cm.add_connection( source_1, OutputPort.standard, model.SignalParameters(weight=512), EnsembleTransmissionParameters( decoders=np.ones((1, 100)), transform=Transform( size_in=1, size_out=1, transform=1.0 ) ), sink, InputPort.standard, model.ReceptionParameters(None, 1, None) ) cm.add_connection( # Make sure the transform is distinct! source_2, OutputPort.standard, model.SignalParameters(weight=512), EnsembleTransmissionParameters( decoders=np.ones((1, 100)), transform=Transform( size_in=1, size_out=1, transform=0.5 ) ), sink, InputPort.standard, model.ReceptionParameters(None, 512, None) ) # Insert and stack the interposers interposers, new_cm = cm.insert_and_stack_interposers() assert len(interposers) == 0 # The connection map should be unchanged assert new_cm._connections == cm._connections
def test_stack_interposers_no_stack(self): # Create a network consisting of two ensembles targeting a sink, insert # interposers into this network and then stack the interposers to # reduce later connectivity. MockEnsemble = collections.namedtuple("MockEnsemble", "size_in") source_1 = EnsembleLIF(MockEnsemble(1)) source_2 = EnsembleLIF(MockEnsemble(1)) sink = object() # Create a connection map and add the connections cm = model.ConnectionMap() cm.add_connection( source_1, OutputPort.standard, model.SignalParameters(weight=512), EnsembleTransmissionParameters( decoders=np.ones((1, 100)), transform=Transform( size_in=1, size_out=512, transform=np.ones((512, 1)) ) ), sink, InputPort.standard, model.ReceptionParameters(None, 512, None) ) cm.add_connection( # Transform and the filter are distinct! source_2, OutputPort.standard, model.SignalParameters(weight=512), EnsembleTransmissionParameters( decoders=np.ones((1, 100)), transform=Transform( size_in=1, size_out=512, transform=0.5*np.ones((512, 1)) ) ), sink, InputPort.standard, model.ReceptionParameters(object(), 512, None) ) # Insert and stack the interposers interposers, new_cm = cm.insert_and_stack_interposers() new_cm = new_cm._connections assert len(interposers) == 2
def test_get_passthrough_node_dependencies(): """Check that the creation of a passthrough Node dependency graph works as expected. """ # Construct a mock network with three sets of passthrough Nodes in the # configuration: # # 0 --> 2 --> 3 # 1 --/ \--> 4 # # 5 --------> 6 # # 7 # Create the Nodes and operators ns = [nengo.Node(size_in=1, label="N{}".format(i), add_to_container=False) for i in range(8)] ops = [mock.Mock(name="O{}".format(i)) for i, _ in enumerate(ns)] # Create the model m = Model() m.object_operators = ptns = dict(zip(ns, ops)) # Add the connections for i, j in ((0, 2), (1, 2), (2, 3), (2, 4), (5, 6)): m.connection_map.add_connection( ops[i], None, model.SignalParameters(), None, ops[j], None, None) # Check the dependencies against the expected dependencies. deps = model_utils.get_passthrough_node_dependencies(m, ptns) assert deps == { (ns[0], ops[0]): set(), (ns[1], ops[1]): set(), (ns[2], ops[2]): {(ns[0], ops[0]), (ns[1], ops[1])}, (ns[3], ops[3]): {(ns[2], ops[2])}, (ns[4], ops[4]): {(ns[2], ops[2])}, (ns[5], ops[5]): set(), (ns[6], ops[6]): {(ns[5], ops[5])}, (ns[7], ops[7]): set(), }
def test_add_connection_repeated(self): # Create parameters for the first connection source1 = mock.Mock(name="Source 1") soport1 = mock.Mock(name="Source Port 1") sp1 = model.SignalParameters(False) tp1 = mock.Mock(name="Transmission Parameters") sink1 = mock.Mock(name="Sink 1") siport1 = mock.Mock(name="Sink Port 1") rp1 = mock.Mock(name="Reception Parameters") # Create the connection map and add the connection cm = model.ConnectionMap() cm.add_connection(source1, soport1, sp1, tp1, sink1, siport1, rp1) # Add the "same" connection again cm.add_connection(source1, soport1, sp1, tp1, sink1, siport1, rp1) # Check that the connection was added twice assert (cm._connections[source1][soport1][(sp1, tp1)] == [(sink1, siport1, rp1)]*2)
def test_add_connection_basic(self): """Test adding connections to a connection map.""" # Create parameters for the first connection source1 = mock.Mock(name="Source 1") soport1 = mock.Mock(name="Source Port 1") sp1 = model.SignalParameters(False) sink1 = mock.Mock(name="Sink 1") siport1 = mock.Mock(name="Sink Port 1") rp1 = mock.Mock(name="Reception Parameters") # Create the connection map and add the connection cm = model.ConnectionMap() assert len(list(cm.get_signals())) == 0 cm.add_connection(source1, soport1, sp1, None, sink1, siport1, rp1) # Assert that this connection was added correctly assert len(list(cm.get_signals())) == 1 assert len(cm._connections) == 1 assert len(cm._connections[source1]) == 1 assert (cm._connections[source1][soport1][(sp1, None)] == [(sink1, siport1, rp1)])
def test_get_coarsened_graph_and_extract_cliques(self): """Check that a coarsened representation of the graph can be extracted. """ # Construct a graph of the form: # # /--<-------------<--\ # | | # E ->-\ v /->- E ->-\ | # E ->--> o -->- E ->--> o ->-/ # E ->-/ \->- E ->-/ # # Where E are ensembles and `o' is a passthrough node. cm = model.ConnectionMap() # Network objects ens_a = [object() for _ in range(3)] ens_b = [object() for _ in range(3)] ptns = [model.PassthroughNode(), model.PassthroughNode()] # Add connections to the network, excluding the feedback loop. for ens in ens_a: cm.add_connection( ens, OutputPort.standard, model.SignalParameters(), object(), ptns[0], InputPort.standard, None ) for ens in ens_b: cm.add_connection( ptns[0], OutputPort.standard, model.SignalParameters(), object(), ens, InputPort.standard, None ) cm.add_connection( ens, OutputPort.standard, model.SignalParameters(), object(), ptns[1], InputPort.standard, None ) # Get a coarsened representation of the connectivity graph = cm.get_coarsened_graph() for ens in ens_a: assert graph[ens].inputs == set() assert graph[ens].outputs == {ptns[0]} for ens in ens_b: assert graph[ens].inputs == {ptns[0]} assert graph[ens].outputs == {ptns[1]} assert graph[ptns[0]].inputs == set(ens_a) assert graph[ptns[0]].outputs == set(ens_b) assert graph[ptns[1]].inputs == set(ens_b) assert graph[ptns[1]].outputs == set() # Extract cliques from this graph, without the feedback in place there # should be two cliques. for sources, nodes in cm.get_cliques(): if nodes == {ptns[0]}: assert sources == set(ens_a) elif nodes == {ptns[1]}: assert sources == set(ens_b) else: assert False, "Unexpected clique." # Add a feedback connection to the graph, this should mean that only a # single clique exists. cm.add_connection( ptns[1], OutputPort.standard, model.SignalParameters(), object(), ptns[0], InputPort.standard, None ) # Get a coarsened representation of the connectivity graph = cm.get_coarsened_graph() for ens in ens_a: assert graph[ens].inputs == set() assert graph[ens].outputs == {ptns[0]} for ens in ens_b: assert graph[ens].inputs == {ptns[0]} assert graph[ens].outputs == {ptns[1]} assert graph[ptns[0]].inputs == set(ens_a) | {ptns[1]} assert graph[ptns[0]].outputs == set(ens_b) assert graph[ptns[1]].inputs == set(ens_b) assert graph[ptns[1]].outputs == {ptns[0]} # Extract cliques from this graph, without the feedback in place there # should be two cliques. for sources, nodes in cm.get_cliques(): assert nodes == set(ptns) assert sources == set(ens_a) | set(ens_b)
def test_remove_operator_from_connection_map_unforced(): """Check that a calculation is made to determine whether it is better to keep or remove an operator depending on the density of the outgoing connections. In the example: /- G[0] A[0] --\ /-- D[0] --\ /-- G[1] A[1] --- B --- C --- D[1] --- E --- F --- G[2] A[n] --/ \-- D[n] --/ \-- G[3] \- G[n] B, C and F should be removed but E should be retained: /- G[0] A[0] --- D[0] --\ /-- G[1] A[1] --- D[1] --- E --- G[2] A[n] --- D[n] --/ \-- G[3] \- G[n] """ # Create the operators D = 512 SD = 16 op_A = [mock.Mock(name="A{}".format(i)) for i in range(D // SD)] op_B = mock.Mock(name="B") op_C = mock.Mock(name="C") op_D = [mock.Mock(name="D{}".format(i)) for i in range(D // SD)] op_E = mock.Mock(name="E") op_F = mock.Mock(name="F") op_G = [mock.Mock(name="G{}".format(i)) for i in range(D)] # Create a connection map cm = model.ConnectionMap() # Create the fan-in connections for sources, sink in ((op_A, op_B), (op_D, op_E)): # Get the signal and reception parameters sps = model.SignalParameters(True, D) rps = model.ReceptionParameters(None, D) for i, source in enumerate(sources): # Get the transform transform = np.zeros((D, SD)) transform[i * SD:(i + 1) * SD, :] = np.eye(SD) # Get the parameters tps = EnsembleTransmissionParameters(np.ones((1, SD)), transform) cm.add_connection(source_object=source, source_port=None, signal_parameters=sps, transmission_parameters=tps, sink_object=sink, sink_port=None, reception_parameters=rps) # Create the fan-out connection C to D[...] # Get the signal and reception parameters sps = model.SignalParameters(True, SD) rps = model.ReceptionParameters(None, SD) for i, sink in enumerate(op_D): # Get the transform transform = np.zeros((SD, D)) transform[:, i * SD:(i + 1) * SD] = np.eye(SD) # Get the parameters tps = PassthroughNodeTransmissionParameters(transform) cm.add_connection(source_object=op_C, source_port=None, signal_parameters=sps, transmission_parameters=tps, sink_object=sink, sink_port=None, reception_parameters=rps) # Create the connection B to C sps = model.SignalParameters(True, D) rps = model.ReceptionParameters(None, D) tps = PassthroughNodeTransmissionParameters(np.eye(D)) cm.add_connection(source_object=op_B, source_port=None, signal_parameters=sps, transmission_parameters=tps, sink_object=op_C, sink_port=None, reception_parameters=rps) # Create the connection E to F transform = np.zeros((D, D)) for i in range(D): for j in range(D): transform[i, j] = i + j sps = model.SignalParameters(True, D) rps = model.ReceptionParameters(None, D) tps = PassthroughNodeTransmissionParameters(transform) cm.add_connection(source_object=op_E, source_port=None, signal_parameters=sps, transmission_parameters=tps, sink_object=op_F, sink_port=None, reception_parameters=rps) # Create the fan-out connections from F sps = model.SignalParameters(True, SD) rps = model.ReceptionParameters(None, SD) for i, sink in enumerate(op_G): # Get the transform transform = np.zeros((1, D)) transform[:, i] = 1.0 # Get the parameters tps = PassthroughNodeTransmissionParameters(transform) cm.add_connection(source_object=op_F, source_port=None, signal_parameters=sps, transmission_parameters=tps, sink_object=sink, sink_port=None, reception_parameters=rps) # Remove all of the passthrough Nodes, only E should be retained assert model_utils.remove_operator_from_connection_map(cm, op_B, force=False) assert model_utils.remove_operator_from_connection_map(cm, op_C, force=False) assert not model_utils.remove_operator_from_connection_map( cm, op_E, force=False) assert model_utils.remove_operator_from_connection_map(cm, op_F, force=False) # Check that each A has only one outgoing signal and that it terminates at # the paired D. Additionally check that each D has only one outgoing # signal and that it terminates at E. for a, d in zip(op_A, op_D): # Connections from A[n] from_a = cm._connections[a] assert len(from_a) == 1 assert len(from_a[None]) == 1 ((signal_parameters, transmission_parameters), sinks) = from_a[None][0] assert signal_parameters == model.SignalParameters(True, SD, None) assert transmission_parameters.transform.shape == (SD, SD) assert np.all(transmission_parameters.transform == np.eye(SD)) assert sinks == [(d, None, model.ReceptionParameters(None, SD))] # Connection(s) from D[n] from_d = cm._connections[d] assert len(from_d) == 1 assert len(from_d[None]) == 1 ((signal_parameters, transmission_parameters), sinks) = from_d[None][0] assert signal_parameters == model.SignalParameters(True, D, None) assert transmission_parameters.transform.shape == (D, SD) # Check that there are many connections from E from_e = cm._connections[op_E] assert len(from_e) == 1 print(from_e[None][0].parameters[1].transform) assert len(from_e[None]) == D
def test_remove_operator_from_connection_map(): """Test that operators are correctly removed from connection maps. We test the following model: O1 ------> O3 -----> O5 /-----/ \ / \----> O6 O2 --> O4 Removing `O3' should result in: O1 --> O6 / /-> 05 O2 --> O4 """ # Construct the operators operators = [mock.Mock(name="O{}".format(i + 1)) for i in range(6)] # Create a connection map cm = model.ConnectionMap() # Add the connection O1 to O3 sps = model.SignalParameters(True, 6) tps = PassthroughNodeTransmissionParameters( np.vstack([np.eye(3), np.zeros((3, 3))])) rps = model.ReceptionParameters(None, 6) cm.add_connection(source_object=operators[0], source_port=None, signal_parameters=sps, transmission_parameters=tps, sink_object=operators[2], sink_port=None, reception_parameters=rps) # Add the connection O2 to O3 sps = model.SignalParameters(False, 6) tps = PassthroughNodeTransmissionParameters( np.vstack([np.zeros((3, 3)), np.eye(3)])) rps = model.ReceptionParameters(None, 6) cm.add_connection(source_object=operators[1], source_port=None, signal_parameters=sps, transmission_parameters=tps, sink_object=operators[2], sink_port=None, reception_parameters=rps) # Add the connection O2 to O4 (with a custom keyspace) sps = model.SignalParameters(False, 6, mock.Mock("Keyspace 1")) tps = PassthroughNodeTransmissionParameters( np.vstack([np.eye(3), np.eye(3)])) rps = model.ReceptionParameters(None, 6) cm.add_connection(source_object=operators[1], source_port=None, signal_parameters=sps, transmission_parameters=tps, sink_object=operators[3], sink_port=None, reception_parameters=rps) # Add the connection O3 to O5 sps = model.SignalParameters(False, 3) tps = PassthroughNodeTransmissionParameters( np.hstack((np.zeros((3, 3)), np.eye(3)))) rps = model.ReceptionParameters(None, 3) cm.add_connection(source_object=operators[2], source_port=None, signal_parameters=sps, transmission_parameters=tps, sink_object=operators[4], sink_port=None, reception_parameters=rps) # Add the connection O3 to O6 sps = model.SignalParameters(False, 3) tps = PassthroughNodeTransmissionParameters( np.hstack([np.eye(3), np.eye(3)]) * 2) rps = model.ReceptionParameters(None, 3) cm.add_connection(source_object=operators[2], source_port=None, signal_parameters=sps, transmission_parameters=tps, sink_object=operators[5], sink_port=None, reception_parameters=rps) # Remove O3 from the connection map model_utils.remove_operator_from_connection_map(cm, operators[2]) # Check that the received and transmitted signals are as expected # FROM O1 from_o1 = cm._connections[operators[0]] assert len(from_o1) == 1 assert len(from_o1[None]) == 1 ((signal_parameters, transmission_parameters), sinks) = from_o1[None][0] assert signal_parameters == model.SignalParameters(True, 3, None) assert transmission_parameters.transform.shape == (3, 3) assert np.all(transmission_parameters.transform == np.eye(3) * 2) assert sinks == [(operators[5], None, model.ReceptionParameters(None, 3))] # FROM O2 from_o2 = cm._connections[operators[1]] assert len(from_o2) == 1 assert len(from_o2[None]) == 3 for ((signal_parameters, transmission_parameters), sinks) in from_o2[None]: if transmission_parameters.transform.shape == (3, 3): assert (signal_parameters == model.SignalParameters( False, 3, None)) if np.any(transmission_parameters.transform == 2.0): # TO O6 assert np.all(transmission_parameters.transform == np.eye(3) * 2) assert sinks == [(operators[5], None, model.ReceptionParameters(None, 3))] else: # TO O5 assert np.all(transmission_parameters.transform == np.eye(3)) assert sinks == [(operators[4], None, model.ReceptionParameters(None, 3))] else: # TO O4 assert transmission_parameters.transform.shape == (6, 3) assert np.all( transmission_parameters.transform == np.vstack([np.eye(3)] * 2)) assert sinks == [(operators[3], None, model.ReceptionParameters(None, 6))] # We now add a connection from O4 to O6 with a custom keyspace. Removing # O4 will fail because keyspaces can't be merged. signal_params = model.SignalParameters(False, 1, mock.Mock("Keyspace 2")) transmission_params = PassthroughNodeTransmissionParameters(1.0) reception_params = model.ReceptionParameters(None, 1) cm.add_connection(source_object=operators[3], source_port=None, signal_parameters=signal_params, transmission_parameters=transmission_params, sink_object=operators[5], sink_port=None, reception_parameters=reception_params) with pytest.raises(NotImplementedError) as err: model_utils.remove_operator_from_connection_map(cm, operators[3]) assert "keyspace" in str(err.value).lower()
def test_stack_interposers(self): """Test that interposers with equivalent output sets are stacked correctly and that this modifies both their incoming and output connections. """ # Create a network consisting of two ensembles targeting a sink, insert # interposers into this network and then stack the interposers to # reduce later connectivity. MockEnsemble = collections.namedtuple("MockEnsemble", "size_in") source_1 = EnsembleLIF(MockEnsemble(1)) source_2 = EnsembleLIF(MockEnsemble(1)) sink = object() # Create a connection map and add the connections cm = model.ConnectionMap() cm.add_connection( source_1, OutputPort.standard, model.SignalParameters(weight=512), EnsembleTransmissionParameters( decoders=np.ones((1, 100)), transform=Transform( size_in=1, size_out=512, transform=np.ones((512, 1)) ) ), sink, InputPort.standard, model.ReceptionParameters(None, 512, None) ) cm.add_connection( # Make sure the transform is distinct! source_2, OutputPort.standard, model.SignalParameters(weight=512), EnsembleTransmissionParameters( decoders=np.ones((1, 100)), transform=Transform( size_in=1, size_out=512, transform=0.5*np.ones((512, 1)) ) ), sink, InputPort.standard, model.ReceptionParameters(None, 512, None) ) # Insert and stack the interposers interposers, new_cm = cm.insert_and_stack_interposers() new_cm = new_cm._connections assert len(interposers) == 1 # Should only be one interposer interposer = interposers[0] assert interposer.size_in == 2 # The connections from the sources and from the interposer can be one # of two forms, attempt to determine which and then check that the rest # of the connections are correct. assert set(new_cm.keys()) == {source_1, source_2, interposer} assert len(new_cm[source_1][OutputPort.standard]) == 1 assert len(new_cm[source_2][OutputPort.standard]) == 1 assert len(new_cm[interposer][OutputPort.standard]) == 1 for params, sinks in iteritems(new_cm[source_1][OutputPort.standard]): sps, tps = params # Extract signal and transmission parameters assert isinstance(sps, model.SignalParameters) assert sps.weight == 1 # Weight should not be modified assert isinstance(tps, EnsembleTransmissionParameters) assert tps.size_in == 1 assert tps.size_out == 2 if np.array_equal(tps.slice_out, np.array([0])): # Source 1 points to the first dimension of the interposer. # Assert that Source 2 points to the other dimension of the # interposer. expected_source_2_slice = np.array([1]) expected_transform = np.hstack( (np.ones((512, 1)), 0.5*np.ones((512, 1))) ) elif np.array_equal(tps.slice_out, np.array([1])): # Source 2 points to the second dimension of the interposer. # Assert that Source 2 points to the other dimension of the # interposer. expected_source_2_slice = np.array([0]) expected_transform = np.hstack( (0.5*np.ones((512, 1)), np.ones((512, 1))) ) else: assert False, "Unexpected dimension in slice" # Check that the sinks are as expected assert len(sinks) == 1 for new_sink in sinks: assert new_sink.sink_object is interposer for params, sinks in iteritems(new_cm[source_2][OutputPort.standard]): sps, tps = params # Extract signal and transmission parameters assert isinstance(sps, model.SignalParameters) assert sps.weight == 1 assert isinstance(tps, EnsembleTransmissionParameters) assert tps.size_in == 1 assert tps.size_out == 2 assert np.array_equal(tps.slice_out, expected_source_2_slice) assert len(sinks) == 1 for new_sink in sinks: assert new_sink.sink_object is interposer # Check the connection from the interposer for params, sinks in iteritems( new_cm[interposer][OutputPort.standard]): sps, tps = params # Extract signal and transmission parameters assert isinstance(sps, model.SignalParameters) assert sps.weight == 512 # Not changed assert isinstance(tps, PassthroughNodeTransmissionParameters) assert tps.size_in == 2 assert tps.size_out == 512 assert np.array_equal( tps.full_transform(False, False), expected_transform ) assert len(sinks) == 1 for new_sink in sinks: assert new_sink.sink_object is sink
def test_insert_interposer_after_ensemble(self): """Test that an interposer can be inserted after a connection from an ensemble. """ # Create an ensemble and connect it to another ensemble with a # significantly larger size in. mock_ensemble_1 = mock.Mock(spec_set=["size_in"]) mock_ensemble_1.size_in = 1 e1 = EnsembleLIF(mock_ensemble_1) mock_ensemble_2 = mock.Mock(spec_set=["size_in"]) mock_ensemble_2.size_in = 512 e2 = EnsembleLIF(mock_ensemble_2) # Create a new connection map and add a connection between the # ensembles. transform = Transform(2, 512, np.ones((512, 2))) tps = EnsembleTransmissionParameters(np.ones((2, 100)), transform) sps = model.SignalParameters(weight=512) filt = object() cm = model.ConnectionMap() cm.add_connection( e1, OutputPort.standard, sps, tps, e2, InputPort.standard, model.ReceptionParameters(filt, 512, None) ) # Insert interposers interposers, new_cm = cm.insert_interposers() # Check that an interposer was inserted assert len(interposers) == 1 interposer = interposers[0] # Grab the interposer assert interposer.size_in == 2 # Check that the connection from e1 to the interposer is as expected assert len(new_cm._connections[e1][OutputPort.standard]) == 1 for conn, sinks in iteritems( new_cm._connections[e1][OutputPort.standard]): new_sps, new_tps = conn # Get signal and transmission pars assert isinstance(tps, EnsembleTransmissionParameters) assert np.array_equal(tps.decoders, new_tps.decoders) assert new_tps.size_out == 2 assert new_sps.weight == 2 assert np.array_equal( new_tps.full_transform(False, False), np.eye(2) ) assert len(sinks) == 1 for sink in sinks: assert sink.sink_object is interposer assert sink.reception_parameters == model.ReceptionParameters( None, 2, None ) # Check that the connection from the interposer to e2 is as expected assert len(new_cm._connections[interposer][OutputPort.standard]) == 1 for conn, sinks in iteritems( new_cm._connections[interposer][OutputPort.standard]): new_sps, new_tps = conn # Check that the transform is as expected assert isinstance(new_tps, PassthroughNodeTransmissionParameters) assert np.array_equal(tps.full_transform(False, False), new_tps.full_transform(False, False)) assert np.array_equal(tps.slice_out, new_tps.slice_out) # Check the sinks are correct assert len(sinks) == 1 for sink in sinks: assert sink.sink_object is e2 assert sink.reception_parameters == model.ReceptionParameters( filt, 512, None )
def test_insert_interposers_earliest_interposer_only(self): """Test that only the first interposer in a network of possible interposers is inserted. """ cm = model.ConnectionMap() node = object() ptn1 = model.PassthroughNode() ptn2 = model.PassthroughNode() sink = object() # Add connections cm.add_connection( node, OutputPort.standard, model.SignalParameters(), NodeTransmissionParameters(Transform(16, 512, 1, slice_out=slice(16, 32))), ptn1, InputPort.standard, model.ReceptionParameters(None, 512, None) ) cm.add_connection( ptn1, OutputPort.standard, model.SignalParameters(), PassthroughNodeTransmissionParameters( Transform(512, 512, np.ones((512, 512))) ), ptn2, InputPort.standard, model.ReceptionParameters(None, 512, None) ) cm.add_connection( ptn2, OutputPort.standard, model.SignalParameters(), PassthroughNodeTransmissionParameters( Transform(512, 1024, np.ones((1024, 512))) ), sink, InputPort.standard, model.ReceptionParameters(None, 1024, None) ) # Insert interposers, only one should be included interposers, new_cm = cm.insert_interposers() assert len(interposers) == 1 interposer = interposers[0] # Check that the node connects to the interposer and that the # interposer was the first passthrough node. from_node = new_cm._connections[node][OutputPort.standard] for (_, transmission_pars), sinks in iteritems(from_node): assert transmission_pars == NodeTransmissionParameters( Transform(16, 512, 1, slice_out=slice(16, 32)) ) assert len(sinks) == 1 for s in sinks: assert s.sink_object is interposer # Check that the interposer connects to the sink from_interposer = new_cm._connections[interposer][OutputPort.standard] for (_, transmission_pars), sinks in iteritems(from_interposer): assert transmission_pars.size_in == 512 assert transmission_pars.size_out == 1024 assert len(sinks) == 1 for s in sinks: assert s.sink_object is sink
def test_add_default_keyspace(self): # Adding a default keyspace will modify all transmission parameters # which have their keyspace as None by adding a new keyspace instance # with the "object" and "connection" ID automatically set. # # Test this by creating 3 objects. The first object will have 2 # outgoing connection, the second will have an outgoing connection with # a pre-existing keyspace and the third will have 1 outgoing connection # only. Check that the object and connection IDs are set sequentially # only for connections without existing keyspaces. # Create 3 source objects sources = [mock.Mock(name="Source {}".format(i)) for i in range(3)] # Create transmission parameters sp00 = model.SignalParameters() sp01 = model.SignalParameters(latching=True) ks10 = mock.Mock(name="Keyspace") sp10 = model.SignalParameters(keyspace=ks10) sp20 = model.SignalParameters() port21 = mock.Mock(name="Port 2.1") sp21 = model.SignalParameters() # Manually create the connection map so that the order can be # guaranteed. cm = model.ConnectionMap() cm._connections = collections.OrderedDict() cm._connections[sources[0]] = collections.OrderedDict() cm._connections[sources[0]][None] = [ model._ParsSinksPair((sp00, None)), model._ParsSinksPair((sp01, None)) ] cm._connections[sources[1]] = collections.OrderedDict() cm._connections[sources[1]][None] = [ model._ParsSinksPair((sp10, None)) ] cm._connections[sources[2]] = collections.OrderedDict() cm._connections[sources[2]][None] = [ model._ParsSinksPair((sp20, None)) ] cm._connections[sources[2]][port21] = [ model._ParsSinksPair((sp21, None)) ] # Add the default keyspace ks = mock.Mock() kss = { 0: mock.Mock(name="Keyspace00"), 1: mock.Mock(name="Keyspace01"), 2: mock.Mock(name="Keyspace20"), 3: mock.Mock(name="Keyspace21"), } ks.side_effect = lambda connection_id: kss[connection_id] cm.add_default_keyspace(ks) # Ensure that the correct calls were made to "ks" in the correct order. ks.assert_has_calls([mock.call(connection_id=j) for j in range(4)]) # Assert that the correct keyspaces were assigned to the correct # objects. assert sp00.keyspace is kss[0] assert sp01.keyspace is kss[1] assert sp10.keyspace is ks10 assert sp20.keyspace is kss[2] assert sp21.keyspace is kss[3]