fly-instinct 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ """
2
+ fly-instinct — 基于真实果蝇连接组的“本能引擎” (Instinct Engine)
3
+ ================================================================
4
+
5
+ 一个可复用、非学习(权重永久冻结)的“本能”信号源:
6
+ 把输入刺激打散成高维、非平凡、结构化的反应信号。
7
+ 它既不是白噪声(死的),也不是脚本规则(可预测),
8
+ 而是“像活物一样对刺激有反应”。
9
+
10
+ 快速开始:
11
+ # 1) 替身模式(零依赖数据,直接跑)
12
+ >>> from fly_instinct import FlyInstinct
13
+ >>> fly = FlyInstinct(n_neurons=150, seed=7)
14
+ >>> reaction, spikes = fly.react(stimulus)
15
+
16
+ # 2) 真实 MaleCNS 连接组模式
17
+ >>> python -m fly_instinct fetch-data --out data # 一次性下载 25MB 数据
18
+ >>> fly = FlyInstinct.from_malecns("data/edges.csv")
19
+ >>> reaction, spikes = fly.react(stimulus)
20
+
21
+ 数据出处:MaleCNS v1.0, Janelia FlyEM (CC-BY-4.0)
22
+ """
23
+ from .engine import FlyInstinct
24
+ from .loader import load_malecns
25
+ from .datafetch import fetch_data, DATA_URLS, EXPECTED_SIZES
26
+
27
+ __version__ = "0.1.0"
28
+ __all__ = ["FlyInstinct", "load_malecns", "fetch_data",
29
+ "DATA_URLS", "EXPECTED_SIZES", "__version__"]
@@ -0,0 +1,12 @@
1
+ """支持:
2
+ python -m fly_instinct fetch-data [--out data] [--force]
3
+ python -m fly_instinct [--out data] [--force] # 省略子命令亦可
4
+ """
5
+ import sys
6
+
7
+ from .datafetch import main
8
+
9
+ if __name__ == "__main__":
10
+ if len(sys.argv) > 1 and sys.argv[1] == "fetch-data":
11
+ sys.argv.pop(1)
12
+ main()
@@ -0,0 +1,106 @@
1
+ """
2
+ datafetch.py — 下载 MaleCNS 真实连接组子集(25MB,3 个 CSV)
3
+ ============================================================
4
+
5
+ 数据出处(CC-BY-4.0,发布时须署名):
6
+ - 上游:MaleCNS v1.0, Janelia FlyEM, https://male-cns.janelia.org
7
+ - 子集:nosuchstudios/fruit-fly-brain-runtime (HuggingFace)
8
+ 9,999 节点 / 1,587,930 条边,从 CX/MBON/descending 等核心类抽取。
9
+
10
+ 用法:
11
+ python -m fly_instinct fetch-data --out data
12
+ # 或安装后:
13
+ fly-instinct-fetch-data --out data
14
+
15
+ 只标准库(urllib),无额外依赖;支持断点续传(Range 头)。
16
+ """
17
+ from __future__ import annotations
18
+ import argparse
19
+ import os
20
+ import sys
21
+ import urllib.request
22
+
23
+ _BASE = ("https://hf-mirror.com/datasets/nosuchstudios/"
24
+ "fruit-fly-brain-runtime/resolve/main/artifacts/malecns-v1.0")
25
+
26
+ # 文件名 -> (URL, 预期字节数;与上游 PROVENANCE.md 一致)
27
+ DATA_URLS = {
28
+ "edges.csv": f"{_BASE}/edges.csv",
29
+ "annotations.csv": f"{_BASE}/annotations.csv",
30
+ "neurotransmitters.csv": f"{_BASE}/neurotransmitters.csv",
31
+ }
32
+ EXPECTED_SIZES = {
33
+ "edges.csv": 25_207_711,
34
+ "annotations.csv": 283_053,
35
+ "neurotransmitters.csv": 364_160,
36
+ }
37
+
38
+ _UA = "Mozilla/5.0 (fly-instinct datafetch)"
39
+
40
+
41
+ def _download_one(url: str, dest: str) -> int:
42
+ """下载单个文件(已存在且大小正确则跳过;否则断点续传)。返回最终字节数。"""
43
+ expected = EXPECTED_SIZES.get(os.path.basename(dest))
44
+ if os.path.exists(dest):
45
+ sz = os.path.getsize(dest)
46
+ if expected is None or sz == expected:
47
+ print(f" [skip] {os.path.basename(dest)} 已存在 ({sz:,} B)")
48
+ return sz
49
+
50
+ req = urllib.request.Request(url, headers={"User-Agent": _UA})
51
+ # 断点续传:若已有部分文件
52
+ start = os.path.getsize(dest) if os.path.exists(dest) else 0
53
+ if start > 0:
54
+ req.add_header("Range", f"bytes={start}-")
55
+
56
+ with urllib.request.urlopen(req, timeout=60) as resp, \
57
+ open(dest, "ab" if start > 0 else "wb") as f:
58
+ total = start + int(resp.headers.get("Content-Length") or 0)
59
+ got = start
60
+ chunk = 1024 * 256
61
+ while True:
62
+ b = resp.read(chunk)
63
+ if not b:
64
+ break
65
+ f.write(b)
66
+ got += len(b)
67
+ if total:
68
+ pct = 100.0 * got / total
69
+ sys.stdout.write(f"\r {os.path.basename(dest)}: "
70
+ f"{got/1e6:.1f}/{total/1e6:.1f} MB "
71
+ f"({pct:.0f}%)")
72
+ sys.stdout.flush()
73
+ sys.stdout.write("\n")
74
+ sz = os.path.getsize(dest)
75
+ if expected is not None and sz != expected:
76
+ raise IOError(f"{dest}: 大小 {sz:,} != 预期 {expected:,},"
77
+ f"文件可能损坏,请删除后重试")
78
+ print(f" [ok] {os.path.basename(dest)} ({sz:,} B)")
79
+ return sz
80
+
81
+
82
+ def fetch_data(out_dir: str, force: bool = False) -> dict:
83
+ """下载全部 3 个 CSV 到 out_dir,校验大小。返回 {文件名: 字节数}。"""
84
+ os.makedirs(out_dir, exist_ok=True)
85
+ print(f"下载 MaleCNS 子集数据 -> {os.path.abspath(out_dir)}")
86
+ sizes = {}
87
+ for name, url in DATA_URLS.items():
88
+ dest = os.path.join(out_dir, name)
89
+ if force and os.path.exists(dest):
90
+ os.remove(dest)
91
+ sizes[name] = _download_one(url, dest)
92
+ print("完成。用法:FlyInstinct.from_malecns('<out_dir>/edges.csv')")
93
+ return sizes
94
+
95
+
96
+ def main(argv=None):
97
+ p = argparse.ArgumentParser(
98
+ description="下载 MaleCNS 真实连接组子集 (25MB, 3 个 CSV)")
99
+ p.add_argument("--out", default="data", help="输出目录 (默认 ./data)")
100
+ p.add_argument("--force", action="store_true", help="删除已有文件重新下载")
101
+ a = p.parse_args(argv)
102
+ fetch_data(a.out, force=a.force)
103
+
104
+
105
+ if __name__ == "__main__":
106
+ main()
fly_instinct/engine.py ADDED
@@ -0,0 +1,197 @@
1
+ """
2
+ fly_instinct.py — “本能引擎” (Instinct Engine)
3
+ ================================================
4
+
5
+ 一个可复用、非学习的“本能”信号源。
6
+
7
+ 核心思想(储备池计算 / Reservoir Computing):
8
+ 用一个【权重冻结、不学习】的递归网络,把输入刺激打散成
9
+ 高维、非平凡、结构化的“本能反应”。它既不是白噪声(死的),
10
+ 也不是脚本规则(可预测),而是“像活物一样对刺激有反应”。
11
+
12
+ 真实完整版会加载 MaleCNS 连接组(~16.67 万神经元)的某个采样子图
13
+ 作为冻结网络;本 PoC 用一个【结构匹配的冻结递归网络】扮演这个角色,
14
+ 接口与行为完全一致,后续可直接替换为真实连接组子图。
15
+
16
+ 用法:
17
+ from fly_instinct import FlyInstinct
18
+ fly = FlyInstinct(n_neurons=150, seed=7)
19
+ reaction, spikes = fly.react(stimulus) # stimulus: 1D/2D 数组
20
+
21
+ 设计原则:
22
+ - 权重在 __init__ 时生成后【永久冻结】,react() 不做任何学习。
23
+ - 给定 seed + 刺激,输出是【确定性的】(除非显式注入噪声)。
24
+ - 噪声是可选的:真实动物的本能也带一点热噪声,可加可不加。
25
+ """
26
+
27
+ from __future__ import annotations
28
+ import numpy as np
29
+
30
+
31
+ class FlyInstinct:
32
+ """冻结递归网络 + LIF 动力学 组成的“本能”信号源。"""
33
+
34
+ def __init__(self, n_neurons: int = 150, n_input: int = 1,
35
+ n_output: int = 1, seed: int = 0,
36
+ tau: float = 20.0, dt: float = 1.0,
37
+ v_th: float = 1.0, v_reset: float = 0.0,
38
+ gain: float = 6.0, spectral_radius: float = 0.95):
39
+ self.n = n_neurons
40
+ self.n_in = n_input
41
+ self.n_out = n_output
42
+ self.tau = tau
43
+ self.dt = dt
44
+ self.v_th = v_th
45
+ self.v_reset = v_reset
46
+ self.gain = gain
47
+
48
+ rng = np.random.default_rng(seed)
49
+
50
+ # 递归权重:冻结的“本能回路”。缩放到临界点附近以保留丰富动力学。
51
+ W = rng.normal(0.0, 1.0, size=(n_neurons, n_neurons))
52
+ W *= spectral_radius / np.linalg.norm(W, ord=2)
53
+
54
+ # 输入权重:刺激 -> 网络。
55
+ Win = rng.normal(0.0, 1.0, size=(n_neurons, n_input)) * (1.0 / np.sqrt(n_input + 1.0))
56
+
57
+ # 读出权重:网络活动 -> 单一“反应”标量(冻结,不训练)。
58
+ Wout = rng.normal(0.0, 1.0, size=(n_output, n_neurons)) / np.sqrt(n_neurons)
59
+
60
+ # —— 冻结:之后不再改变 ——
61
+ self.W = W
62
+ self.Win = Win
63
+ self.Wout = Wout
64
+ self._rng = np.random.default_rng(seed + 1)
65
+
66
+ self._v = np.zeros(n_neurons)
67
+ self._sp_prev = np.zeros(n_neurons)
68
+ self._reset_internal()
69
+
70
+ def _reset_internal(self):
71
+ self._v = np.zeros(self.n)
72
+ self._sp_prev = np.zeros(self.n)
73
+
74
+ def reset(self):
75
+ """清空网络内部状态(保留冻结权重)。"""
76
+ self._reset_internal()
77
+
78
+ def step(self, stimulus: np.ndarray, noise: float = 0.0) -> np.ndarray:
79
+ """推进一个时间步,返回该步的“反应”标量数组(长度 n_out)。"""
80
+ stim = np.asarray(stimulus, dtype=float).ravel()
81
+ I = self.W @ self._sp_prev + self.Win @ stim
82
+ if noise and noise > 0.0:
83
+ I = I + self._rng.normal(0.0, noise, size=self.n)
84
+ self._v = self._v + (self.dt / self.tau) * (0.0 - self._v) + self.dt * (I * self.gain)
85
+ sp = (self._v >= self.v_th).astype(float)
86
+ self._v = np.where(sp == 1.0, self.v_reset, self._v)
87
+ self._sp_prev = sp
88
+ return (self.Wout @ sp.T).ravel()
89
+
90
+ def react(self, stimulus: np.ndarray, noise: float = 0.0,
91
+ smooth: int = 1):
92
+ """
93
+ 对一段刺激序列做“本能反应”。
94
+
95
+ 参数
96
+ ----
97
+ stimulus : 1D 或 2D 数组,形状 (T,) 或 (T, n_input)
98
+ noise : 注入的网络内噪声幅度(0=确定性)
99
+ smooth : 输出滑动平均窗宽(1=不平滑)
100
+
101
+ 返回
102
+ ----
103
+ reaction : (T,) 反应强度序列(静息基线对齐 0、峰值对齐 1;
104
+ 若网络被抑制到低于静息水平会出现小幅负值)
105
+ spikes : (n, T) 每个神经元的发放(0/1),供可视化
106
+ """
107
+ stimulus = np.asarray(stimulus, dtype=float)
108
+ if stimulus.ndim == 1:
109
+ stimulus = stimulus[:, None]
110
+ T = stimulus.shape[0]
111
+ self.reset()
112
+
113
+ # 先测静息读出(零输入,待动力学稳定后取均值),作为反应基线
114
+ for _ in range(max(10, int(self.tau))):
115
+ self.step(np.zeros(self.n_in), noise=0.0)
116
+ resting = float((self.Wout @ self._sp_prev).ravel()[0])
117
+
118
+ reaction = np.zeros(T)
119
+ spikes = np.zeros((self.n, T))
120
+ for t in range(T):
121
+ reaction[t] = self.step(stimulus[t], noise=noise)[0] - resting
122
+ spikes[:, t] = self._sp_prev
123
+
124
+ if smooth > 1:
125
+ k = min(smooth, T)
126
+ kernel = np.ones(k) / k
127
+ reaction = np.convolve(reaction, kernel, mode="same")
128
+
129
+ # 归一化:静息基线对齐 0,峰值对齐 1(便于跨项目复用)
130
+ peak = reaction.max()
131
+ if peak > 0:
132
+ reaction = reaction / peak
133
+ return reaction, spikes
134
+
135
+ @classmethod
136
+ def from_malecns(cls, edges_path, ann_path=None, nt_path=None,
137
+ seed=0, in_neurons=800, tau=20.0, dt=1.0,
138
+ v_th=1.0, v_reset=0.0, gain=1.0, spectral_radius=0.9):
139
+ """
140
+ 用【真实 MaleCNS 稀疏连接组】作为冻结递归核心构造本能引擎。
141
+
142
+ - 递归权重 W:真实连接组(按递质赋兴奋/抑制符号,谱半径归一到临界点),永久冻结。
143
+ - 输入接口 Win:刺激 -> 顶层 out-degree 神经元的固定兴奋投影(冻结、非学习)。
144
+ - 读出接口 Wout:全部神经元 -> 单一反应标量的固定随机投影(冻结、非学习)。
145
+ (该子集未标注明确的感受/运动神经元,用冻结投影是诚实且非学习的做法。)
146
+
147
+ 与替身版接口完全一致:fly.react(stimulus) -> (reaction, spikes)
148
+ """
149
+ from scipy import sparse
150
+ from .loader import load_malecns
151
+
152
+ d = load_malecns(edges_path, ann_path, nt_path,
153
+ spectral_radius=spectral_radius)
154
+ W = d["W"]
155
+ n = d["n"]
156
+
157
+ obj = cls.__new__(cls)
158
+ obj.n = n
159
+ obj.n_in = 1
160
+ obj.n_out = 1
161
+ obj.tau = tau
162
+ obj.dt = dt
163
+ obj.v_th = v_th
164
+ obj.v_reset = v_reset
165
+ obj.gain = gain
166
+ obj.W = W # 冻结:真实连接组
167
+
168
+ rng = np.random.default_rng(seed)
169
+
170
+ # 输入接口:刺激投到顶层 out-degree 神经元(兴奋驱动)
171
+ coo = W.tocoo()
172
+ outdeg = np.bincount(coo.col, minlength=n)
173
+ order = np.lexsort((np.arange(n), -outdeg)) # out-degree 降序,同度按 idx 升序
174
+ top = order[:min(in_neurons, n)]
175
+ in_w = rng.uniform(0.6, 1.2, size=top.size)
176
+ Win = sparse.csr_matrix((in_w, (top, np.zeros(top.size, dtype=int))),
177
+ shape=(n, 1))
178
+
179
+ # 读出接口:固定随机投影 -> 单一反应
180
+ Wout = sparse.csr_matrix(rng.normal(0.0, 1.0, (1, n)) / np.sqrt(n))
181
+
182
+ obj.Win = Win
183
+ obj.Wout = Wout
184
+ obj._rng = np.random.default_rng(seed + 1)
185
+ obj._v = np.zeros(n)
186
+ obj._sp_prev = np.zeros(n)
187
+
188
+ obj.is_real = True
189
+ obj.meta = {
190
+ "n_nodes": n,
191
+ "n_edges": d["n_edges"],
192
+ "inhibit_frac": d["inhibit_frac"],
193
+ "sr_raw": d["sr_raw"],
194
+ "sr_target": spectral_radius,
195
+ "in_neurons": int(top.size),
196
+ }
197
+ return obj
fly_instinct/loader.py ADDED
@@ -0,0 +1,130 @@
1
+ """
2
+ malecns_loader.py — 读取 MaleCNS 真实连接组子集,产出“冻结”稀疏权重矩阵
3
+ =======================================================================
4
+
5
+ 只做数据这一件事:
6
+ - 读 edges.csv (pre, post, weight) -> 节点 ID 映射 -> scipy 稀疏矩阵 W[post, pre]
7
+ - 用 neurotransmitters.csv 给突触赋符号:GABA/glycine = 抑制(-),其余 = 兴奋(+)
8
+ * 注意(PROVENANCE 明确):递质符号是“先验预测”,不是 ground truth
9
+ - 按谱半径把 |W| 归一到临界点附近,保证递归网络有丰富而不爆炸的动力学
10
+ - 权重一经生成即视为“冻结”(本模块不做任何学习)
11
+
12
+ 不依赖引擎代码,可单独测试。返回一个 dict(W + 元信息),交给 FlyInstinct 组装。
13
+ """
14
+ from __future__ import annotations
15
+ import csv
16
+ import os
17
+ import numpy as np
18
+ from scipy import sparse
19
+
20
+ # 抑制性递质(小写比对)
21
+ INHIBIT = {"gaba", "glycine"}
22
+
23
+
24
+ def _spectral_radius(M, iters: int = 60, seed: int = 0) -> float:
25
+ """用幂迭代近似 |M| 的谱半径(M 为稀疏矩阵,速度快)。"""
26
+ n = M.shape[0]
27
+ v = np.abs(np.random.default_rng(seed).random(n)) + 1e-9
28
+ v /= np.linalg.norm(v)
29
+ sr = 0.0
30
+ for _ in range(iters):
31
+ w = np.abs(M @ v)
32
+ nr = np.linalg.norm(w)
33
+ if nr < 1e-12:
34
+ return 0.0
35
+ v = w / nr
36
+ sr = float(nr)
37
+ return sr
38
+
39
+
40
+ def load_malecns(edges_path: str, ann_path: str | None = None,
41
+ nt_path: str | None = None,
42
+ spectral_radius: float = 0.9) -> dict:
43
+ """
44
+ 加载 MaleCNS 真实连接组子集。
45
+
46
+ 参数
47
+ ----
48
+ edges_path : edges.csv,列 pre,post,weight
49
+ ann_path : annotations.csv(root_id,cell_type,superclass),可选
50
+ nt_path : neurotransmitters.csv(root_id,transmitter,confidence),可选
51
+ spectral_radius : 目标谱半径(递归强度的标尺,近临界 ~0.9)
52
+
53
+ 返回
54
+ ----
55
+ dict: W(稀疏 CSR, post x pre, 带符号已缩放), n, n_edges, node_ids,
56
+ id2idx, inhibit_frac, sr_raw, sr_target, cell_type
57
+ """
58
+ # 1) 读边表,收集节点
59
+ rows = []
60
+ ids = set()
61
+ with open(edges_path, newline="") as f:
62
+ r = csv.reader(f)
63
+ next(r) # header
64
+ for row in r:
65
+ if len(row) < 3:
66
+ continue
67
+ pre, post, w = int(row[0]), int(row[1]), float(row[2])
68
+ rows.append((pre, post, w))
69
+ ids.add(pre)
70
+ ids.add(post)
71
+
72
+ # 2) 读细胞类型注释(顺带把注释里的节点也纳入,保证 ID 覆盖)
73
+ cell_type = {}
74
+ if ann_path and os.path.exists(ann_path):
75
+ with open(ann_path, newline="") as f:
76
+ r = csv.reader(f)
77
+ next(r)
78
+ for row in r:
79
+ if len(row) < 2:
80
+ continue
81
+ rid = int(row[0])
82
+ ids.add(rid)
83
+ ct = row[1]
84
+ sup = row[2] if len(row) > 2 else ""
85
+ cell_type[rid] = (ct, sup)
86
+
87
+ id2idx = {i: k for k, i in enumerate(sorted(ids))}
88
+ n = len(id2idx)
89
+
90
+ # 3) 每个(突触前)神经元的符号
91
+ sign = np.ones(n) # 默认兴奋
92
+ if nt_path and os.path.exists(nt_path):
93
+ with open(nt_path, newline="") as f:
94
+ r = csv.reader(f)
95
+ next(r)
96
+ for row in r:
97
+ if len(row) < 2:
98
+ continue
99
+ rid = int(row[0])
100
+ tr = row[1].strip().lower()
101
+ if rid in id2idx and tr in INHIBIT:
102
+ sign[id2idx[rid]] = -1.0
103
+
104
+ # 4) 组装 W[post, pre]
105
+ ii, jj, data = [], [], []
106
+ for pre, post, w in rows:
107
+ if pre in id2idx and post in id2idx:
108
+ ii.append(id2idx[post])
109
+ jj.append(id2idx[pre])
110
+ data.append(w * sign[id2idx[pre]])
111
+ W = sparse.csr_matrix((data, (ii, jj)), shape=(n, n))
112
+ W.sum_duplicates()
113
+
114
+ # 5) 谱半径归一到临界点附近
115
+ sr_raw = _spectral_radius(W, seed=0)
116
+ if sr_raw > 0:
117
+ W = W * (spectral_radius / sr_raw)
118
+
119
+ n_inhib = int((sign < 0).sum())
120
+ return {
121
+ "W": W,
122
+ "n": n,
123
+ "n_edges": len(rows),
124
+ "node_ids": sorted(ids),
125
+ "id2idx": id2idx,
126
+ "inhibit_frac": n_inhib / n,
127
+ "sr_raw": sr_raw,
128
+ "sr_target": spectral_radius,
129
+ "cell_type": cell_type,
130
+ }
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.4
2
+ Name: fly-instinct
3
+ Version: 0.1.0
4
+ Summary: Instinct Engine: a frozen, non-learning 'instinct' signal source driven by a real fruit-fly (MaleCNS) connectome
5
+ Author: roblitz
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/roblitz-spec/fly-instinct
8
+ Project-URL: Data, https://male-cns.janelia.org
9
+ Keywords: connectome,fruit-fly,neuroscience,reservoir-computing,spiking-neurons,generative
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Scientific/Engineering
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE.md
18
+ Requires-Dist: numpy>=1.24
19
+ Requires-Dist: scipy>=1.10
20
+ Provides-Extra: demo
21
+ Requires-Dist: matplotlib>=3.6; extra == "demo"
22
+ Dynamic: license-file
23
+
24
+ # fly-instinct · 果蝇连接组“本能引擎”
25
+
26
+ 一个**可复用、非学习、纯本地**的“本能”信号源:把输入刺激喂进一个**权重冻结、不训练**的递归网络(核心是**真实果蝇 MaleCNS 连接组子图**),得到一个“像活物本能反应”的信号——**既不是白噪声(死的),也不是脚本规则(可预测)**。
27
+
28
+ > 定位一句话:它是一个**有结构的、非学习的反应算子**,用于给“需要‘活的反应’”的项目提供信号。
29
+ > 它**不是智能、不是大脑、没有意识、不会学习**。
30
+
31
+ ---
32
+
33
+ ## 文件结构
34
+
35
+ ```
36
+ fly-instinct/
37
+ ├── pyproject.toml # 打包配置(pip install . 或从 PyPI 安装)
38
+ ├── fly_instinct/ # Python 包(发布进 PyPI 的部分)
39
+ │ ├── engine.py # 引擎(FlyInstinct):冻结递归网络 + LIF 动力学
40
+ │ ├── loader.py # 数据加载:真实 MaleCNS 子集 -> 冻结稀疏矩阵
41
+ │ ├── datafetch.py # 数据下载工具(25MB 子集,断点续传+校验)
42
+ │ └── __main__.py # python -m fly_instinct fetch-data 入口
43
+ ├── examples/
44
+ │ ├── poc_demo.py # PoC(替身网络版,150 节点,无需数据)
45
+ │ └── poc_real.py # PoC(真实 MaleCNS 连接组版)
46
+ ├── data/ # 真实 MaleCNS 子集(~25 MB,不进 pip 包)
47
+ │ ├── edges.csv # 连接权重图 pre,post,weight(1,587,930 条边)
48
+ │ ├── annotations.csv # 细胞类型/超类(root_id,cell_type,superclass)
49
+ │ └── neurotransmitters.csv# 递质(root_id,transmitter,confidence)
50
+ ├── instinct_poc.png # 替身版三方对比图
51
+ ├── instinct_poc_real.png # 真实数据三方对比图
52
+ ├── README.md # 本文件(操作文档)
53
+ ├── MODEL.md # 模型说明(架构/数据出处/完整连接组接入)
54
+ ├── LICENSE.md # 许可与署名
55
+ └── PUBLISH.md # 发布清单(署名/合规/商用边界)
56
+ ```
57
+
58
+ ---
59
+
60
+ ## 安装
61
+
62
+ **方式 A:pip 安装(发布后)**
63
+
64
+ ```bash
65
+ pip install fly-instinct # 核心(numpy + scipy)
66
+ pip install fly-instinct[demo] # 含 matplotlib,可跑 examples 出图
67
+ ```
68
+
69
+ **方式 B:从本仓库安装**
70
+
71
+ ```bash
72
+ cd fly-instinct
73
+ pip install . # 或 pip install .[demo]
74
+ ```
75
+
76
+ **下载数据(一次性,25MB)**
77
+
78
+ ```bash
79
+ python -m fly_instinct fetch-data --out data
80
+ # 或安装后的命令行入口:
81
+ fly-instinct-fetch-data --out data
82
+ ```
83
+
84
+ > 数据是 **CC-BY 4.0 的 MaleCNS 子集**(上游 Janelia),体积 25MB 故不进 pip 包;
85
+ > 下载走 HuggingFace 镜像(国内可达),断点续传 + 大小校验,已存在则自动跳过。
86
+
87
+ **纯本地、无 API、无密钥、数据不外传。** 计算全部在本机 CPU 完成(唯一联网动作是上面这一次性数据下载)。
88
+
89
+ ---
90
+
91
+ ## 快速开始:跑 PoC
92
+
93
+ ```bash
94
+ cd fly-instinct
95
+
96
+ # 真实连接组版(推荐,需先 fetch-data 到 data/)
97
+ python examples/poc_real.py # 自动选增益 + 三方对比 + 输出 instinct_poc_real.png
98
+
99
+ # 替身版(不需要数据,150 节点,秒级)
100
+ python examples/poc_demo.py # 输出 instinct_poc.png
101
+ ```
102
+
103
+ `poc_real.py` 在同一“威胁逼近”刺激下对比三种反应:
104
+
105
+ | 反应 | 响应延迟* | lag-1 自相关(结构) | 与刺激相关 | 威胁期发放率 |
106
+ |---|---|---|---|---|
107
+ | 纯随机(白噪声·死的) | 47 | ≈0 | ≈0 | — |
108
+ | 纯脚本(规则·可预测) | 131 | 1.000 | 0.998 | — |
109
+ | **真实本能(MaleCNS·非学习)** | 128 | 0.551 | −0.204 | 15.9% |
110
+
111
+ \* 响应延迟 = 相对刺激前基线上升超过 0.25 的第一个时间步(威胁在 t=120 出现)。
112
+
113
+ ---
114
+
115
+ ## 作为插件使用(一行)
116
+
117
+ ```python
118
+ from fly_instinct import FlyInstinct
119
+
120
+ # 真实连接组(推荐)
121
+ fly = FlyInstinct.from_malecns(
122
+ "data/edges.csv",
123
+ ann_path="data/annotations.csv",
124
+ nt_path="data/neurotransmitters.csv",
125
+ seed=7, in_neurons=800, gain=2.0, spectral_radius=0.9,
126
+ )
127
+ reaction, spikes = fly.react(stimulus) # stimulus: 1D/2D 数组
128
+ # reaction: (T,) 本能反应强度(静息基线=0,峰值=1;被抑制时可能有小幅负值)
129
+ # spikes: (n, T) 每个神经元发放(0/1),供可视化
130
+
131
+ # 或替身版(不需要数据)
132
+ fly = FlyInstinct(n_neurons=150, seed=7, gain=6.0)
133
+ reaction, spikes = fly.react(stimulus)
134
+ ```
135
+
136
+ **接口约定**
137
+ - `fly.react(stimulus, noise=0.0, smooth=1) -> (reaction, spikes)`
138
+ - 权重在构造时**永久冻结**,`react()` 不做任何学习;给定 `seed`+刺激,输出**确定性**(除非显式 `noise>0`)。
139
+ - `noise>0` 可叠加一点“热噪声”,更接近真实生物(可选)。
140
+
141
+ **典型用途**:游戏 NPC 本能行为、生成艺术/ComfyUI 的有机扰动、交互装置(“碰它,它有本能反应”)、科普 demo。
142
+
143
+ ---
144
+
145
+ ## 接入完整版 MaleCNS(1.05 GB)
146
+
147
+ 当前 `data/` 是**真实子集**(约 1 万节点,够跑、够真)。要升级到**完整校对版**(16.67 万神经元 / 2560 万条连接),用官方 **1.05 GB** 连接权重文件:
148
+
149
+ **下载地址(官方,CC-BY 4.0)**
150
+ - 官方下载页:<https://male-cns.janelia.org/download/>
151
+ - 直链(GCS,可断点续传):
152
+ `https://storage.googleapis.com/flyem-male-cns/v1.0/connectome-data/flat-connectome/connectome-weights-male-cns-v1.0-minconf-0.5.feather`
153
+ - 文件:`connectome-weights-male-cns-v1.0-minconf-0.5.feather`(feather 格式,列同 `pre,post,weight`)
154
+
155
+ > 说明:`storage.googleapis.com` 在部分网络(含中国大陆)可能不可达;届时可用可达的镜像/代理下载该文件,**只要列名仍是 pre/post/weight 即可**。
156
+
157
+ **接入步骤**
158
+ 1. 下载 `.feather` 到本地。
159
+ 2. 转成 CSV(或扩展 loader 直接读 feather):
160
+ ```python
161
+ import pandas as pd
162
+ df = pd.read_feather("connectome-weights-male-cns-v1.0-minconf-0.5.feather")
163
+ df.to_csv("full_edges.csv", index=False) # 列:pre,post,weight
164
+ ```
165
+ 3. 用同一接口加载(完整版的 `annotations`/`neurotransmitters` feather 同理转 CSV 后传入):
166
+ ```python
167
+ from fly_instinct import FlyInstinct
168
+ fly = FlyInstinct.from_malecns("full_edges.csv",
169
+ ann_path="full_annotations.csv",
170
+ nt_path="full_neurotransmitters.csv",
171
+ seed=7, in_neurons=2000, gain=2.0, spectral_radius=0.9)
172
+ reaction, spikes = fly.react(stimulus)
173
+ ```
174
+
175
+ **代价与注意**
176
+ - 完整版稀疏矩阵约 2560 万非零元,加载后内存数百 MB;单次 `react(T)` 计算量约为当前子集的 ~16 倍,建议 T 不要太大、机器内存 ≥ 8 GB。
177
+ - 完整版的**递质符号仍是先验预测**(见 MODEL.md 诚实边界)。
178
+
179
+ ---
180
+
181
+ ## 许可与署名(必须)
182
+
183
+ 底层连接组数据为 **MaleCNS v1.0(Janelia FlyEM),CC-BY 4.0,需署名**;本包代码为你可自定授权的衍生作品。商用/分发前请阅读 `LICENSE.md` 与 `PUBLISH.md`。
184
+
185
+ ## 诚实声明
186
+
187
+ 本引擎输出的是“**冻结结构对刺激给出的、非学习的本能式反应**”。它**不具备智能、记忆、学习或意识**;递质兴奋/抑制符号是**先验预测**而非实验测定。请勿以“果蝇大脑 / 有意识 / 能学习”等措辞宣传。
@@ -0,0 +1,11 @@
1
+ fly_instinct/__init__.py,sha256=na_eFwO0sz4Ukvr5E1iIFMzU47lNNuBEsSLNp1MMIZw,1196
2
+ fly_instinct/__main__.py,sha256=60iZgZylcTsxajUXvcO0P4e7TsxXWHcP55JT2mDu0xs,315
3
+ fly_instinct/datafetch.py,sha256=dBm59pb0-EFzSdBI_3SNqYPJyRDRm-gArtE9Imqj5oo,3896
4
+ fly_instinct/engine.py,sha256=xwbpXEzpb5kXGdCKMO0X3hnVVMaUIZvNoQbA_PC-Me0,7833
5
+ fly_instinct/loader.py,sha256=_Ipf0_lgu0TbShpUPCbSgu8_wKnnsBR3ULNlQb4huNc,4388
6
+ fly_instinct-0.1.0.dist-info/licenses/LICENSE.md,sha256=92tQJS2lLU-sZaYPKf1bNb9tKIAYcuZ3gb3VYlLGj9g,3548
7
+ fly_instinct-0.1.0.dist-info/METADATA,sha256=TIaQJXS0G9wG6KB4FXeoSJiSh4f1EVJerZHKY3chQng,8520
8
+ fly_instinct-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
9
+ fly_instinct-0.1.0.dist-info/entry_points.txt,sha256=qy0X0fciswZNsYe_NnYK30VFRT-EmO9aRJdnSFFThyI,72
10
+ fly_instinct-0.1.0.dist-info/top_level.txt,sha256=kK7lE3K_j9VBDnA3EDG2xWXe1sBo5UQzh7RwH7rrXs8,13
11
+ fly_instinct-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ fly-instinct-fetch-data = fly_instinct.datafetch:main
@@ -0,0 +1,71 @@
1
+ # LICENSE.md · 许可与署名
2
+
3
+ 本包由**两部分**组成,许可不同,发布时**都要带上**。
4
+
5
+ ---
6
+
7
+ ## A. 连接组数据 —— CC-BY 4.0(不可改、必须署名)
8
+
9
+ `data/` 下的连接组数据源自 **MaleCNS v1.0(Janelia FlyEM)**,许可为 **CC-BY 4.0**。
10
+ CC-BY 4.0 **允许**:商用、修改、分发、做衍生作品。
11
+ CC-BY 4.0 **要求**:署名;且**不得**施加比 CC-BY 更严格的限制(例如不能禁止他人再分发数据)。
12
+
13
+ **发布时必须包含的署名块(可直接复制):**
14
+
15
+ > 本软件使用的果蝇连接组数据来自 **MaleCNS v1.0**(成年雄性果蝇中枢神经连接组),
16
+ > 由 Janelia Research Campus / Howard Hughes Medical Institute 提供,
17
+ > 来源 <https://male-cns.janelia.org/>,许可 **CC-BY 4.0**。
18
+ > 本包内的 25 MB 子集取自 <https://huggingface.co/datasets/nosuchstudios/fruit-fly-brain-runtime>(CC-BY 4.0)。
19
+
20
+ > ⚠️ 如果你把 `data/` 里的 CSV 随包一起分发,这些文件本身仍是 CC-BY 4.0,**不能**用“禁止再分发”之类的条款限制它们。
21
+
22
+ ---
23
+
24
+ ## B. 本包代码 —— 你的衍生作品(可自定授权)
25
+
26
+ `fly_instinct/` 包(`engine.py`、`loader.py`、`datafetch.py`、`__init__.py`、`__main__.py`)与 `examples/` 下的 PoC 脚本,是**独立编写的衍生代码**,
27
+ **你可以自由选择授权方式**:
28
+
29
+ - **想开源 / 攒口碑 / 招人**:用 **MIT**(最宽松,允许商用、闭源集成)。
30
+ - **想闭源卖产品 / 卖服务**:可用**专有许可**(保留所有权利,仅授予使用许可),
31
+ 但**数据部分(A)仍须保持 CC-BY 4.0 + 署名**,不能把数据锁死。
32
+
33
+ ### 代码默认授权(MIT,可替换)
34
+
35
+ ```
36
+ MIT License
37
+
38
+ Copyright (c) 2026 roblitz
39
+
40
+ Permission is hereby granted, free of charge, to any person obtaining a copy
41
+ of this software and associated documentation files (the "Software"), to deal
42
+ in the Software without restriction, including without limitation the rights
43
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
44
+ copies of the Software, and to permit persons to whom the Software is
45
+ furnished to do so, subject to the following conditions:
46
+
47
+ The above copyright notice and this permission notice shall be included in all
48
+ copies or substantial portions of the Software.
49
+
50
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
51
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
52
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
53
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
54
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
55
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
56
+ SOFTWARE.
57
+ ```
58
+
59
+ > 本包代码采用 **MIT**(署名 roblitz)。若日后改为专有许可,替换上面的 MIT 段即可,
60
+ > 但**必须保留 A 部分的数据 CC-BY 4.0 署名**。
61
+
62
+ ---
63
+
64
+ ## 商用边界(一句话)
65
+
66
+ - ✅ 可以:把“引擎 + 接口 + 体验”做成产品/插件/SaaS 并收费(你卖的是你的工程与产品)。
67
+ - ✅ 必须:数据保持 CC-BY 4.0 + 署名;不得夸大(见 MODEL.md 诚实边界)。
68
+ - ❌ 不能:宣称拥有/独占“果蝇大脑”数据;用更严条款锁死 CC-BY 数据;以“有意识/能学习”等虚假措辞宣传。
69
+
70
+ > 本文件不构成法律意见。正式上架收费前,建议花小钱做一次快速合规审查,
71
+ > 重点核对署名条款与“不得再限制”的表述。
@@ -0,0 +1 @@
1
+ fly_instinct