sopsim 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sopsim/__init__.py +5 -0
- sopsim/__main__.py +4 -0
- sopsim/artifacts.py +79 -0
- sopsim/cli.py +157 -0
- sopsim/config.py +174 -0
- sopsim/core.py +69 -0
- sopsim/delta_in.py +111 -0
- sopsim/filop_lent.py +69 -0
- sopsim/filop_vectors.py +61 -0
- sopsim/mat_files/Ang.mat +0 -0
- sopsim/mat_files/Delta.mat +0 -0
- sopsim/mat_files/F2.mat +0 -0
- sopsim/mat_files/Notch.mat +0 -0
- sopsim/mat_files/kappa.mat +0 -0
- sopsim/reference_data.py +46 -0
- sopsim/run_sopsim.py +284 -0
- sopsim/server.py +303 -0
- sopsim/two_d_geom.py +63 -0
- sopsim/web_dist/__init__.py +1 -0
- sopsim/web_dist/assets/index-Bg57AWq2.js +32 -0
- sopsim/web_dist/assets/index-Bop8xgS8.css +1 -0
- sopsim/web_dist/index.html +13 -0
- sopsim-0.2.0.dist-info/METADATA +130 -0
- sopsim-0.2.0.dist-info/RECORD +27 -0
- sopsim-0.2.0.dist-info/WHEEL +5 -0
- sopsim-0.2.0.dist-info/entry_points.txt +2 -0
- sopsim-0.2.0.dist-info/top_level.txt +1 -0
sopsim/__init__.py
ADDED
sopsim/__main__.py
ADDED
sopsim/artifacts.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Utilities for writing local SOPSim outputs."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import pathlib
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from .run_sopsim import generate_plots
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _jsonable(value: Any) -> Any:
|
|
14
|
+
if isinstance(value, np.ndarray):
|
|
15
|
+
return value.tolist()
|
|
16
|
+
if isinstance(value, (np.integer, np.floating)):
|
|
17
|
+
return value.item()
|
|
18
|
+
if isinstance(value, dict):
|
|
19
|
+
return {key: _jsonable(item) for key, item in value.items()}
|
|
20
|
+
if isinstance(value, (list, tuple)):
|
|
21
|
+
return [_jsonable(item) for item in value]
|
|
22
|
+
return value
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def write_metadata(out_dir: pathlib.Path, inputs: Dict[str, Any], FData, extra: Dict[str, Any] | None = None) -> dict:
|
|
26
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
metadata = {
|
|
28
|
+
"inputs": _jsonable(inputs),
|
|
29
|
+
"FData": _jsonable(FData),
|
|
30
|
+
"summary": {
|
|
31
|
+
"FinalTime": float(FData[0]),
|
|
32
|
+
"RSA": float(FData[1]),
|
|
33
|
+
"mean_LSA": float(FData[2]),
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
if extra:
|
|
37
|
+
metadata.update(_jsonable(extra))
|
|
38
|
+
|
|
39
|
+
(out_dir / "metadata.json").write_text(json.dumps(metadata, indent=2))
|
|
40
|
+
return metadata
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def save_simulation_outputs(
|
|
44
|
+
out_dir: pathlib.Path,
|
|
45
|
+
inputs: Dict[str, Any],
|
|
46
|
+
FData,
|
|
47
|
+
LSA,
|
|
48
|
+
MeanDin,
|
|
49
|
+
Notch_time_step,
|
|
50
|
+
MeanSOPNotch,
|
|
51
|
+
MeanEPNotch,
|
|
52
|
+
FinalNotch,
|
|
53
|
+
ContP,
|
|
54
|
+
*,
|
|
55
|
+
generate_figures: bool = True,
|
|
56
|
+
) -> dict:
|
|
57
|
+
"""Persist the standard output bundle for a completed simulation."""
|
|
58
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
if generate_figures:
|
|
60
|
+
generate_plots(
|
|
61
|
+
inputs["Nc"],
|
|
62
|
+
inputs["r"],
|
|
63
|
+
ContP,
|
|
64
|
+
MeanDin,
|
|
65
|
+
Notch_time_step,
|
|
66
|
+
MeanSOPNotch,
|
|
67
|
+
MeanEPNotch,
|
|
68
|
+
FinalNotch,
|
|
69
|
+
LSA,
|
|
70
|
+
out_dir,
|
|
71
|
+
)
|
|
72
|
+
return write_metadata(
|
|
73
|
+
out_dir,
|
|
74
|
+
inputs,
|
|
75
|
+
FData,
|
|
76
|
+
extra={
|
|
77
|
+
"result_files": sorted(path.name for path in out_dir.iterdir() if path.is_file()),
|
|
78
|
+
},
|
|
79
|
+
)
|
sopsim/cli.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Command line entry point for SOPSim."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import pathlib
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Sequence
|
|
9
|
+
|
|
10
|
+
import uvicorn
|
|
11
|
+
|
|
12
|
+
from .artifacts import save_simulation_outputs
|
|
13
|
+
from .config import (
|
|
14
|
+
FILOPODIA_LIFE_TIME_VALIDATION_MESSAGE,
|
|
15
|
+
FILOPODIA_TYPE_VALIDATION_MESSAGE,
|
|
16
|
+
FILOPODIA_TYPES,
|
|
17
|
+
MATLAB_REFERENCE_NC,
|
|
18
|
+
MATLAB_REFERENCE_VALIDATION_MESSAGE,
|
|
19
|
+
MIN_FILOPODIA_LIFE_TIME,
|
|
20
|
+
MIN_NC,
|
|
21
|
+
NC_VALIDATION_MESSAGE,
|
|
22
|
+
SimulationInputs,
|
|
23
|
+
build_simulation_parameters,
|
|
24
|
+
)
|
|
25
|
+
from .core import sopsim
|
|
26
|
+
|
|
27
|
+
DEFAULT_FILOPODIA_TYPE = SimulationInputs().filopodia_type
|
|
28
|
+
DEFAULT_FILOPODIA_LIFE_TIME = SimulationInputs().filopodia_life_time
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SOPSimArgumentParser(argparse.ArgumentParser):
|
|
32
|
+
def error(self, message: str) -> None: # pragma: no cover - exercised through CLI behavior
|
|
33
|
+
self.print_help(sys.stderr)
|
|
34
|
+
self.exit(2, f"\nError: {message}\n")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def positive_int(value: str, *, minimum: int, label: str) -> int:
|
|
38
|
+
try:
|
|
39
|
+
parsed = int(value)
|
|
40
|
+
except ValueError as exc:
|
|
41
|
+
raise argparse.ArgumentTypeError(f"{label} must be an integer >= {minimum}.") from exc
|
|
42
|
+
if parsed < minimum:
|
|
43
|
+
raise argparse.ArgumentTypeError(f"{label} must be an integer >= {minimum}.")
|
|
44
|
+
return parsed
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def run_simulation(args: argparse.Namespace) -> int:
|
|
48
|
+
inputs = SimulationInputs(
|
|
49
|
+
Nc=args.nc,
|
|
50
|
+
filopodia_type=args.filopodia_type,
|
|
51
|
+
filopodia_life_time=args.filopodia_life_time,
|
|
52
|
+
use_mat_files=args.use_mat_files,
|
|
53
|
+
)
|
|
54
|
+
params = build_simulation_parameters(inputs)
|
|
55
|
+
result = sopsim(
|
|
56
|
+
Nc=inputs.Nc,
|
|
57
|
+
filopodia_type=inputs.filopodia_type,
|
|
58
|
+
filopodia_life_time=inputs.filopodia_life_time,
|
|
59
|
+
use_mat_files=inputs.use_mat_files,
|
|
60
|
+
)
|
|
61
|
+
out_dir = pathlib.Path(args.out_dir)
|
|
62
|
+
metadata = save_simulation_outputs(
|
|
63
|
+
out_dir,
|
|
64
|
+
params["inputs"],
|
|
65
|
+
result["FData"],
|
|
66
|
+
result["LSA"],
|
|
67
|
+
result["MeanDin"],
|
|
68
|
+
result["Notch_time_step"],
|
|
69
|
+
result["MeanSOPNotch"],
|
|
70
|
+
result["MeanEPNotch"],
|
|
71
|
+
result["FinalNotch"],
|
|
72
|
+
params["ContP"],
|
|
73
|
+
generate_figures=not args.no_plots,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
print(json.dumps(metadata["summary"], indent=2))
|
|
77
|
+
print(f"Saved outputs to {out_dir}")
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def serve_app(args: argparse.Namespace) -> int:
|
|
82
|
+
uvicorn.run("sopsim.server:app", host=args.host, port=args.port, reload=args.reload)
|
|
83
|
+
return 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
87
|
+
parser = SOPSimArgumentParser(
|
|
88
|
+
prog="sopsim",
|
|
89
|
+
description="Run SOPSim locally.",
|
|
90
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
91
|
+
epilog=(
|
|
92
|
+
"Input summary:\n"
|
|
93
|
+
f" Nc: integer >= {MIN_NC}. Required input.\n"
|
|
94
|
+
f" filopodia_type: one of {', '.join(FILOPODIA_TYPES)}. Default is {DEFAULT_FILOPODIA_TYPE}.\n"
|
|
95
|
+
f" filopodia_life_time: integer >= {MIN_FILOPODIA_LIFE_TIME}. Default is {DEFAULT_FILOPODIA_LIFE_TIME}.\n"
|
|
96
|
+
f" use_mat_files: only valid when Nc={MATLAB_REFERENCE_NC}\n"
|
|
97
|
+
" r: fixed at 1.0\n\n"
|
|
98
|
+
"Examples:\n"
|
|
99
|
+
" sopsim run --nc 4 --filopodia-type A --filopodia-life-time 10 --out-dir output\n"
|
|
100
|
+
" sopsim run --nc 4 --use-mat-files --out-dir output\n"
|
|
101
|
+
" sopsim serve" #ALT: sopsim serve --host 127.0.0.1 --port 8000
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
105
|
+
|
|
106
|
+
run_parser = subparsers.add_parser("run", help="Run one simulation and write local outputs.")
|
|
107
|
+
run_parser.add_argument(
|
|
108
|
+
"--nc",
|
|
109
|
+
type=lambda value: positive_int(value, minimum=MIN_NC, label="Nc"),
|
|
110
|
+
required=True,
|
|
111
|
+
help=NC_VALIDATION_MESSAGE,
|
|
112
|
+
)
|
|
113
|
+
run_parser.add_argument(
|
|
114
|
+
"--filopodia-type",
|
|
115
|
+
choices=FILOPODIA_TYPES,
|
|
116
|
+
default=DEFAULT_FILOPODIA_TYPE,
|
|
117
|
+
help=f"{FILOPODIA_TYPE_VALIDATION_MESSAGE} (default: {DEFAULT_FILOPODIA_TYPE})",
|
|
118
|
+
)
|
|
119
|
+
run_parser.add_argument(
|
|
120
|
+
"--filopodia-life-time",
|
|
121
|
+
type=lambda value: positive_int(value, minimum=1, label="filopodia_life_time"),
|
|
122
|
+
default=DEFAULT_FILOPODIA_LIFE_TIME,
|
|
123
|
+
help=f"{FILOPODIA_LIFE_TIME_VALIDATION_MESSAGE} (default: {DEFAULT_FILOPODIA_LIFE_TIME})",
|
|
124
|
+
)
|
|
125
|
+
run_parser.add_argument(
|
|
126
|
+
"--out-dir",
|
|
127
|
+
type=pathlib.Path,
|
|
128
|
+
default=pathlib.Path("output"),
|
|
129
|
+
help="Directory for plots and metadata",
|
|
130
|
+
)
|
|
131
|
+
run_parser.add_argument("--no-plots", action="store_true", help="Skip generating figure files")
|
|
132
|
+
run_parser.add_argument(
|
|
133
|
+
"--use-mat-files",
|
|
134
|
+
action="store_true",
|
|
135
|
+
help=MATLAB_REFERENCE_VALIDATION_MESSAGE,
|
|
136
|
+
)
|
|
137
|
+
run_parser.set_defaults(func=run_simulation)
|
|
138
|
+
|
|
139
|
+
serve_parser = subparsers.add_parser("serve", help="Start SOPSim as a website, local to your computer.")
|
|
140
|
+
serve_parser.add_argument("--host", default="127.0.0.1", help="Host interface to bind")
|
|
141
|
+
serve_parser.add_argument("--port", type=int, default=8000, help="Port to bind")
|
|
142
|
+
serve_parser.add_argument("--reload", action="store_true", help="Enable uvicorn reload mode")
|
|
143
|
+
serve_parser.set_defaults(func=serve_app)
|
|
144
|
+
|
|
145
|
+
return parser
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
149
|
+
parser = build_parser()
|
|
150
|
+
try:
|
|
151
|
+
args = parser.parse_args(argv)
|
|
152
|
+
if not hasattr(args, "func"):
|
|
153
|
+
parser.print_help()
|
|
154
|
+
return 0
|
|
155
|
+
return int(args.func(args))
|
|
156
|
+
except ValueError as exc:
|
|
157
|
+
parser.error(str(exc))
|
sopsim/config.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Shared simulation configuration helpers."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from numbers import Integral
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from .reference_data import load_reference_data
|
|
11
|
+
|
|
12
|
+
FILOPODIA_PROFILES: Dict[str, Dict[str, Any]] = {
|
|
13
|
+
"A": {"k_mu": [0.0125, 4, 6], "t_frac": [0.45, 0.45]},
|
|
14
|
+
"B": {"k_mu": [0.0125, 4, 6], "t_frac": [0.4, 0.65]},
|
|
15
|
+
"C": {"k_mu": [0.0125, 4, 12], "t_frac": [0.65, 0.65]},
|
|
16
|
+
"D": {"k_mu": [0.0125, 5, 9], "t_frac": [0.25, 0.8]},
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
DEFAULT_ALPHA = np.array(
|
|
20
|
+
[
|
|
21
|
+
[0.3, 0.65, 0.8],
|
|
22
|
+
[0.25, 0.6, 0.75],
|
|
23
|
+
[0.2, 0.75, 0.90],
|
|
24
|
+
]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
DEFAULT_RADIUS = 1.0
|
|
28
|
+
MATLAB_REFERENCE_NC = 4
|
|
29
|
+
MIN_NC = 1
|
|
30
|
+
MIN_FILOPODIA_LIFE_TIME = 1
|
|
31
|
+
NC_RUNTIME_WARNING_THRESHOLD = 10
|
|
32
|
+
FILOPODIA_TYPES = tuple(FILOPODIA_PROFILES.keys())
|
|
33
|
+
NC_VALIDATION_MESSAGE = f"Number of cells per row (Nc) must be an integer >= {MIN_NC}."
|
|
34
|
+
FILOPODIA_LIFE_TIME_VALIDATION_MESSAGE = f"filopodia_life_time must be an integer >= {MIN_FILOPODIA_LIFE_TIME}."
|
|
35
|
+
FILOPODIA_TYPE_VALIDATION_MESSAGE = f"filopodia_type must be one of {', '.join(FILOPODIA_TYPES)}."
|
|
36
|
+
MATLAB_REFERENCE_VALIDATION_MESSAGE = f"MATLAB reference mode is only supported when Nc={MATLAB_REFERENCE_NC}."
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class SimulationInputs:
|
|
41
|
+
Nc: int
|
|
42
|
+
filopodia_type: str = "A"
|
|
43
|
+
filopodia_life_time: int = 10
|
|
44
|
+
use_mat_files: bool = False
|
|
45
|
+
|
|
46
|
+
def validate(self) -> None:
|
|
47
|
+
if not isinstance(self.Nc, Integral) or isinstance(self.Nc, bool):
|
|
48
|
+
raise ValueError(NC_VALIDATION_MESSAGE)
|
|
49
|
+
if not isinstance(self.filopodia_life_time, Integral) or isinstance(self.filopodia_life_time, bool):
|
|
50
|
+
raise ValueError(FILOPODIA_LIFE_TIME_VALIDATION_MESSAGE)
|
|
51
|
+
if self.Nc < MIN_NC:
|
|
52
|
+
raise ValueError(NC_VALIDATION_MESSAGE)
|
|
53
|
+
if self.filopodia_life_time < MIN_FILOPODIA_LIFE_TIME:
|
|
54
|
+
raise ValueError(FILOPODIA_LIFE_TIME_VALIDATION_MESSAGE)
|
|
55
|
+
if self.filopodia_type not in FILOPODIA_PROFILES:
|
|
56
|
+
raise ValueError(FILOPODIA_TYPE_VALIDATION_MESSAGE)
|
|
57
|
+
if self.use_mat_files and self.Nc != MATLAB_REFERENCE_NC:
|
|
58
|
+
raise ValueError(MATLAB_REFERENCE_VALIDATION_MESSAGE)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def build_simulation_parameters(inputs: SimulationInputs) -> Dict[str, Any]:
|
|
62
|
+
"""Build the low-level arrays used by the simulator."""
|
|
63
|
+
inputs.validate()
|
|
64
|
+
|
|
65
|
+
if inputs.Nc >= NC_RUNTIME_WARNING_THRESHOLD:
|
|
66
|
+
import warnings
|
|
67
|
+
|
|
68
|
+
warnings.warn(
|
|
69
|
+
f"Nc={inputs.Nc} can take many hours to run. Consider a smaller value for testing.",
|
|
70
|
+
RuntimeWarning,
|
|
71
|
+
stacklevel=2,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
tf = 3000
|
|
75
|
+
dt = 1.0
|
|
76
|
+
t_check = 120
|
|
77
|
+
MinConvergeTime = 600
|
|
78
|
+
MinSaveTime = 0
|
|
79
|
+
FreqSave = 10
|
|
80
|
+
MaxIt = tf / dt + 1
|
|
81
|
+
LifeTime = int(inputs.filopodia_life_time)
|
|
82
|
+
|
|
83
|
+
TimeP = [tf, dt, t_check, MinConvergeTime, MinSaveTime, FreqSave, MaxIt, LifeTime]
|
|
84
|
+
|
|
85
|
+
R_N = 0.1
|
|
86
|
+
R_D = 0.1
|
|
87
|
+
mu = 0.002
|
|
88
|
+
rho = 0.002
|
|
89
|
+
a = 0.01
|
|
90
|
+
b = 100
|
|
91
|
+
h = 9
|
|
92
|
+
k = 9
|
|
93
|
+
ModelP = [R_N, R_D, mu, rho, a, b, h, k]
|
|
94
|
+
|
|
95
|
+
MinStableValue = 2
|
|
96
|
+
F_rate = 0.01
|
|
97
|
+
N_sigma = 0.01
|
|
98
|
+
D_sigma = 0.01
|
|
99
|
+
F_sigma = 0.3
|
|
100
|
+
N_thresh = 1.0
|
|
101
|
+
CellsAway = 3
|
|
102
|
+
ContP = [inputs.Nc, MinStableValue, F_rate, DEFAULT_RADIUS, N_sigma, D_sigma, F_sigma, N_thresh, CellsAway]
|
|
103
|
+
|
|
104
|
+
N_mu = 1.0
|
|
105
|
+
D_mu = 1.0
|
|
106
|
+
F_mu = 2.50
|
|
107
|
+
a_apical = 0.1
|
|
108
|
+
a_basal = 0.1
|
|
109
|
+
FixVals = [N_mu, D_mu, F_mu, a_apical, a_basal]
|
|
110
|
+
|
|
111
|
+
fdelta = 2.7e-3
|
|
112
|
+
fa0 = 10
|
|
113
|
+
fKbT = 4.1e-3
|
|
114
|
+
fN = 15
|
|
115
|
+
fD = 5
|
|
116
|
+
feta = 20
|
|
117
|
+
fFt = 20
|
|
118
|
+
ftau = 2000
|
|
119
|
+
fK0 = 10
|
|
120
|
+
fm = 0.1
|
|
121
|
+
fw = 0.01
|
|
122
|
+
fk_sigma = 6e-3
|
|
123
|
+
fNum_filop = inputs.Nc * inputs.Nc * 6
|
|
124
|
+
fdt = 0.1
|
|
125
|
+
ftf = LifeTime * 60
|
|
126
|
+
fselectIndx = 60 / fdt
|
|
127
|
+
fFs = fKbT * fN / fdelta
|
|
128
|
+
fP = 60
|
|
129
|
+
|
|
130
|
+
LentPar = [
|
|
131
|
+
fdelta,
|
|
132
|
+
fa0,
|
|
133
|
+
fKbT,
|
|
134
|
+
fN,
|
|
135
|
+
fD,
|
|
136
|
+
feta,
|
|
137
|
+
fFt,
|
|
138
|
+
ftau,
|
|
139
|
+
fK0,
|
|
140
|
+
fm,
|
|
141
|
+
fw,
|
|
142
|
+
fk_sigma,
|
|
143
|
+
fNum_filop,
|
|
144
|
+
fdt,
|
|
145
|
+
ftf,
|
|
146
|
+
fselectIndx,
|
|
147
|
+
fFs,
|
|
148
|
+
LifeTime,
|
|
149
|
+
fP,
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
selected_profile = FILOPODIA_PROFILES[inputs.filopodia_type]
|
|
153
|
+
k_mu = selected_profile["k_mu"]
|
|
154
|
+
Tswitch = np.array(selected_profile["t_frac"]) * ftf
|
|
155
|
+
reference_data = load_reference_data(inputs.Nc) if inputs.use_mat_files else None
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
"inputs": {
|
|
159
|
+
"Nc": inputs.Nc,
|
|
160
|
+
"r": DEFAULT_RADIUS,
|
|
161
|
+
"filopodia_type": inputs.filopodia_type,
|
|
162
|
+
"filopodia_life_time": LifeTime,
|
|
163
|
+
"use_mat_files": inputs.use_mat_files,
|
|
164
|
+
},
|
|
165
|
+
"TimeP": TimeP,
|
|
166
|
+
"ModelP": ModelP,
|
|
167
|
+
"ContP": ContP,
|
|
168
|
+
"FixVals": FixVals,
|
|
169
|
+
"LentPar": LentPar,
|
|
170
|
+
"k_mu": k_mu,
|
|
171
|
+
"alpha": DEFAULT_ALPHA.copy(),
|
|
172
|
+
"Tswitch": Tswitch,
|
|
173
|
+
"reference_data": reference_data,
|
|
174
|
+
}
|
sopsim/core.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Public API for SOPSim."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import warnings
|
|
5
|
+
import threading
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
from .config import SimulationInputs, build_simulation_parameters
|
|
9
|
+
from .config import NC_RUNTIME_WARNING_THRESHOLD
|
|
10
|
+
from .run_sopsim import run_sopsim
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def sopsim(
|
|
14
|
+
Nc: int,
|
|
15
|
+
filopodia_type: str = "A",
|
|
16
|
+
filopodia_life_time: int = 10,
|
|
17
|
+
use_mat_files: bool = False,
|
|
18
|
+
cancel_event: threading.Event | None = None,
|
|
19
|
+
) -> Dict[str, Any]:
|
|
20
|
+
"""Run the SOPSim simulation and return results.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
Nc: Number of cells per row.
|
|
24
|
+
filopodia_type: Filopodia profile type (A, B, C, D).
|
|
25
|
+
filopodia_life_time: Filopodia lifetime in minutes.
|
|
26
|
+
use_mat_files: Use bundled MATLAB reference inputs for Nc=4.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Dictionary with simulation outputs and input parameters.
|
|
30
|
+
"""
|
|
31
|
+
if Nc >= NC_RUNTIME_WARNING_THRESHOLD:
|
|
32
|
+
warnings.warn(
|
|
33
|
+
f"Nc={Nc} can take many hours to run. Consider a smaller value for testing.",
|
|
34
|
+
RuntimeWarning,
|
|
35
|
+
stacklevel=2,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
params = build_simulation_parameters(
|
|
39
|
+
SimulationInputs(
|
|
40
|
+
Nc=Nc,
|
|
41
|
+
filopodia_type=filopodia_type,
|
|
42
|
+
filopodia_life_time=filopodia_life_time,
|
|
43
|
+
use_mat_files=use_mat_files,
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
FData, LSA, MeanDin, Notch_time_step, MeanSOPNotch, MeanEPNotch, FinalNotch = run_sopsim(
|
|
48
|
+
params["LentPar"],
|
|
49
|
+
params["TimeP"],
|
|
50
|
+
params["ModelP"],
|
|
51
|
+
params["ContP"],
|
|
52
|
+
params["FixVals"],
|
|
53
|
+
params["k_mu"],
|
|
54
|
+
params["alpha"],
|
|
55
|
+
params["Tswitch"],
|
|
56
|
+
reference_data=params["reference_data"],
|
|
57
|
+
cancel_event=cancel_event,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
"FData": FData,
|
|
62
|
+
"LSA": LSA,
|
|
63
|
+
"MeanDin": MeanDin,
|
|
64
|
+
"Notch_time_step": Notch_time_step,
|
|
65
|
+
"MeanSOPNotch": MeanSOPNotch,
|
|
66
|
+
"MeanEPNotch": MeanEPNotch,
|
|
67
|
+
"FinalNotch": FinalNotch,
|
|
68
|
+
"inputs": params["inputs"],
|
|
69
|
+
}
|
sopsim/delta_in.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import os
|
|
3
|
+
import atexit
|
|
4
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
5
|
+
|
|
6
|
+
from .filop_vectors import FilopVectors
|
|
7
|
+
|
|
8
|
+
_DEFAULT_EXECUTOR = None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _get_default_executor():
|
|
12
|
+
global _DEFAULT_EXECUTOR
|
|
13
|
+
if _DEFAULT_EXECUTOR is None:
|
|
14
|
+
max_workers = os.cpu_count() or 1
|
|
15
|
+
_DEFAULT_EXECUTOR = ProcessPoolExecutor(max_workers=max_workers)
|
|
16
|
+
return _DEFAULT_EXECUTOR
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _shutdown_default_executor():
|
|
20
|
+
global _DEFAULT_EXECUTOR
|
|
21
|
+
if _DEFAULT_EXECUTOR is not None:
|
|
22
|
+
_DEFAULT_EXECUTOR.shutdown(wait=True, cancel_futures=False)
|
|
23
|
+
_DEFAULT_EXECUTOR = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
atexit.register(_shutdown_default_executor)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _process_cell(args):
|
|
30
|
+
cellp, DirVec, Fbase, FilopDisc, JunctDisc, a_apical, a_basal, Delta = args
|
|
31
|
+
|
|
32
|
+
SumOfDeltaJ = 0
|
|
33
|
+
SumOfDeltaF = 0
|
|
34
|
+
count = 0
|
|
35
|
+
|
|
36
|
+
JList = JunctDisc[cellp]
|
|
37
|
+
for ijunc in range(len(JList)):
|
|
38
|
+
SumOfDeltaJ += (a_apical / 2.0) * float(Delta[JList[ijunc] - 1])
|
|
39
|
+
|
|
40
|
+
FList = FilopDisc[cellp]
|
|
41
|
+
for ifilop in range(len(FList)):
|
|
42
|
+
neighbor = FList[ifilop] - 1
|
|
43
|
+
for ip in range(0, 6):
|
|
44
|
+
Vp = DirVec[ip, cellp, :]
|
|
45
|
+
bp = Fbase[ip, cellp, :]
|
|
46
|
+
|
|
47
|
+
Vq_all = np.squeeze(DirVec[:, neighbor, :])
|
|
48
|
+
bq_all = np.squeeze(Fbase[:, neighbor, :])
|
|
49
|
+
|
|
50
|
+
for jq in range(0, 6):
|
|
51
|
+
Vq = Vq_all[jq, :]
|
|
52
|
+
bq = bq_all[jq, :]
|
|
53
|
+
|
|
54
|
+
A = np.vstack((Vp, -Vq)).T
|
|
55
|
+
bb = bq.reshape(2, 1) - bp.reshape(2, 1)
|
|
56
|
+
detA = A[0, 0] * A[1, 1] - A[0, 1] * A[1, 0]
|
|
57
|
+
if abs(detA) > 1e-10:
|
|
58
|
+
T1 = (bb[0] * A[1, 1] - bb[1] * A[0, 1]) / detA
|
|
59
|
+
T2 = (A[0, 0] * bb[1] - A[1, 0] * bb[0]) / detA
|
|
60
|
+
if (T1 >= 0 and T1 <= 1) and (T2 >= 0 and T2 <= 1):
|
|
61
|
+
SumOfDeltaF = SumOfDeltaF + (a_basal / 24) * Delta[neighbor]
|
|
62
|
+
count += 1
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
return SumOfDeltaJ, SumOfDeltaF, count
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def DeltaIn(Nc, r, F, Ang, CellsAway, a_apical, a_basal, Delta, executor=None):
|
|
69
|
+
DirVec, Fbase, _, FilopDisc, JunctDisc = FilopVectors(Nc, r, F, Ang, CellsAway)
|
|
70
|
+
|
|
71
|
+
args_list = [
|
|
72
|
+
(cellp, DirVec, Fbase, FilopDisc, JunctDisc, a_apical, a_basal, Delta)
|
|
73
|
+
for cellp in range(Nc * Nc)
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
if executor is None:
|
|
77
|
+
executor = _get_default_executor()
|
|
78
|
+
|
|
79
|
+
results = list(executor.map(_process_cell, args_list))
|
|
80
|
+
|
|
81
|
+
DinJ = [r[0] for r in results]
|
|
82
|
+
DinF = [r[1] for r in results]
|
|
83
|
+
Fcount = [r[2] for r in results]
|
|
84
|
+
|
|
85
|
+
return DinJ, DinF, Fcount
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def KonBar(t, Tswitch, K0, m):
|
|
89
|
+
t0 = Tswitch[1]
|
|
90
|
+
if t < t0:
|
|
91
|
+
Kon = K0
|
|
92
|
+
else:
|
|
93
|
+
Kon = K0 * np.exp(-m * (t - t0))
|
|
94
|
+
return Kon
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def OneMyosin(t, myo_alpha, f0, P):
|
|
98
|
+
tloc = np.fmod(t, P)
|
|
99
|
+
a1 = myo_alpha[0]
|
|
100
|
+
a2 = myo_alpha[1]
|
|
101
|
+
a3 = myo_alpha[2]
|
|
102
|
+
|
|
103
|
+
if tloc <= (a1 * P):
|
|
104
|
+
Fm = (f0 / (a1 * P)) * tloc
|
|
105
|
+
elif (tloc > a1 * P) and (tloc <= a2 * P):
|
|
106
|
+
Fm = f0
|
|
107
|
+
elif (tloc > a2 * P) and (tloc <= a3 * P):
|
|
108
|
+
Fm = ((-f0 / ((a3 - a2) * P)) * (tloc - a2 * P)) + f0
|
|
109
|
+
elif (tloc > a3 * P) and (tloc <= P):
|
|
110
|
+
Fm = 0
|
|
111
|
+
return Fm
|
sopsim/filop_lent.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from .delta_in import KonBar, OneMyosin
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def FilopLent(LentPar, k_mu, alpha, Tswitch, kappa_override=None):
|
|
7
|
+
delta = LentPar[0]
|
|
8
|
+
a0 = LentPar[1]
|
|
9
|
+
KbT = LentPar[2]
|
|
10
|
+
N = LentPar[3]
|
|
11
|
+
D = LentPar[4]
|
|
12
|
+
eta = LentPar[5]
|
|
13
|
+
Ft = LentPar[6]
|
|
14
|
+
tau = LentPar[7]
|
|
15
|
+
K0 = LentPar[8]
|
|
16
|
+
m = LentPar[9]
|
|
17
|
+
w = LentPar[10]
|
|
18
|
+
k_sigma = LentPar[11]
|
|
19
|
+
Num_filop = LentPar[12]
|
|
20
|
+
dt = LentPar[13]
|
|
21
|
+
tf = LentPar[14]
|
|
22
|
+
selectIndx = LentPar[15]
|
|
23
|
+
Fs = LentPar[16]
|
|
24
|
+
P = LentPar[18]
|
|
25
|
+
t = np.arange(0, tf + 0.1, dt)
|
|
26
|
+
|
|
27
|
+
N0 = (Ft * delta) / KbT
|
|
28
|
+
c0 = (1 - (Ft / Fs)) ** w
|
|
29
|
+
|
|
30
|
+
if kappa_override is not None:
|
|
31
|
+
kappa = np.array(kappa_override, dtype=float)
|
|
32
|
+
if kappa.size != Num_filop:
|
|
33
|
+
raise ValueError(f"Reference kappa size {kappa.size} does not match expected {Num_filop}.")
|
|
34
|
+
kappa = kappa.reshape((Num_filop, 1), order="F")
|
|
35
|
+
else:
|
|
36
|
+
rng = np.random.default_rng()
|
|
37
|
+
kappa = k_sigma * rng.standard_normal(Num_filop).reshape((Num_filop, 1), order="F")
|
|
38
|
+
|
|
39
|
+
def Vp(L, Kon):
|
|
40
|
+
return (Kon * delta * a0 * c0) * (
|
|
41
|
+
1 - ((Kon * L * N) / ((Kon * L * N) + (D * eta * np.exp(N0 / N))))
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
Lmat = np.zeros((Num_filop, t.shape[0]))
|
|
45
|
+
for n in range(t.shape[0] - 1):
|
|
46
|
+
if t[n] <= Tswitch[0]:
|
|
47
|
+
f0 = Ft * k_mu[0]
|
|
48
|
+
Fm = OneMyosin(t[n], alpha[0, :], f0, P)
|
|
49
|
+
Vr = (((Ft + Fm) / tau) + kappa).ravel()
|
|
50
|
+
elif (t[n] > Tswitch[0]) and (t[n] <= Tswitch[1]):
|
|
51
|
+
f0 = Ft * k_mu[1]
|
|
52
|
+
Fm = OneMyosin(t[n], alpha[1, :], f0, P)
|
|
53
|
+
Vr = (((Ft + Fm) / tau) + kappa).ravel()
|
|
54
|
+
else:
|
|
55
|
+
f0 = Ft * k_mu[2]
|
|
56
|
+
Fm = OneMyosin(t[n], alpha[2, :], f0, P)
|
|
57
|
+
Vr = (((Ft + Fm) / tau) + kappa).ravel()
|
|
58
|
+
|
|
59
|
+
Kon = KonBar(t[n], Tswitch, K0, m)
|
|
60
|
+
Lnew = (Lmat[:, n] + dt * (Vp(Lmat[:, n], Kon) - Vr))
|
|
61
|
+
Lnew[Lnew < 0] = 0
|
|
62
|
+
Lmat[:, n + 1] = Lnew
|
|
63
|
+
|
|
64
|
+
Lmat = Lmat / 5
|
|
65
|
+
selectIndx = int(selectIndx)
|
|
66
|
+
|
|
67
|
+
Lsub = Lmat[:, ::selectIndx]
|
|
68
|
+
|
|
69
|
+
return Lsub
|