File size: 6,774 Bytes
5e0b58b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
"""End-to-end publication for the registered pair-timing STDP experiment."""

from __future__ import annotations

import hashlib
import json
from datetime import datetime, timezone
from pathlib import Path
from time import perf_counter
from typing import Any

from .experiment_recorder import ExperimentRecorder
from .registry import REPO_ROOT, ResearchRegistry
from .stdp_pair_timing import PROTOCOL_ID, run_pair_timing_protocol

EXPERIMENT_ID = "EXP-STDP-0001"
QUESTION_ID = "RQ-STDP-001"
HYPOTHESIS_ID = "H-STDP-001-A"


def execute_stdp_pair_experiment(
    experiment_id: str = EXPERIMENT_ID,
    research_root: Path | None = None,
) -> dict[str, str]:
    """Publish the pre-registered isolated STDP timing experiment once."""
    research_root = research_root or REPO_ROOT / "research"
    experiment_dir = research_root / "experiments" / experiment_id
    if (experiment_dir / "manifest.json").exists():
        raise FileExistsError(f"{experiment_id} has already been published.")
    experiment_dir.mkdir(parents=True, exist_ok=False)

    started = perf_counter()
    data = run_pair_timing_protocol()
    duration = perf_counter() - started
    protocol_path = experiment_dir / "protocol.json"
    protocol_path.write_text(
        json.dumps(data["conditions"], indent=2) + "\n", encoding="utf-8"
    )
    data_path = _write_data(research_root, data, experiment_id)

    recorder = ExperimentRecorder(experiment_id, output_dir=experiment_dir)
    recorder.record_software_version("brain5d_version", _brain5d_version())
    recorder.record_config(
        _artifact_reference(protocol_path, research_root), _sha256(protocol_path)
    ).record_research_links([QUESTION_ID], [HYPOTHESIS_ID]).record_simulation_params(
        seed=data["seed"],
        ticks=len(data["measurements"]),
        learning=True,
        input_pattern="isolated pre/post spike pair timing sweep",
        protocol=PROTOCOL_ID,
    ).record_artifact(
        "data", _artifact_reference(data_path, research_root)
    ).record_artifact(
        "protocol", _artifact_reference(protocol_path, research_root)
    ).record_results(
        metrics_summary=data["summary"],
        hypothesis_supported=data["summary"]["hypothesis_supported"],
    ).record_runtime(
        duration
    ).mark_completed()
    recorder.manifest["data_ids"] = [data_path.stem]
    recorder.save()

    report_path = experiment_dir / "report.md"
    report_path.write_text(
        _render_report(data, duration, experiment_id), encoding="utf-8"
    )
    _rebuild_reports(research_root)
    return {
        "experiment_id": experiment_id,
        "data_id": data_path.stem,
        "evidence_id": "",
        "report": _artifact_reference(report_path, research_root),
    }


def _artifact_reference(path: Path, research_root: Path) -> str:
    """Return a stable repository-relative reference for local or test roots."""
    try:
        return path.relative_to(REPO_ROOT).as_posix()
    except ValueError:
        return path.relative_to(research_root).as_posix()


def _write_data(research_root: Path, data: dict[str, Any], experiment_id: str) -> Path:
    directory = research_root / "generated" / "data"
    directory.mkdir(parents=True, exist_ok=True)
    year = datetime.now(timezone.utc).year
    index = 1
    while (directory / f"DATA-{year}-{index:02d}.json").exists():
        index += 1
    path = directory / f"DATA-{year}-{index:02d}.json"
    record = {
        "data_id": path.stem,
        "experiment_id": experiment_id,
        "generated": datetime.now(timezone.utc).isoformat(),
        **data,
    }
    path.write_text(
        json.dumps(record, indent=2, ensure_ascii=False) + "\n", encoding="utf-8"
    )
    return path


def _rebuild_reports(research_root: Path) -> None:
    from .report_builder import ReportBuilder

    registry = ResearchRegistry(research_root / "registry").load_all()
    builder = ReportBuilder(registry)
    generated = research_root / "generated"
    generated.mkdir(parents=True, exist_ok=True)
    (generated / "RESEARCH_CATALOG.md").write_text(
        builder.build_research_catalog(), encoding="utf-8"
    )
    (generated / "EVIDENCE_MATRIX.md").write_text(
        builder.build_evidence_matrix(), encoding="utf-8"
    )
    (generated / "OPEN_QUESTIONS.md").write_text(
        builder.build_open_questions(), encoding="utf-8"
    )
    (generated / "CLAIM_REGISTER.md").write_text(
        builder.build_claim_register(), encoding="utf-8"
    )


def _brain5d_version() -> str:
    from src.version import BRAIN5D_VERSION_DISPLAY

    return BRAIN5D_VERSION_DISPLAY


def _sha256(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def _render_report(
    data: dict[str, Any], duration: float, experiment_id: str = EXPERIMENT_ID
) -> str:
    summary = data["summary"]
    return "\n".join(
        [
            f"# {experiment_id}: Pair-Timing STDP",
            "",
            "## Forschungsfrage",
            QUESTION_ID,
            "",
            "## Hypothese",
            HYPOTHESIS_ID,
            "",
            "## Bedingungen",
            f"Protokoll: `{PROTOCOL_ID}`; Seed: {data['seed']}; Startgewicht: 0.5.",
            "Delta t: -50, -20, -10, -5, -1, 0, +1, +5, +10, +20, +50 ms.",
            "Zehn identische Wiederholungspruefungen pro Delta t; feste STDP-Parameter.",
            "Es gibt keine unabhaengigen stochastischen Runs und keinen statistischen Test.",
            "",
            "## Ergebnis",
            f"LTP-Mittelwert: {summary['mean_ltp']:.8f}; LTD-Mittelwert: {summary['mean_ltd']:.8f}; Delta t = 0: {summary['zero_delta_weight']:.8f}.",
            f"Bedingungen: {summary['conditions']}; wiederholte Auswertungen: {summary['repeated_evaluations']}; unabhaengige Runs: 0; Dauer: {duration:.6f} s.",
            "",
            "## Wissenschaftliche Einordnung",
            "Pilot- und Methodenvalidierung. Der Lauf bestaetigt die deterministische Implementierung der isolierten Pair-STDP-Regel: negatives Delta t fuehrt zu LTD, positives Delta t zu LTP und Delta t = 0 zu keiner Aenderung.",
            "Er erzeugt absichtlich keine wissenschaftliche EVID und zaehlt nicht fuer Claim oder Forschungsfrage, weil ein produktiver Brain-5D-Lernpfad hier nicht gemessen wird.",
            "",
            "## Reproduzierbarkeit und Grenzen",
            "Der Protokoll-Snapshot, Startgewicht und STDP-Parameter sind im Manifest und in `protocol.json` hinterlegt. Der angegebene Seed ist fuer dieses deterministische Laborprotokoll nicht Teil des Messpfads.",
            "Fuer evidenzfaehige Folgerungen sind ein sauberer Source-Freeze sowie unabhaengige Runs ueber NeuralNetwork, LearningEngine und reale Network-Synapsen erforderlich.",
            "",
        ]
    )