Skip to content

Add one line installer and pulsatile case - #6

Open
Eleven7825 wants to merge 53 commits into
StanfordCBCL:masterfrom
Eleven7825:master
Open

Add one line installer and pulsatile case#6
Eleven7825 wants to merge 53 commits into
StanfordCBCL:masterfrom
Eleven7825:master

Conversation

@Eleven7825

Copy link
Copy Markdown
Contributor
  • Add a one line installer for the svFSGe
  • Add a pulsatile case using mean wss

shiyi and others added 30 commits February 5, 2026 16:09
- Add converted pulsatile Q waveform (pulsatile_flow.dat) from VMR source
  .flow files, converted from cm/s to mm/s to match kg/mm/s unit system
- Update steady_full.xml inlet BC to use Unsteady time dependence with
  the pulsatile temporal values file
- Add static BC file copy step in svfsi.py setup_files so pulsatile_flow.dat
  is staged into the run directory automatically
- Include source .flow files from Vascular Model Repository

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Increase Number_of_time_steps from 10 to 960 (10 cardiac cycles at dt=0.01s)
- Add run_fluid_only.py script for standalone fluid simulation without FSG
  coupling, handles mesh/BC file staging and runs svFSI directly

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Container named 'fsg-dev' now persists after exit
- Rerunning the script reconnects to existing container
- Uses 'sleep infinity' + 'docker exec' pattern for persistence
- Updated pip install to use requirements.txt (includes meshio)
- Shows container management commands on completion

Users can now:
- Exit and reconnect without losing setup
- Stop/start the container as needed
- See the container in 'docker ps -a'

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Instead of requiring requirements.txt, the script now has the package
list hardcoded. This makes it work even if requirements.txt is missing
or the repo state is inconsistent.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Creates a bootstrap script that can be downloaded and run via curl.
Handles repo cloning and Docker setup in one command.

Usage:
  curl -fsSL https://raw.githubusercontent.com/Eleven7825/svFSGe/master/scripts/install.sh | bash

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Prominently featured at the top of Quickstart section
- Explains prerequisites and what the command does
- Updated Quick Setup Script section to mention persistent container

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add setup_singularity.sh for automated Singularity environment setup
- Add install_singularity.sh for one-command installation on HPC
- Add run_simulation.sh helper script for easy simulation execution
- Update README.md with HPC installation instructions
- Support for read-only containers with user-space Python package installation
- Include verification step to ensure all dependencies are properly installed
…ive VTU copying

- Copies folder structure and all files except VTU/BIN from pulsatile/steady/gr_restart
- Copies only last 400 pulsatile VTU files to minimize transfer size
- Copies only last 10 steady VTU files
- Excludes all gr_restart VTU files completely
- Uses SSH ControlMaster for single authentication
- Strips carriage returns from remote SSH output for proper path handling
…be read from the json file"

This reverts commit 7c671a3.
Shiyi Chen and others added 23 commits March 27, 2026 10:24
Informational prints in extract_pulsatile_time_average and
extract_pulsatile_amplitude are now gated behind verbose=False by default.
Enable with "debug": true in the JSON config.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fsg.py/svfsi.py: add iqn_ils_debug option (off by default) to save V, W,
  Q, R, column counts before/after QR filtering, and cc coefficients each
  IQN-ILS call; saved to debug_qr.npy on archive
- post.py: add plot_cc() to plot cc norm and heatmap with load-step
  transition lines; called automatically from main_arg when debug_qr.npy exists
- in_sim/partitioned_full_steep.json: new steep profile case (nmax=5,
  profile_beta with beta_min=2/beta_max=10, iqn_ils_debug enabled)
- in_svfsi_plus/gr_full_restart_steep.xml: solid XML with n_t_end=4 for
  steeper GR ramp (full load reached at t=3 out of 5 steps)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…verbose output

- fsg.py/svfsi.py: save load step t and sub-iter n alongside each IQN-ILS
  debug entry, removing need for fragile formula to reconstruct transitions
- post.py: derive transition lines directly from saved t array; fix path
  resolution for plot_cc in main_arg (two levels up from partitioned/converged)
- svfsi.py: suppress "extracted from N geometries" print unless debug=true
- in_sim/partitioned_full_debug.json: normal progressive profile case with
  iqn_ils_debug enabled for comparison against steep profile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Draw a step line at ncols_after - 0.5 on the heatmap to clearly show
the boundary between active coefficients and NaN, making filtering
events (drops in the line) immediately visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When iqn_ils_reset=true, mat_V and mat_W are cleared at n=0 of each
new load step so IQN-ILS starts from scratch without reusing vectors
from previous time steps. Default is false (existing behavior).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fsg.py: add iqn_ils_debug saving (cc, t, n per call), iqn_ils_reset flag
  to clear history vectors at each new load step, fallback to relaxation
  when mat_V is empty after reset
- post.py: add plot_cc() with 3-panel GridSpec figure (|c| heatmap log scale,
  ||c|| norm, coupling residual before/after IQN update); called from main_arg;
  use pcolormesh + dedicated colorbar column to fix x-axis alignment
