This tutorial explains how to install and run simSOMA from the GitHub repository. It is intended for users who want copy-and-paste commands for a local Linux machine or for a simple cluster login node using tmux. The repository contains the simulator code, run scripts, example configuration files, and example topology inputs. Simulation outputs are generated locally when the pipeline is run; they are not part of the repository.
The workflow has eight steps:
- Clone or update the GitHub repository.
- Set up the Python environment.
- Prepare a topology JSON file, either directly or by converting branch/organ CSV files.
- Prepare a simulation config JSON file.
- Run the simulation locally or on a cluster.
- Inspect the output tables.
- Optionally transform VAF spectra into observation-model outputs.
- Optionally extract fixed, intermediate, private, and sharedness statistics.
All commands below assume that they are run from the top-level simSOMA repository folder unless stated otherwise.
Clone the repository once on each machine where you want to run simSOMA. A clone is a local working copy of the GitHub repository. It contains the code and example files needed to run the simulator.
Using SSH:
mkdir -p "$HOME/Projects"
cd "$HOME/Projects"
git clone git@github.com:jlab-code/simSOMA.git
cd simSOMAUsing HTTPS:
mkdir -p "$HOME/Projects"
cd "$HOME/Projects"
git clone https://github.com/jlab-code/simSOMA.git
cd simSOMAIf the repository is private, the GitHub account or SSH key used on that machine must have access to jlab-code/simSOMA.
For later sessions, return to the same local clone:
cd "$HOME/Projects/simSOMA"To update an existing clone with the latest version from GitHub, run:
git pullRunning simSOMA creates local files such as .venv/, simSOMA_output/, and log files. These files remain on the machine where the run was performed. They are ignored by Git and are not uploaded to GitHub.
After cloning, the repository has the following layout:
simSOMA/
|-- README.md # short GitHub landing page
|-- LICENSE # MIT license
|-- CITATION.cff # citation metadata
|-- CHANGELOG.md # release notes
|-- VERSION # current software version
|-- requirements.txt
|-- requirements.lock.txt
|-- simSOMA_corefunc/ # simulator source code, including topology checking
|-- simSOMA_scripts/ # setup, local-run, cluster-run, and utility scripts
|-- simSOMA_configs/ # simulation configuration files
|-- simSOMA_inputs/
| |-- examples/ # tutorial inputs, quick-test topology, CSV examples
| |-- templates/ # reusable topology templates
| `-- topologies/ # main simulation topology files
|-- simSOMA_docs/ # stable design notes
`-- docs/ # short repository documentation and links
During setup and simulation runs, additional local folders are created:
.venv/ # project-local Python environment
simSOMA_output/ # generated simulation outputs
These folders can be deleted and regenerated. They are not part of the distributed code repository.
The same environment setup is used on a laptop and on a cluster. simSOMA builds one project-local virtual environment:
simSOMA/.venv
Run the setup script once from the project root:
bash simSOMA_scripts/01_setup_env.shThe setup script expects Python 3.11. If Python 3.11 is not already visible, it tries to use Conda directly, without requiring conda init. It creates or reuses the Conda environment simsoma311, then builds .venv from that Python.
After setup, the simulator should use:
.venv/bin/python
You normally do not need to activate .venv manually. The run scripts use it automatically.
If your cluster uses a non-standard Conda installation, provide the Conda setup file explicitly:
export CONDA_SH=$HOME/miniconda3/etc/profile.d/conda.sh
bash simSOMA_scripts/01_setup_env.sh
unset CONDA_SHIf you want to force a specific Python 3.11 interpreter, use:
PYTHON_BIN=/path/to/python3.11 bash simSOMA_scripts/01_setup_env.shCheck the version and source-file checksums:
bash simSOMA_scripts/02_version_report.shBefore running a simulation, plot the topology JSON and verify that the branching structure and sampled organs are correct. This step does not require a simulation config file. If you prefer to enter the topology as branch and organ CSV tables, first convert those tables to a topology JSON as described in the topology-input section below, then run the topology check on the generated JSON.
Run the topology check.
TOPO=simSOMA_inputs/examples/quick_test_2organs_topology.json
bash simSOMA_scripts/check_topology.sh "$TOPO"For the quick-test topology, the output is written to a subfolder named after the topology JSON file:
simSOMA_output/topology_check/quick_test_2organs_topology/topology_plot.png
simSOMA_output/topology_check/quick_test_2organs_topology/topology_plot.pdf
simSOMA_output/topology_check/quick_test_2organs_topology/topology_plot_layout.csv
simSOMA_output/topology_check/quick_test_2organs_topology/topology_plot_report.json
Open the PNG or PDF. Continue only after checking that the branch structure and sampled organs match the intended topology.
This folder is intentionally keyed by topology filename, not by simulation config. Different config files may use the same topology, and it is often useful to check a topology before writing any config file. Existing files with the same names are overwritten when the same topology is checked again. This is intentional: the folder is a live check of the current topology JSON file.
An explicit output folder can still be set with OUTDIR:
simSOMA uses one standard user-facing topology JSON file. This JSON can be written manually, or it can be generated from two simpler CSV files. The simulation config always points to the JSON file; the CSV converter is only a preprocessing helper.
branch/organ CSV files -> user-friendly topology JSON -> internal step-based topology
A topology JSON contains two main blocks:
{
"unit": "years",
"tree_coordinate_max": 10.0,
"tree_age": 10.0,
"branches": [],
"events": []
}The unit field can be steps, years, or meters. The coordinates themselves are interpreted through the usual config fields topology.mapping_unit and topology.mapping_rate. For consistency, the JSON unit and config mapping_unit should describe the same coordinate system.
Each branch has an id, a parent, a branch length, and branch-axis coordinates. The coordinates are deliberately general; they may represent time, distance, or another branch-axis coordinate.
{
"id": "B0_trunk",
"parent": null,
"length": 10.0,
"start": 0.0,
"end": 10.0,
"start_age": 0.0,
"end_age": 10.0,
"branch_order": 0
}Important fields:
id unique branch name
parent parent branch ID, or null for the root/trunk
length branch length in the topology unit
start branch-axis coordinate where this branch begins
end branch-axis coordinate where this branch ends
start_age backward-compatible plotting alias for start
end_age backward-compatible plotting alias for end
branch_order branch order used for plotting and summaries
The start_age and end_age names are retained as legacy plotting aliases. They are aliases for the general start and end coordinates and do not imply that the topology must be measured in years. The topology-check plotter labels the axis according to the declared unit.
Events specify branch formation or sampled organs. Each event must provide either a relative position pos from 0 to 1 along the branch, or a branch-local coordinate time in the same unit as length. The recommended form is to provide pos, because this is unambiguous after CSV conversion.
A branch event:
{
"branch": "B0_trunk",
"pos": 0.5,
"type": "branch",
"target": "B1_lateral",
"position": 5.0,
"age": 5.0
}An organ event:
{
"branch": "B1_lateral",
"pos": 1.0,
"type": "organ",
"target": "O_lateral_tip",
"position": 10.0,
"age": 10.0,
"terminal_tip": true
}Important fields:
branch branch on which the event occurs
pos relative position along that branch, from 0 to 1
type "branch" or "organ"
target child branch ID or sampled organ ID
position absolute branch-axis coordinate used for human readability
age backward-compatible plotting alias for position
terminal_tip whether an organ is sampled at the end of its branch segment
For a branch event, target must match the id of a branch listed in the branches block. The age field is retained as a legacy plotting alias for the general position field and does not restrict the topology to age or years.
Many users find it easier to first describe the topology with two CSV files. The branch CSV defines branch segments. The organ CSV defines sampled organs on those branch segments.
The recommended branch CSV headers are:
branch_id,parent_id,start,end
B0_trunk,,0,10
B1_lateral,B0_trunk,5,10The recommended organ CSV headers are:
organ_id,branch_id,position
O_trunk_tip,B0_trunk,10
O_lateral_tip,B1_lateral,10Here, start, end, and position are general coordinates along the same branch axis. They can represent years, meters, or another user-defined axis. The CSV files are therefore unit-neutral. The biological interpretation is set later by the topology JSON unit field and by the config fields mapping_unit and mapping_rate.
For convenience, the converter also accepts older or more informal aliases:
Branch CSV aliases:
id -> branch_id
parent -> parent_id
start_age -> start
end_age -> end
Organ CSV aliases:
id -> organ_id
branch -> branch_id
age -> position
Convert the CSV files to the standard topology JSON with the Python converter:
python simSOMA_scripts/convert_topology_csv_to_json.py \
--branches simSOMA_inputs/examples/example_topology_branches.csv \
--organs simSOMA_inputs/examples/example_topology_organs.csv \
--out simSOMA_inputs/examples/example_topology_from_csv.json \
--unit yearsThe shell helper provides the same conversion with shorter syntax:
bash simSOMA_scripts/run_convert_topology_csv_to_json.sh \
simSOMA_inputs/examples/example_topology_branches.csv \
simSOMA_inputs/examples/example_topology_organs.csv \
simSOMA_inputs/examples/example_topology_from_csv.json \
yearsUse meters instead of years if the branch-axis coordinates are distances:
bash simSOMA_scripts/run_convert_topology_csv_to_json.sh \
simSOMA_inputs/examples/example_topology_branches.csv \
simSOMA_inputs/examples/example_topology_organs.csv \
simSOMA_inputs/examples/example_topology_from_csv.json \
metersThen check the generated topology JSON:
TOPO=simSOMA_inputs/examples/example_topology_from_csv.json
bash simSOMA_scripts/check_topology.sh "$TOPO"Finally, point the usual simulation config to the generated JSON:
"topology": {
"topology_json": "simSOMA_inputs/examples/example_topology_from_csv.json",
"mapping_unit": "years",
"mapping_mode": "deterministic",
"mapping_rate": 5.0
}No config-schema changes are required. The converter simply creates the same user-friendly JSON format that can also be written manually.
A simulation config file tells simSOMA which topology to use, which parameters to scan, and where outputs should go.
The quick-test config is:
simSOMA_configs/quick_test_2organs.json
"run": {
"experiment_name": "quick_test_2organs",
"outdir_root": "simSOMA_output",
"seed": 1
}Important fields:
experiment_name name of the output folder
outdir_root parent output folder; in the Git repository this is `simSOMA_output` in the project root
seed base random seed
"topology": {
"topology_json": "simSOMA_inputs/examples/quick_test_2organs_topology.json",
"mapping_unit": "years",
"mapping_mode": "deterministic",
"mapping_rate": 5.0
}Important fields:
topology_json path to the topology JSON
mapping_unit steps, years, or meters
mapping_mode deterministic or poisson
mapping_rate self-renewal divisions per topology unit
If mapping_unit is years or meters, mapping_rate is required.
"simulation": {
"mode": "grid_parameter",
"n_sim": 1,
"summaries": true,
"vaf_nbins": 20
}The cleaned workflow uses grid_parameter mode. Fixed parameters are specified as one-element value lists. Parameter scans are specified as multi-value lists.
Example fixed parameter:
"m": {
"values": [4]
}Example parameter scan:
"m": {
"values": [4, 8, 12]
}The main scan parameters are stored under simulation.modules. Each parameter is written as a JSON object with a values list. A one-element list fixes the parameter. A multi-value list creates a grid scan over those values.
simulation.modules.<module>.<parameter>.values
For example, this scans three values of m and keeps rho fixed:
"self_renewal": {
"m": { "values": [4, 8, 12] },
"rho": { "values": [0.25] }
}The main biological parameters are listed below. This tutorial denotes sam_boundary_cells by C, matching the notation used in the accompanying manuscript.
self_renewal.m
Number of long-lived apical stem-cell (ASC) positions in the simulated niche.
Manuscript notation: m.
self_renewal.rho
ASC turnover parameter. In each self-renewal round, rho is the probability that
one symmetric replacement / displacement event occurs. Larger values increase
lineage turnover and can increase fixation within the ASC niche.
Manuscript notation: rho.
self_renewal.mu_year
Legacy implementation field for the mutation input per topology unit. If the
topology unit is years, this is the mutation input per year; if the topology
unit is meters, it is the mutation input per meter. Internally, simSOMA converts
this to a per-division mutation input by dividing by topology.mapping_rate. In
the bundled configs, this is usually used as a relative mutation input because
the simulator focuses on VAF spectra and sharing patterns.
Manuscript notation: mu_unit; internal per-division value: mu_div.
self_renewal.victim_locality
Locality of the displaced ASC during replacement. A value of 0 gives globally
uniform displacement across the niche. Larger values make displacement more
local; value 1 corresponds to nearest-neighbor displacement.
self_renewal.bias_mode
How branch-local self-renewal bias is specified. The bundled manuscript configs
usually use "fixed".
self_renewal.branch_bias_value
Fixed branch-local bias value when bias_mode is "fixed". The bundled manuscript
configs usually use 0.0, meaning no branch-local bias.
self_renewal.branch_bias_mean
Mean branch-local bias when bias_mode uses a drawn bias value.
self_renewal.branch_bias_kappa
Concentration parameter for drawn branch-local bias values.
pre_branching.sam_boundary_cells
Target number of peripheral/SAM-boundary cells generated before branch or organ
founder sampling. This controls the size of the local amplified cell population
from which founders are sampled. Current configs require sam_boundary_cells >= m.
Manuscript/tutorial notation: C.
branching.branch_precursor_number
Requested number of branch founder/precursor cells sampled from the amplified
SAM-boundary population to establish a new branch SAM. Conceptually, the
realized/effective branch founder count is capped by the requested number, the
number of ASC positions, and the SAM-boundary population size. In manuscript
notation, this is requested P_b with P_b^eff = min(P_b, m, C). In the current
valid configs C >= m, so the driver-level cap min(branch_precursor_number, m)
is equivalent. The realized value is recorded as
branch_precursor_number_realized_cells.
organ.organ_precursor_number
Requested number of organ founder/precursor cells sampled from the amplified
SAM-boundary population to establish an organ. The current driver caps the
realized value at min(organ_precursor_number, sam_boundary_cells,
organ_total_cells). In normal configs this equals the requested value. The
realized value is recorded as organ_precursor_number_realized_cells.
Manuscript notation: requested P_o.
organ.organ_total_cells
Final number of cells in the simulated terminal organ before sequencing
subsampling. The realized value is recorded as organ_total_cells_realized.
Manuscript/tutorial notation: O.
organ.seq_fraction
Fraction of terminal organ cells sampled as observed/sequenced cells. The number
of sampled cells is ceil(seq_fraction * organ_total_cells), bounded between 1
and organ_total_cells. The realized value is recorded as sequenced_cells_used.
A compact module block therefore looks like this:
"modules": {
"self_renewal": {
"m": { "values": [4, 8, 12] },
"rho": { "values": [0.0, 0.25, 1.0] },
"mu_year": { "values": [1.0] },
"victim_locality": { "values": [0.0] },
"bias_mode": { "values": ["fixed"] },
"branch_bias_value": { "values": [0.0] },
"branch_bias_mean": { "values": [0.0] },
"branch_bias_kappa": { "values": [1.0] }
},
"pre_branching": {
"sam_boundary_cells": { "values": [64] }
},
"branching": {
"branch_precursor_number": { "values": [1, 4, 16] }
},
"organ": {
"organ_precursor_number": { "values": [1, 8] },
"organ_total_cells": { "values": [64, 4096] },
"seq_fraction": { "values": [1.0] }
}
}Phyllotaxy is configured in the topology block, not in simulation.modules. The bundled no-phyllotaxy configs use mode: "off". The spiral-phyllotaxy configs use mode: "spiral".
"phyllotaxy": {
"mode": "off",
"divergence_angle_deg": 137.5,
"tie_tol": 1e-9
}phyllotaxy.mode
Phyllotaxy overlay mode. Allowed values are off, random, spiral, distichous,
tristichous, and decussate. Use off if no phyllotactic placement should be
imposed.
phyllotaxy.divergence_angle_deg
Divergence angle in degrees for spiral phyllotaxy. The bundled spiral configs
use 137.5 degrees.
phyllotaxy.tie_tol
Tolerance for grouping events that occur at effectively the same developmental
position before assigning phyllotactic placement.
The main output/export options are stored directly under simulation:
simulation.n_sim
Number of replicate simulations per parameter combination.
simulation.summaries
Whether summary tables are computed. This should normally be true.
simulation.vaf_nbins
Number of bins for legacy binned VAF summaries. The main recommended VAF
output is the exact allele-count spectrum, not the legacy binned table.
simulation.summary_private_shared
Whether private/shared summary statistics are computed.
simulation.store_full_results
Whether full replicate-level internal JSON result objects are retained. This
can create large output folders and is usually false.
simulation.export_raw_vafs
Whether raw VAF rows are exported. This can create large files and is usually
false.
simulation.export_vaf_count_spectra
Whether exact allele-count/VAF spectra are exported. This is the recommended
VAF output and should normally be true.
simulation.export_sharing_summaries
Whether aggregated among-organ sharing summaries are exported. This should
normally be true.
simulation.export_replicate_sharing_summaries
Whether replicate-level sharing summaries are exported. Useful for downstream
uncertainty calculations, but larger than aggregated summaries.
simulation.export_legacy_binned_vaf_summaries
Whether old aggregated binned-VAF summary files are exported. Usually false.
simulation.export_legacy_binned_vaf_replicates
Whether old replicate-level binned-VAF files are exported. Usually false.
A typical compact output block is:
"simulation": {
"mode": "grid_parameter",
"n_sim": 10,
"summaries": true,
"vaf_nbins": 20,
"summary_private_shared": true,
"store_full_results": false,
"export_raw_vafs": false,
"export_vaf_count_spectra": true,
"export_sharing_summaries": true,
"export_replicate_sharing_summaries": true,
"export_legacy_binned_vaf_summaries": false,
"export_legacy_binned_vaf_replicates": false
}The check.topology_plot block controls the appearance of topology-check figures. These settings do not change the simulation. Most users can leave them unchanged. Commonly edited settings are:
check.topology_plot.enabled
Whether the topology-check plot is produced during check steps.
check.topology_plot.fig_width, check.topology_plot.fig_height
Figure size in inches.
check.topology_plot.dpi
Output resolution.
check.topology_plot.y_ticks
Tick labels shown on the topology-coordinate axis. Use "auto" for automatic
ticks, or provide a comma-separated string such as "0,50,100,150,200". If
manual ticks are outside the plotted coordinate range, simSOMA ignores them
and falls back to automatic ticks to avoid compressing small tutorial plots.
check.topology_plot.y_label
Axis label for the topology-check plot. Use "auto" to derive the label from
the topology JSON unit. For example, years are labeled as "Age (years)",
meters as "Position (meters)", and steps as "Self-renewal steps".
check.topology_plot.show_nonterminal_organs
Whether organs that are not terminal tips should be shown in the topology
check plot.
check.topology_plot.title
Plot title.
The fastest end-to-end test is:
bash simSOMA_scripts/run_quick_test.shThe script does three things:
1. Ensures .venv exists, using the same setup script as above if needed.
2. Plots the quick-test topology.
3. Runs a one-split quick simulation.
The main result folder is:
simSOMA_output/quick_test_2organs/grid_parameter/
For normal local use, run:
CFG=simSOMA_configs/quick_test_2organs.json
bash simSOMA_scripts/run_config_local.sh "$CFG"For a small local split run, assign the config and options first, then call the runner. This keeps each command line short and safe to copy from the PDF.
CFG=simSOMA_configs/simulation_04_organ_formation_rough.json
export N_SPLITS=4
export N_PARALLEL_JOBS=2
export RUN_CHECK=yes
bash simSOMA_scripts/run_config_local.sh "$CFG"
unset N_SPLITS N_PARALLEL_JOBS RUN_CHECKUseful options:
N_SPLITS number of split jobs
N_PARALLEL_JOBS number of parallel local jobs
RUN_CHECK yes/no; run topology/config check before simulation
CLEANUP_SPLITS yes/no; remove split folders after merging
SPLIT_AXIS parameter is the usual setting
For a first run, keep RUN_CHECK=yes. This uses the same topology-check script described above. Rechecking the same topology overwrites the generated topology_plot.* files for that topology.
On the cluster, clone the same Git repository once, or update an existing clone before running. For a first clone using SSH:
cd "$HOME"
git clone git@github.com:jlab-code/simSOMA.git
cd simSOMAFor an existing clone:
cd "$HOME/simSOMA"
git pullIf the repository is still private, the cluster must use a GitHub account or SSH key that has access to jlab-code/simSOMA.
Use the same setup step as on a laptop:
bash simSOMA_scripts/01_setup_env.shThe cluster wrapper then starts the run inside a detached tmux session. It does not require a scheduler. If .venv is missing, the wrapper calls the same setup script once before starting tmux; no separate cluster-specific environment setup is used. If RUN_CLUSTER_CHECK=yes, the wrapper uses the same topology-check script and overwrites the generated topology-check files for the same topology.
From the project root on the cluster, assign the config and options first, then call the cluster wrapper:
CFG=simSOMA_configs/quick_test_2organs.json
export PROJECT_DIR="$PWD"
export SESSION=simsoma_test
export N_SPLITS=20
export N_PARALLEL_JOBS=4
export RUN_CLUSTER_CHECK=yes
bash simSOMA_scripts/cluster_prepare_and_start.sh "$CFG"
unset PROJECT_DIR SESSION N_SPLITS N_PARALLEL_JOBS RUN_CLUSTER_CHECKFor a larger run, increase N_SPLITS and N_PARALLEL_JOBS, for example:
CFG=simSOMA_configs/simulation_01_topology_depth_fine.json
export PROJECT_DIR="$PWD"
export SESSION=simsoma_simulation_01
export N_SPLITS=300
export N_PARALLEL_JOBS=20
export CLEANUP_SPLITS=yes
export SPLIT_AXIS=parameter
export RUN_CLUSTER_CHECK=yes
bash simSOMA_scripts/cluster_prepare_and_start.sh "$CFG"
unset PROJECT_DIR SESSION N_SPLITS N_PARALLEL_JOBS
unset CLEANUP_SPLITS SPLIT_AXIS RUN_CLUSTER_CHECKMonitor the run:
tail -f cluster_run.logAttach to the tmux session:
tmux attach -t simsoma_simulation_01Detach from tmux without stopping the run:
Ctrl-b, then d
List relevant running processes:
pgrep -a -u "$USER" -f '00_pipeline.sh|launch_grid_splits.py|run_from_config.py'After a successful run, the main output folder is:
simSOMA_output/<experiment_name>/grid_parameter/
For the quick test:
OUT="simSOMA_output/quick_test_2organs/grid_parameter"
ls -lh "$OUT"Important files:
parameter_sets.csv
aggregated_summaries.csv
organ_aggregated_summaries.csv
sharing_aggregated_summaries.csv
vaf_count_spectrum_aggregated_summaries.csv
sharing_replicate_summaries.csv.gz
vaf_count_spectrum_replicate_summaries.csv.gz
manifest.json
split_plan.json
python - <<'PY'
import pandas as pd
out = "simSOMA_output/quick_test_2organs/grid_parameter"
params = pd.read_csv(f"{out}/parameter_sets.csv")
print(params.head())
print("Number of parameter sets:", len(params))
PYpython - <<'PY'
import pandas as pd
out = "simSOMA_output/quick_test_2organs/grid_parameter"
org = pd.read_csv(f"{out}/organ_aggregated_summaries.csv")
print(org.head())
print(org.columns.tolist())
PYpython - <<'PY'
import pandas as pd
out = "simSOMA_output/quick_test_2organs/grid_parameter"
vaf = pd.read_csv(f"{out}/vaf_count_spectrum_aggregated_summaries.csv")
print(vaf.head())
print(vaf.columns.tolist())
PYThe VAF spectrum table is the main output for allele-count/VAF distributions. It stores exact allele-count spectra and can be re-binned downstream.
python - <<'PY'
import pandas as pd
out = "simSOMA_output/quick_test_2organs/grid_parameter"
sharing = pd.read_csv(f"{out}/sharing_aggregated_summaries.csv")
print(sharing.head())
print(sharing.columns.tolist())
PYThe main simulator output represents a layer-equivalent developmental VAF spectrum. In some applications, the biological source of mutations and the way reads are observed are not the same thing. For example, a tissue may contain several independently mutating histogenic layers, and reads may be interpreted from a phased or collapsed assembly. The observation-model transform is a post-processing step. It does not rerun the developmental simulation and it does not modify the original grid_parameter/ files. Instead, it reads an existing simSOMA output folder and writes a derived folder under:
simSOMA_output/<experiment_name>/observation_model_transforms/<scenario_name>/
The transform treats the simulated spectrum as a layer-equivalent template. For a multi-layer model, the template is copied to the requested source layers, and those copied variants are treated as distinct layer-specific mutations. The transform therefore does not assume that the same mutation is present in all layers.
For deterministic layer weighting, the observed VAF is:
observed_vaf = source_vaf * effective_layer_contribution * phase_factor
In manuscript notation, source_vaf corresponds to the developmental source-layer VAF v_io^(k), effective_layer_contribution corresponds to the layer contribution a_k^(s), and phase_factor corresponds to the phasing factor eta_s. The transformed observed VAF is therefore:
v_tilde_io^(k,s) = eta_s * a_k^(s) * v_io^(k)
For bulk sampling, effective_layer_contribution is the user-specified layer weight and the weights across layers should sum to 1. For layer-specific sampling, the target layer is observed with contribution 1.0 and non-target layers are not retained in the layer-specific output. The phase factor is 1.0 for phased observations and 0.5 for unphased observations.
This example transforms the quick-test output into a three-layer bulk-unphased observation model with layer contributions L1 = 0.10, L2 = 0.70, and L3 = 0.20.
RUN=simSOMA_output/quick_test_2organs
SCENARIO=three_layer_bulk_unphased_L1_0p1_L2_0p7_L3_0p2
LAYERS=L1,L2,L3
WEIGHTS=0.10,0.70,0.20
SAMPLING=bulk
PHASE=unphased
export OVERWRITE=yes
CMD=(bash simSOMA_scripts/run_observation_model_transform.sh)
CMD+=("$RUN" "$SCENARIO" "$LAYERS" "$WEIGHTS")
CMD+=("$SAMPLING" "$PHASE")
"${CMD[@]}"
unset OVERWRITEThe output folder is:
simSOMA_output/quick_test_2organs/observation_model_transforms/three_layer_bulk_unphased_L1_0p1_L2_0p7_L3_0p2/
Check the generated files:
OUT=simSOMA_output/quick_test_2organs/observation_model_transforms/three_layer_bulk_unphased_L1_0p1_L2_0p7_L3_0p2
ls -lh "$OUT"Expected files:
observed_vaf_count_spectrum_aggregated_summaries.tsv
observed_vaf_count_spectrum_replicate_summaries.tsv.gz
observation_model_metadata.tsv
manifest.tsv
README.txt
Inspect the transformed aggregated spectrum:
head -n 20 "$OUT/observed_vaf_count_spectrum_aggregated_summaries.tsv" | column -t -s $'\t'Inspect the transform metadata:
cat "$OUT/observation_model_metadata.tsv" | column -t -s $'\t'
cat "$OUT/manifest.tsv" | column -t -s $'\t'Check the expected transformation for fixed source-layer variants:
python - <<'PY'
import pandas as pd
out = "simSOMA_output/quick_test_2organs/observation_model_transforms/three_layer_bulk_unphased_L1_0p1_L2_0p7_L3_0p2"
d = pd.read_csv(f"{out}/observed_vaf_count_spectrum_aggregated_summaries.tsv", sep="\t")
fixed = d[d["source_vaf"].round(6).eq(1.0)]
cols = ["source_layer", "source_vaf", "effective_layer_contribution", "phase_factor", "observed_vaf"]
print(fixed[cols].drop_duplicates().sort_values("source_layer").to_string(index=False))
PYFor this bulk-unphased example, source-layer fixed variants should be shifted to approximately:
L1: 1.0 * 0.10 * 0.5 = 0.05
L2: 1.0 * 0.70 * 0.5 = 0.35
L3: 1.0 * 0.20 * 0.5 = 0.10
This example observes only L2 in an unphased assembly. The effective layer contribution is 1.0 because the sample is layer-specific; the unphased factor is 0.5.
RUN=simSOMA_output/quick_test_2organs
SCENARIO=three_layer_L2_layer_specific_unphased
LAYERS=L1,L2,L3
WEIGHTS=0.10,0.70,0.20
SAMPLING=layer_specific
PHASE=unphased
TARGET=L2
export OVERWRITE=yes
CMD=(bash simSOMA_scripts/run_observation_model_transform.sh)
CMD+=("$RUN" "$SCENARIO" "$LAYERS" "$WEIGHTS")
CMD+=("$SAMPLING" "$PHASE" --target-layer "$TARGET")
"${CMD[@]}"
unset OVERWRITEA source-layer fixed variant in L2 should be transformed as:
observed_vaf = 1.0 * 1.0 * 0.5 = 0.5
The original simulation output remains unchanged. To remove only transformed observation outputs, delete the corresponding scenario folder under observation_model_transforms/.
The formula-statistics extractor is a post-processing step that summarizes the VAF and sharing spectra into the statistics used in the manuscript: fixed fraction, intermediate fraction, private fraction, and normalized sharedness. It does not rerun the simulation and does not modify the primary output tables. These statistics are analysis summaries, not developmental hyperparameters. The extractor is designed to run on an experiment folder. It automatically looks for the primary simSOMA output and, if present, all observation-model transform folders with standardized table names. For the primary simSOMA output, the VAF used for fixed and intermediate calls is:
formula_vaf = sampled_vaf
For observation-model transformed outputs, fixed and intermediate calls are made relative to the expected fixed VAF under that observation scenario:
formula_vaf = observed_vaf / expected_fixed_observed_vaf
Thus, a layer-fixed mutation in a bulk-unphased scenario is classified as fixed at the VAF expected after layer weighting and phasing, not only at raw observed VAF = 1. The LOW_THRESHOLD parameter is therefore a relative threshold. For example, LOW_THRESHOLD=0.05 means 5% of the scenario-specific fixed VAF.
Private fraction and normalized sharedness are computed per focal organ. They are cross-organ statistics because they use the sharing degree of each variant across all sampled organs, but each organ-level output row answers the question: among variants observed in this organ, what fraction is private, and how broadly shared are they?
Run the extractor on an experiment folder:
RUN=simSOMA_output/quick_test_2organs
bash simSOMA_scripts/run_extract_formula_statistics.sh "$RUN"This command searches for:
simSOMA_output/<experiment_name>/grid_parameter/vaf_count_spectrum_aggregated_summaries.csv
simSOMA_output/<experiment_name>/observation_model_transforms/*/observed_vaf_count_spectrum_aggregated_summaries.tsv
If multiple observation scenarios are present, the extractor runs on all of them. Existing formula_statistics_tables/ folders are overwritten. The primary-output statistics are written to:
simSOMA_output/<experiment_name>/grid_parameter/formula_statistics_tables/
Observation-transform statistics are written inside each scenario folder:
simSOMA_output/<experiment_name>/observation_model_transforms/<scenario_name>/formula_statistics_tables/
Each statistics folder contains:
organ_level_formula_statistics.tsv
parameter_set_formula_statistics_summary.tsv
statistic_definitions.tsv
extraction_run_summary.tsv
The experiment folder also receives a manifest of all processed sources:
formula_statistics_extraction_manifest.tsv
The extractor writes two analysis levels.
organ_level_formula_statistics.tsv
One row per set_id x organ_id.
This is the detailed diagnostic table.
parameter_set_formula_statistics_summary.tsv
One row per set_id x statistic.
This is the figure-ready table.
The parameter-set summary is built from the organ-level table. For each set_id and each statistic, the extractor averages the organ-level values across sampled organs and computes their standard deviation, minimum, median, and maximum. The summary columns are organ_mean, organ_sd, organ_min, organ_median, organ_max, and n_organs_with_stat. The summary table is therefore conditioned only on the fixed combinatorial parameter setting, represented by set_id. The input parameter values for that set_id are then copied from:
simSOMA_output/<experiment_name>/grid_parameter/parameter_sets.csv
This means that the summary table keeps true config/grid input parameters, such as m, rho, sam_boundary_cells, branch_precursor_number, organ_precursor_number, organ_total_cells, and seq_fraction, but it does not group by organ-derived descriptors. For example, n_bottlenecks_root_to_organ is an organ-level descriptor, not a simulation input parameter, and should not split the parameter-set summary.
For Figure-style plotting, use:
parameter_set_formula_statistics_summary.tsv
and plot:
organ_mean = point or line value
organ_sd = organ-to-organ standard deviation
For the bundled quick test, there is one parameter combination and two sampled organs. The expected summary table therefore has:
1 parameter setting x 4 statistics = 4 rows
n_organs_with_stat = 2 for each statistic
For a larger grid scan, the expected number of rows is:
number of parameter combinations x number of statistics
With the default four statistics, this is:
number of parameter combinations x 4
Inspect the generated files:
RUN=simSOMA_output/quick_test_2organs
find "$RUN" -path '*/formula_statistics_tables/*' -type f -print
find "$RUN" -name 'formula_statistics_extraction_manifest.tsv' -printInspect the primary organ-level statistics:
RUN=simSOMA_output/quick_test_2organs
STATS="$RUN/grid_parameter/formula_statistics_tables"
head -n 20 "$STATS/organ_level_formula_statistics.tsv" | column -t -s $'\t'Inspect the parameter-set-level summary:
RUN=simSOMA_output/quick_test_2organs
STATS="$RUN/grid_parameter/formula_statistics_tables"
head -n 20 "$STATS/parameter_set_formula_statistics_summary.tsv" | column -t -s $'\t'Check how many parameter combinations and organs were summarized:
python - <<'PY'
import pandas as pd
run = "simSOMA_output/quick_test_2organs"
params = pd.read_csv(f"{run}/grid_parameter/parameter_sets.csv")
summary = pd.read_csv(f"{run}/grid_parameter/formula_statistics_tables/parameter_set_formula_statistics_summary.tsv", sep="\t")
print("Number of parameter combinations:", len(params))
print("Statistics in summary:", sorted(summary["statistic"].unique()))
print("Rows in summary table:", len(summary))
print("n_organs_with_stat values:", sorted(summary["n_organs_with_stat"].dropna().unique()))
PYInspect the statistic definitions:
RUN=simSOMA_output/quick_test_2organs
STATS="$RUN/grid_parameter/formula_statistics_tables"
cat "$STATS/statistic_definitions.tsv" | column -t -s $'\t'If an observation-model transform exists, inspect its summary in the same way:
RUN=simSOMA_output/quick_test_2organs
SCENARIO=three_layer_bulk_unphased_L1_0p1_L2_0p7_L3_0p2
STATS="$RUN/observation_model_transforms/$SCENARIO/formula_statistics_tables"
head -n 20 "$STATS/parameter_set_formula_statistics_summary.tsv" | column -t -s $'\t'
cat "$STATS/statistic_definitions.tsv" | column -t -s $'\t'Use a different relative low-VAF threshold if needed:
RUN=simSOMA_output/quick_test_2organs
export LOW_THRESHOLD=0.05
bash simSOMA_scripts/run_extract_formula_statistics.sh "$RUN"
unset LOW_THRESHOLDOptional fixed filters can be passed through FIXED_FILTERS, for example:
RUN=simSOMA_output/quick_test_2organs
export LOW_THRESHOLD=0.05
export FIXED_FILTERS="--fixed rho=0 --fixed m=min,max"
bash simSOMA_scripts/run_extract_formula_statistics.sh "$RUN"
unset LOW_THRESHOLD FIXED_FILTERSIf you rerun with the same experiment_name, remove the previous output first:
rm -rf simSOMA_output/quick_test_2organs
rm -rf simSOMA_output/quick_test_2organs__psplit_*Then rerun:
CFG=simSOMA_configs/quick_test_2organs.json
bash simSOMA_scripts/run_config_local.sh "$CFG"For publication-scale or cluster-scale runs, it is often safer to use a new experiment_name rather than overwriting an old result. These generated output folders are ignored by Git and should remain local unless you deliberately archive them elsewhere.
Run the setup script from the project root:
bash simSOMA_scripts/01_setup_env.shThe default workflow expects Python 3.11. First try the standard setup command:
bash simSOMA_scripts/01_setup_env.shIf Conda is installed in a non-standard location, provide CONDA_SH explicitly:
export CONDA_SH=$HOME/miniconda3/etc/profile.d/conda.sh
bash simSOMA_scripts/01_setup_env.sh
unset CONDA_SHIf you already know the path to a Python 3.11 interpreter, provide it explicitly:
PYTHON_BIN=/path/to/python3.11 bash simSOMA_scripts/01_setup_env.shDo not use SIMSOMA_ALLOW_OTHER_PYTHON=1 for normal runs. It is only an emergency override for users who deliberately accept non-reproducible Python versions.
Make sure you are in the Git repository root:
pwd
ls simSOMA_configsThen run with a path relative to the project root:
CFG=simSOMA_configs/quick_test_2organs.json
bash simSOMA_scripts/run_config_local.sh "$CFG"If you are in an older non-Git copy such as simSOMA_beta, stop and switch to the Git-controlled repository before running or editing the pipeline.
If the repository is still private, the machine must authenticate to GitHub with an account or SSH key that has access to jlab-code/simSOMA.
For SSH clones, test access with:
ssh -T git@github.comFor HTTPS clones, GitHub may require a personal access token rather than a password.
Topology checks are written to simSOMA_output/topology_check/<topology_json_name>/. Rerunning the check for the same topology overwrites the previous topology_plot.* files. This is intentional, because the folder represents the current version of that topology file.
To preserve multiple versions, either rename the topology JSON file or set a custom output folder:
TOPO=simSOMA_inputs/examples/quick_test_2organs_topology.json
export OUTDIR=simSOMA_output/topology_check/my_saved_check
bash simSOMA_scripts/check_topology.sh "$TOPO"
unset OUTDIRAttach to it:
tmux attach -t simsoma_testor choose a different session name:
CFG=simSOMA_configs/quick_test_2organs.json
export SESSION=simsoma_test_02
bash simSOMA_scripts/cluster_prepare_and_start.sh "$CFG"
unset SESSION