11"""Global sensitivity analysis.
22
33TODO:
4- - [ ] multi-output model
5- - [ ] calculation of pharmacokinetic parameters (improved PK calculation)
6- - [ ] get all parameter ids from model
4+
75- [ ] get defined parameter bounds from model (annotate information);
8- - [ ] storage of results
9- - [ ] visualization of results
6+ - [ ] storage of results simulation
7+ - [ ] storage of results sensitivity analysis
8+ - [ ] visualization of results (heatmap)
109- [ ] alternative methods:
1110 - [ ] local sensitivity analysis
1211 - [ ] SOBOL indices Sobol Sensitivity Analysis (Sobol 2001, Saltelli 2002, Saltelli et al. 2010)
3130from pathlib import Path
3231from dataclasses import dataclass
3332
34- from pkdb_analysis . pk . pharmacokinetics import TimecoursePK
33+
3534from sbmlutils .console import console
3635from roadrunner ._roadrunner import NamedArray
37- from pint import UnitRegistry
36+
37+
3838
3939@dataclass
40- class SBMLSensitivityAnalysis :
41- """Parent class for sensitivity analysis."""
40+ class SensitivitySimulation :
41+ """Base class for sensitivity calculation.
42+
43+ The sensitivity simulation runs a model simulation under a given set of
44+ model changes and returns a dictionary of scalar outputs.
45+ This function is called repeatedly during the sensitivity calculation.
46+ """
47+
4248 model_path : Path
4349 selections : list [str ]
4450 rr : roadrunner .RoadRunner = None
51+ outputs : list [str ] = None
4552
4653 def __init__ (self , model_path : Path , selections : list [str ]):
4754 self .model_path = model_path
@@ -52,168 +59,93 @@ def __init__(self, model_path: Path, selections: list[str]):
5259 integrator .setSetting ("variable_step_size" , True )
5360 # state = rr.saveStateS()
5461
62+ # get the outputs from the simulation
63+ y = self .simulate (changes = {})
64+ self .outputs = list (y .keys ())
5565
56- def losartan_simulation (self , changes : dict [str , float ]) -> dict [str , float ]:
57- """Run a given model simulation and create scalar readouts.
5866
59- Applies the changes and gets the readouts. This is the general structure of the abstract
60- run simulation. This can afterwards be parallelized .
67+ def simulate ( self , changes : dict [ str , float ]) -> dict [ str , float ]:
68+ """Runs a model simulation and returns the scalar results dictionary .
6169
62- This must be a small subset of data
70+ This must be implemented by the subclass to work.
6371 """
72+ raise NotImplemented
6473
65- # rr = roadrunner.RoadRunner()
66- # rr.loadStateS(state)
67- self .rr .resetAll ()
68- all_changes = {
69- "PODOSE_los" : 10.0 , # [mg]
70- ** changes
71- }
72- for key , value in all_changes .items ():
74+ def plot (self ) -> None :
75+ """Plots the model simulation for debugging."""
76+ raise NotImplemented
77+
78+ def apply_changes (self , changes : dict [str , float ], reset_all : bool = True ) -> None :
79+ """Apply changes after possible reset of the model."""
80+ if reset_all :
81+ self .rr .resetAll ()
82+ for key , value in changes .items ():
7383 # print(f"{key=} {value=}")
7484 self .rr .setValue (key , value )
7585
76- s : NamedArray = self .rr .simulate (start = 0 , end = 5 * 24 * 60 )
77- console .print (type (s ))
78- # self.plot_simulation(s)
79-
80- y : dict [str , float ] = self .losartan_pkpd_parameters (s )
81- return y
82-
8386
8487
85- def losartan_pkpd_parameters (self , s : NamedArray ) -> dict [str , float ]:
86- """This calculation is highly model dependent.
87-
88- This requires units.
89- """
88+ @dataclass
89+ class SBMLSensitivityAnalysis :
90+ """Parent class for sensitivity analysis."""
9091
91- y : dict [str , float ] = {}
92-
93- # pharmacokinetics
94- ureg = UnitRegistry ()
95- Q_ = ureg .Quantity
96-
97- # losartan
98- time = Q_ (s ["time" ], "min" )
99- tcpk = TimecoursePK (
100- time = time ,
101- concentration = Q_ (s ["[Cve_los]" ], "mM" ),
102- substance = "losartan" ,
103- ureg = ureg ,
104- dose = Q_ (10 , "mg" ),
105- )
106- pk_dict = tcpk .pk .to_dict ()
107- # console.print(pk_dict)
108- for pk_key in [
109- "aucinf" ,
110- "cmax" ,
111- "thalf" ,
112- "kel" ,
113- ]:
114- y [f"[Cve_los]_{ pk_key } " ] = pk_dict [pk_key ]
115-
116- # E3174, L158
117- for sid in ["[Cve_e3174]" , "[Cve_l158]" ]:
118- tcpk = TimecoursePK (
119- time = time ,
120- concentration = Q_ (s [sid ], "mM" ),
121- substance = "losartan" ,
122- ureg = ureg ,
123- dose = None ,
124- )
125- pk_dict = tcpk .pk .to_dict ()
126- # console.print(pk_dict)
127- for pk_key in [
128- "aucinf" ,
129- "cmax" ,
130- "thalf" ,
131- "kel" ,
132- ]:
133- y [f"{ sid } _{ pk_key } " ] = pk_dict [pk_key ]
134-
135- # pharmacodynamics
136- for sid , f in [
137- ("[ang1]" , "max" ),
138- ("[ang2]" , "max" ),
139- ("[ren]" , "max" ),
140- ("[ald]" , "min" ),
141- ("SBP" , "min" ),
142- ("DBP" , "min" ),
143- ("MAP" , "min" ),
144- ]:
145- # minimal and maximal value of readout
146- if f == "max" :
147- y [f"{ sid } _max" ] = np .min (s [sid ])
148- elif f == "min" :
149- y [f"{ sid } _min" ] = np .max (s [sid ])
150-
151- return y
152-
153- def _plot_simulation (self , s : NamedArray ) -> None :
154-
155- # plotting
156- from matplotlib import pyplot as plt
157- plt .plot (
158- s ["time" ], s ["[Cve_los]" ],
159- # df["time"], df["[Cve_los]"],
160- marker = "o" ,
161- markeredgecolor = "black" ,
162- color = "tab:blue" ,
163- )
164- plt .show ()
165-
166-
167- def wrapped_run_simulation (self , X , func = losartan_simulation ):
168- # We transpose to obtain each column (the model factors) as separate variables
169- changes : dict [str , float ] = {}
170- for k , key in enumerate (self .names ):
171- changes [key ] = X [k ]
172-
173- # Then call the original model
174- return list (func (self , changes ).values ())
175-
176-
177- def calculate_sensitivity (self ):
178-
179- y = self .losartan_simulation (changes = {})
180- self .outputs = list (y .keys ())
181- self .names = ['BW' ]
182-
183- # Defining the model inputs
184- sp = ProblemSpec ({
185- 'num_vars' : len (self .names ),
186- 'names' : self .names ,
187- 'bounds' : [
188- [50 , 150 ],
189- # [0.003, 0.005]
190- ],
191- "outputs" : self .outputs ,
192- })
193-
194- # Generate samples
195- samples = saltelli .sample (sp , 1024 )
196- sp .set_samples (samples )
197-
198-
199- # Evaluate model
200- # sp.evaluate(wrapped_run_simulation)
201-
202- Y = np .zeros ((samples .shape [0 ], len (self .outputs )))
203- for k , X in enumerate (samples ):
204- print (k )
205- Y [k , :] = self .wrapped_run_simulation (X )
206- sp .set_results (Y )
207-
208-
209- # Perform Analysis
210- Si = sp .analyze (SALib .analyze .sobol )
211- print (Si ['S1' ])
212- print (Si ['ST' ])
213- total_Si , first_Si , second_Si = Si .to_df ()
214- Si .plot ()
215- from matplotlib import pyplot as plt
216- plt .show ()
92+ sensitivity_simulation : SensitivitySimulation
93+
94+ def __init__ (self , sensitivity_simulation : SensitivitySimulation ):
95+ # assign simulation
96+ self .sensitivity_simulation = sensitivity_simulation
97+
98+
99+ # def wrapped_run_simulation(self, X, func=losartan_simulation):
100+ # # We transpose to obtain each column (the model factors) as separate variables
101+ # changes: dict[str, float] = {}
102+ # for k, key in enumerate(self.names):
103+ # changes[key] = X[k]
104+ #
105+ # # Then call the original model
106+ # return list(func(self, changes).values())
107+
108+
109+ # def calculate_sensitivity(self):
110+ #
111+ # y = self.losartan_simulation(changes={})
112+ # self.outputs = list(y.keys())
113+ # self.names = ['BW']
114+ #
115+ # # Defining the model inputs
116+ # sp = ProblemSpec({
117+ # 'num_vars': len(self.names),
118+ # 'names': self.names,
119+ # 'bounds': [
120+ # [50, 150],
121+ # # [0.003, 0.005]
122+ # ],
123+ # "outputs": self.outputs,
124+ # })
125+ #
126+ # # Generate samples
127+ # samples = saltelli.sample(sp, 1024)
128+ # sp.set_samples(samples)
129+ #
130+ #
131+ # # Evaluate model
132+ # # sp.evaluate(wrapped_run_simulation)
133+ #
134+ # Y = np.zeros((samples.shape[0], len(self.outputs)))
135+ # for k, X in enumerate(samples):
136+ # print(k)
137+ # Y[k, :] = self.wrapped_run_simulation(X)
138+ # sp.set_results(Y)
139+ #
140+ #
141+ # # Perform Analysis
142+ # Si = sp.analyze(SALib.analyze.sobol)
143+ # print(Si['S1'])
144+ # print(Si['ST'])
145+ # total_Si, first_Si, second_Si = Si.to_df()
146+ # Si.plot()
147+ # from matplotlib import pyplot as plt
148+ # plt.show()
217149
218150
219151
0 commit comments