Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
835f506
new cylindrical tumor shape
salva24 Jun 29, 2026
ca45f49
fix: resolve bug preventing tumor cell type export from TOML function…
salva24 Jul 2, 2026
acff8dc
New Dirichlet boundaty contitions with modifiable z boundaries to mim…
salva24 Jul 2, 2026
2eac904
Delete unecessary parameters for the Dirichlet boundary condition
salva24 Jul 2, 2026
d7bf4c1
delete oxygen saturation, redundant hyperparameter
salva24 Jul 2, 2026
bd52981
oxygen diffusion coefficient
salva24 Jul 2, 2026
315a5ac
OutputInformationBasedOnRadiusCSV for analysing the tumor for differe…
salva24 Jul 3, 2026
f88dbf8
Fix in OutputInformationBasedOnRadiusCSV, stoere the time just once. …
salva24 Jul 3, 2026
8737900
atribute analysis
salva24 Jul 3, 2026
90edaac
change units from mmHg to mol/m3
salva24 Jul 4, 2026
a5eda84
solve bug of exygen increasing with the number of newborn cells
salva24 Jul 6, 2026
3bbac51
oxygen reference level and consumption rate optimization
salva24 Jul 6, 2026
3a2e1de
tunning oxygen_reference_level and cancer cell oxygen consumption
salva24 Jul 9, 2026
3d09674
oxygen diffusion ready
salva24 Jul 9, 2026
565e587
new duplication rate values and new hyperparameter for a basal probab…
salva24 Jul 11, 2026
a359901
new parameters for oxygen diffusion
salva24 Jul 14, 2026
1f5ab3e
fitting parameter basal necrosis death rate
salva24 Jul 19, 2026
3aacf25
fit basal_necrosis_probability_cancer_cells
salva24 Jul 19, 2026
3c301cb
new parameter for basal death rate in 2800 cells
salva24 Jul 20, 2026
c9f0145
new aging factor for modelling cells natural death and trial to fit b…
salva24 Jul 22, 2026
8c5e88f
fit of basal necrosis rate and aging facotr 28000 cells no cup
salva24 Jul 24, 2026
458016d
new files for experimentation with different seeds. model fit with no…
salva24 Jul 27, 2026
29a8626
new parameters for model replication under hypoxia
salva24 Jul 31, 2026
0e343c9
new physical barriers to avoid cell movement outside the cilinder bou…
salva24 Aug 1, 2026
e6b4bb5
new volumes for tumor cells and cart cells
salva24 Aug 3, 2026
b0457f7
new glucose gradient, and fit of associated diffusion and consumption…
salva24 Aug 5, 2026
c8d3394
add necrosis probability dempendent on glucose levels
salva24 Aug 5, 2026
6454e72
glucose affecting duplication rate, old version
salva24 Aug 8, 2026
9599467
new basal_necrosis_probability_cancer_cells
salva24 Aug 8, 2026
5cf074e
new maximum_necrosis_lack_of_glucose_rate after glucose fit
salva24 Aug 9, 2026
6898286
new distinctions on the tumoral cell death procedures: apoptosis vs n…
salva24 Aug 10, 2026
0e18917
add parameter to avoid glucose diffusion on the z axis to model a 2D …
salva24 Aug 10, 2026
4bc6a20
max radius allowed for a cylindrical or spherical bounded space
salva24 Aug 12, 2026
76434f0
Merge branch 'compiler-research:master' into add_glucose_gradient
salva24 Aug 17, 2026
5ddf9c3
new parameters for treatment under normoxia
salva24 Aug 18, 2026
0a64027
Merge branch 'add_glucose_gradient' of https://github.com/salva24/CAR…
salva24 Aug 18, 2026
c3ae9b5
new configuration maximun necrosis rate for model without treatment a…
salva24 Aug 21, 2026
f890078
parameter for cart adhesion rate?
salva24 Sep 5, 2026
3086811
new parameter 09_09_2026 for car cells pushing from the edges
salva24 Sep 9, 2026
f9bf20f
cart cytotoxicity and glucose consumption
salva24 Sep 13, 2026
75ce721
Merge pull request #5 from salva24/add_glucose_gradient
salva24 Sep 16, 2026
9d2dc16
cart cell oxygen consumption and necrosis limit with treatment
salva24 Sep 25, 2026
eb36cf7
cart cell oxygen consumption and necrosis limit with treatment on jso…
salva24 Sep 25, 2026
2f67623
correct the oxygen consumption for 560000 cart cells
salva24 Sep 28, 2026
f25b8f0
parameters hypoxia treatment
salva24 Sep 30, 2026
c4d5efb
Merge pull request #6 from salva24/add_glucose_gradient
salva24 Sep 30, 2026
325e1f6
cleaning the code and deleting draft files
salva24 Sep 30, 2026
f8645cc
clang format and clang tidy
salva24 Sep 30, 2026
6bf8360
add analysis on the cause of cancer cell death
salva24 Oct 3, 2026
7abf77c
clean all python scripts
salva24 Oct 3, 2026
0d27b51
updated readme file with new model schema and the added python script…
salva24 Oct 3, 2026
9f5ff44
updated readme file with new parameters
salva24 Oct 3, 2026
cc6858e
remove axuliar python scripts
salva24 Oct 4, 2026
56f5000
remove final from cart cell class
salva24 Oct 4, 2026
7bc3f43
new parameters to test for proliferation dependent on oxygen levels
salva24 Oct 6, 2026
0d717ca
fix missleading comment on voxel size for mechanics
salva24 Oct 6, 2026
f413841
fix bugs and feedback from the PR
salva24 Oct 15, 2026
45a003e
avoid magic numbers warning
salva24 Oct 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,7 @@ doc/latex
*.csv
*.xyz
*.dat

