open-interpretability 0.0.20__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.
Files changed (256) hide show
  1. open_interpretability-0.0.20.dist-info/METADATA +157 -0
  2. open_interpretability-0.0.20.dist-info/RECORD +256 -0
  3. open_interpretability-0.0.20.dist-info/WHEEL +4 -0
  4. open_interpretability-0.0.20.dist-info/entry_points.txt +2 -0
  5. open_interpretability-0.0.20.dist-info/licenses/LICENSE +21 -0
  6. openinterp/__init__.py +6 -0
  7. openinterp/agents/__init__.py +25 -0
  8. openinterp/agents/base.py +229 -0
  9. openinterp/agents/judge.py +149 -0
  10. openinterp/agents/proposer.py +471 -0
  11. openinterp/agents/proposer_profiles.py +79 -0
  12. openinterp/agents/review_measurements.py +173 -0
  13. openinterp/agents/reviewer.py +498 -0
  14. openinterp/agreement.py +63 -0
  15. openinterp/audit_options.py +79 -0
  16. openinterp/baseline.py +254 -0
  17. openinterp/benchmark.py +450 -0
  18. openinterp/blends.py +239 -0
  19. openinterp/builder/__init__.py +6 -0
  20. openinterp/builder/agents.py +260 -0
  21. openinterp/builder/aligned.py +41 -0
  22. openinterp/builder/charts.py +411 -0
  23. openinterp/builder/circuit_pages.py +37 -0
  24. openinterp/builder/circuits.py +264 -0
  25. openinterp/builder/compiler.py +495 -0
  26. openinterp/builder/corpora.py +98 -0
  27. openinterp/builder/coverage.py +46 -0
  28. openinterp/builder/dashboard.py +255 -0
  29. openinterp/builder/disk_memo.py +65 -0
  30. openinterp/builder/experiment.py +105 -0
  31. openinterp/builder/heat.py +121 -0
  32. openinterp/builder/hrefs.py +71 -0
  33. openinterp/builder/learned_baseline.py +204 -0
  34. openinterp/builder/model_facts.py +138 -0
  35. openinterp/builder/neuronpedia_export.py +107 -0
  36. openinterp/builder/parallel.py +100 -0
  37. openinterp/builder/parquet_export.py +458 -0
  38. openinterp/builder/pieces.py +127 -0
  39. openinterp/builder/pii.py +211 -0
  40. openinterp/builder/registry_stats.py +144 -0
  41. openinterp/builder/research.py +109 -0
  42. openinterp/builder/sections.py +28 -0
  43. openinterp/builder/significance.py +103 -0
  44. openinterp/builder/sister_graph.py +180 -0
  45. openinterp/builder/standing.py +185 -0
  46. openinterp/builder/statuses.py +34 -0
  47. openinterp/builder/stopped.py +91 -0
  48. openinterp/builder/stops.py +30 -0
  49. openinterp/builder/superpositions.py +102 -0
  50. openinterp/builder/templates/_agent_role_cells.html +9 -0
  51. openinterp/builder/templates/_causal_chart.html +67 -0
  52. openinterp/builder/templates/_circuit.html +8 -0
  53. openinterp/builder/templates/_dashboard.html +133 -0
  54. openinterp/builder/templates/_experiment.html +500 -0
  55. openinterp/builder/templates/_macros.html +82 -0
  56. openinterp/builder/templates/_patchscope.html +33 -0
  57. openinterp/builder/templates/_prior_art.html +24 -0
  58. openinterp/builder/templates/_specificity.html +21 -0
  59. openinterp/builder/templates/_steering.html +34 -0
  60. openinterp/builder/templates/_superposition.html +62 -0
  61. openinterp/builder/templates/_vocab_chart.html +88 -0
  62. openinterp/builder/templates/_wiring.html +44 -0
  63. openinterp/builder/templates/about.html +95 -0
  64. openinterp/builder/templates/agent.html +69 -0
  65. openinterp/builder/templates/agents.html +80 -0
  66. openinterp/builder/templates/base.html +103 -0
  67. openinterp/builder/templates/circuit.html +64 -0
  68. openinterp/builder/templates/circuits.html +46 -0
  69. openinterp/builder/templates/experiment.html +21 -0
  70. openinterp/builder/templates/hub.html +16 -0
  71. openinterp/builder/templates/index.html +387 -0
  72. openinterp/builder/templates/layer.html +69 -0
  73. openinterp/builder/templates/learned-baseline.html +44 -0
  74. openinterp/builder/templates/model.html +151 -0
  75. openinterp/builder/templates/models.html +47 -0
  76. openinterp/builder/templates/neuron.html +234 -0
  77. openinterp/builder/templates/paper.html +39 -0
  78. openinterp/builder/templates/redirect.html +1 -0
  79. openinterp/builder/templates/research.html +31 -0
  80. openinterp/builder/templates/user.html +54 -0
  81. openinterp/builder/templates/users.html +39 -0
  82. openinterp/builder/templates/wiki/experiment.md +271 -0
  83. openinterp/builder/templates/wiki/index.md +32 -0
  84. openinterp/builder/templates/wiki/neuron.md +30 -0
  85. openinterp/builder/templates/wiki/stop.md +36 -0
  86. openinterp/builder/tree.py +470 -0
  87. openinterp/builder/users.py +142 -0
  88. openinterp/builder/wiki.py +427 -0
  89. openinterp/checks.py +429 -0
  90. openinterp/cli.py +494 -0
  91. openinterp/cli_baseline.py +332 -0
  92. openinterp/cli_benchmark.py +300 -0
  93. openinterp/cli_build.py +128 -0
  94. openinterp/cli_check.py +51 -0
  95. openinterp/cli_concepts.py +76 -0
  96. openinterp/cli_corpus.py +252 -0
  97. openinterp/cli_costs.py +257 -0
  98. openinterp/cli_coverage.py +76 -0
  99. openinterp/cli_crossref.py +86 -0
  100. openinterp/cli_errata.py +142 -0
  101. openinterp/cli_evidence.py +495 -0
  102. openinterp/cli_evidence_store.py +93 -0
  103. openinterp/cli_explain.py +101 -0
  104. openinterp/cli_loop.py +125 -0
  105. openinterp/cli_memory.py +28 -0
  106. openinterp/cli_neuronpedia.py +175 -0
  107. openinterp/cli_pause.py +45 -0
  108. openinterp/cli_pole_effects.py +133 -0
  109. openinterp/cli_probing.py +119 -0
  110. openinterp/cli_registry.py +348 -0
  111. openinterp/cli_reproduce.py +446 -0
  112. openinterp/cli_review.py +302 -0
  113. openinterp/cli_scan.py +223 -0
  114. openinterp/cli_score.py +446 -0
  115. openinterp/cli_sisters.py +86 -0
  116. openinterp/cli_submission.py +217 -0
  117. openinterp/cli_superpositions.py +118 -0
  118. openinterp/cli_wiring.py +54 -0
  119. openinterp/codeid.py +73 -0
  120. openinterp/concepts.py +400 -0
  121. openinterp/core/__init__.py +59 -0
  122. openinterp/core/agent_names.py +76 -0
  123. openinterp/core/attribution.py +101 -0
  124. openinterp/core/cache_dirs.py +15 -0
  125. openinterp/core/causal.py +82 -0
  126. openinterp/core/confounds.py +103 -0
  127. openinterp/core/contributors.py +26 -0
  128. openinterp/core/corpora.py +126 -0
  129. openinterp/core/costs.py +430 -0
  130. openinterp/core/draws.py +109 -0
  131. openinterp/core/errata.py +244 -0
  132. openinterp/core/evidence.py +160 -0
  133. openinterp/core/examples.py +93 -0
  134. openinterp/core/exceptions.py +29 -0
  135. openinterp/core/explanation.py +130 -0
  136. openinterp/core/fence.py +42 -0
  137. openinterp/core/heavy_lock.py +114 -0
  138. openinterp/core/identifiers.py +198 -0
  139. openinterp/core/isolation.py +149 -0
  140. openinterp/core/ledger.py +130 -0
  141. openinterp/core/licenses.py +28 -0
  142. openinterp/core/manifold.py +117 -0
  143. openinterp/core/partners.py +32 -0
  144. openinterp/core/patchscope.py +59 -0
  145. openinterp/core/paths.py +40 -0
  146. openinterp/core/pause.py +96 -0
  147. openinterp/core/pricing.py +178 -0
  148. openinterp/core/prior_art.py +44 -0
  149. openinterp/core/probing.py +146 -0
  150. openinterp/core/profiles.py +221 -0
  151. openinterp/core/project.py +28 -0
  152. openinterp/core/run_environment.py +38 -0
  153. openinterp/core/schemas.py +484 -0
  154. openinterp/core/specificity.py +49 -0
  155. openinterp/core/stats.py +172 -0
  156. openinterp/core/steering.py +49 -0
  157. openinterp/core/strict.py +19 -0
  158. openinterp/core/takedowns.py +136 -0
  159. openinterp/core/transcripts.py +130 -0
  160. openinterp/core/wiring.py +80 -0
  161. openinterp/corpus.py +476 -0
  162. openinterp/corpus_cache.py +145 -0
  163. openinterp/corpus_layouts.py +108 -0
  164. openinterp/corpus_presets.py +168 -0
  165. openinterp/corpus_rebuild.py +93 -0
  166. openinterp/coverage.py +155 -0
  167. openinterp/crossref.py +442 -0
  168. openinterp/data/__init__.py +9 -0
  169. openinterp/data/offensive_words.txt +58 -0
  170. openinterp/data/public_suffix_list.dat +15771 -0
  171. openinterp/data/sample_corpus.txt +40 -0
  172. openinterp/draw_steps.py +359 -0
  173. openinterp/errata_checks.py +129 -0
  174. openinterp/evidence.py +462 -0
  175. openinterp/evidence_branch.py +119 -0
  176. openinterp/evidence_lenses.py +75 -0
  177. openinterp/experiment_loop.py +357 -0
  178. openinterp/explain.py +252 -0
  179. openinterp/half_precision.py +79 -0
  180. openinterp/harness/__init__.py +37 -0
  181. openinterp/harness/ane/__init__.py +9 -0
  182. openinterp/harness/ane/backend.py +324 -0
  183. openinterp/harness/ane/cache.py +276 -0
  184. openinterp/harness/ane/chunks.py +163 -0
  185. openinterp/harness/ane/convert.py +116 -0
  186. openinterp/harness/ane/forward.py +258 -0
  187. openinterp/harness/ane/qualified.py +40 -0
  188. openinterp/harness/annotations.py +329 -0
  189. openinterp/harness/attribution.py +96 -0
  190. openinterp/harness/causal.py +264 -0
  191. openinterp/harness/confounds.py +184 -0
  192. openinterp/harness/covariance.py +301 -0
  193. openinterp/harness/crossref.py +195 -0
  194. openinterp/harness/cuda_devices.py +88 -0
  195. openinterp/harness/dead.py +124 -0
  196. openinterp/harness/devices.py +401 -0
  197. openinterp/harness/dictionaries.py +496 -0
  198. openinterp/harness/dreaming.py +116 -0
  199. openinterp/harness/environment.py +64 -0
  200. openinterp/harness/explanation_scores.py +371 -0
  201. openinterp/harness/features.py +276 -0
  202. openinterp/harness/first_position.py +137 -0
  203. openinterp/harness/heads.py +226 -0
  204. openinterp/harness/hooks.py +499 -0
  205. openinterp/harness/input_attribution.py +367 -0
  206. openinterp/harness/jlens.py +183 -0
  207. openinterp/harness/layer_cache.py +301 -0
  208. openinterp/harness/layer_major.py +274 -0
  209. openinterp/harness/lookahead.py +93 -0
  210. openinterp/harness/manifold.py +200 -0
  211. openinterp/harness/memory.py +471 -0
  212. openinterp/harness/moe.py +134 -0
  213. openinterp/harness/patcher.py +322 -0
  214. openinterp/harness/patchscope.py +177 -0
  215. openinterp/harness/probing.py +383 -0
  216. openinterp/harness/scan.py +462 -0
  217. openinterp/harness/scan_backend.py +201 -0
  218. openinterp/harness/sister_passes.py +463 -0
  219. openinterp/harness/sisters.py +363 -0
  220. openinterp/harness/sites.py +101 -0
  221. openinterp/harness/specificity.py +107 -0
  222. openinterp/harness/steering.py +159 -0
  223. openinterp/harness/superpositions.py +116 -0
  224. openinterp/harness/units.py +203 -0
  225. openinterp/harness/wiring.py +432 -0
  226. openinterp/harness/wiring_activations.py +285 -0
  227. openinterp/langid.py +73 -0
  228. openinterp/names.py +129 -0
  229. openinterp/neuronpedia_baseline.py +88 -0
  230. openinterp/neuronpedia_review.py +148 -0
  231. openinterp/planner.py +466 -0
  232. openinterp/planner_effects.py +89 -0
  233. openinterp/planner_heads.py +92 -0
  234. openinterp/planner_models.py +174 -0
  235. openinterp/planner_sisters.py +122 -0
  236. openinterp/planner_states.py +57 -0
  237. openinterp/planner_superpositions.py +114 -0
  238. openinterp/pole_effects.py +102 -0
  239. openinterp/prior_art.py +143 -0
  240. openinterp/reproduce.py +450 -0
  241. openinterp/revision.py +45 -0
  242. openinterp/runner_cache.py +35 -0
  243. openinterp/score_controls.py +169 -0
  244. openinterp/sensitive.py +243 -0
  245. openinterp/show.py +236 -0
  246. openinterp/signing.py +209 -0
  247. openinterp/sisters.py +476 -0
  248. openinterp/superposition_finders.py +225 -0
  249. openinterp/sync/__init__.py +5 -0
  250. openinterp/sync/r2.py +280 -0
  251. openinterp/syntax.py +399 -0
  252. openinterp/takedown_checks.py +76 -0
  253. openinterp/toxicity.py +158 -0
  254. openinterp/verdict_regression.py +79 -0
  255. openinterp/wiring_backfill.py +170 -0
  256. openinterp/wordnet.py +362 -0
