viva-munk 0.0.2__tar.gz

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 (48) hide show
  1. viva_munk-0.0.2/LICENSE +21 -0
  2. viva_munk-0.0.2/PKG-INFO +153 -0
  3. viva_munk-0.0.2/README.md +120 -0
  4. viva_munk-0.0.2/pyproject.toml +81 -0
  5. viva_munk-0.0.2/setup.cfg +4 -0
  6. viva_munk-0.0.2/viva_munk/__init__.py +174 -0
  7. viva_munk-0.0.2/viva_munk/composites/__init__.py +104 -0
  8. viva_munk-0.0.2/viva_munk/core.py +5 -0
  9. viva_munk-0.0.2/viva_munk/experiments/cli.py +105 -0
  10. viva_munk-0.0.2/viva_munk/experiments/documents/__init__.py +27 -0
  11. viva_munk-0.0.2/viva_munk/experiments/documents/attachment.py +122 -0
  12. viva_munk-0.0.2/viva_munk/experiments/documents/bending_pressure.py +119 -0
  13. viva_munk-0.0.2/viva_munk/experiments/documents/biofilm.py +86 -0
  14. viva_munk-0.0.2/viva_munk/experiments/documents/chemotaxis.py +174 -0
  15. viva_munk-0.0.2/viva_munk/experiments/documents/daughter_machine.py +92 -0
  16. viva_munk-0.0.2/viva_munk/experiments/documents/glucose_growth.py +143 -0
  17. viva_munk-0.0.2/viva_munk/experiments/documents/inclusion_bodies.py +143 -0
  18. viva_munk-0.0.2/viva_munk/experiments/documents/mother_machine.py +132 -0
  19. viva_munk-0.0.2/viva_munk/experiments/documents/quorum_sensing.py +241 -0
  20. viva_munk-0.0.2/viva_munk/experiments/registry.py +373 -0
  21. viva_munk-0.0.2/viva_munk/experiments/replay.py +107 -0
  22. viva_munk-0.0.2/viva_munk/experiments/report.py +429 -0
  23. viva_munk-0.0.2/viva_munk/experiments/runner.py +488 -0
  24. viva_munk-0.0.2/viva_munk/experiments/test_suite.py +58 -0
  25. viva_munk-0.0.2/viva_munk/plots/__init__.py +0 -0
  26. viva_munk-0.0.2/viva_munk/plots/multibody_plots.py +874 -0
  27. viva_munk-0.0.2/viva_munk/processes/__init__.py +0 -0
  28. viva_munk-0.0.2/viva_munk/processes/cell_field_exchange.py +201 -0
  29. viva_munk-0.0.2/viva_munk/processes/chemotaxis.py +204 -0
  30. viva_munk-0.0.2/viva_munk/processes/diffusion_advection.py +412 -0
  31. viva_munk-0.0.2/viva_munk/processes/field_decay.py +61 -0
  32. viva_munk-0.0.2/viva_munk/processes/grow_divide.py +533 -0
  33. viva_munk-0.0.2/viva_munk/processes/inclusion_body.py +267 -0
  34. viva_munk-0.0.2/viva_munk/processes/multibody.py +1163 -0
  35. viva_munk-0.0.2/viva_munk/processes/pressure.py +125 -0
  36. viva_munk-0.0.2/viva_munk/processes/quorum_sensing.py +137 -0
  37. viva_munk-0.0.2/viva_munk/processes/remove_crossing.py +99 -0
  38. viva_munk-0.0.2/viva_munk/processes/secrete_eps.py +169 -0
  39. viva_munk-0.0.2/viva_munk/pymunk_agent_type.py +286 -0
  40. viva_munk-0.0.2/viva_munk/types/__init__.py +16 -0
  41. viva_munk-0.0.2/viva_munk/types/positive.py +139 -0
  42. viva_munk-0.0.2/viva_munk/visualizations/__init__.py +412 -0
  43. viva_munk-0.0.2/viva_munk/visualizations/cell_mass_traces.py +172 -0
  44. viva_munk-0.0.2/viva_munk.egg-info/PKG-INFO +153 -0
  45. viva_munk-0.0.2/viva_munk.egg-info/SOURCES.txt +46 -0
  46. viva_munk-0.0.2/viva_munk.egg-info/dependency_links.txt +1 -0
  47. viva_munk-0.0.2/viva_munk.egg-info/requires.txt +15 -0
  48. viva_munk-0.0.2/viva_munk.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eran Agmon, Ryan Spangler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: viva-munk