- in_sim/partitioned_full_steep.json: set q=5, reset=true, profile_beta=true
…ot_cc

- Dummy invisible subplots in colorbar column for all rows so column widths match
- MaxNLocator(integer=True) for integer x-axis ticks
- Fix sharex() call (replace deprecated get_shared_x_axes().join())
- Remove legend from heatmap and norm subplots; residual shows only before/after/tol
- Sparser y-ticks on heatmap (every 2)
Saves and restores all state needed to resume from a converged load step:
- IQN-ILS history (mat_W, mat_V, dk, res, dtk)
- Coupling error and relaxation history (err, omega)
- Current and all previous converged solutions
- svFSI binary restart files (stFile_last.bin) snapshotted at checkpoint
  time to avoid corruption from subsequent failed load steps

Usage: set "save_restart": true in JSON to enable checkpointing.
Restart via "restart": "path/to/restart.npz" in JSON or -restart CLI flag.

Tested on steady case (nmax=2 + restart to nmax=3): sub-iteration counts,
IQN-ILS residuals, and displacement fields match to floating-point precision.
The load profile (how the G&R insult is ramped over the load steps) was
hard-coded inside svMultiPhysics' gr_equilibrated.cpp and could not be
changed from here. Expose it through the GR_equilibrated material params
and inject it from the simulation JSON:

- svfsi.py set_gr_load(): reads an optional "gr_load" section and patches
  the solid solver XML (<load_profile>/<load_steep>/<load_file>) per run,
  using the same regex-on-XML approach as the pulsatile step-count patch.
    profile: linear | tanh (default) | power | file
    steep:   tanh steepness / power exponent
    curve:   for "file", the load factor per step ([step, factor] pairs or
             a plain list); step 0 = pre-stress, 1..nloads = G&R loads.
  Omitting "gr_load" leaves the XML untouched -> svFSI defaults (tanh, 2.0),
  reproducing the historical behavior.

- Rename the top-level G&R load-step count nmax -> nloads (it clashed with
  coup["nmax"], the coupling-iteration cap). The old "nmax" is still
  accepted for backward compatibility. coup["nmax"] is unchanged.

Verified: fsg.py in_sim/partitioned_test.json matches test_reference/nmax_2
with both the tanh default and an equivalent file curve.

Requires the matching gr_equilibrated.cpp parser changes in svMultiPhysics
(FSGe branch); without them an older binary rejects the injected XML tags.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the per-sub-iteration MESH+FLUID svFSI solves with a DeepONet WSS +
pressure surrogate.

neural_operator.py:
- New "direct" backend (lddmm_backend: "direct"): the svFSGe solid interface
  nodes coincide with the cylinder template, so the displacement is projected
  straight onto the SVD basis (cached exact-NN reorder) and the solid reference
  positions are used as the trunk — no MATLAB/fshapesTk, no per-call LDDMM, and
  consistent with FSG-direct-trained models.
- Refactor: geometry encoding extracted into _encode_geometry dispatch with two
  interchangeable strategies, _encode_direct and _encode_registration (wrapping
  the existing matlab/matlab_engine/python LDDMM backends); predict_wss_and_pressure
  is now backend-agnostic with a single shared NN forward-pass tail.

fsg.py:
- Wire in NeuralOperator (import, init, _neural_operator_step); skip the fluid
  step when the surrogate is active.
- _wss_relax_beta: optional WSS growth under-relaxation (constant or
  residual/sub-iteration ramp), defaulting to 1.0 = no-op. predictor_relax knob,
  also defaulting to original behaviour.
- _save_failure_case: dump geometry to failed_cases/ on a failed solve.

