-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodel.py
More file actions
3130 lines (2874 loc) · 187 KB
/
model.py
File metadata and controls
3130 lines (2874 loc) · 187 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# +====================================================================+
# | Model: CELLFOUNDRY |
# | Last update: 26/02/2026 - 13:28:03 |
# +====================================================================+
# +====================================================================+
# | IMPORTS |
# +====================================================================+
import sys as _sys
import sys # keep 'sys' available for existing usage
import pathlib
_ORIGINAL_ARGV = list(_sys.argv) # snapshot BEFORE pyflamegpu touches sys.argv
from pyflamegpu import *
import time, math
from dataclasses import make_dataclass
import pandas as pd
import numpy as np
import random
import os
import pickle
import matplotlib.pyplot as plt
import check_hard_coded_values
from helper_module import compute_expected_boundary_pos_from_corners, getRandomVectors3D, build_model_config_from_namespace, load_fibre_network, getRandomCoordsAroundPoint, getRandomCoords3D, compute_u_ref_from_anchor_pos, getRadialOrientations, build_save_data_context, save_data_to_file_step, print_fibre_calibration_summary, print_focad_birth_calibration_summary, apply_param_overrides, load_param_overrides_from_cli, loadCachedCellInitialization, generateCellInitializationData, recompute_derived_params
# TODO LIST:
# A- Add cell guidance by fibre orientation (cells prefer to move along the main fibre orientation, which could be implemented by making them prefer to move towards areas where the fibre segments are more aligned in a certain direction)
start_time = time.time()
# +====================================================================+
# | GLOBAL SIMULATION PARAMETERS |
# +====================================================================+
# Set whether to run single model or ensemble, agent population size, and simulation steps
ENSEMBLE = False
ENSEMBLE_RUNS = 0
VISUALISATION = False # Change to false if pyflamegpu has not been built with visualisation support
DEBUG_PRINTING = True
ABORT_ON_UNSTABLE_FNODE_MOVE = False # If True, abort when any FNODE moves farther than one segment rest length in a single step.
PAUSE_EVERY_STEP = False # If True, the visualization stops every step until P is pressed
SAVE_PICKLE = True # If True, dumps model configuration into a pickle file for post-processing
SHOW_PLOTS = False # Show plots at the end of the simulation
SAVE_DATA_TO_FILE = True # If true, agent data is exported to .vtk file every SAVE_EVERY_N_STEPS steps
SAVE_EVERY_N_STEPS = 20 # Affects both the .vtk files and the Dataframes storing boundary data
CURR_PATH = pathlib.Path(__file__).resolve().parent
RES_PATH = CURR_PATH / 'result_files'
RES_PATH.mkdir(parents=True, exist_ok=True)
EPSILON = 0.0000000001
CELL_INIT_CACHE_DIR = CURR_PATH
print("Executing in ", CURR_PATH)
# Minimum number of ECM agents per direction (x,y,z).
# If domain is not cubical, N is asigned to the shorter dimension and more agents are added to the longer ones
# NOTE: ECM agents are always present (mandatory) eventhough they are only used when INCLUDE_DIFFUSION is True. If there is no diffusion, set N to a small value to reduce computational cost.
# ----------------------------------------------------------------------
N = 21
# Time simulation parameters
# ----------------------------------------------------------------------
TIME_STEP = 1.0 # s. WARNING: diffusion and cell migration events might need different scales
STEPS = 200
# +====================================================================+
# | BOUNDARY CONDITIONS |
# +====================================================================+
# Boundary interactions and mechanical parameters
# ----------------------------------------------------------------------
ECM_K_ELAST = 0.2 # [nN/um]
ECM_D_DUMPING = 0.04 # [nN·s/um]
ECM_ETA = 0.15 # [nN·s/µm] Effective drag for overdamped FNODE motion (calibration parameter)
#BOUNDARY_COORDS = [0.5, -0.5, 0.5, -0.5, 0.5, -0.5] # +X,-X,+Y,-Y,+Z,-Z
BOUNDARY_COORDS = [500.0, -500.0, 500.0, -500.0, 500.0, -500.0]# microdevice dimensions in um
#BOUNDARY_COORDS = [coord / 1000.0 for coord in BOUNDARY_COORDS] # in mm
BOUNDARY_DISP_RATES = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]# perpendicular to each surface (+X,-X,+Y,-Y,+Z,-Z) [um/s]
BOUNDARY_DISP_RATES_PARALLEL = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]# parallel to each surface (+X_y,+X_z,-X_y,-X_z,+Y_x,+Y_z,-Y_x,-Y_z,+Z_x,+Z_y,-Z_x,-Z_y)[um/s]
POISSON_DIRS = [0, 1] # 0: xdir, 1:ydir, 2:zdir. poisson_ratio ~= -incL(dir1)/incL(dir2) dir2 is the direction in which the load is applied
ALLOW_BOUNDARY_ELASTIC_MOVEMENT = [0, 0, 0, 0, 0, 0] # [bool]
RELATIVE_BOUNDARY_STIFFNESS = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
BOUNDARY_STIFFNESS_VALUE = 10.0 # nN/um
BOUNDARY_DUMPING_VALUE = 5.0
BOUNDARY_STIFFNESS = [BOUNDARY_STIFFNESS_VALUE * x for x in RELATIVE_BOUNDARY_STIFFNESS]
BOUNDARY_DUMPING = [BOUNDARY_DUMPING_VALUE * x for x in RELATIVE_BOUNDARY_STIFFNESS]
CLAMP_AGENT_TOUCHING_BOUNDARY = [1, 1, 1, 1, 1, 1]# +X,-X,+Y,-Y,+Z,-Z [bool] - shear assay
#CLAMP_AGENT_TOUCHING_BOUNDARY = [1, 1, 1, 1, 1, 1]# +X,-X,+Y,-Y,+Z,-Z [bool]
ALLOW_AGENT_SLIDING = [0, 0, 0, 0, 0, 0]# +X,-X,+Y,-Y,+Z,-Z [bool]
if any(rate != 0.0 for rate in BOUNDARY_DISP_RATES_PARALLEL) or any(rate != 0.0 for rate in BOUNDARY_DISP_RATES):
MOVING_BOUNDARIES = True
else:
MOVING_BOUNDARIES = False
# Adjust number of agents if domain is not cubical
# ----------------------------------------------------------------------
# Calculate the differences between opposite pairs along each axis
diff_x = abs(BOUNDARY_COORDS[0] - BOUNDARY_COORDS[1])
diff_y = abs(BOUNDARY_COORDS[2] - BOUNDARY_COORDS[3])
diff_z = abs(BOUNDARY_COORDS[4] - BOUNDARY_COORDS[5])
# Check if the differences are equal
if diff_x == diff_y == diff_z:
ECM_AGENTS_PER_DIR = [N, N, N] # cubical domain
else:
min_length = min(diff_x, diff_y, diff_z)
dist_agents = min_length / (N - 1)
ECM_AGENTS_PER_DIR = [int(diff_x / dist_agents) + 1, int(diff_y / dist_agents) + 1, int(diff_z / dist_agents) + 1]
# Redefine BOUNDARY_COORDS due to rounding values
diff_x = dist_agents * (ECM_AGENTS_PER_DIR[0] - 1)
diff_y = dist_agents * (ECM_AGENTS_PER_DIR[1] - 1)
diff_z = dist_agents * (ECM_AGENTS_PER_DIR[2] - 1)
BOUNDARY_COORDS = [round(diff_x / 2, 2), -round(diff_x / 2, 2), round(diff_y / 2, 2), -round(diff_y / 2, 2), round(diff_z / 2, 2), -round(diff_z / 2, 2)]
L0_x = abs(BOUNDARY_COORDS[0] - BOUNDARY_COORDS[1])
L0_y = abs(BOUNDARY_COORDS[2] - BOUNDARY_COORDS[3])
L0_z = abs(BOUNDARY_COORDS[4] - BOUNDARY_COORDS[5])
ECM_POPULATION_SIZE = ECM_AGENTS_PER_DIR[0] * ECM_AGENTS_PER_DIR[1] * ECM_AGENTS_PER_DIR[2]
ECM_ECM_EQUILIBRIUM_DISTANCE = L0_x / (ECM_AGENTS_PER_DIR[0] - 1) # in units, all agents are evenly spaced
ECM_BOUNDARY_INTERACTION_RADIUS = 0.05
ECM_BOUNDARY_EQUILIBRIUM_DISTANCE = 0.0
ECM_VOXEL_VOLUME = (L0_x / (ECM_AGENTS_PER_DIR[0] - 1)) * (L0_y / (ECM_AGENTS_PER_DIR[1] - 1)) * (L0_z / (ECM_AGENTS_PER_DIR[2] - 1))
MAX_SEARCH_RADIUS_VASCULARIZATION = ECM_ECM_EQUILIBRIUM_DISTANCE # this strongly affects the number of bins and therefore the memory allocated for simulations (more bins -> more memory -> faster (in theory))
MAX_SEARCH_RADIUS_CELL_ECM_INTERACTION = ECM_ECM_EQUILIBRIUM_DISTANCE # this radius is used to find ECM agents
# NOTE: MAX_SEARCH_RADIUS_CELL_CELL_INTERACTION is defined after CELL_RADIUS (see below)
OSCILLATORY_SHEAR_ASSAY = False # if True, BOUNDARY_DISP_RATES_PARALLEL options are overrun but used to make the boundaries oscillate in their corresponding planes following a sin() function
MAX_STRAIN = 0.25 # maximum strain applied during oscillatory shear assay (used to compute OSCILLATORY_AMPLITUDE)
OSCILLATORY_AMPLITUDE = MAX_STRAIN * (BOUNDARY_COORDS[2] - BOUNDARY_COORDS[3]) # range [0-1] * domain size in the direction of oscillation
OSCILLATORY_FREQ = 0.05 # strain oscillation frequency [s^-1]
OSCILLATORY_W = 2 * math.pi * OSCILLATORY_FREQ * TIME_STEP
# Compute expected boundary positions after motion, WARNING: make sure the direction matches with OSCILLATORY_AMPLITUDE definition
MAX_EXPECTED_BOUNDARY_POS_OSCILLATORY = 0.25 * (BOUNDARY_COORDS[2] - BOUNDARY_COORDS[3]) + BOUNDARY_COORDS[2] # max pos reached at sin()=1
# Parallel disp rate values are overrun in oscillatory assays
# ----------------------------------------------------------------------
if OSCILLATORY_SHEAR_ASSAY:
for d in range(12):
if abs(BOUNDARY_DISP_RATES_PARALLEL[d]) > 0.0:
BOUNDARY_DISP_RATES_PARALLEL[d] = OSCILLATORY_AMPLITUDE * math.cos(
OSCILLATORY_W * 0.0) * OSCILLATORY_W / TIME_STEP # cos(w*t)*w is used because the slope of the sin(w*t) function is needed. Expressed in units/sec
# +====================================================================+
# | FIBRE NETWORK PARAMETERS |
# +====================================================================+
INCLUDE_FIBRE_NETWORK = True
NETWORK_FILE = 'network_medium_density.pkl' # path to the .pkl file with node_coords + connectivity
ALLOW_IRREGULAR_NETWORK = False # default: False, meaning that all boundaries must have network nodes attached (e.g. a network going from -y to y and touching the other boundaries should have this variable set to True)
# Fitting parameters for the fiber strain-stiffening phenomena
# Ref: https://bio.physik.fau.de/publications/Steinwachs%20Nat%20Meth%202016.pdf
# ----------------------------------------------------------------------
BUCKLING_COEFF_D0 = 0.15
STRAIN_STIFFENING_COEFF_DS = 0.85
CRITICAL_STRAIN = 0.1
MAX_STRAIN_K_FACTOR = 12.0 # Cap on the strain-dependent stiffness multiplier (plateau / damage limit)
MAX_CONNECTIVITY = 8 # must match hard-coded C++ values
# NOTE: These are calibrated model parameters (effective segment-level mechanics), not universal material constants.
# They depend on collagen type/concentration, crosslinking, architecture and coarse-graining choices.
FIBRE_SEGMENT_K_ELAST = 0.08 # [nN/um] Effective fibre-segment stiffness (baseline for tuning)
FIBRE_SEGMENT_D_DUMPING = 0.0 # [nN*s/um] Effective fibre-segment damping (baseline for tuning)
FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE = 5 # WARNING: must match the value used in network generation
FIBRE_SECTION_AREA_UM2 = 0.05 # [um^2] Approximate collagen-fibre cross-section used for effective stress normalization
FIBRE_NODE_BOUNDARY_INTERACTION_RADIUS = 0.05
FIBRE_NODE_BOUNDARY_EQUILIBRIUM_DISTANCE = 0.0
MAX_SEARCH_RADIUS_FNODES = FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE / 10.0 # must me smaller than FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE
FIBRE_NODE_REPULSION_K = 0.2 * FIBRE_SEGMENT_K_ELAST # [nN/um] Short-range FNODE-FNODE exclusion stiffness (kept below segment stiffness)
# WARNING: THESE VARIABLES SIZE DEPENDS ON N_CELL_TYPES (DEFINED BELOW IN THE CELL PARAMETERS SECTION)
# FNODE remodeling (degradation/deposition + birth/death)
INCLUDE_NETWORK_REMODELING = False
FNODE_DEGRADATION_RATE = [5.0e-4, 5.0e-4, 5.0e-4] # [1/s] per-neighbor degradation contribution (per cell-type)
FNODE_DEPOSITION_RATE = [2.0e-4, 2.0e-4, 2.0e-4] # [1/s] baseline repair/deposition (per cell-type)
FNODE_CELL_DEGRADATION_RADIUS = 0.75 * FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE # [um] cutoff radius (scalar)
# CELL-driven FNODE birth — per-cell-type arrays
FNODE_BIRTH_K_0 = [2.0, 2.0, 2.0] # [1/s] baseline probability rate for CELL-driven FNODE birth
FNODE_BIRTH_K_MAX = [2.0, 2.0, 2.0] # [1/s] gated additive birth-rate gain
FNODE_BIRTH_SPECIES_INDEX = 0 # species index (global, same for all types)
FNODE_BIRTH_K_C = [5.0, 5.0, 5.0] # concentration half-saturation for birth gate
FNODE_BIRTH_HILL_CONC = [2.0, 2.0, 2.0]
FNODE_BIRTH_K_SIGMA = [0.1, 0.1, 0.1] # [kPa] stress half-saturation for birth gate
FNODE_BIRTH_HILL_SIGMA = [1.0, 1.0, 1.0]
FNODE_BIRTH_RADIUS = [0.5 * FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE, 0.5 * FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE, 0.5 * FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE] # [um] newborn offset around CELL center
FNODE_BIRTH_LINK_MAX_DISTANCE = [2.0 * FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE, 2.0 * FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE, 2.0 * FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE] # [um] parent FNODE search radius
FNODE_BIRTH_REFRACTORY = [20.0, 20.0, 20.0] # [s]
# +====================================================================+
# | DIFFUSION PARAMETERS |
# +====================================================================+
INCLUDE_DIFFUSION = True
N_SPECIES = 2 # number of diffusing species.WARNING: make sure that the value coincides with the one declared in TODO
DIFFUSION_COEFF_MULTI = [5.0, 5.0] # diffusion coefficient in [um^2/s] per specie
BOUNDARY_CONC_INIT_MULTI = [[2.5, 2.5, 2.5, 2.5, 2.5, 2.5],
# initial concentration at each surface (+X,-X,+Y,-Y,+Z,-Z) [ng/ml]. -1.0 means no condition assigned. All agents are assigned 0 by default.
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]] # add as many lines as different species
BOUNDARY_CONC_FIXED_MULTI = [[2.5, 2.5, 2.5, 2.5, 2.5, 2.5],
# concentration boundary conditions at each surface. WARNING: -1.0 means initial condition prevails. Don't use 0.0 as initial condition if that value is not fixed. Use -1.0 instead
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]] # add as many lines as different species
HETEROGENEOUS_DIFFUSION = True # if True, diffusion coefficient is multiplied by (1 - local ECM density) to simulate hindered diffusion through the ECM. WARNING: this is a very simple approximation of the phenomenon and highly depends on grid density (N).
# +====================================================================+
# | CELL PARAMETERS |
# +====================================================================+
# --- Per-cell-type configuration ------------------------------------------
# N_CELL_TYPES controls the length of all per-type array variables below.
# WARNING: must match the hard-coded N_CELL_TYPES in every .cpp agent
# function that reads per-type environment arrays (same pattern as N_SPECIES).
# When a parameter below is given as a *scalar*, the helper function
# _broadcast_cell_type_params() will replicate it to N_CELL_TYPES copies
# so that every cell type shares the same default value.
# Use tools/resize_cell_types.py to automatically resize all per-type arrays and automatically update all c++ files using N_CELL_TYPES.
# --------------------------------------------------------------------------
N_CELL_TYPES = 3
INCLUDE_CELLS = True
INCLUDE_CELL_CELL_INTERACTION = True # If True, cells interact with each other through short-range repulsion and adhesion forces.
INCLUDE_CELL_CYCLE = True # If True, cells go through a simplified cell cycle with G1, S, G2 and M phases, which can affect their behavior. Also includes birth/death dynamics (WARNING: USER-DEFINED in cell_cycle.cpp).
DEAD_CELLS_DISAPPEAR = False # If True, dead CELL agents are removed; if False, they remain inert with dead=1.
PERIODIC_BOUNDARIES_FOR_CELLS = False
INCLUDE_CELL_FNODE_REPULSION = False
N_CELLS = 100
ORGANOID_ASSAY = False # If True, cells are initialized in a small cluster in the center of the domain to simulate an organoid. If False, they are initialized randomly in the whole domain.
ORGANOID_INIT_RADIUS = 20.0 # [um] Radius of the initial cell cluster when ORGANOID_ASSAY is True. Cells are placed randomly within a sphere of this radius centered at the domain origin.
ORGANOID_ORIENTATION_NOISE = 0.0 # [rad] Std-dev of Gaussian angular noise added to the initial radially-outward cell orientations when ORGANOID_ASSAY is True. 0 = perfectly radial; ~0.3 = ~17 deg RMS jitter; increase towards pi for fully random.
# Per-cell-type mechanical & morphological properties
# Each is a list of length N_CELL_TYPES. A scalar is broadcast to all types.
CELL_K_ELAST = [2.0, 2.0, 2.0] # [nN/um]
CELL_D_DUMPING = [0.4, 0.4, 0.4] # [nN·s/um]
CELL_RADIUS = [5.0, 5.0, 5.0] # [um]
CELL_NUCLEUS_RADIUS = [r / 2 for r in CELL_RADIUS] # [um]
CELL_SPEED_REF = [0.00041817020062396415, 0.0006199050301202626, 0.0004034913399763545] # [um/s] Another option is to define it according to grid distance ECM_ECM_EQUILIBRIUM_DISTANCE / TIME_STEP / X. WARNING: if cell speed is too high, consider increasing N or reducing TIME_STEP.
BROWNIAN_MOTION_STRENGTH_FACTOR = [0.001, 0.001, 0.001]
BROWNIAN_MOTION_STRENGTH = [s * f for s, f in zip(CELL_SPEED_REF, BROWNIAN_MOTION_STRENGTH_FACTOR)] # [um/s] # [um/s] Strength of random movement added to cell velocity.
ROTATIONAL_DIFFUSION_RATE = [0.001, 0.001, 0.001] # [rad^2/s] Rotational diffusion coefficient per cell type. Controls how fast cell orientation decorrelates (persistence time ~ 1/(2*D_rot)). Set > 0 for tortuous random-walk trajectories. e.g. D_rot = 0.001 gives a persistence time of ~500s.
CELL_CELL_REPULSION_K = [2.0 * k for k in CELL_K_ELAST] # [nN/um] contact exclusion stiffness
CELL_CELL_ADHESION_K = [0.2 * k for k in CELL_K_ELAST] # [nN/um] weak cohesion in near-contact shell
CELL_CELL_ADHESION_RANGE = [1.0 * r for r in CELL_RADIUS] # [um] adhesive shell thickness outside contact
# Search radius for the cell-cell spatial message.
# Must cover the farthest distance at which two cells can interact:
# contact distance (r1 + r2) + adhesion shell (max of CELL_CELL_ADHESION_RANGE)
# Using 3 * max(CELL_RADIUS) provides ~ 2*(R + 0.5*R) with a small margin.
MAX_SEARCH_RADIUS_CELL_CELL_INTERACTION = 3.0 * max(CELL_RADIUS) # [um]
CELL_CELL_DV_MAX = [0.5 * s for s in CELL_SPEED_REF] # [um/s] cap for cell-cell interaction velocity contribution
CELL_FNODE_REPULSION_K = [0.5 * k for k in CELL_K_ELAST] # [nN/um] exclusion stiffness around fibre nodes
CELL_FNODE_EXCLUSION_DISTANCE = list(CELL_RADIUS) # [um] minimum distance from cell center to fibre nodes
CELL_FNODE_DV_MAX = [0.5 * s for s in CELL_SPEED_REF] # [um/s] cap for cell-fnode interaction velocity contribution
print(f'Initial cell speed reference (per type): {CELL_SPEED_REF} um/s')
print(f'Initial Brownian motion strength (per type): {BROWNIAN_MOTION_STRENGTH} um/s')
print(f'Rotational diffusion rate (per type): {ROTATIONAL_DIFFUSION_RATE} rad^2/s')
# LUMEN parameters — only active when INCLUDE_CELLS and ORGANOID_ASSAY are both True.
INCLUDE_LUMEN = False # If True, LUMEN agents are present (secreted by cells into the organoid interior).
LUMEN_RADIUS = 3.0 # [um] Radius of a single LUMEN droplet.
LUMEN_ETA = 0.15 # [nN·s/um] Overdamped drag coefficient for LUMEN agents.
LUMEN_K_LUMEN_LUMEN_REPULSION = 4.0 # [nN/um] Stiffness of volume-exclusion repulsion between LUMEN agents.
LUMEN_K_LUMEN_LUMEN_ADHESION = 0.8 # [nN/um] Cohesive surface-tension adhesion strength between LUMEN agents.
LUMEN_LUMEN_ADHESION_RANGE = 1.5 * LUMEN_RADIUS # [um] Thickness of the adhesive shell outside contact distance.
LUMEN_K_LUMEN_CELL_REPULSION = 3.0 # [nN/um] Stiffness of repulsion between LUMEN and CELL agents.
LUMEN_LUMEN_CELL_DV_MAX = 0.5 * max(CELL_SPEED_REF) # [um/s] Velocity cap for LUMEN-CELL interaction contributions.
MAX_SEARCH_RADIUS_LUMEN_LUMEN_INTERACTION = 3.0 * LUMEN_RADIUS # [um] Spatial message search radius for LUMEN-LUMEN interactions.
MAX_SEARCH_RADIUS_LUMEN_CELL_INTERACTION = 2.0 * (max(CELL_RADIUS) + LUMEN_RADIUS) # [um] Spatial message search radius for LUMEN-CELL interactions.
LUMEN_SECRETION_RATE = 5e-4 # [1/s] Probability rate of secreting a LUMEN droplet per cell per second.
LUMEN_SECRETION_COOLDOWN = 100.0 # [s] Refractory time after a CELL secretes a LUMEN droplet before it can secrete another.
LUMEN_DIFFUSION_COEFF_MULTI = [5.0, 5.0] # [um^2/s] Diffusion coefficients inside LUMEN voxels (overrides fibre-adjusted D_sp).
# Per-cell-type cell cycle timing
debug_acc = 1.0 # [s] If > 1.0, accelerates the cell cycle for faster testing (all durations are divided by this value). Set to 1.0 for realistic timing.
_g1 = 10.0 * 3600 / debug_acc
_s = 8.0 * 3600 / debug_acc
_g2 = 4.0 * 3600 / debug_acc
_m = 2.0 * 3600 / debug_acc
CYCLE_PHASE_G1_DURATION = [_g1, _g1, _g1] # [s]
CYCLE_PHASE_S_DURATION = [_s, _s, _s] # [s]
CYCLE_PHASE_G2_DURATION = [_g2, _g2, _g2] # [s]
CYCLE_PHASE_M_DURATION = [_m, _m, _m] # [s]
CYCLE_PHASE_G1_START = [0.0, 0.0, 0.0] # [s]
CYCLE_PHASE_S_START = [CYCLE_PHASE_G1_DURATION[0], CYCLE_PHASE_G1_DURATION[1], CYCLE_PHASE_G1_DURATION[2]]
CYCLE_PHASE_G2_START = [CYCLE_PHASE_G1_DURATION[0] + CYCLE_PHASE_S_DURATION[0], CYCLE_PHASE_G1_DURATION[1] + CYCLE_PHASE_S_DURATION[1], CYCLE_PHASE_G1_DURATION[2] + CYCLE_PHASE_S_DURATION[2]]
CYCLE_PHASE_M_START = [CYCLE_PHASE_G1_DURATION[0] + CYCLE_PHASE_S_DURATION[0] + CYCLE_PHASE_G2_DURATION[0], CYCLE_PHASE_G1_DURATION[1] + CYCLE_PHASE_S_DURATION[1] + CYCLE_PHASE_G2_DURATION[1], CYCLE_PHASE_G1_DURATION[2] + CYCLE_PHASE_S_DURATION[2] + CYCLE_PHASE_G2_DURATION[2]]
CELL_CYCLE_DURATION = [CYCLE_PHASE_G1_DURATION[0] + CYCLE_PHASE_S_DURATION[0] + CYCLE_PHASE_G2_DURATION[0] + CYCLE_PHASE_M_DURATION[0], CYCLE_PHASE_G1_DURATION[1] + CYCLE_PHASE_S_DURATION[1] + CYCLE_PHASE_G2_DURATION[1] + CYCLE_PHASE_M_DURATION[1], CYCLE_PHASE_G1_DURATION[2] + CYCLE_PHASE_S_DURATION[2] + CYCLE_PHASE_G2_DURATION[2] + CYCLE_PHASE_M_DURATION[2]] # typically 24h
# Per-cell-type cycle multipliers
DIVISION_RATE_MULTIPLIER = [1.0, 1.15, 0.85] # [-] scales division probability per cell type
DAMAGE_ACCUMULATION_MULTIPLIER = [1.0, 0.85, 1.25] # [-] scales damage accrual per cell type
DAMAGE_REPAIR_MULTIPLIER = [1.0, 1.10, 0.85] # [-] scales damage repair per cell type
DAMAGE_DEATH_THRESHOLD = [1.0, 1.0, 0.80] # [-] damage threshold for death per cell type
# Per-cell-type species multipliers
# These multiply the base per-species rates (INIT_CELL_*_RATES) for each cell type.
CELL_CONSUMPTION_MULTIPLIER = [1.0, 1.0, 1.0] # [-] per-type scaling of consumption rates
CELL_PRODUCTION_MULTIPLIER = [1.0, 1.0, 1.0] # [-] per-type scaling of production rates
CELL_REACTION_MULTIPLIER = [1.0, 1.0, 1.0] # [-] per-type scaling of reaction rates
CELL_INIT_CONCENTRATION_MULTIPLIER = [1.0, 1.0, 1.0] # [-] per-type scaling of initial species concentrations
INIT_ECM_CONCENTRATION_VALS = [2.5, 2.5] # initial concentration of each species on the ECM agents
INIT_CELL_CONCENTRATION_VALS = [2.5, 2.5] # initial concentration of each species on the CELL agents
# Reference concentrations (actual per-agent mass is computed at init using per-type volume & conc multiplier).
INIT_CELL_CONC_MASS_VALS = [x for x in INIT_CELL_CONCENTRATION_VALS]
INIT_ECM_SAT_CONCENTRATION_VALS = [0.0, 0.0] # initial saturation concentration of each species on the ECM agents
INIT_CELL_CONSUMPTION_RATES = [0.0, 0.0] # base consumption rate of each species by the CELL agents
INIT_CELL_PRODUCTION_RATES = [0.0, 0.0] # base production rate of each species by the CELL agents
INIT_CELL_REACTION_RATES = [0.0, 0.0] # base metabolic reaction rates of each species by the CELL agents
# Species index mapping for death pathways
OXYGEN_SPECIES_INDEX = 0 # index into C_sp[] used as oxygen proxy in cell_cycle
NUTRIENT_SPECIES_INDEX = 1 # index into C_sp[] used as nutrient proxy in cell_cycle
# Per-cell-type damage and death pathway controls
# Note: cell stress variables (sig_xx, sig_eig_1, etc.) are in [nN/um^2], numerically equivalent to [kPa].
CELL_HYPOXIA_THRESHOLD = [0.03, 0.03, 0.03] # [concentration units of C_sp[OXYGEN_SPECIES_INDEX]] chronic hypoxia damage threshold
CELL_NUTRIENT_THRESHOLD = [0.03, 0.03, 0.03] # [concentration units of C_sp[NUTRIENT_SPECIES_INDEX]] chronic nutrient-deprivation threshold
CELL_STRESS_THRESHOLD = [8.0, 8.0, 8.0] # [kPa = nN/um^2] chronic mechanical-overstress damage threshold
CELL_HYPOXIA_DAMAGE_RATE = [2.0e-4, 2.0e-4, 2.0e-4] # [1/s] damage accumulation rate scaling under hypoxia (hour-scale)
CELL_NUTRIENT_DAMAGE_RATE = [1.5e-4, 1.5e-4, 1.5e-4] # [1/s] damage accumulation rate scaling under nutrient deprivation (hour-scale)
CELL_STRESS_DAMAGE_RATE = [1.0e-4, 1.0e-4, 1.0e-4] # [1/s] damage accumulation rate scaling under mechanical overstress (hour-scale)
CELL_BASAL_DAMAGE_REPAIR_RATE = [5.0e-5, 5.0e-5, 5.0e-5] # [1/s] baseline damage repair rate
CELL_ACUTE_HYPOXIA_THRESHOLD = [0.005, 0.005, 0.005] # [concentration units of C_sp[OXYGEN_SPECIES_INDEX]] immediate death threshold
CELL_ACUTE_NUTRIENT_THRESHOLD = [0.005, 0.005, 0.005] # [concentration units of C_sp[NUTRIENT_SPECIES_INDEX]] immediate death threshold
CELL_ACUTE_STRESS_THRESHOLD = [25.0, 25.0, 25.0] # [kPa = nN/um^2] immediate mechanical-failure threshold
# Estimate maximum CELL population for bucket bounds and id allocation.
# Assumes worst-case synchronized proliferative expansion with the shortest cycle period across all cell types.
_sim_time_s = STEPS * TIME_STEP
print(f"Total simulation time: {_sim_time_s/3600:.2f} hours")
_min_cycle_dur = min(CELL_CYCLE_DURATION) if isinstance(CELL_CYCLE_DURATION, list) else CELL_CYCLE_DURATION
print(f"Shortest cell cycle duration across types: {_min_cycle_dur/3600:.2f} hours")
if INCLUDE_CELLS and INCLUDE_CELL_CYCLE and _min_cycle_dur > 0.0:
_doublings = _sim_time_s / _min_cycle_dur
MAX_EXPECTED_N_CELLS = max(N_CELLS, int(math.ceil(N_CELLS * (2.0 ** _doublings) * 2.0))) # * 2.0 is a safety factor.
print(f"Estimated maximum cell population at the end of the simulation: {MAX_EXPECTED_N_CELLS} (doublings: {_doublings:.2f})")
else:
MAX_EXPECTED_N_CELLS = N_CELLS + 1 # add 1 as bucket messages requires min <> max bounds.
# +====================================================================+
# | FOCAL ADHESION PARAMETERS (units: um, s, nN) |
# +====================================================================+
INCLUDE_FOCAL_ADHESIONS = False
INIT_N_FOCAD_PER_CELL = 10 # initial number of focal adhesions per cell.
N_ANCHOR_POINTS = 50 # number of anchor points to which focal adhesions can attach on the nucleus surface. Their positions change with nucleus deformation
MAX_SEARCH_RADIUS_FOCAD = 3.0 * FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE # TEMP(debug attach): increased to strongly favor FA-node encounters. Reasonable baseline: 1.0 * FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE
MAX_FOCAD_ARM_LENGTH = 4 * max(CELL_RADIUS) # maximum length of the focal adhesion "arm". Uses max radius across cell types. WARNING: make sure this value is consistent with CELL_RADIUS and MAX_SEARCH_RADIUS_FOCAD to avoid unrealistic behavior.
# WARNING: rate values below assume global timestep ~ 1.0 s
FOCAD_REST_LENGTH_0 = min(r - r/2 for r in CELL_RADIUS) # [um] Reference rest length (shortest across types); per-agent value uses actual cell-type radii at init.
FOCAD_MIN_REST_LENGTH = FOCAD_REST_LENGTH_0 / 10.0 # [um] Minimum rest length to prevent collapse.
FOCAD_K_FA = [10.0, 10.0, 10.0] # [nN/um] Adhesion stiffness (effective spring constant). Typical range: ~0.1–10 nN/um;
FOCAD_F_MAX= [0.0, 0.0, 0.0] # [nN] Maximum force per adhesion. 0 means "no cap"
FOCAD_V_C = [0.2, 0.2, 0.2] # [um/s] Contractile shortening speed of L(t) (actomyosin-driven).
FOCAD_K_ON = [5.0, 5.0, 5.0] # [1/s] TEMP(debug attach): high binding rate. Reasonable baseline: 0.01 [1/s]
FOCAD_K_OFF_0 = [0.0002, 0.0002, 0.0002] # [1/s] TEMP(debug attach): low baseline detachment. Reasonable baseline: 0.003 [1/s]
FOCAD_F_C = [5.0, 5.0, 5.0] # [nN] Force scale controlling force sensitivity in koff(F).
# Example (simple slip): koff(F)=K_OFF_0*exp(|F|/F_C) => faster turnover under high force.
USE_CATCH_BOND = True # If True, use a two-pathway catch+slip off-rate instead of pure slip-bond.
CATCH_BOND_CATCH_SCALE = [4.0, 4.0, 4.0] # Multiplier of K_OFF_0 for catch branch.
CATCH_BOND_SLIP_SCALE = [0.2, 0.2, 0.2] # Multiplier of K_OFF_0 for slip branch.
CATCH_BOND_F_CATCH = [2.0, 2.0, 2.0] # [nN] Force scale for catch branch.
CATCH_BOND_F_SLIP = [4.0, 4.0, 4.0] # [nN] Force scale for slip branch.
# Suggested starting point when USE_CATCH_BOND=True (to avoid over-stabilization):
# FOCAD_K_ON ~ 0.02-0.1 [1/s], FOCAD_K_OFF_0 ~ 0.001-0.01 [1/s], FOCAD_K_REINF <= 0.001 [1/s].
FOCAD_K_REINF = [0.001, 0.001, 0.001] # [1/s] Reinforcement rate for adhesion strengthening.
FOCAD_F_REINF = [1.0, 1.0, 1.0] # [nN] Force scale for reinforcement saturation: g(F)=F/(F+F_REINF).
FOCAD_K_FA_MAX = [50.0, 50.0, 50.0] # [nN/um] Upper bound for reinforced adhesion stiffness.
FOCAD_K_FA_DECAY = [0.0, 0.0, 0.0] # [1/s] Optional decay towards baseline. 0 disables.
FOCAD_POLARITY_KON_FRONT_GAIN = [2.0, 2.0, 2.0] # [-] Frontness gain for k_on.
FOCAD_POLARITY_KOFF_FRONT_REDUCTION = [0.5, 0.5, 0.5] # [-] Fractional reduction of k_off_0 at front.
FOCAD_POLARITY_KOFF_REAR_GAIN = [1.0, 1.0, 1.0] # [-] Fractional increase of k_off_0 at rear.
FOCAD_F_MATURE = [1.0, 1.0, 1.0] # [nN] force threshold to transition nascent->mature
FOCAD_T_NASCENT_MAX = [120.0, 120.0, 120.0] # [s] max nascent lifetime before disassembly
FOCAD_T_DETACHED_GRACE = [30.0, 30.0, 30.0] # [s] detached grace before disassembly
FOCAD_T_DISASSEMBLY = [20.0, 20.0, 20.0] # [s] disassembling state before deletion
ENABLE_FOCAD_BIRTH = True
FOCAD_BIRTH_SPECIES_INDEX = 0 # species index in CELL C_sp controlling biochemical gate (global)
FOCAD_BIRTH_N_MIN = [1.0, 1.0, 1.0] # minimum target adhesions per cell (float for array registration; cast to int in C++)
FOCAD_BIRTH_N_MAX = [float(3 * INIT_N_FOCAD_PER_CELL)] * N_CELL_TYPES # hard cap / maximum target adhesions per cell
FOCAD_BIRTH_K_0 = [0.001, 0.001, 0.001] # [1/s] baseline birth rate
FOCAD_BIRTH_K_MAX = [0.03, 0.03, 0.03] # [1/s] max stress/biochemical-driven birth gain
FOCAD_BIRTH_K_SIGMA = [0.1, 0.1, 0.1] # [kPa] stress half-saturation for birth gate
FOCAD_BIRTH_HILL_SIGMA = [1.0, 1.0, 1.0] # Hill exponent for stress gate
FOCAD_BIRTH_K_C = [5.0, 5.0, 5.0] # concentration half-saturation for birth gate
FOCAD_BIRTH_HILL_CONC = [2.0, 2.0, 2.0] # Hill exponent for concentration gate
FOCAD_BIRTH_REFRACTORY = [20.0, 20.0, 20.0] # [s] minimum time between consecutive births per cell
# +====================================================================+
# | LINC coupling between cell nucleus and FOCAD |
# +====================================================================+
INCLUDE_LINC_COUPLING = False
LINC_K_ELAST = [10.0, 10.0, 10.0] # [nN/um] Effective LINC stiffness in series with FOCAD stiffness.
LINC_D_DUMPING = [0.0, 0.0, 0.0] # [nN·s/um] Optional damping along FOCAD-LINC axis.
LINC_REST_LENGTH = [0.0, 0.0, 0.0] # [um] Rest length of virtual LINC segment.
# +====================================================================+
# | NUCLEAR MECHANICS (ONLY USED IF FOCAL ADHESIONS ARE INCLUDED) |
# +====================================================================+
# Elasticity (small-strain linear)
NUCLEUS_E = [2.0, 2.0, 2.0] # [nN/µm² = kPa] Young’s modulus of the nucleus.
NUCLEUS_NU = [0.48, 0.48, 0.48] # [-] Poisson ratio. Nearly incompressible nucleus. WARNING: must be < 0.5.
# Viscoelastic relaxation
NUCLEUS_TAU = [0.2, 0.2, 0.2] # [s] Relaxation time.
NUCLEUS_EPS_CLAMP = [0.30, 0.30, 0.30] # [-] Clamp for each strain component.
# +====================================================================+
# | CHEMOTAXIS |
# +====================================================================+
INCLUDE_CHEMOTAXIS = False
CHEMOTAXIS_SENSITIVITY = [1.0, 0.0] # [-1.0 to +1.0] Chemotactic sensitivity for each species. Positive: attraction, Negative: repulsion towards higher concentrations.
CHEMOTAXIS_ONLY_DIR = True # if True, chemotaxis only affects cell orientation, not speed. If False, chemotaxis affects both orientation and speed (e.g. by making cells move faster when they are oriented towards higher concentration gradient)
CHEMOTAXIS_CHI = [0.1, 0.1, 0.10] # [um^2/s] Chemotactic coefficient (χ) per cell-type. Typical range: 0.1–10 µm²/s.
# +====================================================================+
# | CHEMOKINESIS |
# +====================================================================+
INCLUDE_CHEMOKINESIS = True
CHEMOKINESIS_SENSITIVITY = [-100.0, 0.0] # [-1.0 to +1.0] Chemokinesis sensitivity for each species. Positive: speed increases with higher concentrations, Negative: speed decreases with higher concentrations.
CHEMOKINESIS_ALPHA = [0.5, 0.5, 0.5] # [-] Baseline speed is multiplied by (1 + alpha * f(C_sp))
CHEMOKINESIS_K = [2.0, 2.0, 2.0] # [concentration units] Chemokinesis half-saturation constant for concentration-dependent speed modulation.
CHEMOKINESIS_HILL_N = [2.0, 2.0, 2.0] # Hill coefficient for chemokinesis response curve.
CHEMOKINESIS_ADAPT_TAU = [60.0, 60.0, 60.0] # [s] Time constant for chemokinesis adaptation (how quickly cells adjust their internal state to changes in chemoattractant concentration).
CHEMOKINESIS_SIGNAL_SAT_MULTIPLIER = [1.0, 1.0, 1.0] # Multiplier for chemokinesis signal saturation level per cell-type.
CHEMOKINESIS_SIGNAL_SAT = [20.0, 20.0] # [concentration units] Saturation level of the chemokinesis signal for each species. Can be tuned together with CHEMOKINESIS_SIGNAL_SAT_MULTIPLIER to adjust per-species per cell-type sensitivity.
# +====================================================================+
# | CELL MIGRATION RELATED PARAMETERS |
# +====================================================================+
INCLUDE_DUROTAXIS = False # if True, cells prefer to move towards stiffer regions, which is implemented by making them prefer to move in the direction of maximum stress/strain.
DUROTAXIS_ONLY_DIR = True # if True, stress/strain direction changes movement vector (keeps speed), False: changes speed too
FOCAD_MOBILITY_MU = [1e-4, 1e-4, 1e-4] # Mobility scaling for stress contribution
INCLUDE_ORIENTATION_ALIGN = True # True: enable gradual alignment to principal direction
ORIENTATION_ALIGN_RATE = [1.0, 1.0, 1.0] # Alignment rate [1/time]
ORIENTATION_ALIGN_USE_STRESS = True # True: align to stress eigvec1, False: align to strain eigvec1
DUROTAXIS_BLEND_BETA = [0.5, 0.5, 0.5] # 0: traction only, 1: principal direction only
DUROTAXIS_USE_STRESS = True # True: use stress eigenpair, False: use strain eigenpair
# +====================================================================+
# | PARAMETER OVERRIDES (for optimization / batch runs) |
# +====================================================================+
# Load overrides from --overrides <file.json> CLI argument (if any).
# e.g. python model.py --overrides ./optimizer/optuna_results/best_params.json
# This must run AFTER all defaults above so that derived values can be
# recomputed from the (possibly overridden) base parameters.
_PARAM_OVERRIDES, _RESULT_DIR_OVERRIDE = load_param_overrides_from_cli(_ORIGINAL_ARGV)
print(f"[DIAG] _ORIGINAL_ARGV = {_ORIGINAL_ARGV}")
print(f"[DIAG] _PARAM_OVERRIDES keys = {list(_PARAM_OVERRIDES.keys()) if _PARAM_OVERRIDES else '(none)'}")
print(f"[DIAG] _RESULT_DIR_OVERRIDE = {_RESULT_DIR_OVERRIDE}")
if _PARAM_OVERRIDES:
print(f"Applying {len(_PARAM_OVERRIDES)} parameter override(s): {list(_PARAM_OVERRIDES.keys())}")
apply_param_overrides(globals(), _PARAM_OVERRIDES)
if _RESULT_DIR_OVERRIDE:
RES_PATH = pathlib.Path(_RESULT_DIR_OVERRIDE)
RES_PATH.mkdir(parents=True, exist_ok=True)
print(f"Result directory overridden to: {RES_PATH}")
# When running inside an Optuna trial, suppress verbose summaries.
_OPTUNA_QUIET = bool(_PARAM_OVERRIDES)
print(f"[DIAG] After overrides: STEPS={STEPS}, SAVE_PICKLE={SAVE_PICKLE}, RES_PATH={RES_PATH}, _OPTUNA_QUIET={_OPTUNA_QUIET}")
# +====================================================================+
# | OTHER DERIVED PARAMETERS AND MODEL CHECKS |
# +====================================================================+
if not OSCILLATORY_SHEAR_ASSAY:
MIN_EXPECTED_BOUNDARY_POS, MAX_EXPECTED_BOUNDARY_POS, moved_corners = compute_expected_boundary_pos_from_corners(
BOUNDARY_COORDS,
BOUNDARY_DISP_RATES,
BOUNDARY_DISP_RATES_PARALLEL,
STEPS,
TIME_STEP,
)
else:
MIN_EXPECTED_BOUNDARY_POS = -MAX_EXPECTED_BOUNDARY_POS_OSCILLATORY
MAX_EXPECTED_BOUNDARY_POS = MAX_EXPECTED_BOUNDARY_POS_OSCILLATORY
# Dataframe initialization data storage
# ----------------------------------------------------------------------
BPOS = make_dataclass("BPOS", [("xpos", float), ("xneg", float), ("ypos", float), ("yneg", float), ("zpos", float),
("zneg", float)])
# Use a dataframe to store boundary positions over time
BPOS_OVER_TIME = pd.DataFrame([BPOS(BOUNDARY_COORDS[0], BOUNDARY_COORDS[1], BOUNDARY_COORDS[2], BOUNDARY_COORDS[3],
BOUNDARY_COORDS[4], BOUNDARY_COORDS[5])])
OSOT = make_dataclass("OSOT", [("strain", float)])
OSCILLATORY_STRAIN_OVER_TIME = pd.DataFrame([OSOT(0)])
CELL_SPEED_METRICS = pd.DataFrame()
ORGANOID_METRICS_OVER_TIME = pd.DataFrame()
# Checking for incompatible conditions
# ----------------------------------------------------------------------
critical_error = False
try:
hard_coded_check_args = [
"--model-file", str(CURR_PATH / "model.py"),
"--scan-root", str(CURR_PATH),
]
if _OPTUNA_QUIET:
hard_coded_check_args.append("--fail-on-mismatch")
hard_coded_check_exit_code = check_hard_coded_values.main(hard_coded_check_args)
if hard_coded_check_exit_code != 0:
print("ERROR: hard-coded value consistency check found mismatches or failed")
critical_error = True
except Exception as e:
print(f"WARNING: failed to execute hard-coded value consistency check: {e}\nSkipping this check. If execution fails later due to hard-coded value mismatches, please run the check separately and fix the issues")
msg_poisson = "WARNING: poisson ratio directions are not well defined or might not make sense due to boundary conditions \n"
if (BOUNDARY_DISP_RATES[0] != 0.0 or BOUNDARY_DISP_RATES[1] != 0.0) and POISSON_DIRS[1] != 0:
print(msg_poisson)
if (BOUNDARY_DISP_RATES[2] != 0.0 or BOUNDARY_DISP_RATES[3] != 0.0) and POISSON_DIRS[1] != 1:
print(msg_poisson)
if (BOUNDARY_DISP_RATES[4] != 0.0 or BOUNDARY_DISP_RATES[5] != 0.0) and POISSON_DIRS[1] != 2:
print(msg_poisson)
msg_incompatible_conditions = "ERROR: CLAMP_AGENT_TOUCHING_BOUNDARY condition is incompatible with ALLOW_BOUNDARY_ELASTIC_MOVEMENT in position [{}]"
for i in range(6):
if CLAMP_AGENT_TOUCHING_BOUNDARY[i] > 0 and ALLOW_BOUNDARY_ELASTIC_MOVEMENT[i] > 0:
print(msg_incompatible_conditions.format(i))
critical_error = True
if INCLUDE_FIBRE_NETWORK:
nodes, connectivity, n_fib, FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE, fibre_critical_error = load_fibre_network(
file_name=NETWORK_FILE,
boundary_coords=BOUNDARY_COORDS,
epsilon=EPSILON,
fibre_segment_equilibrium_distance=FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE,
allow_warning_on_mismatch=ALLOW_IRREGULAR_NETWORK
)
if fibre_critical_error:
critical_error = True
if nodes is not None and connectivity is not None:
N_NODES = nodes.shape[0]
NODE_COORDS = nodes
INITIAL_NETWORK_CONNECTIVITY = connectivity
AVG_NETWORK_VOXEL_DENSITY = math.ceil((N_NODES / (L0_x * L0_y * L0_z)) * ECM_VOXEL_VOLUME) # average number of fibre nodes per voxel, used to adjust the heterogeneous diffusion effect
print(f'Average network voxel density (number of fibre nodes per voxel): {AVG_NETWORK_VOXEL_DENSITY}')
if nodes is not None and connectivity is not None:
N_FIBRES = n_fib
else:
N_FIBRES = None
# FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE may have been updated by load_fibre_network
# (matched to the network file's EDGE_LENGTH). Recompute derived params.
recompute_derived_params(globals(), pinned=set(_PARAM_OVERRIDES.keys()) if _PARAM_OVERRIDES else None)
else:
N_NODES = None
NODE_COORDS = None
INITIAL_NETWORK_CONNECTIVITY = None
AVG_NETWORK_VOXEL_DENSITY = None
UNSTABLE_DIFFUSION = False
# Check diffusion parameters
if INCLUDE_DIFFUSION:
if (len(DIFFUSION_COEFF_MULTI) != N_SPECIES) or (len(BOUNDARY_CONC_INIT_MULTI) != N_SPECIES) or (
len(BOUNDARY_CONC_FIXED_MULTI) != N_SPECIES):
print('ERROR: you must define a diffusion coefficient and the boundary conditions for each species simulated')
critical_error = True
# Check diffusion values for numerical stability
dx = L0_x / (ECM_AGENTS_PER_DIR[0] - 1)
for i in range(N_SPECIES):
Fi_x = 3 * (DIFFUSION_COEFF_MULTI[i] * TIME_STEP / (dx * dx)) # this value should be < 0.5
# print('Fi_x value: {0} for species {1}'.format(Fi_x, i + 1))
if Fi_x > 0.5:
print(
f'WARNING: diffusion problem is ill conditioned (Fi_x {Fi_x} should be < 0.5), check parameters and consider decreasing time step\nSemi-implicit diffusion will be used instead')
UNSTABLE_DIFFUSION = True
dy = L0_y / (ECM_AGENTS_PER_DIR[1] - 1)
for i in range(N_SPECIES):
Fi_y = 3 * (DIFFUSION_COEFF_MULTI[i] * TIME_STEP / (dy * dy)) # this value should be < 0.5
# print('Fi_y value: {0} for species {1}'.format(Fi_y, i + 1))
if Fi_y > 0.5:
print(
f'WARNING: diffusion problem is ill conditioned (Fi_y {Fi_y} should be < 0.5), check parameters and consider decreasing time step\nSemi-implicit diffusion will be used instead')
UNSTABLE_DIFFUSION = True
dz = L0_z / (ECM_AGENTS_PER_DIR[2] - 1)
for i in range(N_SPECIES):
Fi_z = 3 * (DIFFUSION_COEFF_MULTI[i] * TIME_STEP / (dz * dz)) # this value should be < 0.5
# print('Fi_z value: {0} for species {1}'.format(Fi_z, i + 1))
if Fi_z > 0.5:
print(
f'WARNING: diffusion problem is ill conditioned (Fi_z {Fi_z} should be < 0.5), check parameters and consider decreasing time step\nSemi-implicit diffusion will be used instead')
UNSTABLE_DIFFUSION = True
if not INCLUDE_FIBRE_NETWORK and HETEROGENEOUS_DIFFUSION:
print(f'WARNING: HETEROGENEOUS_DIFFUSION is set to True but no fibre network is included, default D values ({DIFFUSION_COEFF_MULTI}) will be used instead')
HETEROGENEOUS_DIFFUSION = False
if INCLUDE_CELLS:
_max_cell_radius = max(CELL_RADIUS) if isinstance(CELL_RADIUS, list) else CELL_RADIUS
if INCLUDE_LUMEN and not ORGANOID_ASSAY:
print('ERROR: INCLUDE_LUMEN requires ORGANOID_ASSAY to be True')
critical_error = True
if INCLUDE_FOCAL_ADHESIONS and not INCLUDE_FIBRE_NETWORK:
print('ERROR: focal adhesions cannot be included if there is no fibre network to interact with')
critical_error = True
if PERIODIC_BOUNDARIES_FOR_CELLS and INCLUDE_FOCAL_ADHESIONS:
print('ERROR: PERIODIC_BOUNDARIES_FOR_CELLS and INCLUDE_FOCAL_ADHESIONS cannot both be True. Periodic wrapping would break focal adhesion connections to the fibre network.')
critical_error = True
if INCLUDE_FOCAL_ADHESIONS and MAX_FOCAD_ARM_LENGTH < _max_cell_radius:
print('ERROR: MAX_FOCAD_ARM_LENGTH: {0} must be bigger than max(CELL_RADIUS): {1}, as focal adhesions are initiated at the cell surface and should be able to grow away'.format(MAX_FOCAD_ARM_LENGTH, _max_cell_radius))
if INCLUDE_FOCAL_ADHESIONS and any(nmax < INIT_N_FOCAD_PER_CELL for nmax in FOCAD_BIRTH_N_MAX):
print('ERROR: FOCAD_BIRTH_N_MAX: {0} must be >= INIT_N_FOCAD_PER_CELL: {1}'.format(FOCAD_BIRTH_N_MAX, INIT_N_FOCAD_PER_CELL))
critical_error = True
if INCLUDE_FOCAL_ADHESIONS and any(nmax < nmin for nmax, nmin in zip(FOCAD_BIRTH_N_MAX, FOCAD_BIRTH_N_MIN)):
print('ERROR: FOCAD_BIRTH_N_MAX: {0} must be >= FOCAD_BIRTH_N_MIN: {1}'.format(FOCAD_BIRTH_N_MAX, FOCAD_BIRTH_N_MIN))
critical_error = True
elif INCLUDE_FOCAL_ADHESIONS:
print('ERROR: focal adhesions cannot be included if there are no cells to form them (INCLUDE_CELLS is set to False)')
critical_error= True
elif INCLUDE_CELL_CYCLE:
print('ERROR: cell cycle cannot be included if there are no cells (INCLUDE_CELLS is set to False)')
critical_error= True
if INCLUDE_FIBRE_NETWORK and not _OPTUNA_QUIET:
print_fibre_calibration_summary(
fibre_segment_k_elast=FIBRE_SEGMENT_K_ELAST,
fibre_segment_d_dumping=FIBRE_SEGMENT_D_DUMPING,
fibre_segment_equilibrium_distance=FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE,
dt = TIME_STEP,
)
if INCLUDE_CELLS and INCLUDE_FOCAL_ADHESIONS and ENABLE_FOCAD_BIRTH and not _OPTUNA_QUIET:
for _ct_i in range(N_CELL_TYPES):
print(f"\n [cell type {_ct_i}]")
print_focad_birth_calibration_summary(
dt=TIME_STEP,
init_n_focad_per_cell=INIT_N_FOCAD_PER_CELL,
n_min=FOCAD_BIRTH_N_MIN[_ct_i],
n_max=FOCAD_BIRTH_N_MAX[_ct_i],
k0=FOCAD_BIRTH_K_0[_ct_i],
kmax=FOCAD_BIRTH_K_MAX[_ct_i],
refractory_s=FOCAD_BIRTH_REFRACTORY[_ct_i],
k_sigma=FOCAD_BIRTH_K_SIGMA[_ct_i],
hill_sigma=FOCAD_BIRTH_HILL_SIGMA[_ct_i],
k_c=FOCAD_BIRTH_K_C[_ct_i],
hill_conc=FOCAD_BIRTH_HILL_CONC[_ct_i],
species_index=FOCAD_BIRTH_SPECIES_INDEX,
)
if critical_error:
quit()
MODEL_CONFIG = build_model_config_from_namespace(globals())
if not _OPTUNA_QUIET:
MODEL_CONFIG.print_configuration_summary(
n_nodes=locals().get('N_NODES'),
n_fibres=locals().get('N_FIBRES'),
)
# +====================================================================+
# | FLAMEGPU2 IMPLEMENTATION |
# +====================================================================+
# ++==================================================================++
# ++ Files |
# ++==================================================================++
"""
AGENT Files
"""
# Files containing agent functions for agents, which outputs publicly visible properties to a message list
# Agent function files
"""
ECM
"""
ecm_grid_location_data_file = "ecm_grid_location_data.cpp"
ecm_ecm_interaction_file = "ecm_ecm_interaction.cpp"
ecm_boundary_concentration_conditions_file = "ecm_boundary_concentration_conditions.cpp"
ecm_move_file = "ecm_move.cpp"
ecm_Csp_update_file = "ecm_Csp_update.cpp"
ecm_Dsp_update_file = "ecm_Dsp_update.cpp"
ecm_Dsp_lumen_update_file = "ecm_Dsp_lumen_update.cpp"
"""
CELL
"""
cell_spatial_location_data_file = "cell_spatial_location_data.cpp"
cell_ecm_interaction_metabolism_file = "cell_ecm_interaction_metabolism.cpp"
cell_move_file = "cell_move.cpp"
cell_cell_interaction_file = "cell_cell_interaction.cpp"
cell_fnode_repulsion_file = "cell_fnode_repulsion.cpp"
cell_fnode_remodel_file = "cell_fnode_remodel.cpp"
cell_bucket_location_data_file = "cell_bucket_location_data.cpp"
cell_focad_update_file = "cell_focad_update.cpp"
cell_cycle_file = "cell_cycle.cpp"
cell_maxid_update_file = "cell_MaxID_update.cpp"
cell_lumen_interaction_file = "cell_lumen_interaction.cpp"
cell_lumen_secretion_file = "cell_lumen_secretion.cpp"
"""
FOCAD
"""
focad_bucket_location_data_file = "focad_bucket_location_data.cpp"
focad_spatial_location_data_file = "focad_spatial_location_data.cpp"
focad_anchor_update_file = "focad_anchor_update.cpp"
focad_post_cycle_update_file = "focad_post_cycle_update.cpp"
focad_fnode_interaction_file = "focad_fnode_interaction.cpp"
focad_move_file = "focad_move.cpp"
"""
LUMEN
"""
lumen_spatial_location_data_file = "lumen_spatial_location_data.cpp"
lumen_lumen_interaction_file = "lumen_lumen_interaction.cpp"
lumen_cell_interaction_file = "lumen_cell_interaction.cpp"
lumen_move_file = "lumen_move.cpp"
"""
BCORNER
"""
bcorner_output_location_data_file = "bcorner_output_location_data.cpp"
bcorner_move_file = "bcorner_move.cpp"
"""
FIBRE NODES
"""
fnode_spatial_location_data_file = "fnode_spatial_location_data.cpp"
fnode_bucket_location_data_file = "fnode_bucket_location_data.cpp"
fnode_boundary_interaction_file = "fnode_boundary_interaction.cpp"
fnode_update_links_file = "fnode_update_links.cpp"
fnode_fnode_spatial_interaction_file = "fnode_fnode_spatial_interaction.cpp"
fnode_fnode_bucket_interaction_file = "fnode_fnode_bucket_interaction.cpp"
fnode_remodel_file = "fnode_remodel.cpp"
fnode_apply_remodel_updates_file = "fnode_apply_remodel_updates.cpp"
fnode_move_file = "fnode_move.cpp"
fnode_bucket_location_data_postmove_file = "fnode_bucket_location_data_postmove.cpp"
fnode_focad_interaction_file = "fnode_focad_interaction.cpp"
fnode_cell_repulsion_file = "fnode_cell_repulsion.cpp"
model = pyflamegpu.ModelDescription("cellfoundry")
# ++==================================================================++
# ++ Globals |
# ++==================================================================++
"""
GLOBAL SETTINGS
"""
env = model.Environment()
# Starting ID to generate agent populations
env.newPropertyUInt("CURRENT_ID", 0)
# Number of steps to simulate
env.newPropertyUInt("STEPS", STEPS)
# Time increment
env.newPropertyFloat("TIME_STEP", TIME_STEP)
# Number of agents in the ECM grid per direction
env.newPropertyArrayUInt("ECM_AGENTS_PER_DIR", ECM_AGENTS_PER_DIR)
# Diffusion coefficient
env.newPropertyUInt("INCLUDE_DIFFUSION", INCLUDE_DIFFUSION)
env.newPropertyUInt("HETEROGENEOUS_DIFFUSION", HETEROGENEOUS_DIFFUSION)
env.newPropertyUInt("UNSTABLE_DIFFUSION", UNSTABLE_DIFFUSION)
if INCLUDE_FIBRE_NETWORK:
env.newPropertyUInt("AVG_NETWORK_VOXEL_DENSITY", AVG_NETWORK_VOXEL_DENSITY)
env.newPropertyArrayFloat("DIFFUSION_COEFF_MULTI", DIFFUSION_COEFF_MULTI)
env.newPropertyFloat("ECM_VOXEL_VOLUME", ECM_VOXEL_VOLUME)
# ------------------------------------------------------
# BOUNDARY BEHAVIOUR
# ------------------------------------------------------
# Boundaries position
bcs = [BOUNDARY_COORDS[0], BOUNDARY_COORDS[1],
BOUNDARY_COORDS[2], BOUNDARY_COORDS[3],
BOUNDARY_COORDS[4], BOUNDARY_COORDS[5]] # +X,-X,+Y,-Y,+Z,-Z
env.newPropertyArrayFloat("COORDS_BOUNDARIES", bcs)
env.newPropertyArrayFloat("INIT_COORDS_BOUNDARIES",
bcs) # this is used to compute elastic forces with respect to initial position
# Boundaries displacement rate (units/time).
# e.g. DISP_BOUNDARY_X_POS = 0.1 means that this boundary moves 0.1 units per time towards +X
env.newPropertyArrayFloat("DISP_RATES_BOUNDARIES", BOUNDARY_DISP_RATES)
env.newPropertyArrayFloat("DISP_RATES_BOUNDARIES_PARALLEL", BOUNDARY_DISP_RATES_PARALLEL)
# Boundary-Agent behaviour
env.newPropertyArrayUInt("CLAMP_AGENT_TOUCHING_BOUNDARY", CLAMP_AGENT_TOUCHING_BOUNDARY)
env.newPropertyArrayUInt("ALLOW_BOUNDARY_ELASTIC_MOVEMENT", ALLOW_BOUNDARY_ELASTIC_MOVEMENT)
env.newPropertyArrayFloat("BOUNDARY_STIFFNESS", BOUNDARY_STIFFNESS)
env.newPropertyArrayFloat("BOUNDARY_DUMPING", BOUNDARY_DUMPING)
env.newPropertyArrayUInt("ALLOW_AGENT_SLIDING", ALLOW_AGENT_SLIDING)
env.newPropertyFloat("ECM_BOUNDARY_INTERACTION_RADIUS", ECM_BOUNDARY_INTERACTION_RADIUS)
env.newPropertyFloat("ECM_BOUNDARY_EQUILIBRIUM_DISTANCE", ECM_BOUNDARY_EQUILIBRIUM_DISTANCE)
env.newPropertyFloat("FIBRE_NODE_BOUNDARY_INTERACTION_RADIUS", FIBRE_NODE_BOUNDARY_INTERACTION_RADIUS)
env.newPropertyFloat("FIBRE_NODE_BOUNDARY_EQUILIBRIUM_DISTANCE", FIBRE_NODE_BOUNDARY_EQUILIBRIUM_DISTANCE)
env.newPropertyFloat("FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE",FIBRE_SEGMENT_EQUILIBRIUM_DISTANCE)
# Model macro/globals
env.newMacroPropertyFloat("C_SP_MACRO", N_SPECIES, ECM_POPULATION_SIZE)
env.newMacroPropertyFloat("BOUNDARY_CONC_INIT_MULTI", N_SPECIES,
6) # a 2D matrix with the 6 boundary conditions (columns) for each species (rows)
env.newMacroPropertyFloat("BOUNDARY_CONC_FIXED_MULTI", N_SPECIES,
6) # a 2D matrix with the 6 boundary conditions (columns) for each species (rows)
env.newMacroPropertyInt("MACRO_MAX_GLOBAL_CELL_ID", 1) # shared current max CELL id across all proliferating cells
env.newMacroPropertyInt("MACRO_MAX_GLOBAL_FNODE_ID", 1) # shared current max FNODE id across all remodeling cells
if INCLUDE_CELLS and ORGANOID_ASSAY and INCLUDE_LUMEN:
env.newMacroPropertyInt("MACRO_MAX_GLOBAL_LUMEN_ID", 1) # shared current max LUMEN id across all secreted lumen droplets
env.newPropertyUInt("ECM_POPULATION_SIZE", ECM_POPULATION_SIZE)
# Fibre network parameters
env.newPropertyUInt("INCLUDE_FIBRE_NETWORK", INCLUDE_FIBRE_NETWORK)
env.newPropertyFloat("MAX_SEARCH_RADIUS_FNODES",MAX_SEARCH_RADIUS_FNODES)
env.newPropertyFloat("FIBRE_SEGMENT_K_ELAST",FIBRE_SEGMENT_K_ELAST)
env.newPropertyFloat("FIBRE_SEGMENT_D_DUMPING",FIBRE_SEGMENT_D_DUMPING)
env.newPropertyFloat("FIBRE_NODE_REPULSION_K", FIBRE_NODE_REPULSION_K)
env.newPropertyUInt("INCLUDE_NETWORK_REMODELING", INCLUDE_NETWORK_REMODELING)
env.newPropertyArrayFloat("FNODE_DEGRADATION_RATE", FNODE_DEGRADATION_RATE)
env.newPropertyArrayFloat("FNODE_DEPOSITION_RATE", FNODE_DEPOSITION_RATE)
env.newPropertyFloat("FNODE_CELL_DEGRADATION_RADIUS", FNODE_CELL_DEGRADATION_RADIUS)
env.newPropertyArrayFloat("FNODE_BIRTH_K_0", FNODE_BIRTH_K_0)
env.newPropertyArrayFloat("FNODE_BIRTH_K_MAX", FNODE_BIRTH_K_MAX)
env.newPropertyUInt("FNODE_BIRTH_SPECIES_INDEX", FNODE_BIRTH_SPECIES_INDEX)
env.newPropertyArrayFloat("FNODE_BIRTH_K_C", FNODE_BIRTH_K_C)
env.newPropertyArrayFloat("FNODE_BIRTH_HILL_CONC", FNODE_BIRTH_HILL_CONC)
env.newPropertyArrayFloat("FNODE_BIRTH_K_SIGMA", FNODE_BIRTH_K_SIGMA)
env.newPropertyArrayFloat("FNODE_BIRTH_HILL_SIGMA", FNODE_BIRTH_HILL_SIGMA)
env.newPropertyArrayFloat("FNODE_BIRTH_RADIUS", FNODE_BIRTH_RADIUS)
env.newPropertyArrayFloat("FNODE_BIRTH_LINK_MAX_DISTANCE", FNODE_BIRTH_LINK_MAX_DISTANCE)
env.newPropertyArrayFloat("FNODE_BIRTH_REFRACTORY", FNODE_BIRTH_REFRACTORY)
# Cell properties — per-cell-type arrays (length N_CELL_TYPES)
env.newPropertyUInt("N_CELL_TYPES", N_CELL_TYPES)
env.newPropertyUInt("INCLUDE_CELL_CELL_INTERACTION", INCLUDE_CELL_CELL_INTERACTION)
env.newPropertyUInt("INCLUDE_CELL_FNODE_REPULSION", INCLUDE_CELL_FNODE_REPULSION)
env.newPropertyUInt("DEAD_CELLS_DISAPPEAR", DEAD_CELLS_DISAPPEAR)
env.newPropertyUInt("PERIODIC_BOUNDARIES_FOR_CELLS", PERIODIC_BOUNDARIES_FOR_CELLS)
env.newPropertyUInt("N_CELLS", N_CELLS)
env.newPropertyArrayFloat("CELL_K_ELAST", CELL_K_ELAST)
env.newPropertyArrayFloat("CELL_D_DUMPING", CELL_D_DUMPING)
env.newPropertyArrayFloat("CELL_RADIUS", CELL_RADIUS)
env.newPropertyArrayFloat("CELL_NUCLEUS_RADIUS", CELL_NUCLEUS_RADIUS)
env.newPropertyArrayFloat("CELL_SPEED_REF", CELL_SPEED_REF)
env.newPropertyArrayFloat("BROWNIAN_MOTION_STRENGTH", BROWNIAN_MOTION_STRENGTH)
env.newPropertyArrayFloat("ROTATIONAL_DIFFUSION_RATE", ROTATIONAL_DIFFUSION_RATE)
env.newPropertyFloat("MAX_SEARCH_RADIUS_CELL_ECM_INTERACTION", MAX_SEARCH_RADIUS_CELL_ECM_INTERACTION)
env.newPropertyFloat("MAX_SEARCH_RADIUS_CELL_CELL_INTERACTION", MAX_SEARCH_RADIUS_CELL_CELL_INTERACTION)
env.newPropertyArrayFloat("CELL_CELL_REPULSION_K", CELL_CELL_REPULSION_K)
env.newPropertyArrayFloat("CELL_CELL_ADHESION_K", CELL_CELL_ADHESION_K)
env.newPropertyArrayFloat("CELL_CELL_ADHESION_RANGE", CELL_CELL_ADHESION_RANGE)
env.newPropertyArrayFloat("CELL_CELL_DV_MAX", CELL_CELL_DV_MAX)
env.newPropertyArrayFloat("CELL_FNODE_REPULSION_K", CELL_FNODE_REPULSION_K)
env.newPropertyArrayFloat("CELL_FNODE_EXCLUSION_DISTANCE", CELL_FNODE_EXCLUSION_DISTANCE)
env.newPropertyArrayFloat("CELL_FNODE_DV_MAX", CELL_FNODE_DV_MAX)
env.newPropertyArrayFloat("CELL_CYCLE_DURATION", CELL_CYCLE_DURATION)
env.newPropertyArrayFloat("CYCLE_PHASE_G1_DURATION", CYCLE_PHASE_G1_DURATION)
env.newPropertyArrayFloat("CYCLE_PHASE_S_DURATION", CYCLE_PHASE_S_DURATION)
env.newPropertyArrayFloat("CYCLE_PHASE_G2_DURATION", CYCLE_PHASE_G2_DURATION)
env.newPropertyArrayFloat("CYCLE_PHASE_M_DURATION", CYCLE_PHASE_M_DURATION)
env.newPropertyArrayFloat("CYCLE_PHASE_G1_START", CYCLE_PHASE_G1_START)
env.newPropertyArrayFloat("CYCLE_PHASE_S_START", CYCLE_PHASE_S_START)
env.newPropertyArrayFloat("CYCLE_PHASE_G2_START", CYCLE_PHASE_G2_START)
env.newPropertyArrayFloat("CYCLE_PHASE_M_START", CYCLE_PHASE_M_START)
env.newPropertyArrayFloat("DIVISION_RATE_MULTIPLIER", DIVISION_RATE_MULTIPLIER)
env.newPropertyArrayFloat("DAMAGE_ACCUMULATION_MULTIPLIER", DAMAGE_ACCUMULATION_MULTIPLIER)
env.newPropertyArrayFloat("DAMAGE_REPAIR_MULTIPLIER", DAMAGE_REPAIR_MULTIPLIER)
env.newPropertyArrayFloat("DAMAGE_DEATH_THRESHOLD", DAMAGE_DEATH_THRESHOLD)
env.newPropertyArrayFloat("CELL_CONSUMPTION_MULTIPLIER", CELL_CONSUMPTION_MULTIPLIER)
env.newPropertyArrayFloat("CELL_PRODUCTION_MULTIPLIER", CELL_PRODUCTION_MULTIPLIER)
env.newPropertyArrayFloat("CELL_REACTION_MULTIPLIER", CELL_REACTION_MULTIPLIER)
# Species index mapping for death pathways
env.newPropertyUInt("OXYGEN_SPECIES_INDEX", OXYGEN_SPECIES_INDEX)
env.newPropertyUInt("NUTRIENT_SPECIES_INDEX", NUTRIENT_SPECIES_INDEX)
# Per-cell-type damage/death thresholds
env.newPropertyArrayFloat("CELL_HYPOXIA_THRESHOLD", CELL_HYPOXIA_THRESHOLD)
env.newPropertyArrayFloat("CELL_NUTRIENT_THRESHOLD", CELL_NUTRIENT_THRESHOLD)
env.newPropertyArrayFloat("CELL_STRESS_THRESHOLD", CELL_STRESS_THRESHOLD)
env.newPropertyArrayFloat("CELL_HYPOXIA_DAMAGE_RATE", CELL_HYPOXIA_DAMAGE_RATE)
env.newPropertyArrayFloat("CELL_NUTRIENT_DAMAGE_RATE", CELL_NUTRIENT_DAMAGE_RATE)
env.newPropertyArrayFloat("CELL_STRESS_DAMAGE_RATE", CELL_STRESS_DAMAGE_RATE)
env.newPropertyArrayFloat("CELL_BASAL_DAMAGE_REPAIR_RATE", CELL_BASAL_DAMAGE_REPAIR_RATE)
env.newPropertyArrayFloat("CELL_ACUTE_HYPOXIA_THRESHOLD", CELL_ACUTE_HYPOXIA_THRESHOLD)
env.newPropertyArrayFloat("CELL_ACUTE_NUTRIENT_THRESHOLD", CELL_ACUTE_NUTRIENT_THRESHOLD)
env.newPropertyArrayFloat("CELL_ACUTE_STRESS_THRESHOLD", CELL_ACUTE_STRESS_THRESHOLD)
# Focal adhesion properties
env.newPropertyUInt("INCLUDE_FOCAL_ADHESIONS", INCLUDE_FOCAL_ADHESIONS)
env.newPropertyUInt("INIT_N_FOCAD_PER_CELL", INIT_N_FOCAD_PER_CELL)
env.newPropertyUInt("N_ANCHOR_POINTS", N_ANCHOR_POINTS)
env.newPropertyFloat("MAX_SEARCH_RADIUS_FOCAD", MAX_SEARCH_RADIUS_FOCAD)
env.newPropertyFloat("MAX_FOCAD_ARM_LENGTH", MAX_FOCAD_ARM_LENGTH)
env.newPropertyFloat("FOCAD_REST_LENGTH_0", FOCAD_REST_LENGTH_0)
env.newPropertyFloat("FOCAD_MIN_REST_LENGTH", FOCAD_MIN_REST_LENGTH)
env.newPropertyArrayFloat("FOCAD_K_FA", FOCAD_K_FA)
env.newPropertyArrayFloat("FOCAD_F_MAX", FOCAD_F_MAX)
env.newPropertyArrayFloat("FOCAD_V_C", FOCAD_V_C)
env.newPropertyArrayFloat("FOCAD_K_ON", FOCAD_K_ON)
env.newPropertyArrayFloat("FOCAD_K_OFF_0", FOCAD_K_OFF_0)
env.newPropertyArrayFloat("FOCAD_F_C", FOCAD_F_C)
env.newPropertyUInt("USE_CATCH_BOND", USE_CATCH_BOND)
env.newPropertyArrayFloat("CATCH_BOND_CATCH_SCALE", CATCH_BOND_CATCH_SCALE)
env.newPropertyArrayFloat("CATCH_BOND_SLIP_SCALE", CATCH_BOND_SLIP_SCALE)
env.newPropertyArrayFloat("CATCH_BOND_F_CATCH", CATCH_BOND_F_CATCH)
env.newPropertyArrayFloat("CATCH_BOND_F_SLIP", CATCH_BOND_F_SLIP)
env.newPropertyArrayFloat("FOCAD_K_REINF", FOCAD_K_REINF)
env.newPropertyArrayFloat("FOCAD_F_REINF", FOCAD_F_REINF)
env.newPropertyArrayFloat("FOCAD_K_FA_MAX", FOCAD_K_FA_MAX)
env.newPropertyArrayFloat("FOCAD_K_FA_DECAY", FOCAD_K_FA_DECAY)
env.newPropertyArrayFloat("FOCAD_POLARITY_KON_FRONT_GAIN", FOCAD_POLARITY_KON_FRONT_GAIN)
env.newPropertyArrayFloat("FOCAD_POLARITY_KOFF_FRONT_REDUCTION", FOCAD_POLARITY_KOFF_FRONT_REDUCTION)
env.newPropertyArrayFloat("FOCAD_POLARITY_KOFF_REAR_GAIN", FOCAD_POLARITY_KOFF_REAR_GAIN)
env.newPropertyArrayFloat("FOCAD_F_MATURE", FOCAD_F_MATURE)
env.newPropertyArrayFloat("FOCAD_T_NASCENT_MAX", FOCAD_T_NASCENT_MAX)
env.newPropertyArrayFloat("FOCAD_T_DETACHED_GRACE", FOCAD_T_DETACHED_GRACE)
env.newPropertyArrayFloat("FOCAD_T_DISASSEMBLY", FOCAD_T_DISASSEMBLY)
env.newPropertyUInt("ENABLE_FOCAD_BIRTH", ENABLE_FOCAD_BIRTH)
env.newPropertyUInt("FOCAD_BIRTH_SPECIES_INDEX", FOCAD_BIRTH_SPECIES_INDEX)
env.newPropertyArrayFloat("FOCAD_BIRTH_N_MIN", FOCAD_BIRTH_N_MIN)
env.newPropertyArrayFloat("FOCAD_BIRTH_N_MAX", FOCAD_BIRTH_N_MAX)
env.newPropertyArrayFloat("FOCAD_BIRTH_K_0", FOCAD_BIRTH_K_0)
env.newPropertyArrayFloat("FOCAD_BIRTH_K_MAX", FOCAD_BIRTH_K_MAX)
env.newPropertyArrayFloat("FOCAD_BIRTH_K_SIGMA", FOCAD_BIRTH_K_SIGMA)
env.newPropertyArrayFloat("FOCAD_BIRTH_HILL_SIGMA", FOCAD_BIRTH_HILL_SIGMA)
env.newPropertyArrayFloat("FOCAD_BIRTH_K_C", FOCAD_BIRTH_K_C)
env.newPropertyArrayFloat("FOCAD_BIRTH_HILL_CONC", FOCAD_BIRTH_HILL_CONC)
env.newPropertyArrayFloat("FOCAD_BIRTH_REFRACTORY", FOCAD_BIRTH_REFRACTORY)
env.newPropertyUInt("INCLUDE_LINC_COUPLING", INCLUDE_LINC_COUPLING)
env.newPropertyArrayFloat("LINC_K_ELAST", LINC_K_ELAST)
env.newPropertyArrayFloat("LINC_D_DUMPING", LINC_D_DUMPING)
env.newPropertyArrayFloat("LINC_REST_LENGTH", LINC_REST_LENGTH)
# Nucleus mechanical properties
env.newPropertyArrayFloat("NUCLEUS_E", NUCLEUS_E)
env.newPropertyArrayFloat("NUCLEUS_NU", NUCLEUS_NU)
env.newPropertyArrayFloat("NUCLEUS_TAU", NUCLEUS_TAU)
env.newPropertyArrayFloat("NUCLEUS_EPS_CLAMP", NUCLEUS_EPS_CLAMP)
# Chemotaxis properties
env.newPropertyUInt("INCLUDE_CHEMOTAXIS", INCLUDE_CHEMOTAXIS)
env.newPropertyArrayFloat("CHEMOTAXIS_CHI", CHEMOTAXIS_CHI)
env.newPropertyUInt("CHEMOTAXIS_ONLY_DIR", CHEMOTAXIS_ONLY_DIR)
env.newPropertyArrayFloat("CHEMOTAXIS_SENSITIVITY", CHEMOTAXIS_SENSITIVITY)
# Chemokinesis properties
env.newPropertyUInt("INCLUDE_CHEMOKINESIS", INCLUDE_CHEMOKINESIS)
env.newPropertyArrayFloat("CHEMOKINESIS_SENSITIVITY", CHEMOKINESIS_SENSITIVITY)
env.newPropertyArrayFloat("CHEMOKINESIS_ALPHA", CHEMOKINESIS_ALPHA)
env.newPropertyArrayFloat("CHEMOKINESIS_K", CHEMOKINESIS_K)
env.newPropertyArrayFloat("CHEMOKINESIS_HILL_N", CHEMOKINESIS_HILL_N)
env.newPropertyArrayFloat("CHEMOKINESIS_ADAPT_TAU", CHEMOKINESIS_ADAPT_TAU)
env.newPropertyArrayFloat("CHEMOKINESIS_SIGNAL_SAT_MULTIPLIER", CHEMOKINESIS_SIGNAL_SAT_MULTIPLIER)
env.newPropertyArrayFloat("CHEMOKINESIS_SIGNAL_SAT", CHEMOKINESIS_SIGNAL_SAT)
# Cell migration (durotaxis/orientation alignment) properties
env.newPropertyUInt("INCLUDE_DUROTAXIS", INCLUDE_DUROTAXIS)
env.newPropertyUInt("DUROTAXIS_ONLY_DIR", DUROTAXIS_ONLY_DIR)
env.newPropertyArrayFloat("FOCAD_MOBILITY_MU", FOCAD_MOBILITY_MU)
env.newPropertyUInt("INCLUDE_ORIENTATION_ALIGN", INCLUDE_ORIENTATION_ALIGN)
env.newPropertyArrayFloat("ORIENTATION_ALIGN_RATE", ORIENTATION_ALIGN_RATE)
env.newPropertyUInt("ORIENTATION_ALIGN_USE_STRESS", ORIENTATION_ALIGN_USE_STRESS)
env.newPropertyArrayFloat("DUROTAXIS_BLEND_BETA", DUROTAXIS_BLEND_BETA)
env.newPropertyUInt("DUROTAXIS_USE_STRESS", DUROTAXIS_USE_STRESS)
# ECM BEHAVIOUR
# ------------------------------------------------------
# Equilibrium radius at which elastic force is 0. TODO: add ECM_FIBRE elements
# If ECM_ECM_INTERACTION_RADIUS > ECM_ECM_EQUILIBRIUM_DISTANCE: both repulsion/atraction can occur
# If ECM_ECM_INTERACTION_RADIUS <= ECM_ECM_EQUILIBRIUM_DISTANCE: only repulsion can occur
env.newPropertyFloat("ECM_ECM_EQUILIBRIUM_DISTANCE", ECM_ECM_EQUILIBRIUM_DISTANCE)
# Mechanical parameters
env.newPropertyFloat("ECM_K_ELAST", ECM_K_ELAST) # initial K_ELAST for agents
env.newPropertyFloat("ECM_D_DUMPING", ECM_D_DUMPING)
env.newPropertyFloat("ECM_ETA", ECM_ETA)
env.newPropertyFloat("BUCKLING_COEFF_D0", BUCKLING_COEFF_D0)
env.newPropertyFloat("STRAIN_STIFFENING_COEFF_DS", STRAIN_STIFFENING_COEFF_DS)
env.newPropertyFloat("CRITICAL_STRAIN", CRITICAL_STRAIN)
env.newPropertyFloat("MAX_STRAIN_K_FACTOR", MAX_STRAIN_K_FACTOR)
# Other globals
env.newPropertyFloat("PI", 3.1415)
env.newPropertyUInt("DEBUG_PRINTING", DEBUG_PRINTING)
env.newPropertyUInt("DEBUG_DIFFUSION", False)
env.newPropertyFloat("EPSILON", EPSILON)
env.newPropertyUInt("MOVING_BOUNDARIES", MOVING_BOUNDARIES)
env.newPropertyUInt("ABORT_ON_UNSTABLE_FNODE_MOVE", ABORT_ON_UNSTABLE_FNODE_MOVE)
# LUMEN agent properties (only registered when LUMEN is active)
if INCLUDE_CELLS and ORGANOID_ASSAY and INCLUDE_LUMEN:
env.newPropertyFloat("LUMEN_RADIUS", LUMEN_RADIUS)
env.newPropertyFloat("LUMEN_ETA", LUMEN_ETA)
env.newPropertyFloat("LUMEN_K_LUMEN_LUMEN_REPULSION", LUMEN_K_LUMEN_LUMEN_REPULSION)
env.newPropertyFloat("LUMEN_K_LUMEN_LUMEN_ADHESION", LUMEN_K_LUMEN_LUMEN_ADHESION)
env.newPropertyFloat("LUMEN_LUMEN_ADHESION_RANGE", LUMEN_LUMEN_ADHESION_RANGE)
env.newPropertyFloat("LUMEN_K_LUMEN_CELL_REPULSION", LUMEN_K_LUMEN_CELL_REPULSION)
env.newPropertyFloat("LUMEN_LUMEN_CELL_DV_MAX", LUMEN_LUMEN_CELL_DV_MAX)
env.newPropertyFloat("MAX_SEARCH_RADIUS_LUMEN_LUMEN_INTERACTION", MAX_SEARCH_RADIUS_LUMEN_LUMEN_INTERACTION)
env.newPropertyFloat("MAX_SEARCH_RADIUS_LUMEN_CELL_INTERACTION", MAX_SEARCH_RADIUS_LUMEN_CELL_INTERACTION)
env.newPropertyFloat("LUMEN_SECRETION_RATE", LUMEN_SECRETION_RATE)
env.newPropertyFloat("LUMEN_SECRETION_COOLDOWN", LUMEN_SECRETION_COOLDOWN)
env.newPropertyArrayFloat("LUMEN_DIFFUSION_COEFF_MULTI", LUMEN_DIFFUSION_COEFF_MULTI)
# ++==================================================================++
# ++ Messages |
# ++==================================================================++
"""
LOCATION MESSAGES
"""
BCORNER_location_message = model.newMessageSpatial3D("bcorner_location_message")
# Set the range and bounds.
BCORNER_location_message.setRadius(MAX_EXPECTED_BOUNDARY_POS - MIN_EXPECTED_BOUNDARY_POS) # corners are not actually interacting with anything
BCORNER_location_message.setMin(MIN_EXPECTED_BOUNDARY_POS, MIN_EXPECTED_BOUNDARY_POS, MIN_EXPECTED_BOUNDARY_POS)
BCORNER_location_message.setMax(MAX_EXPECTED_BOUNDARY_POS, MAX_EXPECTED_BOUNDARY_POS, MAX_EXPECTED_BOUNDARY_POS)
# A message to hold the location of an agent. WARNING: spatial3D messages already define x,y,z variables internally.
BCORNER_location_message.newVariableInt("id")