3
+ Version: 0.0.2
4
+ Summary: multi-cell simulations with pymunk 2d physics
5
+ Author: Ryan Spangler
6
+ Author-email: Eran Agmon <agmon.eran@gmail.com>
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/vivarium-collective/Viva-munk
9
+ Project-URL: Repository, https://github.com/vivarium-collective/Viva-munk
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: process-bigraph
19
+ Requires-Dist: bigraph-schema>=0.0.60
20
+ Requires-Dist: bigraph-viz
21
+ Requires-Dist: pymunk
22
+ Requires-Dist: numpy
23
+ Requires-Dist: matplotlib
24
+ Requires-Dist: Pillow
25
+ Requires-Dist: plum-dispatch
26
+ Requires-Dist: pbg-superpowers
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest; extra == "dev"
29
+ Requires-Dist: ipdb; extra == "dev"
30
+ Requires-Dist: build; extra == "dev"
31
+ Requires-Dist: twine>=4.0.2; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # viva-munk
35
+
36
+ [![PyPI version](https://img.shields.io/pypi/v/viva-munk.svg)](https://pypi.org/project/viva-munk/)
37
+ [![Python versions](https://img.shields.io/pypi/pyversions/viva-munk.svg)](https://pypi.org/project/viva-munk/)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
39
+
40
+ Multi-cell simulations with 2D physics, built on [process-bigraph](https://github.com/vivarium-collective/process-bigraph) and [pymunk](https://www.pymunk.org/).
41
+
42
+ ### **[Demos](https://vivarium-collective.github.io/Viva-munk/)**
43
+
44
+ ## Goal
45
+
46
+ Provide a composable framework for simulating populations of growing, dividing cells with realistic 2D physics and shared chemical environments. Cells are modeled as capsule-shaped rigid bodies (pymunk segments) — or as multi-segment compound bodies that can flex and bend — that grow, divide, secrete particles, attach to surfaces, and interact physically through collisions and confinement. Concentration fields are modeled with a finite-difference diffusion process and coupled to cells through a per-tick exchange step. The framework uses the bigraph process architecture, making it easy to compose new cell behaviors, environmental structures, and analysis pipelines.
47
+
48
+ Default parameters are calibrated to *E. coli* proportions (~1 μm wide, ~2 μm at birth, ~4 μm at division, ~30–40 min doubling time).
49
+
50
+ ## Installation
51
+
52
+ Requires Python 3.11+.
53
+
54
+ ```bash
55
+ pip install viva-munk
56
+ ```
57
+
58
+ To use as a library:
59
+
60
+ ```python
61
+ from viva_munk import core_import
62
+ from process_bigraph import Composite
63
+
64
+ core = core_import() # registers viva_munk types and processes
65
+ # ... build a spec dict and run: Composite(spec, core=core)
66
+ ```
67
+
68
+ ## Quick Start
69
+
70
+ Run the full experiment suite and generate the HTML report:
71
+
72
+ ```bash
73
+ git clone https://github.com/vivarium-collective/Viva-munk.git
74
+ cd Viva-munk
75
+ pip install -e .[dev]
76
+
77
+ # Run all experiments and open the HTML report
78
+ python -m viva_munk.experiments.test_suite
79
+
80
+ # Run without auto-opening the browser
81
+ python -m viva_munk.experiments.test_suite --no-open
82
+ ```
83
+
84
+ ## Architecture
85
+
86
+ ```
87
+ viva_munk/
88
+ pymunk_agent_type.py # Custom PymunkAgent type with optimized dispatch
89
+ types/
90
+ positive.py # PositiveFloat / PositiveArray / Concentration / SetFloat
91
+ processes/
92
+ multibody.py # PymunkProcess — 2D physics (rigid + bending cells)
93
+ grow_divide.py # GrowDivide — exponential growth, division, mutation
94
+ pressure.py # Pressure — vectorized per-cell mechanical pressure
95
+ diffusion_advection.py # DiffusionAdvection — 2D field diffusion + advection
96
+ cell_field_exchange.py # CellFieldExchange — cell ↔ field uptake/secretion
97
+ secrete_eps.py # SecreteEPS — EPS particle secretion
98
+ remove_crossing.py # RemoveCrossing — remove cells past a boundary
99
+ plots/
100
+ multibody_plots.py # GIF rendering with phylogeny / pressure coloring
101
+ # and concentration-field heatmap overlays
102
+ experiments/
103
+ test_suite.py # Experiment registry, runner, HTML report generator
104
+ ```
105
+
106
+ ### Processes
107
+
108
+ - **PymunkProcess** (Process): Manages the pymunk 2D physics space. Handles three kinds of agents — rigid segment cells, multi-segment bending cells (compound bodies linked by pivot joints + damped rotary springs), and circular particles. Syncs state to bodies, steps the physics with sub-stepping, applies damping/jitter, optionally enforces adhesion to a wall, and emits position/velocity/polyline deltas.
109
+ - **GrowDivide** (Process): Per-cell exponential mass growth. Optionally gated by a Monod factor on a local nutrient (`local[nutrient_key]`) and inhibited by mechanical pressure (`exp(-pressure / pressure_k)`). When mass exceeds a threshold, the cell divides into two daughters along its axis; daughters can inherit mutated parameters and (for bending cells) a fresh straight polyline so they don't inherit the mother's pose.
110
+ - **Pressure** (Step): Computes a per-cell mechanical pressure proxy from cell-cell and cell-wall overlap depths (vectorized via numpy). Written back to each cell's `pressure` field for downstream consumers.
111
+ - **DiffusionAdvection** (Process): Explicit FTCS diffusion + upwind advection on a 2D `map[mol_id → array]` field, with ghost-layer boundary conditions (periodic / neumann / dirichlet / dirichlet_ghost).
112
+ - **CellFieldExchange** (Process): Couples `pymunk_agent` cells to a 2D field map. Each tick it samples each cell's local field concentration into `cell.local`, and applies each cell's `cell.exchange` amounts back into the corresponding field bin (Δconcentration = Δamount / bin_volume).
113
+ - **SecreteEPS** (Process): Per-cell EPS particle secretion at a rate proportional to cell mass. Particles are placed on the cell surface and added to the environment. Optionally gated to attached cells only.
114
+ - **RemoveCrossing** (Step): Removes any cell whose position exceeds a configurable x/y boundary. Used to model the flow channel in mother-machine experiments.
115
+
116
+ ### Custom types
117
+
118
+ `pymunk_agent` is a `Node`-subclass type with hand-optimized `apply`/`reconcile`/`realize`/`check` dispatch (no per-field plum dispatch on the hot path). It carries the geometric and physical state of one cell (mass, radius, length, location, velocity, …) plus optional fields used by downstream processes (`polyline`, `attached`, `pressure`, `local`, `exchange`).
119
+
120
+ The `viva_munk.types.positive` module provides minimal `PositiveFloat`, `PositiveArray`, `Concentration`, and `SetFloat` types for the field state, accumulator-with-clamp semantics adapted from [spatio-flux](https://github.com/vivarium-collective/spatio-flux).
121
+
122
+ ## Experiments
123
+
124
+ The current registry (`viva_munk/experiments/test_suite.py`):
125
+
126
+ - **daughter_machine** — single cell grows in an open chamber with an absorbing right wall.
127
+ - **mother_machine** — narrow dead-end channels (~1.5 μm wide); cells grow vertically and are removed when they reach the flow channel.
128
+ - **with_particles** — cells grow in a chamber seeded with passive particles; cells push and rearrange them.
129
+ - **attachment** — adhesin-bearing cells attach to the bottom surface via PivotJoints; adhesins split between daughters at division.
130
+ - **glucose_growth** — cells on a 2D glucose field. `DiffusionAdvection` spreads glucose, `CellFieldExchange` applies cell consumption every tick, and `GrowDivide` gates rate by Monod kinetics. Cells stop dividing once their local patch is depleted.
131
+ - **bending_pressure** — multi-segment bending capsules grow into a colony. `Pressure` computes mechanical pressure from neighbor / wall contacts and `GrowDivide` applies `exp(-pressure / pressure_k)` inhibition. Cells visibly bend AND slow.
132
+ - **chemotaxis** — twelve non-growing cells run/tumble up a static exponential ligand gradient in a long chamber. Each cell maintains a memory of its local concentration and modulates tumble rate as `λ = λ₀ · exp(-k · dc/dt)`.
133
+ - **inclusion_bodies** — a colony grows while each cell accumulates an inclusion-body aggregate (size in nm, logistic growth toward an 800 nm plateau). Aggregation inhibits growth; at division the IB is transferred entirely to one daughter so the IB-free sibling out-grows the laden one. Cells are soft bending capsules and a `Pressure` step adds a second slowdown as the colony packs. Colored by IB size.
134
+
135
+ `test_suite.py` runs each one, captures a GIF, a bigraph composition viz, and a serialized state JSON, and generates an HTML report (`out/report.html`) with timing, cell counts, descriptions, and embedded media.
136
+
137
+ ## Dependencies
138
+
139
+ - [process-bigraph](https://github.com/vivarium-collective/process-bigraph) — bigraph process framework
140
+ - [bigraph-schema](https://github.com/vivarium-collective/bigraph-schema) — typed schemas (via process-bigraph)
141
+ - [bigraph-viz](https://github.com/vivarium-collective/bigraph-viz) — bigraph visualization
142
+ - [pymunk](https://www.pymunk.org/) — 2D physics engine
143
+ - numpy, matplotlib, Pillow, plum-dispatch
144
+
145
+ ## Releasing
146
+
147
+ Releases are cut from `main` via `./release.sh`, which bumps the patch
148
+ version in `pyproject.toml`, commits + tags `vX.Y.Z`, pushes the tag, builds
149
+ sdist + wheel, and publishes to PyPI (token at `~/.pypi-token`).
150
+
151
+ ## License
152
+
153
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,120 @@
1
+ # viva-munk
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/viva-munk.svg)](https://pypi.org/project/viva-munk/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/viva-munk.svg)](https://pypi.org/project/viva-munk/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+
7
+ Multi-cell simulations with 2D physics, built on [process-bigraph](https://github.com/vivarium-collective/process-bigraph) and [pymunk](https://www.pymunk.org/).
8
+
9
+ ### **[Demos](https://vivarium-collective.github.io/Viva-munk/)**
10
+
11
+ ## Goal
12
+
13
+ Provide a composable framework for simulating populations of growing, dividing cells with realistic 2D physics and shared chemical environments. Cells are modeled as capsule-shaped rigid bodies (pymunk segments) — or as multi-segment compound bodies that can flex and bend — that grow, divide, secrete particles, attach to surfaces, and interact physically through collisions and confinement. Concentration fields are modeled with a finite-difference diffusion process and coupled to cells through a per-tick exchange step. The framework uses the bigraph process architecture, making it easy to compose new cell behaviors, environmental structures, and analysis pipelines.
14
+
15
+ Default parameters are calibrated to *E. coli* proportions (~1 μm wide, ~2 μm at birth, ~4 μm at division, ~30–40 min doubling time).
16
+
17
+ ## Installation
18
+
19
+ Requires Python 3.11+.
20
+
21
+ ```bash
22
+ pip install viva-munk
23
+ ```
24
+
25
+ To use as a library:
26
+
27
+ ```python
28
+ from viva_munk import core_import
29
+ from process_bigraph import Composite
30
+
31
+ core = core_import() # registers viva_munk types and processes
32
+ # ... build a spec dict and run: Composite(spec, core=core)
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ Run the full experiment suite and generate the HTML report:
38
+
39
+ ```bash
40
+ git clone https://github.com/vivarium-collective/Viva-munk.git
41
+ cd Viva-munk
42
+ pip install -e .[dev]
43
+
44
+ # Run all experiments and open the HTML report
45
+ python -m viva_munk.experiments.test_suite
46
+
47
+ # Run without auto-opening the browser
48
+ python -m viva_munk.experiments.test_suite --no-open
49
+ ```
50
+
51
+ ## Architecture
52
+
53
+ ```
54
+ viva_munk/
55
+ pymunk_agent_type.py # Custom PymunkAgent type with optimized dispatch
56
+ types/
57
+ positive.py # PositiveFloat / PositiveArray / Concentration / SetFloat
58
+ processes/
59
+ multibody.py # PymunkProcess — 2D physics (rigid + bending cells)
60
+ grow_divide.py # GrowDivide — exponential growth, division, mutation
61
+ pressure.py # Pressure — vectorized per-cell mechanical pressure
62
+ diffusion_advection.py # DiffusionAdvection — 2D field diffusion + advection
63
+ cell_field_exchange.py # CellFieldExchange — cell ↔ field uptake/secretion
64
+ secrete_eps.py # SecreteEPS — EPS particle secretion
65
+ remove_crossing.py # RemoveCrossing — remove cells past a boundary
66
+ plots/
67
+ multibody_plots.py # GIF rendering with phylogeny / pressure coloring
68
+ # and concentration-field heatmap overlays
69
+ experiments/
70
+ test_suite.py # Experiment registry, runner, HTML report generator
71
+ ```
72
+
73
+ ### Processes
74
+
75
+ - **PymunkProcess** (Process): Manages the pymunk 2D physics space. Handles three kinds of agents — rigid segment cells, multi-segment bending cells (compound bodies linked by pivot joints + damped rotary springs), and circular particles. Syncs state to bodies, steps the physics with sub-stepping, applies damping/jitter, optionally enforces adhesion to a wall, and emits position/velocity/polyline deltas.
76
+ - **GrowDivide** (Process): Per-cell exponential mass growth. Optionally gated by a Monod factor on a local nutrient (`local[nutrient_key]`) and inhibited by mechanical pressure (`exp(-pressure / pressure_k)`). When mass exceeds a threshold, the cell divides into two daughters along its axis; daughters can inherit mutated parameters and (for bending cells) a fresh straight polyline so they don't inherit the mother's pose.
77
+ - **Pressure** (Step): Computes a per-cell mechanical pressure proxy from cell-cell and cell-wall overlap depths (vectorized via numpy). Written back to each cell's `pressure` field for downstream consumers.
78
+ - **DiffusionAdvection** (Process): Explicit FTCS diffusion + upwind advection on a 2D `map[mol_id → array]` field, with ghost-layer boundary conditions (periodic / neumann / dirichlet / dirichlet_ghost).
79
+ - **CellFieldExchange** (Process): Couples `pymunk_agent` cells to a 2D field map. Each tick it samples each cell's local field concentration into `cell.local`, and applies each cell's `cell.exchange` amounts back into the corresponding field bin (Δconcentration = Δamount / bin_volume).
80
+ - **SecreteEPS** (Process): Per-cell EPS particle secretion at a rate proportional to cell mass. Particles are placed on the cell surface and added to the environment. Optionally gated to attached cells only.
81
+ - **RemoveCrossing** (Step): Removes any cell whose position exceeds a configurable x/y boundary. Used to model the flow channel in mother-machine experiments.
82
+
83
+ ### Custom types
84
+
85
+ `pymunk_agent` is a `Node`-subclass type with hand-optimized `apply`/`reconcile`/`realize`/`check` dispatch (no per-field plum dispatch on the hot path). It carries the geometric and physical state of one cell (mass, radius, length, location, velocity, …) plus optional fields used by downstream processes (`polyline`, `attached`, `pressure`, `local`, `exchange`).
86
+
87
+ The `viva_munk.types.positive` module provides minimal `PositiveFloat`, `PositiveArray`, `Concentration`, and `SetFloat` types for the field state, accumulator-with-clamp semantics adapted from [spatio-flux](https://github.com/vivarium-collective/spatio-flux).
88
+
89
+ ## Experiments
90
+
91
+ The current registry (`viva_munk/experiments/test_suite.py`):
92
+
93
+ - **daughter_machine** — single cell grows in an open chamber with an absorbing right wall.
94
+ - **mother_machine** — narrow dead-end channels (~1.5 μm wide); cells grow vertically and are removed when they reach the flow channel.
95
+ - **with_particles** — cells grow in a chamber seeded with passive particles; cells push and rearrange them.
96
+ - **attachment** — adhesin-bearing cells attach to the bottom surface via PivotJoints; adhesins split between daughters at division.
97
+ - **glucose_growth** — cells on a 2D glucose field. `DiffusionAdvection` spreads glucose, `CellFieldExchange` applies cell consumption every tick, and `GrowDivide` gates rate by Monod kinetics. Cells stop dividing once their local patch is depleted.
98
+ - **bending_pressure** — multi-segment bending capsules grow into a colony. `Pressure` computes mechanical pressure from neighbor / wall contacts and `GrowDivide` applies `exp(-pressure / pressure_k)` inhibition. Cells visibly bend AND slow.
99
+ - **chemotaxis** — twelve non-growing cells run/tumble up a static exponential ligand gradient in a long chamber. Each cell maintains a memory of its local concentration and modulates tumble rate as `λ = λ₀ · exp(-k · dc/dt)`.
100
+ - **inclusion_bodies** — a colony grows while each cell accumulates an inclusion-body aggregate (size in nm, logistic growth toward an 800 nm plateau). Aggregation inhibits growth; at division the IB is transferred entirely to one daughter so the IB-free sibling out-grows the laden one. Cells are soft bending capsules and a `Pressure` step adds a second slowdown as the colony packs. Colored by IB size.
101
+
102
+ `test_suite.py` runs each one, captures a GIF, a bigraph composition viz, and a serialized state JSON, and generates an HTML report (`out/report.html`) with timing, cell counts, descriptions, and embedded media.
103
+
104
+ ## Dependencies
105
+
106
+ - [process-bigraph](https://github.com/vivarium-collective/process-bigraph) — bigraph process framework
107
+ - [bigraph-schema](https://github.com/vivarium-collective/bigraph-schema) — typed schemas (via process-bigraph)
108
+ - [bigraph-viz](https://github.com/vivarium-collective/bigraph-viz) — bigraph visualization
109
+ - [pymunk](https://www.pymunk.org/) — 2D physics engine
110
+ - numpy, matplotlib, Pillow, plum-dispatch
111
+
112
+ ## Releasing
113
+
114
+ Releases are cut from `main` via `./release.sh`, which bumps the patch
115
+ version in `pyproject.toml`, commits + tags `vX.Y.Z`, pushes the tag, builds
116
+ sdist + wheel, and publishes to PyPI (token at `~/.pypi-token`).
117
+
118
+ ## License
119
+
120
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,81 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "viva-munk"
7
+ version = "0.0.2"
8
+ description = "multi-cell simulations with pymunk 2d physics"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.11"
12
+ authors = [
13
+ {name = "Eran Agmon", email = "agmon.eran@gmail.com"},
14
+ {name = "Ryan Spangler"},
15
+ ]
16
+ classifiers = [
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Operating System :: OS Independent",
22
+ ]
23
+ dependencies = [
24
+ "process-bigraph",
25
+ "bigraph-schema>=0.0.60",
26
+ "bigraph-viz",
27
+ "pymunk",
28
+ "numpy",
29
+ "matplotlib",
30
+ "Pillow",
31
+ "plum-dispatch",
32
+ # viva_munk/visualizations/* subclass pbg_superpowers.visualization.Visualization.
33
+ "pbg-superpowers",
34
+ # NOTE on spatio-flux: viva_munk/__init__.py imports
35
+ # `from spatio_flux.visualizations import (...)` at module-import
36
+ # time, but no PyPI-published spatio-flux release ships a
37
+ # `visualizations/` submodule (it was dropped in 1.4 in favour of
38
+ # `plots/`; the dev 1.0.0 still has it but is not on PyPI). Listing
39
+ # `spatio-flux` here would let pip install succeed but the import
40
+ # would still fail. Tracked separately: either refactor viva_munk's
41
+ # __init__ to use spatio_flux.plots, vendor what we need, or
42
+ # publish a 1.0.x patch with the legacy visualizations layout.
43
+ # Until then, viva-munk is installable only against a local
44
+ # editable spatio-flux checkout that has visualizations/.
45
+ ]
46
+
47
+ [project.optional-dependencies]
48
+ dev = [
49
+ "pytest",
50
+ "ipdb",
51
+ "build",
52
+ "twine>=4.0.2",
53
+ ]
54
+
55
+ [project.urls]
56
+ Homepage = "https://github.com/vivarium-collective/Viva-munk"
57
+ Repository = "https://github.com/vivarium-collective/Viva-munk"
58
+
59
+ # Auto-discover every viva_munk subpackage at build time. The hand-
60
+ # maintained list previously skipped `viva_munk.composites` AND
61
+ # `viva_munk.visualizations`, so the published wheel didn't include
62
+ # those directories at all and `import viva_munk` broke on any clean
63
+ # install (the package's own __init__.py imports them). The
64
+ # auto-discovery pattern matches what spatio-flux does.
65
+ [tool.setuptools.packages.find]
66
+ where = ["."]
67
+ include = ["viva_munk*"]
68
+ exclude = ["tests*", "docs*", "studies*"]
69
+
70
+ # NOTE: this package previously declared a [tool.uv.sources] override
71
+ # pointing process-bigraph at `../process-bigraph` (editable). That
72
+ # section is dev-only convenience for working in a sibling checkout
73
+ # layout — but it leaks into downstream consumers when viva-munk is
74
+ # pulled as a git dependency, because uv resolves the transitive
75
+ # source path as `subdirectory=../process-bigraph` of the git URL,
76
+ # which doesn't exist. Removed for now; if you need the local
77
+ # editable checkout, install it explicitly:
78
+ #
79
+ # pip install -e ../process-bigraph
80
+ #
81
+ # or add the override in YOUR project's pyproject.toml (not here).
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,174 @@
1
+ """
2
+ Registration-related functions
3
+ """
4
+
5
+ __version__ = "0.0.1"
6
+
7
+ from process_bigraph import register_types as pb_register_types
8
+ from process_bigraph.types.process import register_types as pb_types_register
9
+ from process_bigraph.composite import Composite
10
+ from process_bigraph.emitter import RAMEmitter, SQLiteEmitter
11
+ from bigraph_schema.core import Core, BASE_TYPES
12
+ from bigraph_viz import register_types as viz_register_types
13
+
14
+ from viva_munk.processes.multibody import PymunkProcess
15
+ from viva_munk.processes.grow_divide import GrowDivide, AdderGrowDivide
16
+ from viva_munk.processes.remove_crossing import RemoveCrossing
17
+ from viva_munk.processes.secrete_eps import SecreteEPS
18
+ from viva_munk.processes.pressure import Pressure
19
+ from viva_munk.processes.diffusion_advection import DiffusionAdvection
20
+ from viva_munk.processes.cell_field_exchange import CellFieldExchange
21
+ from viva_munk.processes.chemotaxis import Chemotaxis
22
+ from viva_munk.processes.inclusion_body import InclusionBody, IBColony
23
+ from viva_munk.processes.quorum_sensing import QuorumSensing
24
+ from viva_munk.processes.field_decay import FieldDecay
25
+ # Spatio-flux processes referenced by spatio_flux.composites.particles.* —
26
+ # brownian_particles needs both BrownianMovement and ManageBoundaries to
27
+ # resolve their `local:` addresses at run time.
28
+ from spatio_flux.processes.particles import BrownianMovement, ManageBoundaries
29
+ # Spatio-flux Visualization Steps — heatmaps, GIFs, snapshot grids, and
30
+ # emitter-driven timeseries. Registered so dashboard users can attach them
31
+ # to any composite via the Visualizations tab.
32
+ from spatio_flux.visualizations import (
33
+ FieldHeatmap,
34
+ FieldAnimationGif,
35
+ FieldSnapshotsGrid,
36
+ ParticleTraces,
37
+ TestSuiteTimeSeries,
38
+ )
39
+ from viva_munk.pymunk_agent_type import PymunkAgent, register_pymunk_agent_dispatches
40
+ from viva_munk.types import positive_types
41
+ from viva_munk.visualizations import MultibodyVizStep, CellMassTraces
42
+
43
+ # Register custom dispatches once at module import
44
+ register_pymunk_agent_dispatches()
45
+
46
+
47
+ def _patch_composite_realize_on_sentinels():
48
+ """Ensure Composite re-realizes newly-added subtrees on `_add`/`_remove`.
49
+
50
+ `Map.apply` drops `_add` entries straight into state without calling
51
+ the value type's realize path, and `Composite.apply_updates` only
52
+ triggers `core.realize` on schema-level merges — not on structural
53
+ sentinels. So embedded process specs (e.g. `grow_divide` on a freshly
54
+ divided daughter) are never instantiated: the spec is visible in
55
+ state but its `'instance'` slot stays unset and the Process class
56
+ never runs on that cell. Result: lineages freeze after the first
57
+ division in every experiment that uses per-cell embedded processes.
58
+
59
+ The fix is a one-line amend: when structural sentinels are detected,
60
+ run `core.realize` (which follows `Link`/process specs and creates
61
+ instances via `realize_link`) before `find_instance_paths` scans
62
+ state for them.
63
+ """
64
+ _original_apply_updates = Composite.apply_updates
65
+
66
+ def apply_updates_with_realize(self, updates):
67
+ update_paths = _original_apply_updates(self, updates)
68
+ if getattr(self, '_last_apply_structural', False):
69
+ # bigraph_schema.core.realize now returns a 3-tuple
70
+ # (schema, state, escape_merges). At top-level realize there
71
+ # is nothing above us for merges to escape to, so discard.
72
+ self.schema, self.state, _escape_merges = self.core.realize(
73
+ self.schema, self.state)
74
+ self.find_instance_paths(self.state)
75
+ self._build_view_project_cache()
76
+ return update_paths
77
+
78
+ Composite.apply_updates = apply_updates_with_realize
79
+
80
+
81
+ _patch_composite_realize_on_sentinels()
82
+
83
+
84
+ def _patch_list_apply_tuple_update():
85
+ """Make ``apply(schema: List, state: list, update: tuple)`` do
86
+ element-wise add when shape + numeric content match.
87
+
88
+ JSON serialization drops the tuple type, so a state that started as a
89
+ ``(x, y)`` tuple becomes a ``[x, y]`` list and schema inference picks
90
+ ``List(_element=Float)`` instead of ``Tuple(_values=[Float, Float])``.
91
+ The default ``apply(List)`` then does ``state + update`` — concat, not
92
+ element-wise — and crashes on the ``list + tuple`` type mismatch the
93
+ moment a process emits a tuple delta (``PymunkProcess`` does this for
94
+ ``location``/``velocity`` every tick). Pin element-wise behavior for
95
+ the JSON-roundtripped case so the dashboard's subprocess flow works.
96
+ """
97
+ from plum import dispatch
98
+ from bigraph_schema.schema import List
99
+ from bigraph_schema.methods.apply import apply as _apply
100
+
101
+ @_apply.dispatch
102
+ def apply_list_with_tuple_update(schema: List, state: list, update: tuple, path):
103
+ if len(state) == len(update) and all(
104
+ isinstance(s, (int, float)) for s in state
105
+ ):
106
+ return [s + u for s, u in zip(state, update)], []
107
+ # Length mismatch or non-numeric: fall back to concat (matches
108
+ # the default ``state + list(update)`` semantics).
109
+ return list(state) + list(update), []
110
+
111
+
112
+ _patch_list_apply_tuple_update()
113
+
114
+
115
+ from viva_munk import composites as _composites # noqa: E402,F401 — fires @composite_generator side-effects
116
+
117
+
118
+ def register_pymunk_types(core):
119
+ # Use the optimized PymunkAgent Node subclass instead of a dict schema.
120
+ # This eliminates per-field dispatch overhead in apply/reconcile/realize.
121
+ core.register_type('pymunk_agent', PymunkAgent())
122
+ # NOTE: viva_munk.types.positive_types overlaps with spatio_flux's on
123
+ # ('positive_float', 'positive_array', 'concentration', 'set_float') —
124
+ # viva_munk uses instances (PositiveFloat()), spatio_flux uses classes
125
+ # (PositiveFloat). Registering both forms triggers a resolve conflict in
126
+ # bigraph_schema. spatio_flux's set is a strict superset (adds count,
127
+ # mass, delta_conc), so we let spatio_flux_register_types own these
128
+ # types in core_import() and skip the duplicate loop here.
129
+
130
+
131
+ def register_processes(core):
132
+ core.register_link('PymunkProcess', PymunkProcess)
133
+ core.register_link('GrowDivide', GrowDivide)
134
+ core.register_link('AdderGrowDivide', AdderGrowDivide)
135
+ core.register_link('RemoveCrossing', RemoveCrossing)
136
+ core.register_link('SecreteEPS', SecreteEPS)
137
+ core.register_link('Pressure', Pressure)
138
+ core.register_link('DiffusionAdvection', DiffusionAdvection)
139
+ core.register_link('CellFieldExchange', CellFieldExchange)
140
+ core.register_link('Chemotaxis', Chemotaxis)
141
+ core.register_link('InclusionBody', InclusionBody)
142
+ core.register_link('IBColony', IBColony)
143
+ core.register_link('QuorumSensing', QuorumSensing)
144
+ core.register_link('FieldDecay', FieldDecay)
145
+ core.register_link('BrownianMovement', BrownianMovement)
146
+ core.register_link('ManageBoundaries', ManageBoundaries)
147
+ core.register_link('MultibodyVizStep', MultibodyVizStep)
148
+ core.register_link('CellMassTraces', CellMassTraces)
149
+ # Spatio-flux Visualization Steps
150
+ core.register_link('FieldHeatmap', FieldHeatmap)
151
+ core.register_link('FieldAnimationGif', FieldAnimationGif)
152
+ core.register_link('FieldSnapshotsGrid', FieldSnapshotsGrid)
153
+ core.register_link('ParticleTraces', ParticleTraces)
154
+ core.register_link('TestSuiteTimeSeries', TestSuiteTimeSeries)
155
+ core.register_link('Composite', Composite)
156
+ core.register_link('RAMEmitter', RAMEmitter)
157
+ core.register_link('SQLiteEmitter', SQLiteEmitter)
158
+
159
+
160
+ def core_import(core=None, config=None):
161
+ if not core:
162
+ core = Core(BASE_TYPES)
163
+ pb_types_register(core)
164
+ pb_register_types(core)
165
+ viz_register_types(core)
166
+ # Spatio-flux types (particle, position, bounds, fields, ...) are
167
+ # referenced by spatio_flux.composites.particles.* and other composites
168
+ # used in this workspace. Their Process classes (registered below) declare
169
+ # ports like 'map[particle]' that won't resolve without these types.
170
+ from spatio_flux import register_types as spatio_flux_register_types
171
+ spatio_flux_register_types(core)
172
+ register_pymunk_types(core)
173
+ register_processes(core)
174
+ return core
@@ -0,0 +1,104 @@
1
+ """Composite-generator registrations for viva-munk's experiment documents.
2
+
3
+ Each function here is a thin `@composite_generator`-decorated wrapper around
4
+ the corresponding `*_document(config=None)` builder in
5
+ `viva_munk.experiments.documents`. The decorator side-effect (registering
6
+ into pbg-superpowers' `_REGISTRY`) is what makes these visible to the
7
+ vivarium-dashboard's composite browser.
8
+
9
+ Parameters intentionally left unexposed for now — call sites get the
10
+ builder's default config. Per-composite kwargs can be surfaced later by
11
+ filling in `parameters=` on the decorator.
12
+ """
13
+ from pbg_superpowers.composite_generator import composite_generator
14
+
15
+ from viva_munk.experiments.documents.attachment import attachment_document
16
+ from viva_munk.experiments.documents.bending_pressure import bending_pressure_document
17
+ from viva_munk.experiments.documents.biofilm import biofilm_document
18
+ from viva_munk.experiments.documents.chemotaxis import chemotaxis_document
19
+ from viva_munk.experiments.documents.daughter_machine import daughter_machine_document
20
+ from viva_munk.experiments.documents.glucose_growth import glucose_growth_document
21
+ from viva_munk.experiments.documents.inclusion_bodies import inclusion_bodies_document
22
+ from viva_munk.experiments.documents.mother_machine import mother_machine_document
23
+ from viva_munk.experiments.documents.quorum_sensing import quorum_sensing_document
24
+
25
+
26
+ @composite_generator(
27
+ name="attachment",
28
+ description="Cells settling/attaching on a surface.",
29
+ default_n_steps=200,
30
+ )
31
+ def attachment(core=None) -> dict:
32
+ return attachment_document()
33
+
34
+
35
+ @composite_generator(
36
+ name="bending_pressure",
37
+ description="Bending-pressure rod-like cells.",
38
+ default_n_steps=200,
39
+ )
40
+ def bending_pressure(core=None) -> dict:
41
+ return bending_pressure_document()
42
+
43
+
44
+ @composite_generator(
45
+ name="biofilm",
46
+ description="Growing biofilm with EPS secretion + pressure dynamics.",
47
+ default_n_steps=500,
48
+ )
49
+ def biofilm(core=None) -> dict:
50
+ return biofilm_document()
51
+
52
+
53
+ @composite_generator(
54
+ name="chemotaxis",
55
+ description="Run/tumble chemotaxis up a static 2D ligand gradient.",
56
+ default_n_steps=200,
57
+ )
58
+ def chemotaxis(core=None) -> dict:
59
+ return chemotaxis_document()
60
+
61
+
62
+ @composite_generator(
63
+ name="daughter_machine",
64
+ description="Daughter-machine geometry: tracking daughter cells.",
65
+ default_n_steps=300,
66
+ )
67
+ def daughter_machine(core=None) -> dict:
68
+ return daughter_machine_document()
69
+
70
+
71
+ @composite_generator(
72
+ name="glucose_growth",
73
+ description="Glucose-driven growth and division.",
74
+ default_n_steps=300,
75
+ )
76
+ def glucose_growth(core=None) -> dict:
77
+ return glucose_growth_document()
78
+
79
+
80
+ @composite_generator(
81
+ name="inclusion_bodies",
82
+ description="Inclusion-body accumulation across a growing colony.",
83
+ default_n_steps=500,
84
+ )
85
+ def inclusion_bodies(core=None) -> dict:
86
+ return inclusion_bodies_document()
87
+
88
+
89
+ @composite_generator(
90
+ name="mother_machine",
91
+ description="Mother-machine geometry: single-channel cell trapping.",
92
+ default_n_steps=300,
93
+ )
94
+ def mother_machine(core=None) -> dict:
95
+ return mother_machine_document()
96
+
97
+
98
+ @composite_generator(
99
+ name="quorum_sensing",
100
+ description="Quorum-sensing autoinducer feedback in a growing colony.",
101
+ default_n_steps=300,
102
+ )
103
+ def quorum_sensing(core=None) -> dict:
104
+ return quorum_sensing_document()
@@ -0,0 +1,5 @@
1
+ from viva_munk import core_import
2
+
3
+
4
+ def build_core():
5
+ return core_import()