svfsi_docker.sh:
- Purge leaked /dev/shm OpenMPI sm_segment.* before each solver call (these
  accumulate over a long run and exhaust the container's 64M /dev/shm).

Verified: drives a 3-step FSG loop on the rebuilt svFSI with the archived
FSG-direct models, reproducing the prior growth trajectory bit-for-bit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The FSG coupling consumes only |WSS| (svfsi.py reduces the vector via
np.linalg.norm before the solid G&R solve), so a single-output magnitude model
is both the correct target and more accurate than predicting the 3-vector and
norming it (0.37% vs 0.64% |WSS| val error on the n833 set).

If cfg["wss_magnitude_pt_file"] is set, load a 1-output magnitude model and
deliver the scalar as a vector with the magnitude in the axial component, so the
downstream norm recovers |WSS| exactly. Falls back to the 3-vector model
(cfg["pt_file"]) otherwise — backward compatible.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Companion to the load-profile work: the aneurysm insult (the spatial
elastin/stimulus knock-down that localizes the lesion) was a super-Gaussian
hard-coded in svMultiPhysics' gr_equilibrated.cpp. Expose it and inject it
from the simulation JSON.

- svfsi.py set_gr_insult(): reads an optional "gr_insult" section and patches
  the solid XML with the GR_equilibrated insult params, writing
  gr_insult_curve.dat for profile=file. Refactor the XML-patch and curve-write
  logic out of set_gr_load() into shared _patch_solid_xml()/_write_curve()
  helpers used by both.
    profile: gaussian (default) | file   (axial shape; azimuth stays gaussian)
    mag:     peak elastin loss fraction
    z_loc/z_wid/z_exp:        axial center/width (frac of length)/exponent
    asym, theta_wid/theta_exp: azimuthal localization
    curve:   for "file", axial factor f_axi vs normalized position z/lo
             ([z/lo, factor] pairs or a plain list)
  Knock-down at a point = mag * f_axi(z) * f_cir(azimuth). Omitting "gr_insult"
  leaves the XML untouched -> svFSI defaults reproduce the historical insult.

- partitioned_test.json: add an explicit gaussian gr_insult so the CI test
  also exercises the injection path (behavior unchanged).

Verified: fsg.py in_sim/partitioned_test.json matches test_reference/nmax_2
with the injected gaussian insult.

Requires the matching gr_equilibrated.cpp parser changes in svMultiPhysics
(FSGe branch); an older binary would reject the injected insult_* XML tags.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gen_insult_configs.py: Latin-Hypercube sample the configurable gr_insult
shape params (mag, z_loc/wid/exp, theta_wid/exp) around the vanilla default,
centered at (theta,z)=(0,0), one real-CFD FSG trajectory config per sample.
fsg_insult_array.sbatch: run the N trajectories as a Singularity array job,
each in its own working dir to avoid mesh-scratch collisions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M6MmCkf7dn8MKZarvtyZYw
Mirror the existing nmax=2 standard-FSI CI test for the neural-operator
direct backend so the NN branch is gated before merge.

- in_sim/partitioned_test_n660.json: nloads=2 test config, neural_operator
  direct backend pointing at vendored assets.
- test_assets/n660/: self-contained NN deps (wss/pressure .pt, SVD basis,
  cylinder.vtk template, shearStressNN_coeff model module) so CI needs
  nothing outside the repo (torch is already in simvascular/solver:latest).
- test_reference/n660_nmax_2/: reference baseline (reference_results.json +
  tube_002.vtu); iterations [2, 18, 36], reproducible bit-for-bit.
- test-fsg.yml: run both configs in one Docker build, copy to fixed
  test_output/{standard,n660} dirs, compare each via compare_results.py.
- .gitignore: allow test_assets/**/*.vtk past the *.vt* rule.
- post.py: force headless Agg backend; close figures to avoid leaks.
- svfsi.py: optional set_gr_growth (tau_ratio_floor) G&R stabilization.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The base simvascular/solver:latest image has no torch, so CI failed at
`from neural_operator import NeuralOperator` (fsg.py top-level) for BOTH
the standard and n660 tests.

- fsg.py: import NeuralOperator lazily inside __init__, only when a
  neural_operator surrogate is enabled. The standard FSI path now runs
  with no torch installed.
- test-fsg.yml: install the CPU torch wheel before the n660 test only.

Verified in a pristine simvascular/solver:latest container: `import fsg`
succeeds without torch, and NeuralOperator loads the vendored test_assets
after the CPU torch install.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a partitioned continuation driver that traverses growth limit points by
solving for the growth-load factor lambda instead of prescribing it, on top
of the new svFSI explicit-restart capability.

- fsg.py: _run_arclength() — single-loop Crisfield spherical arc-length in
  (interface displacement d, growth-load factor lambda), solving the
  partitioned NN-FSG equilibrium R(d,lambda)=solid(NN(d),lambda)-d=0 with a
  spherical constraint so a step can be re-evaluated at a different lambda
  without compounding G&R history. Opt-in via JSON "arc_length".
- svfsi.py: pass --restart-in/--restart-out to the solid solver so each
  arc-length trial re-solves a load step from an exact checkpoint (no
  compounding); read the most-recently-written gr_restart output when the
  arc-length resets decouple solver cTS from the coupling counter.
- neural_operator.py, post.py: supporting changes (WSS handling; headless
  plotting / arc-length convergence output).

Requires the matching svFSI --restart-in/--restart-out support
(svMultiPhysics FSGe branch).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The n660 comparison uses a very tight tolerance (Displacement rtol 1e-8,
Velocity 1e-6), but the neural-operator (torch CPU) prediction is not
bit-reproducible across machines: the committed reference was generated on
a workstation, so it passes there but fails on the GitHub runner (the
standard FSI test, which has no NN, passes on the runner — confirming the
solid solver is consistent and this is NN cross-machine drift).

Add a workflow_dispatch input `regenerate_n660` that re-generates the
reference from the runner's own n660 output and commits it back (curated
convergence.error + tube_002.vtu, matching the documented reference
format), then skips the strict comparison for that run. Normal push/PR runs
still compare strictly against the committed reference. Trigger it once from
the Actions tab to bless a runner-generated baseline; subsequent CI runs on
the same runner class then compare against a matching reference.

Adds `permissions: contents: write` (used only by the regenerate path).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ntinuation, explicit restart

# Conflicts:
#	svfsi.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant