summaryrefslogtreecommitdiff
path: root/experiments/bci_smoke.py
blob: fe3df782ef33a17e2fe82c855456cb682a87d6a4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
"""CPU-only mechanics checks for the continuous Harnett-signature BCI."""
import os
import sys

import torch

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sdil.bci import (BCIConfig, BCISDIL, causal_roles, generate_trajectories,
                      run_day)


def check_roles_and_pairing():
    cfg = BCIConfig(days=2, episodes_per_day=8, steps_per_episode=4)
    role = causal_roles(cfg)
    assert torch.allclose(role[:5], torch.full((5,), 0.2))
    assert torch.allclose(role[5:10], torch.full((5,), -0.2))
    assert torch.equal(role[10:], torch.zeros(30))
    first = generate_trajectories(cfg, 11)
    second = generate_trajectories(cfg, 11)
    third = generate_trajectories(cfg, 12)
    assert torch.equal(first.context, second.context)
    assert torch.equal(first.process_noise, second.process_noise)
    assert torch.equal(first.perturbations, second.perturbations)
    assert not torch.equal(first.context, third.context)
    assert torch.equal(first.context[..., 0], torch.ones_like(first.context[..., 0]))
    print("BCI causal roles and task-seed pairing: exact")


def check_causal_estimator():
    cfg = BCIConfig(days=2, episodes_per_day=4096, steps_per_episode=2)
    model = BCISDIL(cfg, model_seed=3)
    generator = torch.Generator().manual_seed(4)
    soma = torch.randn(4096, cfg.n_neurons, generator=generator) * 0.2
    xi = torch.empty_like(soma).bernoulli_(0.5, generator=generator).mul_(2).sub_(1)
    estimate = model.causal_targets(soma, cfg.target, xi)
    exact = model.exact_causal_direction(soma, cfg.target)
    mean_estimate = estimate.mean(0)
    mean_exact = exact.mean(0)
    cosine = torch.nn.functional.cosine_similarity(
        mean_estimate, mean_exact, dim=0).item()
    assert cosine > 0.99
    assert estimate[:, 10:].abs().mean() > 0  # simultaneous cross-cell variance
    assert mean_estimate[10:].abs().max() < 0.03
    print(f"BCI simultaneous causal estimator mean cosine: {cosine:.6f}")


def check_predictor_identification():
    cfg = BCIConfig(days=2, episodes_per_day=256, steps_per_episode=2,
                    predictor_eta=0.2)
    model = BCISDIL(cfg, model_seed=5)
    generator = torch.Generator().manual_seed(6)
    active = torch.ones(256, dtype=torch.bool)
    for _ in range(100):
        soma = torch.randn(256, cfg.n_neurons, generator=generator).tanh()
        ordinary = model.coupling * soma
        model.update_predictor(soma, ordinary, active)
    assert (model.P - model.coupling).abs().max() < 1e-4
    assert model.P_bias.abs().max() < 1e-5
    print("BCI neutral per-cell predictor identifies ordinary coupling: exact")


def check_phase_masks_and_velocity_reset():
    cfg = BCIConfig(days=2, episodes_per_day=8, steps_per_episode=4,
                    kappa=0.3, feedback="error_velocity")
    trajectories = generate_trajectories(cfg, 7)
    initial = BCISDIL(cfg, model_seed=8)
    intact = initial.clone()
    online_lesion = initial.clone()
    plasticity_lesion = initial.clone()

    active = torch.ones(8, dtype=torch.bool)
    args = (
        trajectories.context[0, :, 0], trajectories.process_noise[0, :, 0],
        trajectories.perturbations[0, :, 0], torch.zeros(8, cfg.n_neurons),
        None, active, 1)
    out_intact = intact.step(*args, control_gain=1.0, plasticity_gain=1.0,
                             learn_vectorizer=False)
    out_online = online_lesion.step(*args, control_gain=0.0, plasticity_gain=1.0,
                                    learn_vectorizer=False)
    out_plastic = plasticity_lesion.step(
        *args, control_gain=1.0, plasticity_gain=0.0, learn_vectorizer=False)
    assert not torch.equal(out_intact["soma"], out_online["soma"])
    assert torch.equal(intact.W, online_lesion.W)
    assert torch.equal(out_intact["soma"], out_plastic["soma"])
    assert torch.equal(plasticity_lesion.W, initial.W)
    assert torch.equal(out_intact["feedback"][:, 1], torch.zeros(8))

    day = run_day(initial.clone(), trajectories, 0, collect=True)
    assert len(day["events"]) == cfg.steps_per_episode
    assert day["global_step"] == cfg.steps_per_episode
    print("BCI online/plasticity phase masks and episode velocity reset: exact")


if __name__ == "__main__":
    check_roles_and_pairing()
    check_causal_estimator()
    check_predictor_identification()
    check_phase_masks_and_velocity_reset()
    print("ALL BCI MECHANICS CHECKS PASSED")