Calibrator
black_it.calibrator.Calibrator
The class used to perform a calibration.
Source code in black_it/calibrator.py
class Calibrator: # pylint: disable=too-many-instance-attributes
"""The class used to perform a calibration."""
STATE_VERSION = 0
def __init__( # pylint: disable=too-many-arguments
self,
samplers: List[BaseSampler],
loss_function: BaseLoss,
real_data: NDArray[np.float64],
model: Callable,
parameters_bounds: Union[NDArray[np.float64], List[List[float]]],
parameters_precision: Union[NDArray[np.float64], List[float]],
ensemble_size: int,
sim_length: Optional[int] = None,
convergence_precision: Optional[int] = None,
verbose: bool = True,
saving_folder: Optional[str] = None,
random_state: Optional[int] = None,
n_jobs: Optional[int] = None,
):
"""
Initialize the Calibrator object.
It must be initialized with details on the parameters to explore,
on the model to calibrate, on the samplers and on the loss function to use.
Args:
samplers: list of methods to be used in the calibration procedure
loss_function: a loss function which evaluates the similarity between simulated and real datasets
real_data: an array containing the real time series
model: a model with free parameters to be calibrated
parameters_bounds: the bounds of the parameter space
parameters_precision: the precisions to be used for the discretization of the parameters
ensemble_size: number of repetitions to be run for each set of parameters to decrease statistical
fluctuations. For deterministic models this should be set to 1.
sim_length: number of periods to simulate the model for, by default this is equal to the length of the
real time series.
convergence_precision: number of significant digits to consider in the convergence check. The check is
not performed if this is set to 'None'.
verbose: whether to print calibration updates
saving_folder: the name of the folder where data should be saved and/or retrieved
random_state: random state of the calibrator, used for model simulations and to initialise the samplers
n_jobs: the maximum number of concurrently running jobs. For more details, see the
[joblib.Parallel documentation](https://joblib.readthedocs.io/en/latest/generated/joblib.Parallel.html).
"""
self.samplers = samplers
self.loss_function = loss_function
self.model = model
self.random_state = random_state
self.real_data = real_data
self.ensemble_size = ensemble_size
if sim_length is None:
self.N = self.real_data.shape[0]
else:
if sim_length != self.real_data.shape[0]:
warnings.warn(
"The length of real time series is different from the simulation length, "
f"got {self.real_data.shape[0]} and {sim_length}. This may or may not be a problem depending "
"on the loss function used.",
RuntimeWarning,
)
self.N = sim_length
self.D = self.real_data.shape[1]
self.verbose = verbose
self.convergence_precision = (
self._validate_convergence_precision(convergence_precision)
if convergence_precision is not None
else None
)
self.saving_folder = saving_folder
# Initialize search grid
self.param_grid = SearchSpace(parameters_bounds, parameters_precision, verbose)
# initialize arrays
self.params_samp = np.zeros((0, self.param_grid.dims))
self.losses_samp = np.zeros(0)
self.batch_num_samp = np.zeros(0, dtype=int)
self.method_samp = np.zeros(0, dtype=int)
self.series_samp = np.zeros((0, self.ensemble_size, self.N, self.D))
# initialize variables before calibration
self.n_sampled_params = 0
self.current_batch_index = 0
# set number of processes for parallel evaluation of model
self.n_jobs = n_jobs if n_jobs is not None else multiprocessing.cpu_count()
print(
f"Selecting {self.n_jobs} processes for the parallel evaluation of the model"
)
self.samplers_id_table = self._construct_samplers_id_table(samplers)
@property
def random_state(self) -> Optional[int]:
"""Get the random state."""
return self._random_state
@random_state.setter
def random_state(self, random_state: Optional[int]) -> None:
"""Set the random state."""
self._random_state = random_state
self._random_generator = default_rng(self.random_state)
@property
def random_generator(self) -> np.random.Generator:
"""Get the random generator."""
return self._random_generator
def _get_random_seed(self) -> int:
"""Get new random seed from the current random generator."""
return get_random_seed(self._random_generator)
def _set_samplers_seeds(self) -> None:
"""Set the calibration seed."""
for sampler in self.samplers:
sampler.random_state = self._get_random_seed()
@staticmethod
def _construct_samplers_id_table(samplers: List[BaseSampler]) -> Dict[str, int]:
"""
Construct the samplers-by-id table.
Given the list (built-in or user-defined) of samplers a calibration
session is going to use, return a map from the sampler human-readable
name to a numeric id (starting from 0).
Different calibration sessions may result in different conversion
tables.
Args:
samplers: the list of samplers of the calibrator
Returns:
A dict that maps from the given sampler names to unique ids.
"""
samplers_id_table = {}
sampler_id = 0
for sampler in samplers:
sampler_name = type(sampler).__name__
if sampler_name in samplers_id_table:
continue
samplers_id_table[sampler_name] = sampler_id
sampler_id = sampler_id + 1
return samplers_id_table
def set_samplers(self, samplers: List[BaseSampler]) -> None:
"""Set the samplers list of the calibrator.
This method overwrites the samplers of a calibrator object with a custom list of samplers.
Args:
samplers: a list of samplers
"""
# overwrite the list of samplers
self.samplers = samplers
# update the samplers_id_table with the new samplers, only if necessary
sampler_id = max(self.samplers_id_table.values()) + 1
for sampler in samplers:
sampler_name = type(sampler).__name__
if sampler_name in self.samplers_id_table:
continue
self.samplers_id_table[sampler_name] = sampler_id
sampler_id = sampler_id + 1
@classmethod
def restore_from_checkpoint( # pylint: disable=too-many-locals
cls, checkpoint_path: str, model: Callable
) -> "Calibrator":
"""
Return an instantiated class from a database file and a model simulator.
Args:
checkpoint_path: the name of the database file to read from
model: the model to calibrate. It must be equal to the one already calibrated
Returns:
An initialised Calibrator object.
"""
(
parameters_bounds,
parameters_precision,
real_data,
ensemble_size,
N,
_D,
convergence_precision,
verbose,
saving_file,
random_state,
random_generator_state,
model_name,
samplers,
loss_function,
current_batch_index,
n_sampled_params,
n_jobs,
params_samp,
losses_samp,
series_samp,
batch_num_samp,
method_samp,
) = load_calibrator_state(checkpoint_path, cls.STATE_VERSION)
_assert(
model_name == model.__name__,
(
"Error: the model provided appears to be different from the one present "
"in the database"
),
)
calibrator = cls(
samplers,
loss_function,
real_data,
model,
parameters_bounds,
parameters_precision,
ensemble_size,
N,
convergence_precision,
verbose,
saving_file,
random_state,
n_jobs,
)
calibrator.current_batch_index = current_batch_index
calibrator.n_sampled_params = n_sampled_params
calibrator.params_samp = params_samp
calibrator.losses_samp = losses_samp
calibrator.series_samp = series_samp
calibrator.batch_num_samp = batch_num_samp
calibrator.method_samp = method_samp
# reset the random number generator state
calibrator.random_generator.bit_generator.state = random_generator_state
return calibrator
def simulate_model(self, params: NDArray) -> NDArray:
"""
Simulate the model.
This method calls the model simulator in parallel on a given set of parameter values, a number of repeated
evaluations are performed for each parameter to average out random fluctuations.
Args:
params: the array of parameters for which the model should be evaluated
# noqa
Returns:
simulated_data: an array of dimensions (batch_size, ensemble_size, N, D) containing all
simulated time series
"""
rep_params = np.repeat(params, self.ensemble_size, axis=0)
simulated_data_list = Parallel(n_jobs=self.n_jobs)(
delayed(self.model)(param, self.N, self._get_random_seed())
for i, param in enumerate(rep_params)
)
simulated_data = np.array(simulated_data_list)
simulated_data = np.reshape(
simulated_data, (params.shape[0], self.ensemble_size, self.N, self.D)
)
return simulated_data
def calibrate(self, n_batches: int) -> Tuple[NDArray, NDArray]:
"""
Run calibration for n batches.
Args:
n_batches (int): number of 'batches' to be executed. Each batch runs over all methods
Returns:
The sampled parameters and the corresponding sampled losses.
Both arrays are sorted by increasing loss values
"""
if self.current_batch_index == 0:
# we only set the samplers' random state at the start of a calibration
self._set_samplers_seeds()
for _ in range(n_batches):
print()
print(f"BATCH NUMBER: {self.current_batch_index + 1}")
print(f"PARAMS SAMPLED: {self.n_sampled_params}")
for method in self.samplers:
t_start = time.time()
print()
print(f"METHOD: {type(method).__name__}")
# get new params from a specific sampler
new_params = method.sample(
self.param_grid,
self.params_samp,
self.losses_samp,
)
t_eval = time.time()
# simulate an ensemble of models for different parameters
new_simulated_data = self.simulate_model(new_params)
new_losses = []
for sim_data_ensemble in new_simulated_data:
new_loss = self.loss_function.compute_loss(
sim_data_ensemble, self.real_data
)
new_losses.append(new_loss)
# update arrays
self.params_samp = np.vstack((self.params_samp, new_params))
self.losses_samp = np.hstack((self.losses_samp, new_losses))
self.series_samp = np.vstack((self.series_samp, new_simulated_data))
self.batch_num_samp = np.hstack(
(
self.batch_num_samp,
[self.current_batch_index] * method.batch_size,
)
)
self.method_samp = np.hstack(
(
self.method_samp,
[self.samplers_id_table[type(method).__name__]]
* method.batch_size,
)
)
# logging
t_end = time.time()
if self.verbose:
min_dist_new_points = np.round(np.min(new_losses), 2)
avg_dist_new_points = np.round(np.average(new_losses), 2)
avg_dist_existing_points = np.round(np.average(self.losses_samp), 2)
elapsed_tot = np.round(t_end - t_start, 1)
elapsed_eval = np.round(t_end - t_eval, 1)
print(
textwrap.dedent(
f"""\
----> sim exec elapsed time: {elapsed_eval}s
----> min loss new params: {min_dist_new_points}
----> avg loss new params: {avg_dist_new_points}
----> avg loss exist params: {avg_dist_existing_points}
----> curr min loss: {np.min(self.losses_samp)}
====> total elapsed time: {elapsed_tot}s
"""
),
end="",
)
# update count of number of params sampled
self.n_sampled_params = self.n_sampled_params + len(new_params)
self.current_batch_index += 1
# check convergence for early termination
if self.convergence_precision is not None:
converged = self.check_convergence(
self.losses_samp, self.n_sampled_params, self.convergence_precision
)
if converged and self.verbose:
print("\nCONVERGENCE CHECK:")
print("Achieved convergence loss, stopping search.")
break
if self.saving_folder is not None:
self.create_checkpoint(self.saving_folder)
idx = np.argsort(self.losses_samp)
return self.params_samp[idx], self.losses_samp[idx]
@staticmethod
def check_convergence(
losses_samp: NDArray, n_sampled_params: int, convergence_precision: int
) -> bool:
"""
Check convergence of the calibration.
Args:
losses_samp: the sampled losses
n_sampled_params: the number of sampled params
convergence_precision: the required convergence precision.
Returns:
True if the calibration converged, False otherwise.
"""
converged = (
np.round(np.min(losses_samp[:n_sampled_params]), convergence_precision)
== 0.0
)
return converged
def create_checkpoint(self, file_name: Union[str, os.PathLike]) -> None:
"""
Save the current state of the object.
Args:
file_name: the name of the folder where the data will be saved
"""
checkpoint_path: str = os.path.join(os.path.realpath(file_name))
t_start = time.time()
model_name = self.model.__name__
save_calibrator_state(
checkpoint_path,
self.param_grid.parameters_bounds,
self.param_grid.parameters_precision,
self.real_data,
self.ensemble_size,
self.N,
self.D,
self.convergence_precision,
self.verbose,
self.saving_folder,
self.random_state,
self.random_generator.bit_generator.state,
model_name,
self.samplers,
self.loss_function,
self.current_batch_index,
self.n_sampled_params,
self.n_jobs,
self.params_samp,
self.losses_samp,
self.series_samp,
self.batch_num_samp,
self.method_samp,
)
t_end = time.time()
elapsed = np.round(t_end - t_start, 1)
print(f"Checkpoint saved in {elapsed}s")
@staticmethod
def _validate_convergence_precision(convergence_precision: int) -> int:
"""Validate convergence precision input."""
_assert(
convergence_precision >= 0,
f"convergence precision must be an integer greater than 0, got {convergence_precision}",
exception_class=ValueError,
)
return convergence_precision
random_generator: Generator
property
readonly
Get the random generator.
random_state: Optional[int]
property
writable
Get the random state.
__init__(self, samplers, loss_function, real_data, model, parameters_bounds, parameters_precision, ensemble_size, sim_length=None, convergence_precision=None, verbose=True, saving_folder=None, random_state=None, n_jobs=None)
special
Initialize the Calibrator object.
It must be initialized with details on the parameters to explore, on the model to calibrate, on the samplers and on the loss function to use.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
samplers |
List[black_it.samplers.base.BaseSampler] |
list of methods to be used in the calibration procedure |
required |
loss_function |
BaseLoss |
a loss function which evaluates the similarity between simulated and real datasets |
required |
real_data |
ndarray |
an array containing the real time series |
required |
model |
Callable |
a model with free parameters to be calibrated |
required |
parameters_bounds |
Union[numpy.ndarray[Any, numpy.dtype[numpy.float64]], List[List[float]]] |
the bounds of the parameter space |
required |
parameters_precision |
Union[numpy.ndarray[Any, numpy.dtype[numpy.float64]], List[float]] |
the precisions to be used for the discretization of the parameters |
required |
ensemble_size |
int |
number of repetitions to be run for each set of parameters to decrease statistical fluctuations. For deterministic models this should be set to 1. |
required |
sim_length |
Optional[int] |
number of periods to simulate the model for, by default this is equal to the length of the real time series. |
None |
convergence_precision |
Optional[int] |
number of significant digits to consider in the convergence check. The check is not performed if this is set to 'None'. |
None |
verbose |
bool |
whether to print calibration updates |
True |
saving_folder |
Optional[str] |
the name of the folder where data should be saved and/or retrieved |
None |
random_state |
Optional[int] |
random state of the calibrator, used for model simulations and to initialise the samplers |
None |
n_jobs |
Optional[int] |
the maximum number of concurrently running jobs. For more details, see the joblib.Parallel documentation. |
None |
Source code in black_it/calibrator.py
def __init__( # pylint: disable=too-many-arguments
self,
samplers: List[BaseSampler],
loss_function: BaseLoss,
real_data: NDArray[np.float64],
model: Callable,
parameters_bounds: Union[NDArray[np.float64], List[List[float]]],
parameters_precision: Union[NDArray[np.float64], List[float]],
ensemble_size: int,
sim_length: Optional[int] = None,
convergence_precision: Optional[int] = None,
verbose: bool = True,
saving_folder: Optional[str] = None,
random_state: Optional[int] = None,
n_jobs: Optional[int] = None,
):
"""
Initialize the Calibrator object.
It must be initialized with details on the parameters to explore,
on the model to calibrate, on the samplers and on the loss function to use.
Args:
samplers: list of methods to be used in the calibration procedure
loss_function: a loss function which evaluates the similarity between simulated and real datasets
real_data: an array containing the real time series
model: a model with free parameters to be calibrated
parameters_bounds: the bounds of the parameter space
parameters_precision: the precisions to be used for the discretization of the parameters
ensemble_size: number of repetitions to be run for each set of parameters to decrease statistical
fluctuations. For deterministic models this should be set to 1.
sim_length: number of periods to simulate the model for, by default this is equal to the length of the
real time series.
convergence_precision: number of significant digits to consider in the convergence check. The check is
not performed if this is set to 'None'.
verbose: whether to print calibration updates
saving_folder: the name of the folder where data should be saved and/or retrieved
random_state: random state of the calibrator, used for model simulations and to initialise the samplers
n_jobs: the maximum number of concurrently running jobs. For more details, see the
[joblib.Parallel documentation](https://joblib.readthedocs.io/en/latest/generated/joblib.Parallel.html).
"""
self.samplers = samplers
self.loss_function = loss_function
self.model = model
self.random_state = random_state
self.real_data = real_data
self.ensemble_size = ensemble_size
if sim_length is None:
self.N = self.real_data.shape[0]
else:
if sim_length != self.real_data.shape[0]:
warnings.warn(
"The length of real time series is different from the simulation length, "
f"got {self.real_data.shape[0]} and {sim_length}. This may or may not be a problem depending "
"on the loss function used.",
RuntimeWarning,
)
self.N = sim_length
self.D = self.real_data.shape[1]
self.verbose = verbose
self.convergence_precision = (
self._validate_convergence_precision(convergence_precision)
if convergence_precision is not None
else None
)
self.saving_folder = saving_folder
# Initialize search grid
self.param_grid = SearchSpace(parameters_bounds, parameters_precision, verbose)
# initialize arrays
self.params_samp = np.zeros((0, self.param_grid.dims))
self.losses_samp = np.zeros(0)
self.batch_num_samp = np.zeros(0, dtype=int)
self.method_samp = np.zeros(0, dtype=int)
self.series_samp = np.zeros((0, self.ensemble_size, self.N, self.D))
# initialize variables before calibration
self.n_sampled_params = 0
self.current_batch_index = 0
# set number of processes for parallel evaluation of model
self.n_jobs = n_jobs if n_jobs is not None else multiprocessing.cpu_count()
print(
f"Selecting {self.n_jobs} processes for the parallel evaluation of the model"
)
self.samplers_id_table = self._construct_samplers_id_table(samplers)
calibrate(self, n_batches)
Run calibration for n batches.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
n_batches |
int |
number of 'batches' to be executed. Each batch runs over all methods |
required |
Returns:
Type | Description |
---|---|
Tuple[numpy.ndarray[Any, numpy.dtype[+ScalarType]], numpy.ndarray[Any, numpy.dtype[+ScalarType]]] |
The sampled parameters and the corresponding sampled losses. Both arrays are sorted by increasing loss values |
Source code in black_it/calibrator.py
def calibrate(self, n_batches: int) -> Tuple[NDArray, NDArray]:
"""
Run calibration for n batches.
Args:
n_batches (int): number of 'batches' to be executed. Each batch runs over all methods
Returns:
The sampled parameters and the corresponding sampled losses.
Both arrays are sorted by increasing loss values
"""
if self.current_batch_index == 0:
# we only set the samplers' random state at the start of a calibration
self._set_samplers_seeds()
for _ in range(n_batches):
print()
print(f"BATCH NUMBER: {self.current_batch_index + 1}")
print(f"PARAMS SAMPLED: {self.n_sampled_params}")
for method in self.samplers:
t_start = time.time()
print()
print(f"METHOD: {type(method).__name__}")
# get new params from a specific sampler
new_params = method.sample(
self.param_grid,
self.params_samp,
self.losses_samp,
)
t_eval = time.time()
# simulate an ensemble of models for different parameters
new_simulated_data = self.simulate_model(new_params)
new_losses = []
for sim_data_ensemble in new_simulated_data:
new_loss = self.loss_function.compute_loss(
sim_data_ensemble, self.real_data
)
new_losses.append(new_loss)
# update arrays
self.params_samp = np.vstack((self.params_samp, new_params))
self.losses_samp = np.hstack((self.losses_samp, new_losses))
self.series_samp = np.vstack((self.series_samp, new_simulated_data))
self.batch_num_samp = np.hstack(
(
self.batch_num_samp,
[self.current_batch_index] * method.batch_size,
)
)
self.method_samp = np.hstack(
(
self.method_samp,
[self.samplers_id_table[type(method).__name__]]
* method.batch_size,
)
)
# logging
t_end = time.time()
if self.verbose:
min_dist_new_points = np.round(np.min(new_losses), 2)
avg_dist_new_points = np.round(np.average(new_losses), 2)
avg_dist_existing_points = np.round(np.average(self.losses_samp), 2)
elapsed_tot = np.round(t_end - t_start, 1)
elapsed_eval = np.round(t_end - t_eval, 1)
print(
textwrap.dedent(
f"""\
----> sim exec elapsed time: {elapsed_eval}s
----> min loss new params: {min_dist_new_points}
----> avg loss new params: {avg_dist_new_points}
----> avg loss exist params: {avg_dist_existing_points}
----> curr min loss: {np.min(self.losses_samp)}
====> total elapsed time: {elapsed_tot}s
"""
),
end="",
)
# update count of number of params sampled
self.n_sampled_params = self.n_sampled_params + len(new_params)
self.current_batch_index += 1
# check convergence for early termination
if self.convergence_precision is not None:
converged = self.check_convergence(
self.losses_samp, self.n_sampled_params, self.convergence_precision
)
if converged and self.verbose:
print("\nCONVERGENCE CHECK:")
print("Achieved convergence loss, stopping search.")
break
if self.saving_folder is not None:
self.create_checkpoint(self.saving_folder)
idx = np.argsort(self.losses_samp)
return self.params_samp[idx], self.losses_samp[idx]
check_convergence(losses_samp, n_sampled_params, convergence_precision)
staticmethod
Check convergence of the calibration.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
losses_samp |
ndarray |
the sampled losses |
required |
n_sampled_params |
int |
the number of sampled params |
required |
convergence_precision |
int |
the required convergence precision. |
required |
Returns:
Type | Description |
---|---|
bool |
True if the calibration converged, False otherwise. |
Source code in black_it/calibrator.py
@staticmethod
def check_convergence(
losses_samp: NDArray, n_sampled_params: int, convergence_precision: int
) -> bool:
"""
Check convergence of the calibration.
Args:
losses_samp: the sampled losses
n_sampled_params: the number of sampled params
convergence_precision: the required convergence precision.
Returns:
True if the calibration converged, False otherwise.
"""
converged = (
np.round(np.min(losses_samp[:n_sampled_params]), convergence_precision)
== 0.0
)
return converged
create_checkpoint(self, file_name)
Save the current state of the object.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
file_name |
Union[str, os.PathLike] |
the name of the folder where the data will be saved |
required |
Source code in black_it/calibrator.py
def create_checkpoint(self, file_name: Union[str, os.PathLike]) -> None:
"""
Save the current state of the object.
Args:
file_name: the name of the folder where the data will be saved
"""
checkpoint_path: str = os.path.join(os.path.realpath(file_name))
t_start = time.time()
model_name = self.model.__name__
save_calibrator_state(
checkpoint_path,
self.param_grid.parameters_bounds,
self.param_grid.parameters_precision,
self.real_data,
self.ensemble_size,
self.N,
self.D,
self.convergence_precision,
self.verbose,
self.saving_folder,
self.random_state,
self.random_generator.bit_generator.state,
model_name,
self.samplers,
self.loss_function,
self.current_batch_index,
self.n_sampled_params,
self.n_jobs,
self.params_samp,
self.losses_samp,
self.series_samp,
self.batch_num_samp,
self.method_samp,
)
t_end = time.time()
elapsed = np.round(t_end - t_start, 1)
print(f"Checkpoint saved in {elapsed}s")
restore_from_checkpoint(checkpoint_path, model)
classmethod
Return an instantiated class from a database file and a model simulator.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
checkpoint_path |
str |
the name of the database file to read from |
required |
model |
Callable |
the model to calibrate. It must be equal to the one already calibrated |
required |
Returns:
Type | Description |
---|---|
Calibrator |
An initialised Calibrator object. |
Source code in black_it/calibrator.py
@classmethod
def restore_from_checkpoint( # pylint: disable=too-many-locals
cls, checkpoint_path: str, model: Callable
) -> "Calibrator":
"""
Return an instantiated class from a database file and a model simulator.
Args:
checkpoint_path: the name of the database file to read from
model: the model to calibrate. It must be equal to the one already calibrated
Returns:
An initialised Calibrator object.
"""
(
parameters_bounds,
parameters_precision,
real_data,
ensemble_size,
N,
_D,
convergence_precision,
verbose,
saving_file,
random_state,
random_generator_state,
model_name,
samplers,
loss_function,
current_batch_index,
n_sampled_params,
n_jobs,
params_samp,
losses_samp,
series_samp,
batch_num_samp,
method_samp,
) = load_calibrator_state(checkpoint_path, cls.STATE_VERSION)
_assert(
model_name == model.__name__,
(
"Error: the model provided appears to be different from the one present "
"in the database"
),
)
calibrator = cls(
samplers,
loss_function,
real_data,
model,
parameters_bounds,
parameters_precision,
ensemble_size,
N,
convergence_precision,
verbose,
saving_file,
random_state,
n_jobs,
)
calibrator.current_batch_index = current_batch_index
calibrator.n_sampled_params = n_sampled_params
calibrator.params_samp = params_samp
calibrator.losses_samp = losses_samp
calibrator.series_samp = series_samp
calibrator.batch_num_samp = batch_num_samp
calibrator.method_samp = method_samp
# reset the random number generator state
calibrator.random_generator.bit_generator.state = random_generator_state
return calibrator
set_samplers(self, samplers)
Set the samplers list of the calibrator.
This method overwrites the samplers of a calibrator object with a custom list of samplers.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
samplers |
List[black_it.samplers.base.BaseSampler] |
a list of samplers |
required |
Source code in black_it/calibrator.py
def set_samplers(self, samplers: List[BaseSampler]) -> None:
"""Set the samplers list of the calibrator.
This method overwrites the samplers of a calibrator object with a custom list of samplers.
Args:
samplers: a list of samplers
"""
# overwrite the list of samplers
self.samplers = samplers
# update the samplers_id_table with the new samplers, only if necessary
sampler_id = max(self.samplers_id_table.values()) + 1
for sampler in samplers:
sampler_name = type(sampler).__name__
if sampler_name in self.samplers_id_table:
continue
self.samplers_id_table[sampler_name] = sampler_id
sampler_id = sampler_id + 1
simulate_model(self, params)
Simulate the model.
This method calls the model simulator in parallel on a given set of parameter values, a number of repeated evaluations are performed for each parameter to average out random fluctuations.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
params |
ndarray |
the array of parameters for which the model should be evaluated |
required |
noqa
Returns:
Type | Description |
---|---|
simulated_data |
an array of dimensions (batch_size, ensemble_size, N, D) containing all simulated time series |
Source code in black_it/calibrator.py
def simulate_model(self, params: NDArray) -> NDArray:
"""
Simulate the model.
This method calls the model simulator in parallel on a given set of parameter values, a number of repeated
evaluations are performed for each parameter to average out random fluctuations.
Args:
params: the array of parameters for which the model should be evaluated
# noqa
Returns:
simulated_data: an array of dimensions (batch_size, ensemble_size, N, D) containing all
simulated time series
"""
rep_params = np.repeat(params, self.ensemble_size, axis=0)
simulated_data_list = Parallel(n_jobs=self.n_jobs)(
delayed(self.model)(param, self.N, self._get_random_seed())
for i, param in enumerate(rep_params)
)
simulated_data = np.array(simulated_data_list)
simulated_data = np.reshape(
simulated_data, (params.shape[0], self.ensemble_size, self.N, self.D)
)
return simulated_data