Overview
svVascularize exposes a set of parameters that control how trees grow, how candidates are proposed, which constraints are enforced, and how the local optimization balances competing criteria. By sweeping these parameters and varying random seeds, you can produce families of trees tailored to specific tissues, printing constraints, or hemodynamic goals.
Key controls
Category | Parameters (examples) | Effect |
---|---|---|
Domain & seeds | geometry (mesh/SDF), inlets/outlets, root pose, initial terminals | Where growth is allowed; initial topology & bias. |
Demand & targets | terminal count, volumetric demand field, sampling scheme (uniform / blue-noise / demand-weighted) | Space-filling behavior; coverage uniformity; perfusion targeting. |
Objective weights | \(J = \alpha\,\text{Power} + \beta\,\text{Volume} + \gamma\,\text{Smoothness} + \dots\) | Trade-off energy vs. material vs. geometry. |
Murray / flow model | exponent \( n \in [2.7,3.2] \), viscosity model, Poiseuille/0D options | Radius scaling at bifurcations; shear/pressure profiles. |
Hard constraints | clearance, non-intersection, domain inclusion, degree limits, min length | Feasibility filter; shapes admissible solutions. |
Soft constraints | angle bounds, taper/smoothness penalties, curvature limits | Regularity of geometry; printability robustness. |
Local optimization | neighborhood size, line-search/trust-region, tolerance, max iters | Quality/speed of bifurcation placement & sizing. |
Stochasticity | RNG seed, candidate pool size, tempered acceptance, resample policy | Diversity and exploration vs. exploitation. |
Objective & constraints (at a glance)
We typically minimize a penalized composite objective:
Here \(R_i\) are segment resistances (Poiseuille), \(Q_i\) flows from a fast 0D solve per candidate, and \(\phi_k\) are soft-constraint penalties; Murray-like scaling \(r_0^{\,n}=r_1^{\,n}+r_2^{\,n}\) is enforced exactly or with a strong penalty during local refinement. Hard constraints (clearance, non-intersection, domain inclusion) act as feasibility filters before scoring.
How to explore the space
- Grid / factorial sweep: Small dimensionality, coarse mapping of sensitivities.
- Random / Sobol / Latin Hypercube: Space-filling samples for moderate/high dimensions.
- Annealed sweeps: Start with relaxed constraints; tighten over stages to avoid stalls.
- Replicates: For each \( \theta \), run \( s=1..R \) seeds to estimate variability and robustness.
- Pareto fronts: When tuning \((\alpha,\beta,\gamma)\), track non-dominated solutions (e.g., power vs volume).
A practical sweep workflow
- Define your domain, inlet/outlet sites, and a base parameter template \( \theta_0 \).
- Choose sweep axes (e.g., Murray exponent \(n\), terminal demand \(q_{\text{term}}\), angle penalty \(\gamma_{\angle}\)).
- Sample \( \Theta = \{\theta^{(1)},\ldots,\theta^{(M)}\} \) (grid, Sobol, etc.) and replicates \( s=1..R \).
- Generate trees \(\mathcal{T}(\theta^{(m)}, s)\); record metrics.
- Select candidates by constraints + objective, or compute a Pareto set for multi-objective trade-offs.
Illustrative sweep (pseudo-API)
This mirrors typical svVascularize usage; adjust names to your actual API.
import itertools, json, pathlib, numpy as np
# 1) Base parameters
base = dict(
domain="heart_slice.sdf", # or mesh
inlets=[(12.0, 6.1, 4.7)],
outlets=[], # optional; use forest-connection for loops
terminals=1200,
sampling="blue-noise",
murray_n=3.0,
weights=dict(alpha_power=1.0, beta_volume=0.1, gamma_smooth=0.05),
constraints=dict(clearance=0.12, min_length=0.4, angle_min=25, angle_max=120),
local_opt=dict(neighborhood=3.0, max_iters=20, tol=1e-3),
)
# 2) Define a sweep over two axes + RNG replicates
n_vals = [2.8, 3.0, 3.2]
q_vals = [0.8, 1.0, 1.2] # relative terminal demand scale (example)
seeds = [11, 12, 13]
def paramsets():
for n, q in itertools.product(n_vals, q_vals):
p = json.loads(json.dumps(base)) # deep-ish copy
p["murray_n"] = n
p["demand_scale"] = q
yield p
# 3) Run
outdir = pathlib.Path("runs/param_sweep")
outdir.mkdir(parents=True, exist_ok=True)
for i, theta in enumerate(paramsets()):
for s in seeds:
theta["seed"] = s
# v = svv.Vascularizer(theta) # construct
# T = v.grow() # generate tree
# metrics = v.metrics() # coverage, collisions, power, volume, etc.
# v.save(outdir / f"tree_{i}_seed{s}.vtp") # geometry
# json.dump(metrics, open(outdir / f"tree_{i}_seed{s}.json","w"), indent=2)
pass # replace with real calls
# 4) Post-process: aggregate metrics and rank / build Pareto set
What to measure
Metric | Why it matters |
---|---|
Coverage (%) / voids | Uniform perfusion and space-filling behavior. |
Collisions / clearance violations | Feasibility and printability. |
Total power & volume | Energy vs. material trade-off in \(J\). |
Radius & length distributions | Physiological realism / manufacturability. |
Bifurcation angles / taper | Geometric regularity and flow quality. |
Shear & pressure ranges | Hemodynamic safety windows. |
Connectivity / loops (forest) | Closed-circuit perfusion for tissues. |
Starting ranges (tunable)
- Murray exponent \(n \in [2.7, 3.2]\)
- Angle bounds \([20^\circ, 120^\circ]\) with a soft penalty beyond
- Clearance \(0.05\text{–}0.2\) × local radius scale (domain-dependent)
- Min segment length \(0.3\text{–}1.0\) × voxel/feature size
- Objective weights start \((\alpha,\beta,\gamma)=(1,0.1,0.05)\), then tune by Pareto analysis
Reproducibility checklist
- Record: parameter vector \( \theta \), RNG seed \( s \), commit hash / version, domain asset checksums.
- Persist: metrics JSON + geometry artifacts (e.g., VTP/VTK) for each run.
- Log: accept/reject counts, constraint violations, local-opt iterations (helps diagnose stalls).