Example 1: Context Setup and Internals — Educational Walkthrough

(GitHub link)

This notebook walks through ModelAnalysisContext step by step with verbose logging, so you can see exactly what the library configures internally before and during a simulation run. It mirrors the same API used in ex2 and ex3+ — the only difference is the inspection of context internals between steps.

What you will learn:

  • How params_info and qois_info are stored and serialized inside the context

  • How the QoI lambda dispatch works (what df_cell, df_subs, mcds mean)

  • What context.run() does internally, step by step — revealed by the INFO log output

The INFO log emitted by context.run() includes the full PhysiCell model configuration (equivalent to what the old PhysiCell_Model.info() call showed), database operations, and per-simulation progress.

import logging, sys
import warnings
warnings.filterwarnings('ignore')

# Verbose logging — context.run() emits INFO messages at every internal step
logging.basicConfig(
    stream=sys.stdout,
    level=logging.INFO,
    format='[%(levelname)s] %(message)s',
    force=True,
)

from uq_physicell import get_physicell
from uq_physicell.model_analysis import ModelAnalysisContext

get_physicell(target_dir=".")
PhysiCell already exists at: PhysiCell-master
Skipping download. Use force_download=True to override.
True

Step 1 — Build the context and inspect its internals

model_config points to the INI file and selects the model structure.
params_info declares parameter metadata stored in the database. What fields are needed depends on the sampler:

Sampler

Required fields

Optional metadata

Sobol, LHS, FAST, FF

lower_bound, upper_bound

ref_value

OAT

ref_value, perturbation

User-defined

none (samples from set_samples())

ref_value

qois_info defines what to measure — the lambda parameter name controls what PhysiCell data is passed:

Parameter name

Data received

df_cell or df

pandas.DataFrame — one row per cell at each timestep

df_subs

pandas.DataFrame — substrate concentrations on the mesh

mcds

pcdl.TimeStep — full snapshot object

mcds_ts

list[pcdl.TimeStep] — full time series (evaluated once, at last step)

After creating the context, we inspect context.qois_dict to see how lambdas are converted to strings — this serialization is what allows QoI functions to be sent across processes in parallel execution.

model_config = {"ini_path": "Model_Struct.ini", "struc_name": "physicell_model_2"}

# For 'User-defined' sampler, params_info is optional metadata stored in the DB.
# Bounds and perturbations are not needed — samples come from set_samples().
# Only ref_value is useful here as a reference point for comparison.
params_info = {
    "viral_replication_rate": {"ref_value": 0.125},
    "min_virion_count":        {"ref_value": 1.0},
}

qoi_funcs = {
    "live_cells":      lambda df_cell: len(df_cell[df_cell['dead'] == False]),
    "dead_cells":      lambda df_cell: len(df_cell[df_cell['dead'] == True]),
    "interferon_mean": lambda df_subs: df_subs['interferon'].mean(),
}

context = ModelAnalysisContext(
    "ex1_inspect.db", model_config,
    sampler='User-defined',
    params_info=params_info,
    qois_info=qoi_funcs,
    num_workers=1,
)

# ── Inspect context internals ────────────────────────────────────────────────
print("params_dict  (parameter space as stored in context):")
for name, props in context.params_dict.items():
    print(f"  {name}: {props}")

print("\nqois_dict  (lambdas serialized to strings for multiprocessing):")
for name, func_str in context.qois_dict.items():
    print(f"  {name!r}: {func_str!r}")

print("\nmetadata:")
for k, v in context.dic_metadata.items():
    print(f"  {k}: {v}")
params_dict  (parameter space as stored in context):
  viral_replication_rate: {'ref_value': 0.125}
  min_virion_count: {'ref_value': 1.0}

qois_dict  (lambdas serialized to strings for multiprocessing):
  'live_cells': "lambda df_cell: len(df_cell[df_cell['dead'] == False])"
  'dead_cells': "lambda df_cell: len(df_cell[df_cell['dead'] == True])"
  'interferon_mean': "lambda df_subs: df_subs['interferon'].mean()"

metadata:
  Sampler: User-defined
  IniFilePath: Model_Struct.ini
  StrucName: physicell_model_2

Step 2 — Provide a sample and run

We pass a single reference point so the simulation finishes quickly. The [INFO] output from context.run() reveals every internal step in order:

  1. PhysiCell model info — executable path, parameters in XML, replicate count (same output as the old PhysiCell_Model.info())

  2. Database setup — creates tables, inserts metadata, parameter space, and QoI definitions

  3. Simulation progress — one line per completed replicate with data size written to the database

context.set_samples([
    {"viral_replication_rate": 0.125, "min_virion_count": 1.0},   # reference point
])

print(f"dic_samples: {context.dic_samples}\n")
context.run()
dic_samples: {0: {'viral_replication_rate': 0.125, 'min_virion_count': 1.0}}

[INFO] PhysiCell Model Information:
Project name: Template 
        Executable: PhysiCell-master/virus-sample
        Number of replicates for each parameter set: 5 
        Config. file of reference: PhysiCell-master/sample_projects/virus_macrophage/config/PhysiCell_settings.xml
        Folder to save config. files: UQ_PC_InputFolder/ 
        Folder to save output folders: UQ_PC_OutputFolder/
        Rules file of reference: None
        Name of output folders: output_S%06d_R%03d/
        Number of omp threads for each simulation: 4
        Number of parameters for sampling in XML: 2
        Parameters in XML: ['viral_replication_rate', 'min_virion_count']
        Number of parameters for sampling in RULES: 0
        Parameters in RULES: []
[INFO] Creating database structure in ex1_inspect.db
[INFO] Inserting metadata, parameter space, and QoIs into the database
Inserting parameter: viral_replication_rate with properties: {'ref_value': 0.125}
Inserting parameter: min_virion_count with properties: {'ref_value': 1.0}
Inserting {'live_cells': "lambda df_cell: len(df_cell[df_cell['dead'] == False])", 'dead_cells': "lambda df_cell: len(df_cell[df_cell['dead'] == True])", 'interferon_mean': "lambda df_subs: df_subs['interferon'].mean()"} QoIs into the database
[INFO] Inserting samples into the database
[INFO] Generating 5 simulations
[INFO] Writing to the database for Sample: 0, Replicate: 0, Result size: 1.49 KB
[INFO] Writing to the database for Sample: 0, Replicate: 1, Result size: 1.49 KB
[INFO] Writing to the database for Sample: 0, Replicate: 2, Result size: 1.49 KB
[INFO] Writing to the database for Sample: 0, Replicate: 3, Result size: 1.49 KB
[INFO] Writing to the database for Sample: 0, Replicate: 4, Result size: 1.49 KB
Simulations completed and results stored in the database: ex1_inspect.db.