Tutorial: Remote Simulations¶
This tutorial walks through authenticating with the VCell server, saving a model, starting a simulation, monitoring progress, and exporting results.
Note: This notebook requires a VCell account and access to the VCell server. It cannot be run in CI.
See also the Remote Simulations reference guide.
Authenticate¶
In [1]:
Copied!
from pyvcell._internal.api.vcell_client.auth.auth_utils import login_interactive
api_client = login_interactive()
from pyvcell._internal.api.vcell_client.auth.auth_utils import login_interactive
api_client = login_interactive()
Build and save model to VCell server¶
In [2]:
Copied!
import pyvcell.vcml as vc
antimony_str = """
compartment ec = 1;
compartment cell = 2;
compartment pm = 1;
species A in cell;
species B in cell;
J0: A -> B; cell * (k1*A - k2*B)
J0 in cell;
k1 = 5.0; k2 = 2.0
A = 10
"""
biomodel = vc.load_antimony_str(antimony_str)
model = biomodel.model
model.get_compartment("pm").dim = 2
geo = vc.Geometry(name="geo", origin=(0, 0, 0), extent=(10, 10, 10), dim=3)
geo.add_sphere(name="cell_domain", radius=4, center=(5, 5, 5))
geo.add_background(name="ec_domain")
geo.add_surface(name="pm_domain", sub_volume_1="cell_domain", sub_volume_2="ec_domain")
app = biomodel.add_application("app1", geometry=geo)
app.map_compartment("cell", "cell_domain")
app.map_compartment("ec", "ec_domain")
app.map_species("A", init_conc="3+sin(x)", diff_coef=1.0)
app.map_species("B", init_conc="2+cos(x+y+z)", diff_coef=1.0)
sim = app.add_sim(name="sim1", duration=2.0, output_time_step=0.05, mesh_size=(50, 50, 50))
import pyvcell.vcml as vc
antimony_str = """
compartment ec = 1;
compartment cell = 2;
compartment pm = 1;
species A in cell;
species B in cell;
J0: A -> B; cell * (k1*A - k2*B)
J0 in cell;
k1 = 5.0; k2 = 2.0
A = 10
"""
biomodel = vc.load_antimony_str(antimony_str)
model = biomodel.model
model.get_compartment("pm").dim = 2
geo = vc.Geometry(name="geo", origin=(0, 0, 0), extent=(10, 10, 10), dim=3)
geo.add_sphere(name="cell_domain", radius=4, center=(5, 5, 5))
geo.add_background(name="ec_domain")
geo.add_surface(name="pm_domain", sub_volume_1="cell_domain", sub_volume_2="ec_domain")
app = biomodel.add_application("app1", geometry=geo)
app.map_compartment("cell", "cell_domain")
app.map_compartment("ec", "ec_domain")
app.map_species("A", init_conc="3+sin(x)", diff_coef=1.0)
app.map_species("B", init_conc="2+cos(x+y+z)", diff_coef=1.0)
sim = app.add_sim(name="sim1", duration=2.0, output_time_step=0.05, mesh_size=(50, 50, 50))
2026-03-09T12:59:51.082123Z main WARN The use of package scanning to locate Log4j plugins is deprecated.
Please remove the `packages` attribute from your configuration file.
See https://logging.apache.org/log4j/2.x/faq.html#package-scanning for details.
2026-03-09 08:59:51,085 ERROR (SBMLDocument.java:573) - There was an error accessing the sbml online validator!
2026-03-09T12:59:51.090261Z main WARN The Logger cbit.vcell.model.Kinetics was created with the message factory org.apache.logging.log4j.message.ReusableMessageFactory@67b1217b and is now requested with a null message factory (defaults to org.apache.logging.log4j.message.ParameterizedMessageFactory), which may create log events with unexpected formatting.
2026-03-09T12:59:51.093216Z main WARN The Logger cbit.vcell.mapping.AbstractMathMapping was created with the message factory org.apache.logging.log4j.message.ReusableMessageFactory@67b1217b and is now requested with a null message factory (defaults to org.apache.logging.log4j.message.ParameterizedMessageFactory), which may create log events with unexpected formatting.
2026-03-09 08:59:51,093 INFO (DiffEquMathMapping.java:1457) - WARNING:::: MathMapping.refreshMathDescription() ... assigning boundary condition types not unique
2026-03-09 08:59:51,093 INFO (DiffEquMathMapping.java:1457) - WARNING:::: MathMapping.refreshMathDescription() ... assigning boundary condition types not unique
2026-03-09 08:59:51,094 INFO (Entrypoints.java:172) - Returning from sbmlToVcell: {"success":true,"message":"Success"}
In [3]:
Copied!
from datetime import datetime
from pyvcell._internal.api.vcell_client.api.bio_model_resource_api import BioModelResourceApi
vcml_str = vc.to_vcml_str(biomodel)
bm_api = BioModelResourceApi(api_client)
model_name = f"MyRemoteModel_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
saved_vcml = bm_api.save_bio_model(body=vcml_str, new_name=model_name)
print(f"Saved model as: {model_name}")
from datetime import datetime
from pyvcell._internal.api.vcell_client.api.bio_model_resource_api import BioModelResourceApi
vcml_str = vc.to_vcml_str(biomodel)
bm_api = BioModelResourceApi(api_client)
model_name = f"MyRemoteModel_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
saved_vcml = bm_api.save_bio_model(body=vcml_str, new_name=model_name)
print(f"Saved model as: {model_name}")
2026-03-09T12:59:51.168514Z main WARN The use of package scanning to locate Log4j plugins is deprecated.
Please remove the `packages` attribute from your configuration file.
See https://logging.apache.org/log4j/2.x/faq.html#package-scanning for details.
2026-03-09T12:59:51.171992Z main WARN The Logger cbit.vcell.model.Kinetics was created with the message factory org.apache.logging.log4j.message.ReusableMessageFactory@3248a0bf and is now requested with a null message factory (defaults to org.apache.logging.log4j.message.ParameterizedMessageFactory), which may create log events with unexpected formatting.
2026-03-09T12:59:51.363477Z main WARN The Logger cbit.vcell.mapping.AbstractMathMapping was created with the message factory org.apache.logging.log4j.message.ReusableMessageFactory@3248a0bf and is now requested with a null message factory (defaults to org.apache.logging.log4j.message.ParameterizedMessageFactory), which may create log events with unexpected formatting.
2026-03-09 08:59:51,364 INFO (DiffEquMathMapping.java:1457) - WARNING:::: MathMapping.refreshMathDescription() ... assigning boundary condition types not unique
2026-03-09 08:59:51,373 INFO (Entrypoints.java:200) - Returning from vcellToVcml: {"success":true,"message":"Success"}
Saved model as: MyRemoteModel_20260309_085951
In [4]:
Copied!
saved_biomodel = vc.load_vcml_str(saved_vcml)
bm_key = saved_biomodel.version.key
saved_app = next(a for a in saved_biomodel.applications if a.name == "app1")
sim_key = saved_app.simulations[0].version.key
sim_name = saved_app.simulations[0].name
print(f"BioModel key: {bm_key}, Simulation key: {sim_key}")
saved_biomodel = vc.load_vcml_str(saved_vcml)
bm_key = saved_biomodel.version.key
saved_app = next(a for a in saved_biomodel.applications if a.name == "app1")
sim_key = saved_app.simulations[0].version.key
sim_name = saved_app.simulations[0].name
print(f"BioModel key: {bm_key}, Simulation key: {sim_key}")
BioModel key: 306602051, Simulation key: 306602048
Start simulation¶
In [5]:
Copied!
from pyvcell._internal.api.vcell_client.api.simulation_resource_api import SimulationResourceApi
sim_api = SimulationResourceApi(api_client)
sim_api.start_simulation(sim_id=sim_key)
print("Simulation started")
from pyvcell._internal.api.vcell_client.api.simulation_resource_api import SimulationResourceApi
sim_api = SimulationResourceApi(api_client)
sim_api.start_simulation(sim_id=sim_key)
print("Simulation started")
Simulation started
Monitor progress¶
In [6]:
Copied!
import time
while True:
status_record = sim_api.get_simulation_status(
sim_id=sim_key,
bio_model_id=bm_key,
)
print(f"Status: {status_record.status}, Details: {status_record.details}")
if status_record.status in ("COMPLETED", "FAILED", "STOPPED"):
break
time.sleep(5)
if status_record.status != "COMPLETED":
raise RuntimeError(f"Simulation ended with status: {status_record.status}")
import time
while True:
status_record = sim_api.get_simulation_status(
sim_id=sim_key,
bio_model_id=bm_key,
)
print(f"Status: {status_record.status}, Details: {status_record.details}")
if status_record.status in ("COMPLETED", "FAILED", "STOPPED"):
break
time.sleep(5)
if status_record.status != "COMPLETED":
raise RuntimeError(f"Simulation ended with status: {status_record.status}")
Status: Status.WAITING, Details: waiting to be dispatched Status: Status.RUNNING, Details: running... Status: Status.RUNNING, Details: running... Status: Status.RUNNING, Details: running... Status: Status.RUNNING, Details: simulation [SimID_306602048_0_] started Status: Status.RUNNING, Details: simulation [SimID_306602048_0_] started Status: Status.RUNNING, Details: simulation [SimID_306602048_0_] started Status: Status.RUNNING, Details: 1.35 Status: Status.RUNNING, Details: 1.35 Status: Status.RUNNING, Details: 1.35 Status: Status.COMPLETED, Details: completed
Export results (N5 format)¶
In [7]:
Copied!
from pyvcell._internal.api.vcell_client.api.export_resource_api import ExportResourceApi
from pyvcell._internal.api.vcell_client.models.n5_export_request import N5ExportRequest
from pyvcell._internal.api.vcell_client.models.standard_export_info import StandardExportInfo
from pyvcell._internal.api.vcell_client.models.exportable_data_type import ExportableDataType
from pyvcell._internal.api.vcell_client.models.variable_specs import VariableSpecs
from pyvcell._internal.api.vcell_client.models.variable_mode import VariableMode
from pyvcell._internal.api.vcell_client.models.time_specs import TimeSpecs
from pyvcell._internal.api.vcell_client.models.time_mode import TimeMode
export_api = ExportResourceApi(api_client)
# Compute time indices from simulation parameters
num_time_points = int(sim.duration / sim.output_time_step) + 1
all_times = [i * sim.output_time_step for i in range(num_time_points)]
request = N5ExportRequest(
standard_export_information=StandardExportInfo(
simulation_name=sim_name,
simulation_key=sim_key,
simulation_job=0,
variable_specs=VariableSpecs(
variable_names=["A", "B"],
mode=VariableMode.VARIABLE_MULTI,
),
time_specs=TimeSpecs(
begin_time_index=0,
end_time_index=num_time_points - 1,
all_times=all_times,
mode=TimeMode.TIME_RANGE,
),
),
exportable_data_type=ExportableDataType.PDE_VARIABLE_DATA,
dataset_name="my_results",
)
job_id = export_api.export_n5(n5_export_request=request)
print(f"Export job started: {job_id}")
from pyvcell._internal.api.vcell_client.api.export_resource_api import ExportResourceApi
from pyvcell._internal.api.vcell_client.models.n5_export_request import N5ExportRequest
from pyvcell._internal.api.vcell_client.models.standard_export_info import StandardExportInfo
from pyvcell._internal.api.vcell_client.models.exportable_data_type import ExportableDataType
from pyvcell._internal.api.vcell_client.models.variable_specs import VariableSpecs
from pyvcell._internal.api.vcell_client.models.variable_mode import VariableMode
from pyvcell._internal.api.vcell_client.models.time_specs import TimeSpecs
from pyvcell._internal.api.vcell_client.models.time_mode import TimeMode
export_api = ExportResourceApi(api_client)
# Compute time indices from simulation parameters
num_time_points = int(sim.duration / sim.output_time_step) + 1
all_times = [i * sim.output_time_step for i in range(num_time_points)]
request = N5ExportRequest(
standard_export_information=StandardExportInfo(
simulation_name=sim_name,
simulation_key=sim_key,
simulation_job=0,
variable_specs=VariableSpecs(
variable_names=["A", "B"],
mode=VariableMode.VARIABLE_MULTI,
),
time_specs=TimeSpecs(
begin_time_index=0,
end_time_index=num_time_points - 1,
all_times=all_times,
mode=TimeMode.TIME_RANGE,
),
),
exportable_data_type=ExportableDataType.PDE_VARIABLE_DATA,
dataset_name="my_results",
)
job_id = export_api.export_n5(n5_export_request=request)
print(f"Export job started: {job_id}")
Export job started: 4919316062
In [8]:
Copied!
while True:
events = export_api.export_status()
for event in events:
if event.job_id == job_id:
if event.event_type == "EXPORT_COMPLETE":
export_url = event.location
print(f"Export complete: {export_url}")
break
elif event.event_type == "EXPORT_FAILURE":
raise RuntimeError(f"Export failed: {event}")
else:
time.sleep(5)
continue
break
while True:
events = export_api.export_status()
for event in events:
if event.job_id == job_id:
if event.event_type == "EXPORT_COMPLETE":
export_url = event.location
print(f"Export complete: {export_url}")
break
elif event.event_type == "EXPORT_FAILURE":
raise RuntimeError(f"Export failed: {event}")
else:
time.sleep(5)
continue
break
Export complete: https://vcell.cam.uchc.edu/n5Data/schaff/ffea0744240babd.n5?dataSetName=4919316062
Read N5 results with TensorStore¶
The export URL is not a direct download — it points to a remote N5 dataset served via S3-compatible storage. Parse the URL and open with TensorStore for lazy chunked reads:
In [9]:
Copied!
from urllib.parse import urlparse, parse_qs
import tensorstore as ts
parsed = urlparse(export_url)
path_parts = parsed.path.strip("/").split("/", 1)
bucket = path_parts[0] # "n5Data"
container_key = path_parts[1] # "{user}/{hash}.n5"
s3_endpoint = f"{parsed.scheme}://{parsed.netloc}" # "https://vcell.cam.uchc.edu"
dataset_name = parse_qs(parsed.query)["dataSetName"][0] # export job ID
store = ts.open({
"driver": "n5",
"kvstore": {
"driver": "http",
"base_url": f"{s3_endpoint}/{bucket}/{container_key}/{dataset_name}",
},
"open": True,
}).result()
print(f"Shape: {store.shape}, Dtype: {store.dtype}")
# Shape is (X, Y, Variables, Z, Time)
# Channels 0..N-2 are exported variables (A, B), channel N-1 is the domain mask
# Read a slice — e.g. variable A, all X/Y, first z-slice, first timepoint
slice_data = store[:, :, 0, 0, 0].read().result()
print(f"Slice shape: {slice_data.shape}")
from urllib.parse import urlparse, parse_qs
import tensorstore as ts
parsed = urlparse(export_url)
path_parts = parsed.path.strip("/").split("/", 1)
bucket = path_parts[0] # "n5Data"
container_key = path_parts[1] # "{user}/{hash}.n5"
s3_endpoint = f"{parsed.scheme}://{parsed.netloc}" # "https://vcell.cam.uchc.edu"
dataset_name = parse_qs(parsed.query)["dataSetName"][0] # export job ID
store = ts.open({
"driver": "n5",
"kvstore": {
"driver": "http",
"base_url": f"{s3_endpoint}/{bucket}/{container_key}/{dataset_name}",
},
"open": True,
}).result()
print(f"Shape: {store.shape}, Dtype: {store.dtype}")
# Shape is (X, Y, Variables, Z, Time)
# Channels 0..N-2 are exported variables (A, B), channel N-1 is the domain mask
# Read a slice — e.g. variable A, all X/Y, first z-slice, first timepoint
slice_data = store[:, :, 0, 0, 0].read().result()
print(f"Slice shape: {slice_data.shape}")
Shape: (98, 98, 3, 98, 41), Dtype: dtype("float64")
Slice shape: (98, 98)
Convenience API¶
The steps above (save, start, monitor, export, open TensorStore) can be replaced with a few convenience functions from pyvcell.vcml:
# One-liner: save, run, export, and open TensorStore
store = vc.run_remote(api_client, biomodel, "sim1")
# Or composable:
saved_bm, saved_sim = vc.save_and_start(api_client, biomodel, "sim1")
vc.wait_for_simulation(api_client, saved_bm, saved_sim)
store = vc.export_n5(api_client, saved_sim, biomodel=saved_bm)
In [11]:
Copied!
# One-liner: save, run, export, and open TensorStore
model_name = f"MyRemoteModel_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
store = vc.run_remote(api_client, biomodel, "sim1", model_name=model_name, on_progress=print)
print(f"Shape: {store.shape}, Dtype: {store.dtype}")
# One-liner: save, run, export, and open TensorStore
model_name = f"MyRemoteModel_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
store = vc.run_remote(api_client, biomodel, "sim1", model_name=model_name, on_progress=print)
print(f"Shape: {store.shape}, Dtype: {store.dtype}")
2026-03-09T13:17:59.738617Z main WARN The use of package scanning to locate Log4j plugins is deprecated.
Please remove the `packages` attribute from your configuration file.
See https://logging.apache.org/log4j/2.x/faq.html#package-scanning for details.
2026-03-09T13:17:59.742812Z main WARN The Logger cbit.vcell.model.Kinetics was created with the message factory org.apache.logging.log4j.message.ReusableMessageFactory@ae32def and is now requested with a null message factory (defaults to org.apache.logging.log4j.message.ParameterizedMessageFactory), which may create log events with unexpected formatting.
2026-03-09T13:17:59.931398Z main WARN The Logger cbit.vcell.mapping.AbstractMathMapping was created with the message factory org.apache.logging.log4j.message.ReusableMessageFactory@ae32def and is now requested with a null message factory (defaults to org.apache.logging.log4j.message.ParameterizedMessageFactory), which may create log events with unexpected formatting.
2026-03-09 09:17:59,932 INFO (DiffEquMathMapping.java:1457) - WARNING:::: MathMapping.refreshMathDescription() ... assigning boundary condition types not unique
2026-03-09 09:17:59,941 INFO (Entrypoints.java:200) - Returning from vcellToVcml: {"success":true,"message":"Success"}
Saving biomodel to server...
Starting simulation...
Status: Status.WAITING, Details: waiting to be dispatched
Status: Status.RUNNING, Details: running...
Status: Status.RUNNING, Details: initializing mesh
Status: Status.RUNNING, Details: simulation [SimID_306603569_0_] started
Status: Status.RUNNING, Details: simulation [SimID_306603569_0_] started
Status: Status.RUNNING, Details: simulation [SimID_306603569_0_] started
Status: Status.RUNNING, Details: 1.35
Status: Status.RUNNING, Details: 1.35
Status: Status.RUNNING, Details: 1.35
Status: Status.COMPLETED, Details: completed
Starting N5 export...
Export job started: 4957758156
Export complete: https://vcell.cam.uchc.edu/n5Data/schaff/98e993615baa16f.n5?dataSetName=4957758156
Shape: (98, 98, 3, 98, 41), Dtype: dtype("float64")