certaraiq 2.2.3__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.
- certaraiq/__init__.py +8 -0
- certaraiq/_exports.py +8 -0
- certaraiq/_helper/__init__.py +0 -0
- certaraiq/_helper/argument_parser.py +157 -0
- certaraiq/_helper/covariance_matrix.py +174 -0
- certaraiq/_helper/create_map.py +180 -0
- certaraiq/_helper/display.py +24 -0
- certaraiq/_helper/get_dose_schedules.py +32 -0
- certaraiq/_helper/get_model.py +107 -0
- certaraiq/_helper/get_table.py +70 -0
- certaraiq/_helper/helper.py +189 -0
- certaraiq/_helper/initialize_parameter_table.py +25 -0
- certaraiq/_helper/nested_map.py +64 -0
- certaraiq/_helper/parameter.py +16 -0
- certaraiq/_helper/parse_output_times.py +48 -0
- certaraiq/_helper/parse_schedule.py +131 -0
- certaraiq/_helper/parser.py +84 -0
- certaraiq/_helper/posterior_sample_list_diagnostics.py +28 -0
- certaraiq/_helper/profile_likelihood.py +286 -0
- certaraiq/_helper/validate_columns.py +207 -0
- certaraiq/_labelset.py +75 -0
- certaraiq/_optimize.py +1152 -0
- certaraiq/_scan.py +108 -0
- certaraiq/_scan_ast.py +261 -0
- certaraiq/_sdk/__init__.py +3 -0
- certaraiq/_sdk/array.py +74 -0
- certaraiq/_sdk/client_interface.py +449 -0
- certaraiq/_sdk/contract.py +54 -0
- certaraiq/_sdk/data_frame.py +248 -0
- certaraiq/_sdk/data_pipe.py +304 -0
- certaraiq/_sdk/data_pipe_builder.py +346 -0
- certaraiq/_sdk/data_pipe_result.py +35 -0
- certaraiq/_sdk/data_type.py +137 -0
- certaraiq/_sdk/data_type_parser.py +23 -0
- certaraiq/_sdk/dataclass_helpers.py +19 -0
- certaraiq/_sdk/distribution_sample.py +152 -0
- certaraiq/_sdk/exceptions.py +15 -0
- certaraiq/_sdk/expression.py +5 -0
- certaraiq/_sdk/job.py +120 -0
- certaraiq/_sdk/legacy.py +294 -0
- certaraiq/_sdk/likelihood/__init__.py +30 -0
- certaraiq/_sdk/likelihood/allen.py +65 -0
- certaraiq/_sdk/likelihood/base.py +22 -0
- certaraiq/_sdk/likelihood/distributions/__init__.py +35 -0
- certaraiq/_sdk/likelihood/distributions/base.py +23 -0
- certaraiq/_sdk/likelihood/distributions/distributions.py +36 -0
- certaraiq/_sdk/likelihood/distributions/histogram.py +46 -0
- certaraiq/_sdk/likelihood/distributions/loguniform.py +46 -0
- certaraiq/_sdk/likelihood/distributions/multivariate_lognormal.py +45 -0
- certaraiq/_sdk/likelihood/distributions/multivariate_normal.py +45 -0
- certaraiq/_sdk/likelihood/distributions/uniform.py +46 -0
- certaraiq/_sdk/likelihood/measurements.py +75 -0
- certaraiq/_sdk/ode_fisher_information_matrix.py +15 -0
- certaraiq/_sdk/ode_gradient.py +17 -0
- certaraiq/_sdk/ode_measurement_likelihood_sample.py +16 -0
- certaraiq/_sdk/ode_model.py +81 -0
- certaraiq/_sdk/ode_model_reference.py +39 -0
- certaraiq/_sdk/ode_optimization.py +40 -0
- certaraiq/_sdk/ode_optimization_batch.py +39 -0
- certaraiq/_sdk/ode_optimization_configuration.py +78 -0
- certaraiq/_sdk/ode_optimization_result.py +21 -0
- certaraiq/_sdk/ode_parameter_posterior_sample.py +43 -0
- certaraiq/_sdk/ode_posterior_sample_configuration.py +36 -0
- certaraiq/_sdk/ode_prediction.py +22 -0
- certaraiq/_sdk/ode_proposal_population_sample.py +45 -0
- certaraiq/_sdk/ode_residual.py +23 -0
- certaraiq/_sdk/ode_residual_batch.py +26 -0
- certaraiq/_sdk/ode_simulation.py +74 -0
- certaraiq/_sdk/ode_simulation_batch.py +47 -0
- certaraiq/_sdk/ode_value.py +26 -0
- certaraiq/_sdk/ode_virtual_population_sample.py +42 -0
- certaraiq/_sdk/progress.py +57 -0
- certaraiq/_sdk/qsp_designer_model/__init__.py +2 -0
- certaraiq/_sdk/qsp_designer_model/from_bytes.py +20 -0
- certaraiq/_sdk/qsp_designer_model/get_all_subclasses.py +14 -0
- certaraiq/_sdk/qsp_designer_model/indexing.py +34 -0
- certaraiq/_sdk/qsp_designer_model/metadata.py +194 -0
- certaraiq/_sdk/qsp_designer_model/model.py +558 -0
- certaraiq/_sdk/qsp_designer_model/simulation_configuration.py +16 -0
- certaraiq/_sdk/reaction_model.py +308 -0
- certaraiq/_sdk/scenario.py +96 -0
- certaraiq/_sdk/solver_configuration.py +99 -0
- certaraiq/_sdk/status.py +9 -0
- certaraiq/_sdk/time_course.py +32 -0
- certaraiq/_sdk/times.py +35 -0
- certaraiq/_sdk/to_latex_ode_model.py +53 -0
- certaraiq/_sdk/to_matlab_ode_simulation.py +55 -0
- certaraiq/_sdk/to_simbiology_ode_simulation.py +55 -0
- certaraiq/_simulate.py +599 -0
- certaraiq/_units/ast.py +139 -0
- certaraiq/_units/comparison.py +15 -0
- certaraiq/_units/deparser.py +64 -0
- certaraiq/_units/parser.py +58 -0
- certaraiq/_units/to_pint.py +52 -0
- certaraiq/_units/valid_units.py +64 -0
- certaraiq/_vpop.py +658 -0
- certaraiq-2.2.3.dist-info/METADATA +55 -0
- certaraiq-2.2.3.dist-info/RECORD +99 -0
- certaraiq-2.2.3.dist-info/WHEEL +4 -0
certaraiq/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from ._exports import *
|
|
2
|
+
from ._helper.helper import linspace, logspace
|
|
3
|
+
from ._helper.initialize_parameter_table import initialize_parameter_table
|
|
4
|
+
from ._optimize import optimize
|
|
5
|
+
from ._scan import fold_scan, value_scan
|
|
6
|
+
from ._sdk.data_pipe_builder import concatenate_columns, concatenate_rows
|
|
7
|
+
from ._simulate import simulate
|
|
8
|
+
from ._vpop import virtual_population
|
certaraiq/_exports.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from inspect import Parameter
|
|
3
|
+
from typing import Any, Generic
|
|
4
|
+
|
|
5
|
+
from parsita import Parser, lit, reg
|
|
6
|
+
from parsita.state import Continue, Output, Reader, State
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True, slots=True)
|
|
10
|
+
class ArgumentParserParameter(Generic[Output]):
|
|
11
|
+
name: str
|
|
12
|
+
parser: Parser[str, Output]
|
|
13
|
+
default: Output = Parameter.empty
|
|
14
|
+
|
|
15
|
+
def __repr__(self):
|
|
16
|
+
if self.default == Parameter.empty:
|
|
17
|
+
return f"{self.__class__.__name__}({self.name}, {self.parser.name_or_repr()})"
|
|
18
|
+
else:
|
|
19
|
+
return f"{self.__class__.__name__}({self.name}, {self.parser.name_or_repr()}, {self.default})"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ArgumentParser(Parser[str, dict[str, Any]]):
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
positional_only: list[ArgumentParserParameter] = [],
|
|
26
|
+
keyword_only: list[ArgumentParserParameter] = [],
|
|
27
|
+
*,
|
|
28
|
+
keyword: Parser = reg(r"[A-Za-z_][A-Za-z_0-9]*"),
|
|
29
|
+
separator: Parser = lit(","),
|
|
30
|
+
equals: Parser = lit("="),
|
|
31
|
+
):
|
|
32
|
+
super().__init__()
|
|
33
|
+
self.positional_only = positional_only
|
|
34
|
+
self.keyword_only = keyword_only
|
|
35
|
+
self.separator = separator
|
|
36
|
+
self.equals = equals
|
|
37
|
+
self.keyword = keyword
|
|
38
|
+
self.keyword_only_by_name = {parameter.name: parameter for parameter in keyword_only}
|
|
39
|
+
|
|
40
|
+
def _consume(self, state: State, reader: Reader[str]):
|
|
41
|
+
output_positional = {}
|
|
42
|
+
output_keyword = {}
|
|
43
|
+
remainder = reader
|
|
44
|
+
|
|
45
|
+
# no argument
|
|
46
|
+
if len(self.keyword_only) == 0 and len(self.positional_only) == 0:
|
|
47
|
+
return Continue(remainder, {})
|
|
48
|
+
|
|
49
|
+
# positional arguments
|
|
50
|
+
for param in self.positional_only:
|
|
51
|
+
status = param.parser.consume(state, remainder)
|
|
52
|
+
if isinstance(status, Continue):
|
|
53
|
+
remainder = status.remainder
|
|
54
|
+
else:
|
|
55
|
+
break
|
|
56
|
+
|
|
57
|
+
output_positional[param.name] = status.value
|
|
58
|
+
|
|
59
|
+
status = self.separator.consume(state, remainder)
|
|
60
|
+
if isinstance(status, Continue):
|
|
61
|
+
remainder = status.remainder
|
|
62
|
+
else:
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
# keyword arguments
|
|
66
|
+
if len(self.keyword_only) > 0:
|
|
67
|
+
while True:
|
|
68
|
+
# if keywords are following by positional, the comma after positional is not optional anymore
|
|
69
|
+
if len(self.positional_only) > 0 and status is None:
|
|
70
|
+
break
|
|
71
|
+
status = self.keyword.consume(state, remainder)
|
|
72
|
+
if isinstance(status, Continue):
|
|
73
|
+
keyword_name = status.value
|
|
74
|
+
if keyword_name not in self.keyword_only_by_name.keys():
|
|
75
|
+
break
|
|
76
|
+
remainder = status.remainder
|
|
77
|
+
else:
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
status = self.equals.consume(state, remainder)
|
|
81
|
+
if isinstance(status, Continue):
|
|
82
|
+
remainder = status.remainder
|
|
83
|
+
else:
|
|
84
|
+
break
|
|
85
|
+
|
|
86
|
+
status = self.keyword_only_by_name[keyword_name].parser.consume(state, remainder)
|
|
87
|
+
if isinstance(status, Continue):
|
|
88
|
+
remainder = status.remainder
|
|
89
|
+
else:
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
output_keyword[keyword_name] = status.value
|
|
93
|
+
|
|
94
|
+
status = self.separator.consume(state, remainder)
|
|
95
|
+
if isinstance(status, Continue):
|
|
96
|
+
remainder = status.remainder
|
|
97
|
+
else:
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
must_positional_argument = {
|
|
101
|
+
parameter.name for parameter in self.positional_only if parameter.default is Parameter.empty
|
|
102
|
+
}
|
|
103
|
+
provided_positional_argument = set(output_positional.keys())
|
|
104
|
+
|
|
105
|
+
must_keyword_arguments = {
|
|
106
|
+
parameter.name for parameter in self.keyword_only if parameter.default is Parameter.empty
|
|
107
|
+
}
|
|
108
|
+
provided_keyword_arguments = set(output_keyword.keys())
|
|
109
|
+
|
|
110
|
+
if not must_positional_argument.issubset(provided_positional_argument):
|
|
111
|
+
state.register_failure(
|
|
112
|
+
f"{must_positional_argument} positional arguments but {provided_positional_argument} are provided",
|
|
113
|
+
reader,
|
|
114
|
+
)
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
if not must_keyword_arguments.issubset(provided_keyword_arguments):
|
|
118
|
+
state.register_failure(
|
|
119
|
+
f"{must_keyword_arguments} keyword arguments but {provided_keyword_arguments} are provided", reader
|
|
120
|
+
)
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
for i, param in enumerate(self.positional_only):
|
|
124
|
+
if param.name not in provided_positional_argument:
|
|
125
|
+
output_positional[param] = self.positional_only[i].default
|
|
126
|
+
|
|
127
|
+
for key in self.keyword_only_by_name.keys():
|
|
128
|
+
if key not in provided_keyword_arguments:
|
|
129
|
+
output_keyword[key] = self.keyword_only_by_name[key].default
|
|
130
|
+
|
|
131
|
+
output = output_positional | output_keyword
|
|
132
|
+
|
|
133
|
+
return Continue(remainder, output)
|
|
134
|
+
|
|
135
|
+
def __repr__(self):
|
|
136
|
+
keyword_list = [param.__repr__() for param in self.keyword_only]
|
|
137
|
+
positional_list = [param.__repr__() for param in self.positional_only]
|
|
138
|
+
keyword_arg_strings = f"keyword_only=[{', '.join(keyword_list)}]" if len(keyword_list) > 0 else ""
|
|
139
|
+
positional_arg_strings = f"positional_only=[{', '.join(positional_list)}]" if len(positional_list) > 0 else ""
|
|
140
|
+
return self.name_or_nothing() + f"arguments({','.join([keyword_arg_strings, positional_arg_strings])})"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def arguments(
|
|
144
|
+
positional_only: list[ArgumentParserParameter] = [],
|
|
145
|
+
keyword_only: list[ArgumentParserParameter] = [],
|
|
146
|
+
*,
|
|
147
|
+
keyword: Parser = reg(r"[A-Za-z_][A-Za-z_0-9]*"),
|
|
148
|
+
separator: Parser = lit(","),
|
|
149
|
+
equals: Parser = lit("="),
|
|
150
|
+
):
|
|
151
|
+
return ArgumentParser(
|
|
152
|
+
positional_only,
|
|
153
|
+
keyword_only,
|
|
154
|
+
keyword=keyword,
|
|
155
|
+
separator=separator,
|
|
156
|
+
equals=equals,
|
|
157
|
+
)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
__all__ = ["AnnotatedFisherRow", "confidence_intervals"]
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from .._sdk.data_frame import DataFrame
|
|
8
|
+
from .._sdk.ode_optimization_result import UnittedValue
|
|
9
|
+
from .._sdk.scenario import Prior
|
|
10
|
+
from .helper import scale_parameter, unscale_parameter
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
14
|
+
class AnnotatedFisherRow:
|
|
15
|
+
"""A row of the Fisher information matrix along with the corresponding parameter value and prior."""
|
|
16
|
+
|
|
17
|
+
parameter_value: float
|
|
18
|
+
parameter_prior: Prior
|
|
19
|
+
values: dict[str, UnittedValue]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _theta(annotated_matrix: dict[str, AnnotatedFisherRow]) -> np.ndarray:
|
|
23
|
+
return np.asarray([row.parameter_value for row in annotated_matrix.values()])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _theta_is_logscale(annotated_matrix: dict[str, AnnotatedFisherRow]) -> list[bool]:
|
|
27
|
+
return [row.parameter_prior.is_logscaled for row in annotated_matrix.values()]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _information_unit(annotated_matrix: dict[str, AnnotatedFisherRow]):
|
|
31
|
+
return np.asarray([[val.unit for val in row.values.values()] for row in annotated_matrix.values()])
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _information_matrix(annotated_matrix: dict[str, AnnotatedFisherRow]) -> list[list[float]]:
|
|
35
|
+
return [[val.value for val in row.values.values()] for row in annotated_matrix.values()]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def covariance_matrix(
|
|
39
|
+
annotated_fisher_information: dict[str, AnnotatedFisherRow],
|
|
40
|
+
uncertainty_ceiling: float = float("inf"),
|
|
41
|
+
) -> list[list[float]]:
|
|
42
|
+
"""Transform information matrix into covariance intervals.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
fisher_information: dict[str, AnnotatedFisherRow]
|
|
47
|
+
uncertainty_ceiling : float, default = inf
|
|
48
|
+
If some parameters are numerically non-identifiable, the information
|
|
49
|
+
on those parameters is very low and the uncertainties very high. In
|
|
50
|
+
this situation, inverting the information matrix is numerically
|
|
51
|
+
unstable, which can result in a bunch of junk for the covariance
|
|
52
|
+
matrix. A value like 1e8 is a good
|
|
53
|
+
number to try if the scenario appears to be non-identifiable.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
List[List[float]]
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
eigenvalue_threshold = 1.0 / uncertainty_ceiling**2
|
|
61
|
+
|
|
62
|
+
information_matrix_values = np.asarray(_information_matrix(annotated_fisher_information))
|
|
63
|
+
theta = _theta(annotated_fisher_information)
|
|
64
|
+
theta_is_logscale = _theta_is_logscale(annotated_fisher_information)
|
|
65
|
+
theta_array = np.where(theta_is_logscale, theta, 1.0)
|
|
66
|
+
|
|
67
|
+
information_matrix = np.einsum("i,ij,j->ij", theta_array, information_matrix_values, theta_array)
|
|
68
|
+
|
|
69
|
+
modified_covariance = fisher_information_inverse(information_matrix, eigenvalue_threshold)
|
|
70
|
+
|
|
71
|
+
return modified_covariance
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def confidence_intervals(
|
|
75
|
+
annotated_fisher_information: dict[str, AnnotatedFisherRow],
|
|
76
|
+
fraction: float,
|
|
77
|
+
uncertainty_ceiling: float = float("inf"),
|
|
78
|
+
) -> DataFrame:
|
|
79
|
+
"""Transform information matrix into confidence intervals.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
annotated_fisher_information: dict[str, AnnotatedFisherRow]
|
|
84
|
+
fraction : float
|
|
85
|
+
The fraction of the distribution to contain within the CI. Use 0.95
|
|
86
|
+
for 95% confidence intervals.
|
|
87
|
+
uncertainty_ceiling : float, default = inf
|
|
88
|
+
If some parameters are numerically non-identifiable, the information
|
|
89
|
+
on those parameters is very low and the uncertainties very high. In
|
|
90
|
+
this situation, inverting the information matrix is numerically
|
|
91
|
+
unstable, which can result in a bunch of junk for the confidence
|
|
92
|
+
intervals. Choosing a ceiling will prevent uncertainties from going
|
|
93
|
+
larger than that, which can prevent large uncertainties from
|
|
94
|
+
polluting the rest of the uncertainties. A value like 1e8 is a good
|
|
95
|
+
number to try if the scenario appears to be non-identifiable.
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
DataFrame with columns:
|
|
100
|
+
parameter: str
|
|
101
|
+
value: float
|
|
102
|
+
unit: str
|
|
103
|
+
scale: "linear" | "log"
|
|
104
|
+
lower: float
|
|
105
|
+
upper: float
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
from scipy import stats
|
|
109
|
+
|
|
110
|
+
if uncertainty_ceiling <= 0.0:
|
|
111
|
+
raise ValueError("Argument 'uncertainty_ceiling' must be positive.")
|
|
112
|
+
|
|
113
|
+
modified_covariance = covariance_matrix(annotated_fisher_information, uncertainty_ceiling)
|
|
114
|
+
|
|
115
|
+
sigma = np.sqrt(np.diag(modified_covariance))
|
|
116
|
+
lower = []
|
|
117
|
+
upper = []
|
|
118
|
+
theta = _theta(annotated_fisher_information)
|
|
119
|
+
theta_is_logscale = _theta_is_logscale(annotated_fisher_information)
|
|
120
|
+
|
|
121
|
+
for t, maybe_scaled_sigma, is_log in zip(theta, sigma, theta_is_logscale, strict=True):
|
|
122
|
+
scaled_mean = scale_parameter(is_log, t)
|
|
123
|
+
scaled_lower, scaled_upper = stats.norm.interval(fraction, scaled_mean, maybe_scaled_sigma)
|
|
124
|
+
lower.append(unscale_parameter(is_log, scaled_lower))
|
|
125
|
+
upper.append(unscale_parameter(is_log, scaled_upper))
|
|
126
|
+
|
|
127
|
+
# These units are ugly and also different (in terms of formatting) from what _optimize returns. This
|
|
128
|
+
# should be fixed in the future when Renan's work for diagnostics is in.
|
|
129
|
+
parameter_units = [f"(1/({unit}))^0.5" for unit in np.diag(_information_unit(annotated_fisher_information))]
|
|
130
|
+
|
|
131
|
+
return DataFrame(
|
|
132
|
+
parameter=list(annotated_fisher_information.keys()),
|
|
133
|
+
value=theta.tolist(),
|
|
134
|
+
unit=parameter_units,
|
|
135
|
+
scale=["log" if is_log else "linear" for is_log in theta_is_logscale],
|
|
136
|
+
lower=lower,
|
|
137
|
+
upper=upper,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def fisher_information_inverse(F: np.ndarray, eigenvalue_threshold: float = 0.0) -> np.ndarray:
|
|
142
|
+
# for stable inversion of Fisher information matrix; used in confidence_intervals() below
|
|
143
|
+
|
|
144
|
+
# locate inf/-inf on diag
|
|
145
|
+
finite_indices = ~np.isinf(np.diag(F))
|
|
146
|
+
|
|
147
|
+
# cut out zero/inf indices before inversion
|
|
148
|
+
F_finite = F[np.ix_(finite_indices, finite_indices)]
|
|
149
|
+
L, Q = np.linalg.eigh(F_finite)
|
|
150
|
+
|
|
151
|
+
# floor small eigenvalues at threshold
|
|
152
|
+
L[L < eigenvalue_threshold] = eigenvalue_threshold
|
|
153
|
+
|
|
154
|
+
# identify indices of exactly zero eigenvalues
|
|
155
|
+
zero_eigs = L == 0.0
|
|
156
|
+
|
|
157
|
+
# invert floored eigenvalues, compose modified inverse
|
|
158
|
+
Finv_finite = Q[:, ~zero_eigs] @ np.diag(1.0 / L[~zero_eigs]) @ Q[:, ~zero_eigs].T
|
|
159
|
+
|
|
160
|
+
# For exactly zero eigenvalues after flooring, examine entries of corresponding eigenvectors. Nonzero (> Qthresh)
|
|
161
|
+
# eigenvector entries correspond to columns of F_finite which are nontrivial contributions to this kernel. Such
|
|
162
|
+
# columns/rows of Finv are set to zero with inf on diagonal.
|
|
163
|
+
Qthresh = F_finite.shape[0] * np.finfo("float").eps
|
|
164
|
+
kernel_cols = np.any(np.abs(Q[:, zero_eigs]) > Qthresh, axis=1)
|
|
165
|
+
Finv_finite[kernel_cols, :] = 0.0
|
|
166
|
+
Finv_finite[:, kernel_cols] = 0.0
|
|
167
|
+
Finv_finite[kernel_cols, kernel_cols] = np.inf
|
|
168
|
+
|
|
169
|
+
# Initialize full output, fill in modified inverse
|
|
170
|
+
# (Note inf on diag(A) -> 0 on corresponding row/col of inverse).
|
|
171
|
+
Finv_out = np.zeros(F.shape)
|
|
172
|
+
Finv_out[np.ix_(finite_indices, finite_indices)] = Finv_finite
|
|
173
|
+
|
|
174
|
+
return Finv_out
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import tabeline as tl
|
|
2
|
+
|
|
3
|
+
from .._labelset import LabelSet
|
|
4
|
+
from .nested_map import NestedMap
|
|
5
|
+
from .parameter import Parameter
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def create_parameters_map(df: tl.DataFrame, parameter_label_columns: list[str]) -> NestedMap:
|
|
9
|
+
labels = LabelSet.from_df(df) # This is necessary because labels need to be standardized
|
|
10
|
+
|
|
11
|
+
param_map = NestedMap()
|
|
12
|
+
|
|
13
|
+
for i, lb in enumerate(labels):
|
|
14
|
+
param_map.set_value(
|
|
15
|
+
# if the label does not exist it is wildcard.
|
|
16
|
+
# Add the parameter column to the list of columns to be used as keys for the last layer and
|
|
17
|
+
# it is guaranteed that it is not wildcard
|
|
18
|
+
[lb.labels.get(k, "*") for k in [*parameter_label_columns, "parameter"]],
|
|
19
|
+
{df[i, "parameter"]: Parameter(value=df[i, "value"], unit=df[i, "unit"])},
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
return param_map
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def create_fitting_parameters_map(df: tl.DataFrame, parameter_label_columns: list[str]) -> NestedMap:
|
|
26
|
+
labels = LabelSet.from_df(df) # This is necessary because labels need to be standardized
|
|
27
|
+
|
|
28
|
+
param_map = NestedMap()
|
|
29
|
+
|
|
30
|
+
for i, lb in enumerate(labels):
|
|
31
|
+
parameter_i = {
|
|
32
|
+
"parameter": df[i, "parameter"],
|
|
33
|
+
"value": df[i, "value"],
|
|
34
|
+
"unit": df[i, "unit"],
|
|
35
|
+
"is_fit": df[i, "is_fit"],
|
|
36
|
+
"lower_bound": df[i, "lower_bound"],
|
|
37
|
+
"upper_bound": df[i, "upper_bound"],
|
|
38
|
+
"global_parameter_name": df[i, "global_parameter_name"],
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if "prior_distribution" in df.column_names:
|
|
42
|
+
parameter_i["prior_distribution"] = df[i, "prior_distribution"]
|
|
43
|
+
if "location" in df.column_names:
|
|
44
|
+
parameter_i["location"] = df[i, "location"]
|
|
45
|
+
if "scale" in df.column_names:
|
|
46
|
+
parameter_i["scale"] = df[i, "scale"]
|
|
47
|
+
|
|
48
|
+
param_map.set_value(
|
|
49
|
+
# if the label does not exist it is wildcard.
|
|
50
|
+
# Add the parameter column to the list of columns to be used as keys for the last layer and
|
|
51
|
+
# it is guaranteed that it is not wildcard
|
|
52
|
+
[lb.labels.get(k, "*") for k in [*parameter_label_columns, "parameter"]],
|
|
53
|
+
{df[i, "parameter"]: parameter_i},
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return param_map
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def create_doses_map(df: tl.DataFrame, dose_label_columns: list[str]) -> NestedMap:
|
|
60
|
+
labels = LabelSet.from_df(df) # This is necessary because labels need to be standardized
|
|
61
|
+
dose_map = NestedMap()
|
|
62
|
+
has_amounts = "amounts" in df.column_names
|
|
63
|
+
has_durations = "durations" in df.column_names
|
|
64
|
+
|
|
65
|
+
for i, lb in enumerate(labels):
|
|
66
|
+
schedule_i = {
|
|
67
|
+
"route": df[i, "route"],
|
|
68
|
+
"times": df[i, "times"],
|
|
69
|
+
"time_unit": df[i, "time_unit"],
|
|
70
|
+
}
|
|
71
|
+
if has_amounts:
|
|
72
|
+
schedule_i.update({"amounts": df[i, "amounts"], "amount_unit": df[i, "amount_unit"]})
|
|
73
|
+
if has_durations:
|
|
74
|
+
schedule_i.update({"durations": df[i, "durations"], "duration_unit": df[i, "duration_unit"]})
|
|
75
|
+
# if the label does not exist it is wildcard.
|
|
76
|
+
# Add the route column to the list of columns to be used as keys for the last layer and
|
|
77
|
+
# it is guaranteed that it is not wildcard
|
|
78
|
+
dose_map.set_value(
|
|
79
|
+
[lb.labels.get(k, "*") for k in [*dose_label_columns, "route"]], {df[i, "route"]: schedule_i}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return dose_map
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def create_models_map(df: tl.DataFrame, model_label_columns: list[str]) -> NestedMap:
|
|
86
|
+
labels = LabelSet.from_df(df) # This is necessary because labels need to be standardized
|
|
87
|
+
model_map = NestedMap()
|
|
88
|
+
|
|
89
|
+
for i, lb in enumerate(labels):
|
|
90
|
+
# if the label does not exist it is wildcard.
|
|
91
|
+
# Add the route column to the list of columns to be used as keys for the last layer and
|
|
92
|
+
# it is guaranteed that it is not wildcard
|
|
93
|
+
model_map.set_value(
|
|
94
|
+
[lb.labels.get(k, "*") for k in [*model_label_columns, "model"]],
|
|
95
|
+
{df[i, "model"]: {"model": df[i, "model"]}},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return model_map
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def create_times_map(df: tl.DataFrame, time_label_columns: list[str]) -> NestedMap:
|
|
102
|
+
labels = LabelSet.from_df(df) # This is necessary because labels need to be standardized
|
|
103
|
+
time_map = NestedMap()
|
|
104
|
+
|
|
105
|
+
for i, lb in enumerate(labels):
|
|
106
|
+
# if the label does not exist it is wildcard.
|
|
107
|
+
# Add the index to the list of columns to be used as keys for the last layer
|
|
108
|
+
# for times duplication is fine
|
|
109
|
+
time_map.set_value(
|
|
110
|
+
[lb.labels.get(k, "*") for k in time_label_columns] + [f"{i}"],
|
|
111
|
+
{"times": df[i, "times"], "times_unit": df[i, "times_unit"]},
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return time_map
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def create_measurements_map(df: tl.DataFrame, measurement_label_columns: list[str]) -> NestedMap:
|
|
118
|
+
labels = LabelSet.from_df(df) # This is necessary because labels need to be standardized
|
|
119
|
+
measurement_map = NestedMap()
|
|
120
|
+
|
|
121
|
+
has_exponential_error = "exponential_error" in df.column_names
|
|
122
|
+
has_constant_error = "constant_error" in df.column_names
|
|
123
|
+
has_proportional_error = "proportional_error" in df.column_names
|
|
124
|
+
|
|
125
|
+
for i, lb in enumerate(labels):
|
|
126
|
+
measurement_i = {
|
|
127
|
+
"time": df[i, "time"],
|
|
128
|
+
"time_unit": df[i, "time_unit"],
|
|
129
|
+
"output": df[i, "output"],
|
|
130
|
+
"measurement": df[i, "measurement"],
|
|
131
|
+
"measurement_unit": df[i, "measurement_unit"],
|
|
132
|
+
}
|
|
133
|
+
if has_exponential_error:
|
|
134
|
+
measurement_i.update({"exponential_error": df[i, "exponential_error"]})
|
|
135
|
+
if has_constant_error:
|
|
136
|
+
measurement_i.update({"constant_error": df[i, "constant_error"]})
|
|
137
|
+
if has_proportional_error:
|
|
138
|
+
measurement_i.update({"proportional_error": df[i, "proportional_error"]})
|
|
139
|
+
|
|
140
|
+
# if the label does not exist it is wildcard.
|
|
141
|
+
# Add the index to the list of columns to be used as keys for the last layer
|
|
142
|
+
# for measurements duplication is fine
|
|
143
|
+
measurement_map.set_value([lb.labels.get(k, "*") for k in measurement_label_columns] + [f"{i}"], measurement_i)
|
|
144
|
+
|
|
145
|
+
return measurement_map
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def create_distributions_map(df: tl.DataFrame, distribution_label_columns: list[str]) -> NestedMap:
|
|
149
|
+
labels = LabelSet.from_df(df) # This is necessary because labels need to be standardized
|
|
150
|
+
distribution_map = NestedMap()
|
|
151
|
+
|
|
152
|
+
has_histogram_cols = "bin_edges" in df.column_names
|
|
153
|
+
has_mvn_cols = "covariance" in df.column_names
|
|
154
|
+
|
|
155
|
+
for i, lb in enumerate(labels):
|
|
156
|
+
distribution_i = {
|
|
157
|
+
"time": df[i, "time"],
|
|
158
|
+
"time_unit": df[i, "time_unit"],
|
|
159
|
+
"target": df[i, "target"],
|
|
160
|
+
"distribution": df[i, "distribution"],
|
|
161
|
+
"distribution_unit": df[i, "distribution_unit"],
|
|
162
|
+
}
|
|
163
|
+
if has_histogram_cols:
|
|
164
|
+
distribution_i.update({"bin_edges": df[i, "bin_edges"]})
|
|
165
|
+
distribution_i.update({"bin_probabilities": df[i, "bin_probabilities"]})
|
|
166
|
+
distribution_i.update({"smoothness": df[i, "smoothness"]})
|
|
167
|
+
|
|
168
|
+
if has_mvn_cols:
|
|
169
|
+
distribution_i.update({"measurements": df[i, "measurements"]})
|
|
170
|
+
distribution_i.update({"measurements_unit": df[i, "measurements_unit"]})
|
|
171
|
+
distribution_i.update({"variance": df[i, "covariance"]}) # TODO: yes, this is stupid
|
|
172
|
+
|
|
173
|
+
# if the label does not exist it is wildcard.
|
|
174
|
+
# Add the index to the list of columns to be used as keys for the last layer
|
|
175
|
+
# for measurements duplication is fine
|
|
176
|
+
distribution_map.set_value(
|
|
177
|
+
[lb.labels.get(k, "*") for k in distribution_label_columns] + [f"{i}"], distribution_i
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
return distribution_map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
__all__ = ["Display"]
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
|
|
6
|
+
from IPython import get_ipython
|
|
7
|
+
from IPython.display import DisplayObject, display, update_display
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(kw_only=True, slots=True)
|
|
11
|
+
class Display:
|
|
12
|
+
display_id: str | None = None
|
|
13
|
+
|
|
14
|
+
def display(self, display_object: DisplayObject) -> None:
|
|
15
|
+
if get_ipython() is None:
|
|
16
|
+
message = display_object.data
|
|
17
|
+
else:
|
|
18
|
+
message = display_object
|
|
19
|
+
|
|
20
|
+
if self.display_id is None:
|
|
21
|
+
self.display_id = str(uuid4())
|
|
22
|
+
display(message, display_id=self.display_id)
|
|
23
|
+
else:
|
|
24
|
+
update_display(message, display_id=self.display_id)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import tabeline as tl
|
|
5
|
+
|
|
6
|
+
from certaraiq._sdk.data_frame import DataFrame
|
|
7
|
+
from certaraiq._sdk.reaction_model import Schedule
|
|
8
|
+
|
|
9
|
+
from .helper import find_duplicate_keys
|
|
10
|
+
from .parse_schedule import parse_schedule
|
|
11
|
+
|
|
12
|
+
DataFrameLike = pd.DataFrame | tl.DataFrame | DataFrame
|
|
13
|
+
PathLike = str | Path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_dose_schedules(
|
|
17
|
+
doses: list[dict[str, dict[str, int | float | str]]], labels: dict[str, str | float | int | bool]
|
|
18
|
+
) -> dict[str, Schedule]:
|
|
19
|
+
duplicate_routes = find_duplicate_keys(doses)
|
|
20
|
+
if len(duplicate_routes) > 0:
|
|
21
|
+
if len(labels) > 0:
|
|
22
|
+
raise ValueError(
|
|
23
|
+
"Duplicate routes found for label(s)"
|
|
24
|
+
f" {', '.join([f'{key}={value}' for key, value in labels.items()])}:"
|
|
25
|
+
f" {', '.join(duplicate_routes)}"
|
|
26
|
+
)
|
|
27
|
+
else:
|
|
28
|
+
raise ValueError(f"Duplicate routes found: {', '.join(duplicate_routes)}")
|
|
29
|
+
|
|
30
|
+
route_schedules_i = {r: parse_schedule(d) for dose in doses for r, d in dose.items()}
|
|
31
|
+
|
|
32
|
+
return route_schedules_i
|