vpop-calibration 2.2.8__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.
@@ -0,0 +1,200 @@
1
+ import torch
2
+ import pandas as pd
3
+ import numpy as np
4
+ import uuid
5
+
6
+ from .model.gp import GP
7
+ from .ode import OdeModel
8
+ from .utils import device
9
+
10
+
11
+ class StructuralModel:
12
+ def __init__(
13
+ self,
14
+ parameter_names,
15
+ output_names,
16
+ protocol_arms,
17
+ tasks,
18
+ task_idx_to_output_idx,
19
+ task_idx_to_protocol,
20
+ ):
21
+ """Initialize a structural model
22
+
23
+ Args:
24
+ parameter_names (list[str]): _description_
25
+ output_names (list[str]): _description_
26
+ protocol_arms (list[str]): _description_
27
+ tasks (list[str]): _description_
28
+ task_idx_to_output_idx (list[str]): _description_
29
+ task_idx_to_protocol (list[str]): _description_
30
+ """
31
+ self.parameter_names: list[str] = parameter_names
32
+ self.nb_parameters: int = len(self.parameter_names)
33
+ self.output_names: list[str] = output_names
34
+ self.nb_outputs: int = len(self.output_names)
35
+ self.protocols: list[str] = protocol_arms
36
+ self.nb_protocols: int = len(self.protocols)
37
+ self.tasks: list[str] = tasks
38
+ self.task_idx_to_output_idx: dict[int, int] = task_idx_to_output_idx
39
+ self.task_idx_to_protocol: dict[int, str] = task_idx_to_protocol
40
+
41
+ def simulate(
42
+ self,
43
+ X: torch.Tensor,
44
+ prediction_index: tuple[torch.Tensor, torch.Tensor, torch.Tensor],
45
+ chunks: list[int],
46
+ ) -> tuple[torch.Tensor, torch.Tensor]:
47
+ raise ValueError("Not implemented")
48
+
49
+
50
+ class StructuralGp(StructuralModel):
51
+ def __init__(self, gp_model: GP):
52
+ """Create a structural model from a GP
53
+
54
+ Args:
55
+ gp_model (GP): The trained GP
56
+ """
57
+ # list the GP parameters, except time, as it will be handled differently in the NLME model
58
+ parameter_names = [p for p in gp_model.data.parameter_names if p != "time"]
59
+ super().__init__(
60
+ parameter_names,
61
+ gp_model.data.output_names,
62
+ gp_model.data.protocol_arms,
63
+ gp_model.data.tasks,
64
+ gp_model.data.task_idx_to_output_idx,
65
+ gp_model.data.task_idx_to_protocol,
66
+ )
67
+ self.gp_model = gp_model
68
+
69
+ def simulate(
70
+ self,
71
+ X: torch.Tensor,
72
+ prediction_index: tuple[torch.Tensor, torch.Tensor, torch.Tensor],
73
+ chunks: list[int],
74
+ ) -> tuple[torch.Tensor, torch.Tensor]:
75
+ # X contains [nb_patients, nb_timesteps, nb_params + 1]
76
+ # Simulate the GP
77
+ (nb_patients, nb_timesteps, nb_params) = X.shape
78
+ X_vertical = X.view(-1, nb_params)
79
+ out_cat, var_cat = self.gp_model.predict_wide_scaled(X_vertical)
80
+ out_wide = out_cat.view(nb_patients, nb_timesteps, -1)
81
+ var_wide = var_cat.view(nb_patients, nb_timesteps, -1)
82
+
83
+ # Retrieve the necessary rows and columns to transform into a single column tensor
84
+ y = out_wide[prediction_index]
85
+ var = var_wide[prediction_index]
86
+ return y, var
87
+
88
+
89
+ class StructuralOdeModel(StructuralModel):
90
+ def __init__(
91
+ self,
92
+ ode_model: OdeModel,
93
+ protocol_design: pd.DataFrame,
94
+ init_conditions: np.ndarray,
95
+ ):
96
+ self.ode_model = ode_model
97
+ protocol_arms = protocol_design["protocol_arm"].drop_duplicates().to_list()
98
+ self.protocol_design = protocol_design
99
+ output_names: list[str] = self.ode_model.variable_names
100
+ tasks: list[str] = [
101
+ output + "_" + protocol
102
+ for protocol in protocol_arms
103
+ for output in output_names
104
+ ]
105
+ # Map tasks to output names
106
+ task_to_output = {
107
+ output_name + "_" + protocol_arm: output_name
108
+ for output_name in output_names
109
+ for protocol_arm in protocol_arms
110
+ }
111
+ # Map task index to output index
112
+ task_idx_to_output_idx = {
113
+ tasks.index(k): output_names.index(v) for k, v in task_to_output.items()
114
+ }
115
+ # Map task to protocol arm
116
+ task_to_protocol = {
117
+ output_name + "_" + protocol_arm: protocol_arm
118
+ for output_name in output_names
119
+ for protocol_arm in protocol_arms
120
+ }
121
+ # Map task index to protocol arm
122
+ task_idx_to_protocol = {tasks.index(k): v for k, v in task_to_protocol.items()}
123
+
124
+ # list the structural model parameters: the protocol overrides are ignored
125
+ self.protocol_overrides = self.protocol_design.drop(
126
+ columns="protocol_arm"
127
+ ).columns.to_list()
128
+ parameter_names = list(
129
+ set(self.ode_model.param_names) - set(self.protocol_overrides)
130
+ )
131
+ self.nb_protocol_overrides = len(self.protocol_overrides)
132
+
133
+ super().__init__(
134
+ parameter_names,
135
+ output_names,
136
+ protocol_arms,
137
+ tasks,
138
+ task_idx_to_output_idx,
139
+ task_idx_to_protocol,
140
+ )
141
+
142
+ self.init_cond_df = pd.DataFrame(
143
+ data=[init_conditions], columns=self.ode_model.initial_cond_names
144
+ )
145
+
146
+ def simulate(
147
+ self,
148
+ X: torch.Tensor,
149
+ prediction_index: tuple[torch.Tensor, torch.Tensor, torch.Tensor],
150
+ chunks: list[int],
151
+ ) -> tuple[torch.Tensor, torch.Tensor]:
152
+ (nb_patients, nb_timesteps, nb_params) = X.shape
153
+ list_X = [ind_X for ind_X in X]
154
+ patient_index_full, rows_full, tasks_full = prediction_index
155
+ list_rows = torch.split(rows_full, chunks)
156
+ list_tasks = torch.split(tasks_full, chunks)
157
+
158
+ input_df_list = []
159
+ for ind_X, ind_rows, ind_tasks in zip(list_X, list_rows, list_tasks):
160
+ temp_id = str(uuid.uuid4())
161
+ # Extract the parameters and time values
162
+ params = ind_X.index_select(0, ind_rows).cpu().detach().numpy()
163
+ # Extract the task order
164
+ task_index = ind_tasks.cpu().detach().numpy()
165
+ # Format the data inputs
166
+ # This step is where the order of parameters is implicit
167
+ input_df_temp = pd.DataFrame(
168
+ data=params, columns=self.parameter_names + ["time"]
169
+ )
170
+ # The passed params include the _global_ time steps
171
+ # Filter the time steps that we actually want for this patient
172
+ input_df_temp = input_df_temp.iloc[ind_rows.cpu().numpy()]
173
+ # Add the task index as a temporary column
174
+ input_df_temp["task_index"] = task_index
175
+ # Deduce protocol arm and output name from task index
176
+ input_df_temp["protocol_arm"] = input_df_temp["task_index"].apply(
177
+ lambda t: self.task_idx_to_protocol[t]
178
+ )
179
+ input_df_temp["output_name"] = input_df_temp["task_index"].apply(
180
+ lambda t: self.output_names[self.task_idx_to_output_idx[t]]
181
+ )
182
+ # Remove the unnecessary task index column
183
+ input_df_temp = input_df_temp.drop(columns=["task_index"])
184
+ input_df_temp["id"] = temp_id
185
+ # Add the protocol overrides
186
+ if self.nb_protocol_overrides > 0:
187
+ input_df_temp = input_df_temp.merge(
188
+ self.protocol_design, how="left", on=["protocol_arm"]
189
+ )
190
+ # Add the initial conditions
191
+ input_df_temp = input_df_temp.merge(self.init_cond_df, how="cross")
192
+ input_df_list.append(input_df_temp)
193
+
194
+ full_input = pd.concat(input_df_list)
195
+ # Simulate the ODE model
196
+ output_df = self.ode_model.simulate_model(full_input)
197
+ # Convert back to tensor
198
+ out_tensor = torch.as_tensor(output_df["predicted_value"].values, device=device)
199
+ out_var = torch.zeros_like(out_tensor, device=device)
200
+ return out_tensor, out_var
@@ -0,0 +1,11 @@
1
+ import numpy as np
2
+ import torch
3
+
4
+ # Initialize the seeds for all random operators used in the tests
5
+ np_rng = np.random.default_rng(42)
6
+ np.random.seed(42)
7
+ torch.manual_seed(0)
8
+ saem_mi_maxfun = 1
9
+ multithreaded = False
10
+
11
+ __all__ = ["np_rng", "saem_mi_maxfun", "multithreaded"]
@@ -0,0 +1,21 @@
1
+ import pandas as pd
2
+
3
+ from vpop_calibration.model.data import TrainingDataSet
4
+ from vpop_calibration.test import *
5
+
6
+ training_df = pd.DataFrame(
7
+ {
8
+ "id": ["1", "1", "2", "2"],
9
+ "protocol_arm": ["arm-A"] * 4,
10
+ "output_name": ["s1", "s2", "s1", "s2"],
11
+ "k1": [1.0, 1.0, 2.0, 2.0],
12
+ "value": [0.0, 1.0, 2.0, 3.0],
13
+ }
14
+ )
15
+
16
+
17
+ def test_loading():
18
+ TrainingDataSet(training_df, ["k1"], 1.0)
19
+ TrainingDataSet(training_df, ["k1"], 1.0, data_already_normalized=True)
20
+ TrainingDataSet(training_df, ["k1"], 1.0, log_inputs=["k1"], log_outputs=["s1"])
21
+ TrainingDataSet(training_df.drop(columns={"protocol_arm"}), ["k1"], 0.5)
@@ -0,0 +1,89 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ import pickle
4
+
5
+ from vpop_calibration import *
6
+ from vpop_calibration.test import *
7
+
8
+ # Create a dummy training data frame
9
+ patients = ["patient-01", "patient-02"]
10
+ nb_patients = len(patients)
11
+ obsIds = ["obs-01", "obs-02"]
12
+ protocol_arms = ["arm-A", "arm-B"]
13
+ time_steps = np.arange(0, 10.0, 1.0)
14
+ patient_descriptors = ["k1", "k2", "k3"]
15
+ gp_params = [*patient_descriptors, "time"]
16
+ training_df = pd.DataFrame({"id": patients})
17
+ for descriptor in patient_descriptors:
18
+ training_df[descriptor] = np_rng.normal(0, 1, nb_patients)
19
+ training_df = training_df.merge(
20
+ pd.DataFrame({"protocol_arm": protocol_arms}), how="cross"
21
+ )
22
+ training_df = training_df.merge(pd.DataFrame({"time": time_steps}), how="cross")
23
+ training_df = training_df.merge(pd.DataFrame({"output_name": obsIds}), how="cross")
24
+ training_df["value"] = np_rng.normal(0, 1, training_df.shape[0])
25
+ training_df_bootstrapped = training_df.sample(frac=0.5, random_state=np_rng)
26
+
27
+ implemented_kernels = ["RBF", "SMK", "Matern"]
28
+ implemented_var_strat = ["IMV", "LMCV"]
29
+ implemented_mll = ["ELBO", "PLL"]
30
+ deep_or_not = [True, False]
31
+
32
+ model_file = "vpop_calibration/test/gp_model_for_tests.pkl"
33
+
34
+
35
+ def gp_init_flavor(var_strat, kernel, deep_kernel, mll):
36
+ gp = GP(
37
+ training_df,
38
+ gp_params,
39
+ var_strat=var_strat,
40
+ mll=mll,
41
+ kernel=kernel,
42
+ deep_kernel=deep_kernel,
43
+ nb_latents=2,
44
+ nb_features=5,
45
+ num_mixtures=3,
46
+ nb_training_iter=2,
47
+ )
48
+ gp.train()
49
+
50
+
51
+ def test_all_gp_flavors():
52
+ for deep_kernel in deep_or_not:
53
+ for kernel in implemented_kernels:
54
+ for var_strat in implemented_var_strat:
55
+ for mll in implemented_mll:
56
+ gp_init_flavor(var_strat, kernel, deep_kernel, mll)
57
+
58
+
59
+ def test_batching_1():
60
+ gp = GP(training_df, gp_params, nb_training_iter=2)
61
+ gp.train(mini_batching=True, mini_batch_size=8)
62
+
63
+
64
+ def test_batching_2():
65
+ gp = GP(training_df, gp_params, nb_training_iter=2)
66
+ gp.train(mini_batching=True, mini_batch_size=None)
67
+
68
+
69
+ def test_eval_with_valid():
70
+ gp = GP(training_df, gp_params, nb_training_iter=2)
71
+ gp.eval_perf()
72
+
73
+
74
+ def test_eval_no_valid():
75
+ gp = GP(training_df, gp_params, nb_training_iter=2, training_proportion=1)
76
+ gp.eval_perf()
77
+
78
+
79
+ def test_gp_incomplete_data():
80
+ gp = GP(training_df_bootstrapped, gp_params, nb_training_iter=2)
81
+ gp.train()
82
+ gp.train(mini_batching=True, mini_batch_size=8)
83
+ gp.eval_perf()
84
+
85
+
86
+ def test_gp_pickle():
87
+ gp = GP(training_df, gp_params)
88
+ with open(model_file, "wb") as file:
89
+ pickle.dump(gp, file)
@@ -0,0 +1,175 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import pickle
4
+ import uuid
5
+
6
+ from vpop_calibration import *
7
+ from vpop_calibration.test import *
8
+
9
+
10
+ def equations_with_abs(t, y, k_a, k_12, k_21, k_el):
11
+ # y[0] is A_absorption, y[1] is A_central, y[2] is A_peripheral
12
+ A_absorption, A_central, A_peripheral = y[0], y[1], y[2]
13
+ dA_absorption_dt = -k_a * A_absorption
14
+ dA_central_dt = (
15
+ k_a * A_absorption + k_21 * A_peripheral - k_12 * A_central - k_el * A_central
16
+ )
17
+ dA_peripheral_dt = k_12 * A_central - k_21 * A_peripheral
18
+
19
+ ydot = [dA_absorption_dt, dA_central_dt, dA_peripheral_dt]
20
+ return ydot
21
+
22
+
23
+ variable_names = ["A0", "A1", "A2"]
24
+ parameter_names = ["k_a", "k_12", "k_21", "k_el"]
25
+
26
+ tmax = 24.0
27
+ initial_conditions = np.array([10.0, 0.0, 0.0])
28
+
29
+ protocol_design = pd.DataFrame(
30
+ {"protocol_arm": ["arm-A", "arm-B"], "k_el": [0.5, 10.0]}
31
+ )
32
+ nb_protocols = len(protocol_design)
33
+
34
+ pk_two_compartments_model = OdeModel(
35
+ equations_with_abs, variable_names, parameter_names, multithreaded=multithreaded
36
+ )
37
+
38
+ model_file = "vpop_calibration/test/gp_model_for_tests.pkl"
39
+
40
+
41
+ def test_gp_training():
42
+ # Define the ode model
43
+
44
+ nb_timesteps = 3
45
+ time_steps = np.linspace(0.0, tmax, nb_timesteps)
46
+
47
+ log_nb_patients = 1
48
+ param_ranges = {
49
+ "k_12": {"low": -2.0, "high": 0.0, "log": True},
50
+ "k_21": {"low": -1.0, "high": 0.3, "log": True},
51
+ "k_a": {"low": -1.0, "high": 0.0, "log": True},
52
+ }
53
+
54
+ dataset = simulate_dataset_from_ranges(
55
+ pk_two_compartments_model,
56
+ log_nb_patients,
57
+ param_ranges,
58
+ initial_conditions,
59
+ protocol_design,
60
+ None,
61
+ None,
62
+ time_steps,
63
+ )
64
+
65
+ learned_ode_params = list(param_ranges.keys())
66
+ descriptors = learned_ode_params + ["time"]
67
+
68
+ # initiate our GP class
69
+ myGP = GP(
70
+ dataset,
71
+ descriptors,
72
+ var_strat="IMV", # either IMV (Independent Multitask Variational) or LMCV (Linear Model of Coregionalization Variational)
73
+ kernel="RBF", # Either RBF or SMK
74
+ data_already_normalized=False, # default
75
+ nb_inducing_points=10,
76
+ mll="ELBO", # default, otherwise PLL
77
+ nb_training_iter=1,
78
+ training_proportion=0.7,
79
+ learning_rate=0.1,
80
+ lr_decay=0.99,
81
+ jitter=1e-6,
82
+ log_inputs=learned_ode_params,
83
+ )
84
+ myGP.train()
85
+ myGP.plot_loss()
86
+ myGP.plot_obs_vs_predicted("training")
87
+ myGP.plot_individual_solution(0)
88
+ myGP.plot_all_solutions("training")
89
+ with open(model_file, "wb") as file:
90
+ pickle.dump(myGP, file)
91
+
92
+
93
+ def test_gp_saem():
94
+ time_span_rw = (0, 24)
95
+ nb_steps_rw = 2
96
+
97
+ # For each output and for each patient, give a list of time steps to be simulated
98
+ time_steps_rw = np.linspace(time_span_rw[0], time_span_rw[1], nb_steps_rw).tolist()
99
+
100
+ # Parameter definitions
101
+ true_log_MI = {"k_21": 0.0}
102
+ true_log_PDU = {
103
+ "k_12": {"mean": -1.0, "sd": 0.25},
104
+ }
105
+ error_model_type = "additive"
106
+ true_res_var = [0.5, 0.02, 0.1]
107
+ true_covariate_map = {
108
+ "k_12": {"foo": {"coef": "cov_foo_k12", "value": 0.2}},
109
+ }
110
+
111
+ # Create a patient data frame
112
+ # It should contain at the very minimum one `id` per patient
113
+ nb_patients = 3
114
+ patients_df = pd.DataFrame({"id": [str(uuid.uuid4()) for _ in range(nb_patients)]})
115
+ patients_df["protocol_arm"] = np_rng.binomial(1, 0.5, nb_patients)
116
+ patients_df["protocol_arm"] = patients_df["protocol_arm"].apply(
117
+ lambda x: "arm-A" if x == 0 else "arm-B"
118
+ )
119
+ patients_df["k_a"] = np_rng.lognormal(-1, 0.1, nb_patients)
120
+ patients_df["foo"] = np_rng.lognormal(0.1, 0.1, nb_patients)
121
+
122
+ print(f"Simulating {nb_patients} patients on {nb_protocols} protocol arms")
123
+ obs_df = simulate_dataset_from_omega(
124
+ pk_two_compartments_model,
125
+ protocol_design,
126
+ time_steps_rw,
127
+ initial_conditions,
128
+ true_log_MI,
129
+ true_log_PDU,
130
+ error_model_type,
131
+ true_res_var,
132
+ true_covariate_map,
133
+ patients_df,
134
+ )
135
+
136
+ # Initial pop estimates
137
+ # Parameter definitions
138
+ init_log_MI = {"k_21": -1.0}
139
+ init_log_PDU = {
140
+ "k_12": {"mean": -0.1, "sd": 0.1},
141
+ }
142
+ error_model_type = "additive"
143
+ init_res_var = [0.1, 0.05, 0.5]
144
+ init_covariate_map = {
145
+ "k_12": {"foo": {"coef": "cov_foo_k12", "value": -0.1}},
146
+ }
147
+
148
+ with open(model_file, "rb") as file:
149
+ myGP = pickle.load(file)
150
+ # Create a structural model
151
+ structural_gp = StructuralGp(myGP)
152
+ # Create a NLME moedl
153
+ nlme_surrogate = NlmeModel(
154
+ structural_gp,
155
+ patients_df,
156
+ init_log_MI,
157
+ init_log_PDU,
158
+ init_res_var,
159
+ init_covariate_map,
160
+ error_model_type,
161
+ )
162
+ # Create an optimizer: here we use SAEM
163
+ optimizer = PySaem(
164
+ nlme_surrogate,
165
+ obs_df,
166
+ nb_phase1_iterations=1,
167
+ nb_phase2_iterations=0,
168
+ optim_max_fun=saem_mi_maxfun,
169
+ )
170
+
171
+ optimizer.run()
172
+ optimizer.continue_iterating(nb_add_iters_ph1=1, nb_add_iters_ph2=1)
173
+ optimizer.plot_convergence_history()
174
+ plot_map_estimates(nlme_surrogate)
175
+ check_surrogate_validity_gp(nlme_surrogate)
@@ -0,0 +1,121 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import uuid
4
+
5
+ from vpop_calibration import *
6
+ from vpop_calibration.test import *
7
+
8
+
9
+ def equations_with_abs(t, y, k_a, k_12, k_21, k_el):
10
+ # y[0] is A_absorption, y[1] is A_central, y[2] is A_peripheral
11
+ A_absorption, A_central, A_peripheral = y[0], y[1], y[2]
12
+ dA_absorption_dt = -k_a * A_absorption
13
+ dA_central_dt = (
14
+ k_a * A_absorption + k_21 * A_peripheral - k_12 * A_central - k_el * A_central
15
+ )
16
+ dA_peripheral_dt = k_12 * A_central - k_21 * A_peripheral
17
+
18
+ ydot = [dA_absorption_dt, dA_central_dt, dA_peripheral_dt]
19
+ return ydot
20
+
21
+
22
+ variable_names = ["A0", "A1", "A2"]
23
+ parameter_names = ["k_a", "k_12", "k_21", "k_el"]
24
+
25
+ tmax = 24.0
26
+ initial_conditions = np.array([10.0, 0.0, 0.0])
27
+
28
+ protocol_design = pd.DataFrame(
29
+ {"protocol_arm": ["arm-A", "arm-B"], "k_el": [0.5, 10.0]}
30
+ )
31
+ nb_protocols = len(protocol_design)
32
+
33
+ pk_two_compartments_model = OdeModel(
34
+ equations_with_abs, variable_names, parameter_names, multithreaded=multithreaded
35
+ )
36
+
37
+ model_file = "vpop_calibration/test/gp_model_for_tests.pkl"
38
+
39
+
40
+ def test_ode_saem():
41
+ time_span_rw = (0, 24)
42
+ nb_steps_rw = 2
43
+
44
+ # For each output and for each patient, give a list of time steps to be simulated
45
+ time_steps_rw = np.linspace(time_span_rw[0], time_span_rw[1], nb_steps_rw).tolist()
46
+
47
+ # Parameter definitions
48
+ true_log_MI = {"k_21": 0.0}
49
+ true_log_PDU = {
50
+ "k_12": {"mean": -1.0, "sd": 0.25},
51
+ }
52
+ error_model_type = "additive"
53
+ true_res_var = [0.5, 0.02, 0.1]
54
+ true_covariate_map = {
55
+ "k_12": {"foo": {"coef": "cov_foo_k12", "value": 0.2}},
56
+ }
57
+
58
+ # Create a patient data frame
59
+ # It should contain at the very minimum one `id` per patient
60
+ nb_patients = 3
61
+ patients_df = pd.DataFrame({"id": [str(uuid.uuid4()) for _ in range(nb_patients)]})
62
+ patients_df["protocol_arm"] = np_rng.binomial(1, 0.5, nb_patients)
63
+ patients_df["protocol_arm"] = patients_df["protocol_arm"].apply(
64
+ lambda x: "arm-A" if x == 0 else "arm-B"
65
+ )
66
+ patients_df["k_a"] = np_rng.lognormal(-1, 0.1, nb_patients)
67
+ patients_df["foo"] = np_rng.lognormal(0.1, 0.1, nb_patients)
68
+
69
+ print(f"Simulating {nb_patients} patients on {nb_protocols} protocol arms")
70
+ obs_df = simulate_dataset_from_omega(
71
+ pk_two_compartments_model,
72
+ protocol_design,
73
+ time_steps_rw,
74
+ initial_conditions,
75
+ true_log_MI,
76
+ true_log_PDU,
77
+ error_model_type,
78
+ true_res_var,
79
+ true_covariate_map,
80
+ patients_df,
81
+ )
82
+
83
+ # Initial pop estimates
84
+ # Parameter definitions
85
+ init_log_MI = {"k_12": -1.0}
86
+ init_log_PDU = {
87
+ "k_21": {"mean": -1.0, "sd": 0.2},
88
+ }
89
+ error_model_type = "additive"
90
+ init_res_var = [0.1, 0.05, 0.5]
91
+ init_covariate_map = {
92
+ "k_21": {"foo": {"coef": "cov_foo_k12", "value": -0.1}},
93
+ }
94
+
95
+ # Create a structural model
96
+ structural_ode = StructuralOdeModel(
97
+ pk_two_compartments_model, protocol_design, initial_conditions
98
+ )
99
+ # Create a NLME moedl
100
+ nlme = NlmeModel(
101
+ structural_ode,
102
+ patients_df,
103
+ init_log_MI,
104
+ init_log_PDU,
105
+ init_res_var,
106
+ init_covariate_map,
107
+ error_model_type,
108
+ )
109
+ # Create an optimizer: here we use SAEM
110
+ optimizer = PySaem(
111
+ nlme,
112
+ obs_df,
113
+ nb_phase1_iterations=1,
114
+ nb_phase2_iterations=0,
115
+ optim_max_fun=saem_mi_maxfun,
116
+ )
117
+
118
+ optimizer.run()
119
+ optimizer.continue_iterating(nb_add_iters_ph1=0, nb_add_iters_ph2=1)
120
+ optimizer.plot_convergence_history()
121
+ plot_map_estimates(nlme)
@@ -0,0 +1,9 @@
1
+ import os
2
+ import torch
3
+
4
+ if "IS_PYTEST_RUNNING" in os.environ:
5
+ smoke_test = True
6
+ else:
7
+ smoke_test = False
8
+
9
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
@@ -0,0 +1,50 @@
1
+ import pandas as pd
2
+ import uuid
3
+ import numpy as np
4
+ from scipy.stats.qmc import Sobol, scale
5
+
6
+
7
+ def generate_vpop_from_ranges(
8
+ log_nb_individuals: int, param_ranges: dict[str, dict[str, float | bool]]
9
+ ) -> pd.DataFrame:
10
+ """Generate a vpop of patients from parameter ranges using Sobol sequences
11
+
12
+ Args:
13
+ log_nb_individuals (int): The vpop size will be 2^log_nb_individuals
14
+ param_ranges (dict[str, dict[str, float | bool]]): One entry for each parameter to be explored
15
+ - `param_name`: {`low`: float, `high`: float, `log`: bool}. Turn `log` to true to define log-scaled ranges
16
+
17
+ Returns:
18
+ pd.DataFrame: A set of patients with a generated `id`, and a column per descriptor
19
+
20
+ Note:
21
+ This method may be called with an empty dict, to return a list of patient ids.
22
+ """
23
+
24
+ nb_individuals = np.power(2, log_nb_individuals)
25
+ params_to_explore = list(param_ranges.keys())
26
+ nb_parameters = len(params_to_explore)
27
+ if nb_parameters != 0:
28
+
29
+ # Create a sobol sampler to generate parameter values
30
+ sobol_engine = Sobol(d=nb_parameters, scramble=True)
31
+ sobol_sequence = sobol_engine.random_base2(log_nb_individuals)
32
+ samples = scale(
33
+ sobol_sequence,
34
+ [param_ranges[param_name]["low"] for param_name in params_to_explore],
35
+ [param_ranges[param_name]["high"] for param_name in params_to_explore],
36
+ )
37
+
38
+ # Handle log-scaled parameters
39
+ for j, param_name in enumerate(params_to_explore):
40
+ if param_ranges[param_name]["log"] == True:
41
+ samples[:, j] = np.exp(samples[:, j])
42
+ # Create the full data frame of patient descriptors
43
+ patients_df = pd.DataFrame(data=samples, columns=params_to_explore)
44
+ else:
45
+ # No parameter requested, create empty data frame
46
+ patients_df = pd.DataFrame()
47
+
48
+ ids = [str(uuid.uuid4()) for _ in range(nb_individuals)]
49
+ patients_df.insert(0, "id", ids)
50
+ return patients_df