Log inGet started
World

Fruit Fly

Branchmain
Branchesmain
Commit83f1b94a81

README.md

brain

A whole connectome running as a leaky integrate-and-fire network on the GPU. Brain.load binds any connectome asset to the package's kernels and returns a network; the FruitFlyBrain component is the usual owner, and anything holding the network can step, drive, cut, read out and draw it.

Exports

  • M.load(connectomeRef, params?) -> Brain: allocate the network at rest. params overrides any key of Params: dtMs, vRest, vThresh, vReset, tauM, tauSyn, refracMs, delayMs, weightMv, gain, minSynapses, traceMs, maxSpikes, seed, timeScale, maxStepsPerFrame. A key that is not one of those raises, as setParams does, and so does a value that is not a number.
  • brain:update(dt): once a frame: queue the steps dt covers, issue and drain the counter readback, and fold the activity snapshot when something has asked for one.
  • brain:step(n): queue n steps outright.
  • brain:select(sel) -> { index }: the neurons a selection names, ascending. The array is the network's own and memoized on the selection, so read it rather than writing into it.
  • brain:stimulate(sel, hz, source?): Poisson drive. The rate a neuron sees is the largest any source asked for, so the fly's senses and the bench's experiments can drive the same neurons without taking each other's drive away. brain:drives() lists { source, count, hz } per source and brain:clearStimulation(source?) clears one source or all of them.
  • brain:silence(sel, on): ablate or restore.
  • brain:watch(name, sel) / brain:unwatch(name) / brain:rates(): read groups out as Hz per neuron. A neuron carries at most three groups at once and a watch that would need a fourth on any neuron is refused, naming the neuron and the groups it holds. Watching a name again with the same selection is a no-op; unwatch frees the slots and the group's id.
  • brain:classRates(limit?): the cell classes with the most activity right now, from the 60 ms spike trace. brain:groupActivity(name): that trace per neuron for a watched group of at most 32, which is what a raster draws a row from. Both arm a per-neuron readback that is taken at most every twelve frames and only while something is asking, because it reads the whole activity lane.
  • brain:activity(): network mean rate, total spikes, dropped spikes, simulated time.
  • brain:info(i): what one neuron is.
  • brain:buildCloud(opts, onReady?) / brain:setCloudDrawn(on) / brain:cloudDrawn() / brain:cloudBounds() / brain:setCloudOrigin(xyz) / brain:showCloud(opts) / brain:hideCloud() / brain:worldPosition(i) / brain:neuronAt(screenX, screenY, radiusPx?): the instanced neuron cloud.
  • brain:setParams(patch), brain:setRunning(on), brain:setTimeScale(x), brain:setMaxStepsPerFrame(n), brain:reset(), brain:ready(), brain:destroy(), brain:describe().

Usage

local Brain = require("~.brain")   -- from inside the package
local brain = Brain.load(asset.resolve("fruitFly.maleCNS", "connectome"), { gain = 0.6 })
brain:watch("MN9", { types = { "MN9" } })
brain:stimulate({ types = { "claw_tpGRN" } }, 100, "bench")
-- every frame:
brain:update(dt)
print(brain:rates().MN9)

A selection is { types?, typePrefix?, pattern?, classes?, superclasses?, sides?, excitatory?, indices?, limit? }; every given key must hold.

The cloud, and why it is built in two halves

A registered instanced population has its whole transform run recomposed on the CPU every frame, whether or not the cull draws any of it, so 139,741 neurons cost about 4.7 ms of frame whether or not anyone is looking at the brain. buildCloud fills the buffers and setCloudDrawn registers or drops the draw without touching them, which is what lets FruitFlyBrain put the registration down while the camera is pointed somewhere else and put it back the frame the cloud enters view. Taking it down leaves worldPosition, neuronAt and cloudBounds answering as before.

Only neurons that have a soma get a slot, which is 139,741 of 164,587. Slot and neuron index are therefore different numbers, so the cloud_gather kernel copies each slot's activity out of the neuron it draws once a frame, and slotToNeuron maps back for picking.

The fill is spread over frames from a task, about forty scan slices and eighteen upload slices, so building the cloud costs about a millisecond a frame for a second rather than one frame of a third of a second.

Notes

  • The network is silent until something is driven; a spike always traces back to a stimulus.
  • Every GPU buffer is named brain.<id>.<name>; compute.observe() lists them. destroy releases them all.
  • The kernels' params are per shader, so two networks on one engine share the model constants the last applyParams wrote.
  • Editing this file hot-reloads its methods onto networks that are already running. A network records the SHAPE it was made with and FruitFlyBrain loads a fresh one when the module's shape has moved past it, so raise SHAPE whenever a field is added to or taken off a network or its cloud.