Skip to content

Robotics — CPG + Swarm Coupling

Spiking neural network primitives for robotic control: central pattern generation for locomotion and multi-agent spike-based swarm synchronization.

StochasticCPG — Central Pattern Generator

Two mutually inhibiting HomeostaticLIFNeuron instances produce rhythmic alternating spike outputs (e.g., left/right leg stepping). The mutual inhibition ensures anti-phase firing: when neuron 1 fires, its spike trace suppresses neuron 2 via inhibitory current, and vice versa.

Parameter Default Meaning
drive_current 2.0 Tonic excitatory input to both neurons
inhibition_weight 2.0 Strength of mutual inhibition

The step() method returns a (spike1, spike2) tuple on each timestep. The adaptation rate (0.1) and target rate (0.3) of the homeostatic neurons control oscillation frequency.

SwarmCoupling — Multi-Agent Synchronization

Synchronizes two SCLearningLayer agents by shifting their weights toward each other via Hebbian cross-correlation: W_a += α * (W_b - W_a), W_b -= α * (W_b - W_a). After sufficient synchronization steps, both agents converge to identical weight configurations.

Parameter Default Meaning
coupling_strength 0.1 Fraction of weight difference applied per step

Both agents must have the same neuron count (raises ValueError otherwise).

sc_neurocore.robotics.swarm is in the scoped public-docstring policy. Its dedicated robotics swarm tests are strict typed and cover construction, weight-shift mutation through SCLearningLayer, repeated convergence, and fail-closed mismatched agent sizes at 100% isolated module coverage. This slice touches only the Python robotics coupling surface; it has no polyglot or benchmark counterpart.

Usage

Python
from sc_neurocore.robotics import StochasticCPG, SwarmCoupling

# Locomotion CPG
cpg = StochasticCPG(drive_current=2.0, inhibition_weight=2.0)
left_steps, right_steps = [], []
for _ in range(500):
    s1, s2 = cpg.step()
    left_steps.append(s1)
    right_steps.append(s2)
print(f"Left spikes: {sum(left_steps)}, Right spikes: {sum(right_steps)}")

# Swarm synchronization
from sc_neurocore.layers.sc_learning_layer import SCLearningLayer
agent_a = SCLearningLayer(n_inputs=8, n_neurons=4)
agent_b = SCLearningLayer(n_inputs=8, n_neurons=4)
coupler = SwarmCoupling(coupling_strength=0.3)
for _ in range(20):
    coupler.synchronize(agent_a, agent_b)
# Weights now converged

sc_neurocore.robotics

sc_neurocore.robotics -- Tier: research (experimental / research).

StochasticCPG dataclass

Central Pattern Generator using two mutually inhibiting neurons. Generates rhythmic alternating outputs (e.g., Left/Right leg).

Source code in src/sc_neurocore/robotics/cpg.py
Python
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@dataclass
class StochasticCPG:
    """
    Central Pattern Generator using two mutually inhibiting neurons.
    Generates rhythmic alternating outputs (e.g., Left/Right leg).
    """

    drive_current: float = 2.0
    inhibition_weight: float = 2.0

    def __post_init__(self) -> None:
        # High adaptation rate to force switching
        self.n1 = HomeostaticLIFNeuron(v_threshold=1.0, adaptation_rate=0.1, target_rate=0.3)
        self.n2 = HomeostaticLIFNeuron(v_threshold=1.0, adaptation_rate=0.1, target_rate=0.3)

        self.s1_trace = 0.0
        self.s2_trace = 0.05  # Small asymmetry to break initial symmetry
        self.decay = 0.8

    def step(self) -> tuple[int, int]:
        # Inhibition logic:
        # Input to N1 = Drive - Weight * N2_Activity
        # Input to N2 = Drive - Weight * N1_Activity

        # We use a trace of spikes for inhibition "potential"
        i1 = self.drive_current - self.inhibition_weight * self.s2_trace
        i2 = self.drive_current - self.inhibition_weight * self.s1_trace

        spike1 = self.n1.step(i1)
        spike2 = self.n2.step(i2)

        # Update traces
        self.s1_trace = self.s1_trace * self.decay + spike1
        self.s2_trace = self.s2_trace * self.decay + spike2

        return spike1, spike2

SwarmCoupling dataclass

Synchronize two learning agents by mutually attracting their weights.

Source code in src/sc_neurocore/robotics/swarm.py
Python
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@dataclass
class SwarmCoupling:
    """Synchronize two learning agents by mutually attracting their weights."""

    coupling_strength: float = 0.1

    def synchronize(self, agent_a: SCLearningLayer, agent_b: SCLearningLayer) -> None:
        """Adjust both agents toward a shared weight configuration.

        The update applies a bounded reciprocal shift:
        ``W_a += alpha * (W_b - W_a)`` and
        ``W_b -= alpha * (W_b - W_a)``.
        """
        # We assume both agents have same number of neurons
        if agent_a.n_neurons != agent_b.n_neurons:
            raise ValueError("Agents must have same size for direct coupling.")

        # Extract weights
        wa = agent_a.get_weights()
        wb = agent_b.get_weights()

        # Mutual Attraction: Weights shift toward each other
        # W_new = W + alpha * (W_other - W)
        delta = self.coupling_strength * (wb - wa)

        # Update Agent A
        new_wa = wa + delta
        for i in range(agent_a.n_neurons):
            for j in range(agent_a.n_inputs):
                agent_a.synapses[i][j].update_weight(new_wa[i, j])

        # Update Agent B (Reciprocal)
        new_wb = wb - delta
        for i in range(agent_b.n_neurons):
            for j in range(agent_b.n_inputs):
                agent_b.synapses[i][j].update_weight(new_wb[i, j])

        logger.info(
            "Swarm Synchronization: Shifted weights by magnitude %.6f", np.mean(np.abs(delta))
        )

synchronize(agent_a, agent_b)

Adjust both agents toward a shared weight configuration.

The update applies a bounded reciprocal shift: W_a += alpha * (W_b - W_a) and W_b -= alpha * (W_b - W_a).

Source code in src/sc_neurocore/robotics/swarm.py
Python
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def synchronize(self, agent_a: SCLearningLayer, agent_b: SCLearningLayer) -> None:
    """Adjust both agents toward a shared weight configuration.

    The update applies a bounded reciprocal shift:
    ``W_a += alpha * (W_b - W_a)`` and
    ``W_b -= alpha * (W_b - W_a)``.
    """
    # We assume both agents have same number of neurons
    if agent_a.n_neurons != agent_b.n_neurons:
        raise ValueError("Agents must have same size for direct coupling.")

    # Extract weights
    wa = agent_a.get_weights()
    wb = agent_b.get_weights()

    # Mutual Attraction: Weights shift toward each other
    # W_new = W + alpha * (W_other - W)
    delta = self.coupling_strength * (wb - wa)

    # Update Agent A
    new_wa = wa + delta
    for i in range(agent_a.n_neurons):
        for j in range(agent_a.n_inputs):
            agent_a.synapses[i][j].update_weight(new_wa[i, j])

    # Update Agent B (Reciprocal)
    new_wb = wb - delta
    for i in range(agent_b.n_neurons):
        for j in range(agent_b.n_inputs):
            agent_b.synapses[i][j].update_weight(new_wb[i, j])

    logger.info(
        "Swarm Synchronization: Shifted weights by magnitude %.6f", np.mean(np.abs(delta))
    )