@@ -0,0 +1,264 @@
1
+ """Circuit pages: the registry's units joined by their wiring edges.
2
+
3
+ Every experiment's wiring (its own, or a backfilled row) names its unit's strongest
4
+ direct connections. An edge between two units that both have pages is kept. The units
5
+ that edges join form one circuit per connected group. Each circuit
6
+ is laid out by layer, so chains read left to right from earlier to later layers.
7
+
8
+ A *hub* is a unit wired to a large share of the model's studied units. An example is a
9
+ late neuron that every earlier unit's write moves a little. A hub would join them all
10
+ into one star-shaped circuit. So a hub's edges don't join circuits, and the page lists
11
+ hubs separately.
12
+
13
+ Each circuit and hub has its own page, named after one unit (:func:`unit_key`). A circuit
14
+ is named after its *root*, its first unit in layout order: the lowest layer, heads
15
+ first, then the lowest index. Circuits merge and split as the registry grows, so a root
16
+ can change. :mod:`openinterp.builder.circuit_pages` therefore writes a redirect at every
17
+ other unit's name. A URL from an earlier build then lands on the unit's current page.
18
+
19
+ :func:`wiring_view` is one experiment's wiring, with each edge's page linked.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from collections import defaultdict
25
+ from dataclasses import dataclass, field
26
+
27
+ from openinterp.builder.hrefs import circuit_href, hub_href
28
+ from openinterp.builder.tree import ModelNode, UnitNode, head_href, neuron_href
29
+ from openinterp.wiring_backfill import record_wiring
30
+
31
+ HREF = {"mlp_neuron": neuron_href, "attn_head": head_href}
32
+ COLUMN, ROW, PAD = 120, 44, 40
33
+ # A hub is wired to at least this share of the model's units with pages, and this many.
34
+ HUB_SHARE, HUB_MIN = 0.05, 10
35
+ HUB_RULE = {"hub_share": HUB_SHARE, "hub_min": HUB_MIN} # for the page
36
+
37
+
38
+ def unit_key(unit: UnitNode) -> str:
39
+ """The unit's name in circuit and hub URLs, from its page: ``L3-N7``, ``L4-tres-dc-F12``."""
40
+ within = unit.href.removesuffix(".html").split("/", 2)[2] # after models/<slug>/
41
+ return within.replace("/", "-")
42
+
43
+
44
+ @dataclass
45
+ class Reading:
46
+ """One reading of one edge: which experiment's wiring found it, and how."""
47
+
48
+ unit: UnitNode # whose experiment it is
49
+ experiment: object # builder.tree.Experiment
50
+ method: str
51
+ weight: float
52
+ channels: dict[str, float]
53
+ backfilled: bool
54
+
55
+
56
+ @dataclass
57
+ class CircuitEdge:
58
+ """One connection between two units, with every reading that names it."""
59
+
60
+ source: UnitNode
61
+ target: UnitNode
62
+ readings: list[Reading] = field(default_factory=list)
63
+
64
+ @property
65
+ def methods(self) -> list[str]:
66
+ """The wiring methods that found this edge, sorted."""
67
+ return sorted({r.method for r in self.readings})
68
+
69
+
70
+ @dataclass
71
+ class Circuit:
72
+ """One connected group of units, laid out by layer."""
73
+
74
+ units: list[UnitNode]
75
+ edges: list[CircuitEdge]
76
+ positions: dict[str, tuple[int, int]] # href -> (x, y) of the node's centre
77
+ width: int
78
+ height: int
79
+
80
+ @property
81
+ def layers(self) -> tuple[int, int]:
82
+ """The first and last layer of the circuit."""
83
+ return min(u.layer for u in self.units), max(u.layer for u in self.units)
84
+
85
+ @property
86
+ def root(self) -> UnitNode:
87
+ """The unit the circuit is named after: its first in layout order."""
88
+ return self.units[0]
89
+
90
+ @property
91
+ def verified(self) -> int:
92
+ """How many of the circuit's units stand VERIFIED."""
93
+ return sum(u.verified for u in self.units)
94
+
95
+
96
+ def circuit_edges(model: ModelNode, rows: dict) -> list[CircuitEdge]:
97
+ """Return the edges between two of ``model``'s units with pages.
98
+
99
+ They come from every experiment's wiring. Each is found once per reading that names it.
100
+ """
101
+ units = {u.href: u for layer in model.layers for u in layer.units}
102
+ edges: dict[tuple[str, str], CircuitEdge] = {}
103
+ for unit in units.values():
104
+ for e in unit.experiments:
105
+ weights, activations, backfilled = record_wiring(e.record, rows)
106
+ for wiring in (weights, activations):
107
+ if wiring is None:
108
+ continue
109
+ sides = [
110
+ (wiring.upstream_neurons, True),
111
+ (wiring.upstream_heads or [], True),
112
+ (wiring.downstream_neurons, False),
113
+ (wiring.downstream_heads or [], False),
114
+ ]
115
+ for found, upstream in sides:
116
+ for edge in found:
117
+ other = units.get(HREF[edge.kind](model.slug, edge.layer, edge.index))
118
+ if other is None or other is unit:
119
+ continue
120
+ source, target = (other, unit) if upstream else (unit, other)
121
+ key = (source.href, target.href)
122
+ if key not in edges:
123
+ edges[key] = CircuitEdge(source, target)
124
+ edges[key].readings.append(
125
+ Reading(unit, e, wiring.method, edge.weight, edge.channels, backfilled)
126
+ )
127
+ return sorted(
128
+ edges.values(), key=lambda c: (c.source.layer, c.target.layer, c.source.href, c.target.href)
129
+ )
130
+
131
+
132
+ @dataclass
133
+ class Hub:
134
+ """A unit wired to so many others that its edges don't join circuits."""
135
+
136
+ unit: UnitNode
137
+ edges: list[CircuitEdge] # every edge touching it, in layer order
138
+
139
+ @property
140
+ def neighbours(self) -> list[UnitNode]:
141
+ """The units wired to the hub, in layer order, neurons before heads."""
142
+ found = {}
143
+ for e in self.edges:
144
+ other = e.target if e.source is self.unit else e.source
145
+ found[other.href] = other
146
+ return sorted(found.values(), key=lambda u: (u.layer, not u.is_head, u.idx))
147
+
148
+
149
+ def hubs(model: ModelNode, edges: list[CircuitEdge]) -> list[Hub]:
150
+ """Return the units wired to at least ``HUB_SHARE`` of ``model``'s units with pages.
151
+
152
+ A hub also needs at least ``HUB_MIN`` edges. Most connected first.
153
+ """
154
+ n_units = sum(len(layer.units) for layer in model.layers)
155
+ bar = max(HUB_MIN, HUB_SHARE * n_units)
156
+ touching: dict[str, list[CircuitEdge]] = defaultdict(list)
157
+ units = {}
158
+ for e in edges:
159
+ for u in (e.source, e.target):
160
+ touching[u.href].append(e)
161
+ units[u.href] = u
162
+ found = [
163
+ Hub(units[h], es)
164
+ for h, es in touching.items()
165
+ if len({o.href for e in es for o in (e.source, e.target)} - {h}) >= bar
166
+ ]
167
+ return sorted(found, key=lambda hub: (-len(hub.neighbours), hub.unit.href))
168
+
169
+
170
+ def circuits(model: ModelNode, rows: dict) -> list[Circuit]:
171
+ """``model``'s circuits, largest first: groups of units joined by wiring edges."""
172
+ return circuits_and_hubs(model, rows)[0]
173
+
174
+
175
+ def circuits_and_hubs(model: ModelNode, rows: dict) -> tuple[list[Circuit], list[Hub]]:
176
+ """Return ``model``'s circuits, largest first, and its hubs.
177
+
178
+ The circuits are joined by the edges that don't touch a hub.
179
+ """
180
+ edges = circuit_edges(model, rows)
181
+ found_hubs = hubs(model, edges)
182
+ hub_hrefs = {hub.unit.href for hub in found_hubs}
183
+ edges = [e for e in edges if not {e.source.href, e.target.href} & hub_hrefs]
184
+ parent: dict[str, str] = {}
185
+
186
+ def find(h: str) -> str:
187
+ while parent.setdefault(h, h) != h:
188
+ parent[h] = parent[parent[h]]
189
+ h = parent[h]
190
+ return h
191
+
192
+ for edge in edges:
193
+ parent[find(edge.source.href)] = find(edge.target.href)
194
+ groups: dict[str, list[CircuitEdge]] = defaultdict(list)
195
+ for edge in edges:
196
+ groups[find(edge.source.href)].append(edge)
197
+ found = [_layout(group) for group in groups.values()]
198
+ return sorted(found, key=lambda c: (-len(c.units), c.layers, c.units[0].href)), found_hubs
199
+
200
+
201
+ def _layout(edges: list[CircuitEdge]) -> Circuit:
202
+ """Columns by layer (heads before neurons, as attention runs first), rows within."""
203
+ units = {u.href: u for e in edges for u in (e.source, e.target)}
204
+ ordered = sorted(units.values(), key=lambda u: (u.layer, not u.is_head, u.idx, u.href))
205
+ columns: dict[int, list[UnitNode]] = defaultdict(list)
206
+ for u in ordered:
207
+ columns[u.layer].append(u)
208
+ positions = {}
209
+ for col, layer in enumerate(sorted(columns)):
210
+ for row, u in enumerate(columns[layer]):
211
+ positions[u.href] = (PAD + col * COLUMN, PAD + row * ROW)
212
+ width = 2 * PAD + (len(columns) - 1) * COLUMN
213
+ height = 2 * PAD + (max(len(c) for c in columns.values()) - 1) * ROW
214
+ return Circuit(ordered, edges, positions, width, height)
215
+
216
+
217
+ def unit_pages(slug: str, found: list[Circuit], found_hubs: list[Hub]) -> dict[str, str]:
218
+ """Return the page each unit appears on, by unit href.
219
+
220
+ That is its circuit's page, else its own hub page, else the first hub it is wired to.
221
+ """
222
+ pages = {}
223
+ for hub in found_hubs:
224
+ for u in hub.neighbours:
225
+ pages.setdefault(u.href, hub_href(slug, unit_key(hub.unit)))
226
+ for hub in found_hubs:
227
+ pages[hub.unit.href] = hub_href(slug, unit_key(hub.unit))
228
+ for c in found:
229
+ for u in c.units:
230
+ pages[u.href] = circuit_href(slug, unit_key(c.root))
231
+ return pages
232
+
233
+
234
+ def wiring_view(wiring, slug: str, studied: set[str]) -> dict | None:
235
+ """A wiring's edge lists with each edge's page linked when the registry has one."""
236
+ if wiring is None:
237
+ return None
238
+
239
+ def rows(edges):
240
+ if edges is None:
241
+ return None
242
+ out = []
243
+ for edge in edges:
244
+ page = HREF[edge.kind](slug, edge.layer, edge.index)
245
+ # The channel the weight came from, when there are several to choose from.
246
+ via = max(edge.channels, key=lambda c: abs(edge.channels[c]), default=None)
247
+ out.append(
248
+ {
249
+ "e": edge,
250
+ "href": page if page in studied else None,
251
+ "via": via if len(edge.channels) > 1 else None,
252
+ }
253
+ )
254
+ return out
255
+
256
+ return {
257
+ "w": wiring,
258
+ "groups": [
259
+ ("Upstream neurons", rows(wiring.upstream_neurons)),
260
+ ("Upstream heads", rows(wiring.upstream_heads)),
261
+ ("Downstream neurons", rows(wiring.downstream_neurons)),
262
+ ("Downstream heads", rows(wiring.downstream_heads)),
263
+ ],
264
+ }