# Draft code for comparing models
draft_code_my_own_analysis/
auxiliar_scripts/out/*.png

# Python environment
.venv/
Expand All @@ -129,3 +127,6 @@ cout.txt

#sqlite database files
*.db

#auxiliar_scripts
auxiliar_scripts/
229 changes: 204 additions & 25 deletions README.md

Large diffs are not rendered by default.

153 changes: 114 additions & 39 deletions abm_calibration/optimize.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,19 @@
import pandas as pd

# Change this: File Parameters for the desired experiment
EXPERIMENT_ID = 1
EXPERIMENT_ID = 0
# "total": compares against target_data/final_data.csv
# "radius": compares against target_data/data_dependent_on_radius_tumor.csv
# "custom": implement your own error function in compute_custom_error()
MODE = "total"

SEED = 42
# You can set the number of trials to 0 to skip the optimization and just load the best result from the database
NUMBER_OF_TRIALS = 8
# Number of Monte Carlo simulations to run for each trial. Use one for an aproximation of the error with a single montecarlo run
NUMBER_MONTE_CARLO = 1
NUMBER_MONTE_CARLO = 3
# BioDynaMo directory to execute the comand source thisbdm.sh, you can change it to your own path
BIODYNAMO_DIR = " /home/usuario/Desktop/biodynamo/build/bin/thisbdm.sh"
BIODYNAMO_DIR = "/home/usuario/Desktop/biodynamo/build/bin/thisbdm.sh"

# Other Hyperparameters for the optimization process
# LOGGING
Expand All @@ -47,10 +52,18 @@
# Directory for the parameters file params.json
PARAMS_PATH = Path(__file__).resolve().parent.parent / "params.json"

# Directories with the simulation output and the target data
OUTPUT_DIR = Path(__file__).resolve().parent.parent / "output"
TARGET_DIR = Path(__file__).resolve().parent / "target_data"

# Directory for this experiment
EXPERIMENT_DIR = Path("abm_calibration") / f"experiment_{EXPERIMENT_ID}"
EXPERIMENT_DIR.mkdir(parents=True, exist_ok=True)

#######################################################
# Change this: Simulations to run the ABM with the given parameters
#######################################################


# Function to run the ABM with the given parameters
def run_ABM(params, seed):
Expand All @@ -61,6 +74,7 @@ def run_ABM(params, seed):
]

# Change this configuration for the ABM run
# (for MODE = "radius" it must set output_information_dependent_on_radius: True)
config = {
"seed": seed,
"output_performance_statistics": False,
Expand All @@ -79,45 +93,59 @@ def run_ABM(params, seed):
subprocess.run(["bash", "-c", f"source {BIODYNAMO_DIR} && bdm run"], check=True)


# Change This: Function to compute the error between the ABM simulation results and the experimental data
def compute_error():
target = Path(__file__).resolve().parent / "target_data" / "final_data.csv"
sim_dir = Path(__file__).resolve().parent.parent / "output" / "final_data.csv"
#######################################################
# Change this: Error functions (examples)
#######################################################


# Error using the data of the whole tumor as a function of time (final_data.csv)
def compute_error_total():
metric = "average_oxygen_all_cells" # Change this: choose the metric to compare, e.g., "average_oxygen_all_cells", "tumor_radius", etc.
return mse_against_target("final_data.csv", [metric])


# Error using the data as a function of the radius at a fixed time (data_dependent_on_radius_tumor.csv)
def compute_error_radius():
csv_name = "data_dependent_on_radius_tumor.csv"
metric = "tumor_cells_type5_dead" # Change this: prefix of the metric columns, e.g., "tumor_cells_type5_dead"
minute = 4320 # Change this: fixed time (total_minutes) at which the radial profile is compared

target = TARGET_DIR / csv_name
if not target.exists():
logging.error("Missing target CSV: %s", target)
logging.error("Missing CSV: %s", target)
return float("inf")

if not sim_dir.exists():
logging.error("Missing simulation CSV: %s", sim_dir)
header = pd.read_csv(target, nrows=0).columns
columns = [c for c in header if c.startswith(f"{metric}_radius_")]
if not columns:
logging.error("No columns for metric %s in %s", metric, target)
return float("inf")
return mse_against_target(csv_name, columns, minute=minute)


# Change this: write your own error. Example: dead cells at the center of a tumor
# of 3000 micrometers of radius split into 20 measurement rings at minute 4320 (72 hours)
def compute_custom_error():
sim = OUTPUT_DIR / "data_dependent_on_radius_tumor.csv"
if not sim.exists():
logging.error("Missing simulation CSV: %s", sim)
return float("inf")

df_t = pd.read_csv(target, usecols=["total_minutes", "average_oxygen_cancer_cells"])
df_s = pd.read_csv(
sim_dir, usecols=["total_minutes", "average_oxygen_cancer_cells"]
sim, usecols=["total_minutes", "tumor_cells_type5_dead_radius_0_to_150"]
)

# Merge the target and simulation dataframes on the "total_minutes" column to align the data for error computation
merged = pd.merge(
df_t, df_s, on="total_minutes", how="inner", suffixes=("_t", "_s")
row = df_s[df_s["total_minutes"] == 4320].iloc[0]
target_value_center = 21
return abs(
float(row["tumor_cells_type5_dead_radius_0_to_150"]) - target_value_center
)

if merged.empty:
logging.error("No common minutes between target and simulation")
return float("inf")

# Compute the mean squared error (MSE) between the average oxygen levels in the target and simulation data
mse = (
(
merged["average_oxygen_cancer_cells_s"]
- merged["average_oxygen_cancer_cells_t"]
)
** 2
).mean()
return float(mse)
#######################################################
# Change this: Objective function for the Optuna optimization process
#######################################################


# Objective function for the Optuna optimization process
def objective(trial):
# Change this: Define the parameters to be optimized and their ranges
params = {
Expand All @@ -144,17 +172,20 @@ def objective(trial):
return error


# Fix the random seed for reproducibility
np.random.seed(SEED)
#######################################################
# Auxiliary functions
#######################################################

# Create an Optuna study to optimize the parameters of the ABM
study = optuna.create_study(
study_name="abm_calibration",
storage=f"sqlite:///{EXPERIMENT_DIR / 'abm_optuna.db'}",
load_if_exists=True,
direction="minimize",
sampler=optuna.samplers.TPESampler(seed=SEED),
)

# Function to compute the error between the ABM simulation results and the target data
def compute_error():
if MODE == "total":
return compute_error_total()
if MODE == "radius":
return compute_error_radius()
if MODE == "custom":
return compute_custom_error()
raise ValueError(f"Unknown MODE: {MODE}")


# Function to set the export value in the bdm.toml file
Expand All @@ -171,7 +202,51 @@ def set_export_value(path: Path, value: bool):
path.write_text("\n".join(new_lines))


# MSE between simulation and target for the given csv file and columns, merged on total_minutes
# (if minute is given, only that time is compared)
def mse_against_target(csv_name, columns, minute=None):
target = TARGET_DIR / csv_name
sim = OUTPUT_DIR / csv_name

for path in (target, sim):
if not path.exists():
logging.error("Missing CSV: %s", path)
return float("inf")

df_t = pd.read_csv(target, usecols=["total_minutes", *columns])
df_s = pd.read_csv(sim, usecols=["total_minutes", *columns])

if minute is not None:
df_t = df_t[df_t["total_minutes"] == minute]
df_s = df_s[df_s["total_minutes"] == minute]

merged = pd.merge(
df_t, df_s, on="total_minutes", how="inner", suffixes=("_t", "_s")
)
if merged.empty:
logging.error("No common minutes between target and simulation")
return float("inf")

squared = [(merged[f"{c}_s"] - merged[f"{c}_t"]) ** 2 for c in columns]
return float(pd.concat(squared, axis=1).stack().mean())


#######################################################
# Main
#######################################################

if __name__ == "__main__":
# Fix the random seed for reproducibility
np.random.seed(SEED)

# Create an Optuna study to optimize the parameters of the ABM
study = optuna.create_study(
study_name="abm_calibration",
storage=f"sqlite:///{EXPERIMENT_DIR / 'abm_optuna.db'}",
load_if_exists=True,
direction="minimize",
sampler=optuna.samplers.TPESampler(seed=SEED),
)

# Save the original content of the bdm.toml and params.json files to restore them later
original_bdm = BDM_TOML_PATH.read_text()
Expand Down
Binary file modified assets/images/model_outline.png

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we ask claude to draw this with mermaid for github?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tried a few attempts but honestly I do not like the results as much as the diagram I've done myself. Let's discuss it tomorrow
image

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is ugly but I believe it can replay 1:1 yours…

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 5 additions & 8 deletions bdm.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
[visualization]
export = true
interval = 7200
interval = 1200

[[visualize_agent]]
name = "TumorCell"
additional_data_members = [
{name = "diameter_", function = "diameter_"},
{name = "volume_", function = "volume_"},
{name = "cell_type", function = "GetTypeAsInt"}
"diameter_",
"volume_",
"cell_type_"
]

[[visualize_agent]]
name = "CarTCell"
additional_data_members = ["diameter_","volume_"]

[[visualize_diffusion]]
name = "oxygen"

[[visualize_diffusion]]
name = "immunostimulatory_factor"
name = "oxygen"
12 changes: 0 additions & 12 deletions delete_me.py

This file was deleted.

2 changes: 1 addition & 1 deletion params.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
"0": 3957,
"8": 3957
}
}
}
74 changes: 74 additions & 0 deletions params4CylindricalModel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"seed": 42,
"bound_space_toplogy": "closed",
"num_radius_intervals": 100,
"lateral_oxygen_production_min_z": -300.0,
"lateral_oxygen_production_max_z": 300.0,
"min_initial_z_substances": -300.0,
"max_initial_z_substances": 300.0,
"diffuse_oxygen_on_z_axis": false,
Comment thread
lmluque marked this conversation as resolved.
Resolved
"diffuse_glucose_on_z_axis": false,
"output_performance_statistics": false,
"total_minutes_to_simulate": 4320,
"output_csv_interval": 600,
"bounded_space_length": 6500.0,
"tumor_shape": "cylinder",
"output_information_dependent_on_radius": true,
"cylindrical_tumor_radius": 3000.0,
"max_radius_analysis_csv_dependent_on_radius": 3000.0,
"cylindrical_tumor_height": 100.0,
"initial_number_of_cylindrical_tumor_cells": 28000,
"default_volume_new_tumor_cell": 1468.0,
"std_volume_new_tumor_cell": 166,
"min_volume_new_tumor_cell": 1136.0,
"max_volume_new_tumor_cell": 1800.0,
"default_volume_new_cart_cell": 269.0,
"std_volume_new_cart_cell": 33.625,
"min_volume_new_cart_cell": 201.75,
"max_volume_new_cart_cell": 336.25,
"oncoprotein_mean": 1.0,
"oncoprotein_standard_deviation": 0.0,
"initial_oxygen_level": 0.0,
Comment thread
lmluque marked this conversation as resolved.
Resolved
"oxygen_reference_level": 165.0,
"default_oxygen_consumption_tumor_cell": 77.47,
"default_glucose_consumption_tumor_cell": 0.045,
"diffusion_coefficient_oxygen": 180000.0,
"decay_constant_oxygen": 0.006666666666666667,
"time_apoptosis": 6000.0,
"time_lysis": 6000.0,
"average_time_transformation_random_rate": 72,
"standard_deviation_transformation_random_rate": 15.0,
"decay_constant_glucose": 0.0003333333333333333,
"oxygen_saturation_for_proliferation": 20,
"oxygen_limit_for_proliferation": 10,
"oxygen_limit_for_necrosis_maximum": 0.0,
"oxygen_limit_for_necrosis": 7.1,
"maximum_necrosis_lack_of_oxygen_rate": 7.2214e-05,
"reduction_consumption_dead_cells": 0.0,
"basal_death_probability_cancer_cells": 5e-06,
"bounded_space_min_allowed_z": -50.0,
"bounded_space_max_allowed_z": 50.0,
"bounded_space_max_allowed_radius": 3250.0,
"minimum_distance_from_tumor_to_spawn_cart": 0.0,
"add_immunostimulatory_factor": true,
"add_glucose": true,
"diffusion_coefficient_glucose": 7800,
"initial_glucose_level": 24.98,
"treatment": {
"0": 560000
},
"default_oxygen_consumption_cart": 16,
"default_glucose_consumption_cart": 0.007,
"adhesion_rate_cart": 8.5e-06,
"glucose_saturation_for_tumor_cell_growth": 24.98,
"glucose_limit_for_tumor_cell_growth": 0,
"glucose_limit_for_death": 5,
"glucose_limit_for_death_maximum": 0,
"maximum_death_lack_of_glucose_rate": 0.000175,
"minimum_tumor_cell_target_volume_fraction_for_division": 0.9,
"avg_migration_bias_cart": 0,
"std_migration_bias_cart": 0.135,
"persistence_time_cart": 0,
"diffusion_coefficient_immunostimulatory_factor": 100000,
"decay_constant_immunostimulatory_factor": 1e-05
}
10 changes: 10 additions & 0 deletions params4SphericalModel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"seed": 42,
"output_performance_statistics": true,
"total_minutes_to_simulate": 43200,
"initial_tumor_radius": 150.0,
"treatment": {
"0": 3957,
"8": 3957
}
}
Loading
Loading