Membrane Solver is a simulation platform inspired by Surface Evolver, designed to model and minimize the energy of geometric structures such as membranes and surfaces. It supports volume and surface area constraints, dynamic refinement of meshes, and customizable energy modules for physical effects like surface tension, line tension, and curvature. The project aims to provide a flexible and extensible framework for exploring complex geometries and their energy-driven evolution.
main.py starts in interactive mode by default, presenting a simple command
prompt after any initial instructions execute. Use --non-interactive to skip
the prompt.
Commands:
g5: Perform five minimization steps.r/rN: Refine the mesh (N times).u: Equiangulate the mesh.V: Vertex average.p/i: Print physical properties (area, volume, surface radius of gyration, target volume).print [entity] [filter]: Query geometry (e.g.,print vertex 0,print edges len > 0.5).print energy breakdown: Show per-module energy contributions.print macros: List available macros.set [param/entity] [value]: Set properties (e.g.,set surface_tension 1.5,set vertex 0 fixed true).lv/live_vis: Toggle live 3D visualization during minimization.history: Show commands entered in the current session.quit/exit: Stop the loop and save the final mesh.
The default minimization stepper is Gradient Descent; switch to Conjugate Gradient
with the cg command if needed.
If no input file is specified on the command line you will be prompted for the
path. File names may be given with or without the .json suffix.
Input files may define named macros (similar to Evolver macros). Each macro is a sequence of existing interactive commands. In interactive mode you can invoke a macro by typing its name directly.
Example:
macros:
gogo: "g 5; r; g 5; u; g 5"
instructions:
- gogoOn load, the CLI prints any available macros for quick discovery.
For readability you can provide explicit IDs for vertices, edges, faces,
and bodies using a mapping form instead of lists. This avoids “counting
lines” when referring to faces/bodies and is closer to Evolver’s explicit IDs.
Example:
vertices:
10: [0, 0, 0]
20: [1, 0, 0]
edges:
1: [10, 20]
faces:
100: [1, r2, 3]
bodies:
0:
faces: [100]
target_volume: 1.0You can attach expression-based energies or hard constraints to entities using
safe arithmetic expressions over x, y, z and global parameters.
Example:
vertices:
0: [1, 2, 3, {expression: "x + y + z"}]
edges:
1: [0, 1, {expression: "x", expression_measure: "length"}]Hard constraint example:
vertices:
0: [0, 0, 0, {constraint_expression: "x", constraint_target: 1.0}]Define reusable symbols for expressions with a top-level defines block:
global_parameters:
angle: 60.0
defines:
WALLT: "-cos(angle*pi/180)"
edges:
1: [0, 1, {expression: "-(WALLT*y)", expression_measure: "length"}]Fixed edges imply fixed endpoints: marking an edge as fixed also freezes its
two vertices and preserves that behavior through refinement.
To profile each benchmark case and generate per-case .pstats outputs:
./tools/profile.shThis runs python tools/suite.py --profile and writes results under
benchmarks/outputs/profiles by default.
Optional hot-loop kernels can be compiled with NumPy f2py for speedups.
- Build on demand:
python -m membrane_solver.build_ext - Build at install time:
MEMBRANE_SOLVER_BUILD_EXT=1 pip install -e .(orpip install .)
If the compiled modules are importable, the solver will use them automatically.
Set MEMBRANE_DISABLE_FORTRAN_SURFACE=1 or MEMBRANE_DISABLE_FORTRAN_BENDING=1
to force the NumPy fallback.
For meshes with boundary loops (e.g., disk rims modeled as holes), enable
Gauss-Bonnet drift checks by setting gauss_bonnet_monitor=true in
global_parameters. Debug logs report the total invariant, the interior
angle-defect sum, and per-loop boundary geodesic sums so drift can be
localized after refinement or remeshing. To exclude facets from the
diagnostic (treating them as “inactive”), set
gauss_bonnet_exclude: true in the facet options.
For gaussian_curvature energy, boundary loops are supported by default:
the module evaluates the Gauss–Bonnet sum (interior defects + boundary
turning) and multiplies by gaussian_modulus. To exclude facets from this
sum, set gauss_bonnet_exclude: true in facet options. Enable
gaussian_curvature_strict_topology=true to raise on non-manifold edges or
invalid boundary loops (tolerance via gaussian_curvature_defect_tol).
parse_geometry supports both JSON (.json) and YAML (.yaml, .yml) formats.
YAML is recommended for adding comments and using anchors/aliases.
The loader automatically triangulates any facet with more than three edges
using refine_polygonal_facets. Triangular facets remain unchanged. The
returned mesh is therefore ready for optimization without further refinement.
For edge‑only or “wire‑frame” geometries, the faces section is optional.
Use the visualization module to inspect geometries:
python -m visualization.cli meshes/cube.jsonYou can also visualize any input mesh directly via main.py:
python main.py -i meshes/cube.json --viz
python main.py -i meshes/cube.json --viz-save outputs/cube.png --viz-no-axesLive Visualization: Inside the interactive console, type lv (or live_vis) to open a real-time plotting window that updates with every minimization step.
The CLI accepts several flags:
-
python -m visualization.cli meshes/cube.json --transparentDraw facets semi‑transparent (now with corrected alpha rendering). -
python -m visualization.cli meshes/cube.json --no-edgesHide edges and show only filled facets (useful for solid views). -
python -m visualization.cli meshes/simple_line.json --no-facets --scatterVisualize line‑only meshes: edges only, plus vertex scatter points. -
python -m visualization.cli meshes/cube.json --no-axes --save outputs/cube.pngRemove axes and save the figure to an image file instead of only showing it.
All interactive visualizations are based on the shared visualization.plotting.plot_geometry
helper, which ensures equal aspect ratios to prevent distortion.
Membrane Solver mirrors Evolver’s mix of penalties and hard constraints. Surface
area (body/facet/global) and volume constraints are configured via
constraint_modules plus the relevant target_* values on entities or in
global_parameters. Perimeter conservation uses a loop of signed edge indices:
Constraint modules that do not provide gradients are enforced geometrically during minimization and emit a warning at runtime.
"constraint_modules": ["perimeter"],
"global_parameters": {
"perimeter_constraints": [
{
"edges": [1, 2, 3, 4],
"target_perimeter": 4.0
}
]
}pin_to_circle supports a fit mode for moving circular rims. Set
pin_to_circle_mode: "fit" (plus pin_to_circle_group on the tagged entities)
to keep the rim circular while letting the circle translate/rotate with the mesh.
The new regression tests in tests/test_perimeter_minimization.py load the same
square loop (see tests/sample_meshes.square_perimeter_input) to verify that
energy drops while the loop returns to the requested perimeter, even after mesh
refinement and equiangulation.
Enable curvature/bending energy with bending_modulus and the bending energy
module. Use:
bending_energy_model:helfrich(default) orwillmorespontaneous_curvature(alias:intrinsic_curvature) for Helfrichbending_gradient_mode:analytic(default),approx(fast), orfinite_difference(slow, for verification)
- The evolving backlog/TODO list now lives in
docs/ROADMAP.mdso changes are tracked alongside the code. Refer to that document for design sketches, medium-term research targets, and open questions.
- Install deps with
pip install -r requirements.txt(adds pytest, pytest-cov, Ruff, etc.). - Run
pytest -qbefore and after significant edits. Recent suites add coverage for the volume penalty path (tests/test_volume_energy.py) and low-level error handling (tests/test_exceptions.py). - Lint via
ruff check .(orpre-commit run -a) to match CI. - Coverage hotspots are tracked via
pytest --cov=. --cov-report=term-missing; focus on geometry entities, module managers, and CLI commands.
By default, runs do not write a log file. Use --log [PATH] to write logs to a
file; if PATH is omitted, the log is written next to the input mesh (same
basename, .log suffix).
- A living dependency diagram is stored at
docs/mermaid_diagram.txt(render with any Mermaid-compatible viewer). It ties CLI entry points, geometry entities, runtime managers, and modules together. - Shared exception types (e.g.,
InvalidEdgeIndexError) live inexceptions.pyso both geometry and runtime code can surface actionable error messages. - Runtime components (
runtime/topology.py,runtime/refinement.py, etc.) now have targeted regression tests to keep mesh maintenance stable as new energy/constraint modules land.
This repo uses Ruff for linting (and can adopt Ruff formatting later).
pip install ruff
ruff check .To enforce linting locally before commits, install and enable pre-commit:
pip install pre-commit
pre-commit install
pre-commit run -aThe detailed development roadmap has been moved to docs/ROADMAP.md. In brief,
near‑term goals include:
- Stabilizing and benchmarking baseline shape problems (cube→sphere, square→circle, capillary bridge).
- Adding curvature energies (mean and Gaussian) and validating against classic examples such as catenoids and pinned caps.
- Implementing tilt fields and caveolin‑like inclusions as a 3D extension of
the model in
docs/caveolin_generate_curvature.pdf.
python tools/suite.pyis the main entry point for performance testing. It runs a set of standard scenarios (cube_good,square_to_circle,catenoid,spherical_cap,dented_cube,two_disks_sphere), tracks execution time history inbenchmarks/results.json, and highlights regressions or improvements.python benchmarks/benchmark_cube_good.pyruns the fullcube_good_min_routinerecipe (minimization, refinement, equiangulation, vertex averaging, etc.) and reports the average wall-clock time.python benchmarks/benchmark_square_to_circle.pyruns thesquare_to_circlescenario (square sheet relaxing to a circle with line tension), serving as a stress test for mesh maintenance operations.python benchmarks/benchmark_catenoid.pyruns thecatenoidscenario (surface tension minimization between two fixed rings), validatingpin_to_circleconstraints and surface minimization.python benchmarks/benchmark_cap.pyvalidates the spherical cap scenario, checking apex height, radius, and spherical fit quality against theoretical predictions. It can also be used as a standalone analysis tool:python benchmarks/benchmark_cap.py outputs/result.json.python benchmarks/benchmark_dented_cube.pyruns a cube→(nearly) sphere benchmark while keeping one face tagged as a planar/circular dent scaffold.python benchmarks/benchmark_two_disks_sphere.pyruns a sphere scaffold with two small flat disk patches, exercising circle/plane constraints on a closed surface.