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 ADDED
@@ -0,0 +1,5 @@
1
+ """SOPSim package."""
2
+ from .core import sopsim
3
+
4
+ __all__ = ["sopsim"]
5
+ __version__ = "0.1.0"
sopsim/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+
4
+ raise SystemExit(main())
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