Source code for probabilistic_library.project

# Copyright (C) Stichting Deltares. All rights reserved.
#
# This file is part of the Probabilistic Library.
#
# The Probabilistic Library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# All names, logos, and references to "Deltares" are registered trademarks of
# Stichting Deltares and remain full property of Stichting Deltares at all times.
# All rights reserved.
#


from __future__ import annotations
import sys
from multiprocessing import Pool, cpu_count
from typing import FrozenSet
from types import FunctionType, MethodType
from enum import Enum

from .statistic import Stochast, DistributionType, CorrelationMatrix, CopulaCorrelation, SelfCorrelationMatrix, Scenario, CorrelationType
from .reliability import DesignPoint, ReliabilityMethod, Settings, CombineSettings, ExcludingCombineSettings, LimitStateFunction
from .sensitivity import SensitivityResult, SensitivityValue, SensitivitySettings, SensitivityMethod
from .uncertainty import UncertaintyResult, UncertaintySettings, UncertaintyMethod
from .logging import Evaluation, Message, ValidationReport
from .utils import FrozenObject, FrozenList, CallbackList
from . import interface

import inspect

if not interface.IsLibraryLoaded():
	interface.LoadDefaultLibrary()

[docs] class ZModel(FrozenObject): """Wrapper around a python function or method. This class is created by a `ModelProject` when its model is set. The ZModel provides additional information about its input and output parameters. This is based on reflection when the ZModel is created.""" _callback = None _multiple_callback = None def __init__(self, callback = None, output_parameter_size = 1): self._model = None source_code = None if isinstance(callback, str): source_code = callback try: dynamic_code = compile(callback, '<string>', 'exec') code_object = dynamic_code.co_consts[0] callback = FunctionType(code_object, globals(), code_object.co_name) except Exception: callback = source_code # so that _is_function will be False self._is_function = inspect.isfunction(callback) or inspect.ismethod(callback) self._is_dirty = False self._has_arrays = False self._array_sizes = None self._pool = None self._max_processes = 1 self._project = None self._project_id = 0 self._z_values_size = 0 ZModel._index = 0; ZModel._callback = callback if self._is_function: self._model_name = callback.__name__ self._input_parameters = self._get_input_parameters(callback) if source_code != None: self._output_parameters = self._get_output_parameters(source_code, output_parameter_size) else: self._output_parameters = self._get_output_parameters(callback, output_parameter_size) else: self._model_name = '' self._input_parameters = [] self._output_parameters = [] super()._freeze() def __dir__(self): return ['name', 'input_parameters', 'output_parameters', 'print'] def __del__(self): try: if not self._pool is None: self._pool.close() except Exception as err: print(f"Unexpected {err=}, {type(err)=}") def __str__(self): return self.name @property def name(self) -> str: """Name of the model""" return self._model_name def _get_input_parameters(self, function): sig = inspect.signature(function) parameters = [] index = 0 for name, param in sig.parameters.items(): modelParameter = ModelParameter() modelParameter.name = name if param.default != param.empty: modelParameter.default_value = param.default if param.annotation != param.empty: modelParameter.is_array = '[' in str(param) modelParameter.index = index parameters.append(modelParameter) index += 1 return FrozenList(parameters) def _get_output_parameters(self, function, output_parameter_size = 1): parameters = [] if isinstance(function, str): source = function else: source = inspect.getsource(function) lines = source.splitlines() for line in lines: if line.strip().startswith('return'): line = line.replace('return', '') line = line.strip().split('#')[0] line = line.lstrip('[').rstrip(';').rstrip(']') words = line.split(',') parameters = [word.strip() for word in words] for i in range(len(parameters)): modelParameter = ModelParameter() modelParameter.name = parameters[i] modelParameter.index = i parameters[i] = modelParameter if (len(parameters) == 1 and output_parameter_size > 1): parameters[i].is_array = True parameters[i].array_size = output_parameter_size return FrozenList(parameters)
[docs] def is_model_valid(self) -> bool: """Indicates whether the model is valid""" if self._is_function: return True elif not self._model is None: return self._model.is_model_valid() else: return False
@property def input_parameters(self) -> list[ModelParameter]: """List of input parameters""" return self._input_parameters @property def output_parameters(self) -> list[ModelParameter]: """List of output parameters""" return self._output_parameters def _set_callback(self, callback): ZModel._callback = callback def _set_multiple_callback(self, multiple_callback): ZModel._multiple_callback = multiple_callback def _set_model(self, value): self._model = value
[docs] def set_max_processes(self, value : int): """Sets the maximum number of parallel processes""" self._max_processes = value if not self._model is None: self._model.set_max_processes(value)
[docs] def initialize_for_run(self): """Method to be called before the first run (this method is used internally by `ModelProject`)""" self._has_arrays = False for parameter in self.input_parameters: if parameter.is_array: self._has_arrays = True if self._has_arrays: self._array_sizes = [] for parameter in self.input_parameters: if parameter.is_array: self._array_sizes.append(parameter.array_size) else: self._array_sizes.append(-1) self._z_values_size = 0 for parameter in self.output_parameters: if parameter.is_array: self._z_values_size += parameter.array_size else: self._z_values_size += 1 if not self._model is None: self._model.initialize_for_run() if self._is_function: if self._max_processes > 1: self._pool = Pool(self._max_processes) elif self._max_processes < 1: self._pool = Pool() else: self._pool = None
[docs] def update(self): """Updates input and output parameters (this method is used internally by `ModelProject`)""" if not self._model is None: if self._model.is_dirty(): self._model.update_model() return True return False
def _get_args(self, values): args = [] index = 0; for i in range(len(self._array_sizes)): if self._array_sizes[i] == -1: args.append(values[index]) index += 1 else: arg_array = [] for j in range(self._array_sizes[i]): arg_array.append(values[index]) index += 1 args.append(arg_array) return args
[docs] def run_multiple(self, samples): """Performs the execution of multiple samples by the model (used internally)""" if self._is_function and self._pool is None: for sample in samples: self.run(sample) elif self._is_function and not self._pool is None: results = {} for sample in samples: sample_input = self._get_input(sample) results[sample] = self._pool.apply_async(func=ZModel._callback, args=(*sample_input,)) for sample in samples: z = results[sample].get() self._assign_output(sample, z) else: ZModel._multiple_callback(samples)
[docs] def run(self, sample): """Performs the execution of a sample by the model (used internally)""" if self._is_function: sample_input = self._get_input(sample) z = ZModel._callback(*sample_input) self._assign_output(sample, z) else: z = ZModel._callback(sample);
def _get_input(self, sample): if self._has_arrays: return self._get_args(sample.input_values) else: return sample.input_values def _assign_output(self, sample, z): if type(z) is list or type(z) is tuple: for i in range(self._z_values_size): sample.output_values[i] = z[i] else: sample.output_values[0] = z def _run_callback(sample_input): return ZModel._callback(*sample_input)
[docs] def print(self): """Prints the model with all input and output parameters""" pre = ' ' if not self.name == '': print(f'Model {self.name}:') print('Input parameters:') for input_parameter in self.input_parameters: if input_parameter.is_array: print(pre + f'{input_parameter.name}[{input_parameter.array_size}]') else: print(pre + f'{input_parameter.name}') print('Output parameters:') for output_parameter in self.output_parameters: if output_parameter.is_array: print(pre + f'{output_parameter.name}[{output_parameter.array_size}]') else: print(pre + f'{output_parameter.name}')
[docs] class ZModelContainer: """Wrapper of a `ZModel`, used internally by ptk.whl to assign a PTK model to a `ModelProject`"""
[docs] def get_model(self) -> ZModel|None: """Gets a ZModel""" return None
[docs] def is_model_valid(self) -> bool: """Indicates whether the model is valid""" return True
[docs] def is_dirty(self): return False
[docs] def update_model(self): pass
[docs] class ModelParameter(FrozenObject): """Input or output parameter of a model A model parameter is part of a `ZModel`, as one of its input or output parameters. It is generated when a `ZModel` is created. By reflection and type hints as much as possible information about the parameter is provided.""" def __init__(self, id = None): if id is None: self._id = interface.Create('model_parameter') else: self._id = id super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['name', 'index', 'default_value', 'is_array', 'array_size'] @property def name(self) -> str: """Name of the parameter""" return interface.GetStringValue(self._id, 'name') @name.setter def name(self, value): interface.SetStringValue(self._id, 'name', value) @property def index(self) -> int: """Sequence number of the parameter in the list of input or output parameters of a `ZModel`""" return interface.GetIntValue(self._id, 'index') @index.setter def index(self, value : int): interface.SetIntValue(self._id, 'index', value) @property def default_value(self) -> float: """Default value of the parameter""" return interface.GetValue(self._id, 'default_value') @default_value.setter def default_value(self, value : float): interface.SetValue(self._id, 'default_value', value) @property def is_array(self) -> bool: """Indicates whether the parameter is an array""" return interface.GetBoolValue(self._id, 'is_array') @is_array.setter def is_array(self, value : bool): interface.SetBoolValue(self._id, 'is_array', value) @property def array_size(self) -> int: """Array size, in case the parameter is an array""" return interface.GetIntValue(self._id, 'array_size') @array_size.setter def array_size(self, value : int): interface.SetIntValue(self._id, 'array_size', value) def __str__(self): return self.name
[docs] class ModelProject(FrozenObject): """Base class for projects, which contain a model When a model is set to `model`, the model input parameters and output parameters are updated. Based on these parameters, variables, correlation matrix and settings and settings per variable are generated in this class.""" _project_id = 0 _zmodel = None def __init__(self): self._known_variables = [] self._variables = FrozenList() self._correlation_matrix = CorrelationMatrix() self._copulas = None self._output_parameters = FrozenList() self._settings = None self._model = None self._correlation_type = CorrelationType.gaussian # do not freeze, this will be done by inheritors def _initialize_callbacks(self, project_id): ModelProject._project_id = project_id self._project_id = project_id self._callback = interface.CALLBACK(self._performCallBack) self._multiple_callback = interface.MULTIPLE_CALLBACK(self._perform_multiple_callback) interface.SetCallBack(project_id, 'model', self._callback) interface.SetMultipleCallBack(project_id, 'model', self._multiple_callback) interface.SetBoolValue(project_id, 'callback_assigned', False) def _set_settings(self, settings): self._settings = settings @interface.CALLBACK def _performCallBack(values, size, output_values): sample = _Sample(values[:size], output_values) ModelProject._zmodel.run(sample) @interface.MULTIPLE_CALLBACK def _perform_multiple_callback(sample_count, values, input_size, output_values): samples = [] for i in range(sample_count): samples.append(_Sample(values[i][:input_size], output_values[i])) ModelProject._zmodel.run_multiple(samples)
[docs] def is_valid(self) -> bool: """Indicates whether the settings are valid""" self._update() return interface.GetBoolValue(self._id, 'is_valid')
[docs] def validate(self): """Prints the validity of the settings""" self._update() id_ = interface.GetIdValue(self._id, 'validate') if id_ > 0: validation_report = ValidationReport(id_) validation_report.print()
@property def variables(self) -> list[Stochast]: """List of variables based on the input parameters of the model""" self._check_model() return self._variables @property def correlation_type(self) -> CorrelationType: return self._correlation_type @correlation_type.setter def correlation_type(self, value): # if the type changes, create an new correlation class for the type now in use, and make the other inaccessable if (value != self._correlation_type): if (value == CorrelationType.gaussian): self._correlation_matrix = CorrelationMatrix() self._correlation_matrix._set_variables(self._copulas._variables) self._copulas = None else: self._copulas = CopulaCorrelation() self._copulas._set_variables(self._correlation_matrix._variables) self._correlation_matrix = None self._correlation_type = value @property def correlation_matrix(self) -> CorrelationMatrix: """Correlation matrix based on the input parameters of the model""" self._check_model() return self._correlation_matrix @property def copulas(self) -> CopulaCorrelation: """Correlation copula based on the input parameters of the model""" self._check_model() return self._copulas @property def model(self) -> ZModel: """Method which serves as a model. A model is a function which calculates real world results based on real world input data (or is an academic function). It often relates to physical processes and is deterministic (it does not use uncertainty) When a model is set, it accepts a python function or python class method. Alternatively, a string defining a function is accepted too. The model should accept a number of input values (floats) or array of input values and returns a single value (float), an array of floats or a tuple of floats. When set, the function/method/string is wrapped in a `ZModel`. The ZModel has information about its input and output parameters (derived from the function signature). When the model is retrieved, the ZModel object is returned. It is also possible to set a `ZModelContainer`, which can provide a `ZModel`. PTK models are set with a `ZModelContainer` (using ptk.whl)""" if not self._model is None: self._model._project = self return self._model @model.setter def model(self, value : FunctionType | MethodType | str | ZModel | ZModelContainer): if isinstance(value, tuple): output_parameter_size = value[1] value = value[0] else: output_parameter_size = 1 if inspect.isfunction(value) or inspect.ismethod(value) or isinstance(value, str): self._model = ZModel(value, output_parameter_size) self._update_model() elif isinstance(value, ZModel): self._model = value self._share(value._project) self._update_model() elif isinstance(value, ZModelContainer): self._model = value.get_model() else: raise ValueError('ZModel container expected') interface.SetBoolValue(self._project_id, 'callback_assigned', self._model.is_model_valid()) def _check_model(self): if not self._model is None: if self._model.update(): self._update_model() def _share(self, shared_project): id1 = self._id id2 = shared_project._id self._known_variables.extend(shared_project.variables) interface.SetIntValue(self._id, 'share_project', shared_project._id) def _update_model(self): interface.SetArrayIntValue(self._project_id, 'input_parameters', [input_parameter._id for input_parameter in self._model.input_parameters]) interface.SetArrayIntValue(self._project_id, 'output_parameters', [output_parameter._id for output_parameter in self._model.output_parameters]) interface.SetStringValue(self._project_id, 'model_name', self._model.name) variables = [] variable_ids = interface.GetArrayIdValue(self._project_id, 'stochasts') for variable_id in variable_ids: variable = None for known_variable in self._known_variables: if known_variable._id == variable_id: variable = known_variable if variable is None: variable = Stochast(variable_id) self._known_variables.append(variable) variables.append(variable) self._variables = FrozenList(variables) if (self._correlation_type == CorrelationType.gaussian): self._correlation_matrix._set_variables(variables) else: self._copulas._set_variables(variables) self._settings._set_variables(variables) for var in self._variables: var._set_variables(self._variables) self._output_parameters = self._model.output_parameters def _update(self): self._check_model() if (self._correlation_type == CorrelationType.gaussian): interface.SetIntValue(self._project_id, 'correlation_matrix', self._correlation_matrix._id) else: interface.SetIntValue(self._project_id, 'copula_correlation', self._copulas._id) interface.SetIntValue(self._project_id, 'settings', self._settings._id) if hasattr(self.settings, 'stochast_settings'): interface.SetArrayIntValue(self.settings._id, 'stochast_settings', [stochast_setting._id for stochast_setting in self.settings.stochast_settings]) if self._model != None: if hasattr(self.settings, 'max_parallel_processes'): self._model.set_max_processes(self.settings.max_parallel_processes) self._model.initialize_for_run() ModelProject._zmodel = self._model def _run(self): if (self.is_valid()): interface.Execute(self._project_id, 'run') else: # print the validation messages self.validate()
[docs] class RunValuesType(Enum): """Enumeration which defines which value to use from a stochastic variable""" median_values = 'median_values' mean_values = 'mean_values' design_values = 'design_values' def __str__(self): return str(self.value)
class _Sample(FrozenObject): """Defines a sample, used internally""" def __init__(self, input_values, output_values): self.input_values = input_values self.output_values = output_values super()._freeze()
[docs] class RunProjectSettings(FrozenObject): """Settings for a model run""" def __init__(self): self._id = interface.Create('run_project_settings') super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['run_values_type', 'reuse_calculations', 'validate', 'is_valid'] @property def run_values_type(self) -> RunValuesType: """Defines which value to extract from the stochastic variables""" return RunValuesType[interface.GetStringValue(self._id, 'run_values_type')] @run_values_type.setter def run_values_type(self, value : RunValuesType): interface.SetStringValue(self._id, 'run_values_type', str(value)) @property def reuse_calculations(self) -> bool: """Indicates whether previous model results will be reused by the model run.""" return interface.GetBoolValue(self._id, 'reuse_calculations') @reuse_calculations.setter def reuse_calculations(self, value : bool): interface.SetBoolValue(self._id, 'reuse_calculations', value)
[docs] def is_valid(self) -> bool: """Indicates whether the settings are valid""" return interface.GetBoolValue(self._id, 'is_valid')
[docs] def validate(self): """Prints the validity of the settings""" id_ = interface.GetIdValue(self._id, 'validate') if id_ > 0: validation_report = ValidationReport(id_) validation_report.print()
def _set_variables(self, variables): pass
[docs] class RunProject(ModelProject): """Project for a running a model. This is the main entry point for running a model. This class is based on the `ModelProject` class. The model to use is defined in this class in `ModelProject.model`. When a model is set, variables and settings are generated. To run the model, use the `run` method. This results are stored in `realization`. """ def __init__(self): super().__init__() self._id = interface.Create('run_project') self._realization = None self._initialize_callbacks(self._id) self._set_settings(RunProjectSettings()) super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['variables', 'correlation_matrix', 'settings', 'model', 'run', 'realization', 'validate', 'is_valid'] @property def settings(self) -> RunProjectSettings: """Settings for running a model""" self._check_model() return self._settings
[docs] def run(self): """Performs the model run and puts the result in `realization`""" self._realization = None self._run()
@property def realization(self) -> Evaluation: """Realization of the performed model run""" if self._realization is None: realizationId = interface.GetIdValue(self._id, 'realization') if realizationId > 0: self._realization = Evaluation(realizationId) return self._realization
[docs] class SensitivityProject(ModelProject): """Project for a sensitivity analysis. This is the main entry point for performing a sensitivity analysis. This class is based on the `ModelProject` class. The model to use is defined in this class in `ModelProject.model`. When a model is set, variables, correlation matrix and settings and settings per variable are generated. To run a sensitivity analysis, use the `run` method. This results are stored in a list of `results`, where each result corresponds with an output parameter. A shortcut is provided in `result`, which corresponds with the first output parameter.""" def __init__(self): super().__init__() self._id = interface.Create('sensitivity_project') self._result = None self._results = None self._initialize_callbacks(self._id) self._set_settings(SensitivitySettings()) super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['variables', 'correlation_matrix', 'settings', 'model', 'parameter', 'run', 'result', 'results', 'validate', 'is_valid', 'total_model_runs'] @property def parameter(self) -> str: """Output parameter for which the sensitivity analysis should be performed, if left blank it will be performed for all output parameters""" return interface.GetStringValue(self._id, 'parameter') @parameter.setter def parameter(self, value : str | ModelParameter): interface.SetStringValue(self._id, 'parameter', str(value)) @property def settings(self) -> SensitivitySettings : """Settings for the sensitivity algorithm""" self._check_model() return self._settings
[docs] def run(self): """Performs the sensitivity analysis Results will be stored in `results`, for the first output `parameter` in `result`.""" self._result = None self._results = None self._run()
@property def result(self) -> SensitivityResult: """Sensitivity result corresponding with the output parameters""" if self._result is None: result_id = interface.GetIdValue(self._id, 'result') if result_id > 0: self._result = SensitivityResult(result_id) return self._result @property def results(self) -> list[SensitivityResult]: """List of sensitivity results, corresponding with the output parameters""" if self._results is None: results = [] result_ids = interface.GetArrayIdValue(self._id, 'results') for result_id in result_ids: result = SensitivityResult(result_id) result._set_variables(self.variables) results.append(result) self._results = FrozenList(results) return self._results @property def total_model_runs(self) -> int: """Total model runs performed by the last `run`""" return interface.GetIntValue(self._id, 'total_model_runs')
[docs] class UncertaintyProject(ModelProject): """Project for an uncertainty analysis. This is the main entry point for performing an uncertainty analysis. This class is based on the `ModelProject` class. The model to use is defined in this class in 'ModelProject.model'. When a model is set, variables, correlation matrix and settings and settings per variable are generated. The uncertainty analysis calculates the uncertainty for the output parameter selected in 'parameter'. If left blank, uncertainty analyses are performed for each output parameter. To run an uncertainty analysis, use the `run` method. This results in a list of `results`. Each result contains the uncertainty for a certain output parameter. Part of the result is a stochast, which contains the distribution of the output parameter. A shortcut to the stochasts is available via `stochasts`. The first output parameter result can also be accessed by `result` and `stochast`. If correlation between output parameters is requested via the `settings`, the resulting correlation matrix can be found in `output_correlation_matrix`.""" def __init__(self): super().__init__() self._id = interface.Create('uncertainty_project') self._stochast = None self._stochasts = None self._result = None self._results = None self._output_correlation_matrix = None self._initialize_callbacks(self._id) self._set_settings(UncertaintySettings()) super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['variables', 'correlation_matrix', 'settings', 'model', 'parameter', 'run', 'stochast', 'stochasts', 'result', 'results', 'output_correlation_matrix', 'validate', 'is_valid', 'total_model_runs'] @property def parameter(self) -> str: """Output parameter for which the uncertainty analysis should be performed, if left blank it will be performed for all output parameters""" return interface.GetStringValue(self._id, 'parameter') @parameter.setter def parameter(self, value : str | ModelParameter): interface.SetStringValue(self._id, 'parameter', str(value)) @property def settings(self) -> UncertaintySettings : """Settings for the uncertainty algorithm""" self._check_model() return self._settings
[docs] def run(self): """Performs the uncertainty analysis Results will be stored in `results`, for the first output `parameter` in `result`. The stochasts in the results are available directly in `stochasts` and `stochast`""" self._stochast = None self._stochasts = None self._result = None self._results = None self._output_correlation_matrix = None self._run()
@property def stochast(self) -> Stochast: """Stochast distribution of the first output `parameter`""" if self._stochast is None: if not self.result is None: self._stochast = self.result.variable return self._stochast @property def stochasts(self) -> list[Stochast]: """List of probability distributions, corresponding with the output parameters""" if self._stochasts is None: stochasts = [] for result in self.results: if not result is None: stochasts.append(result.variable) else: stochasts.append(None) self._stochasts = FrozenList(stochasts) return self._stochasts @property def result(self) -> UncertaintyResult: """Uncertainty result of the first output parameter""" if self._result is None: result_id = interface.GetIdValue(self._id, 'uncertainty_result') if result_id > 0: self._result = UncertaintyResult(result_id, self.variables) return self._result @property def results(self) -> list[UncertaintyResult]: """List of uncertainty results, corresponding with the output parameters""" if self._results is None: results = [] result_ids = interface.GetArrayIdValue(self._id, 'uncertainty_results') for result_id in result_ids: results.append(UncertaintyResult(result_id, self.variables)) self._results = FrozenList(results) return self._results @property def output_correlation_matrix(self) -> CorrelationMatrix: """Correlation matrix between output parameters and possibly with input parameters Depends on settings `UncertaintySettings.calculate_correlations` and `UncertaintySettings.calculate_input_correlations` whether this correlation matrix is generated""" if self._output_correlation_matrix is None: correlationMatrixId = interface.GetIdValue(self._id, 'output_correlation_matrix') if correlationMatrixId > 0: self._output_correlation_matrix = CorrelationMatrix(correlationMatrixId) self._output_correlation_matrix._update_variables(self.variables.get_list() + self.stochasts.get_list()) return self._output_correlation_matrix @property def total_model_runs(self) -> int: """Total model runs performed by the last `run`""" return interface.GetIntValue(self._id, 'total_model_runs')
[docs] class ReliabilityProject(ModelProject): """Project for reliability analysis. This is the main entry point for performing a reliability analysis. This class is based on the `ModelProject` class. The model to use is defined in this class in `ModelProject.model`. When a model is set, variables, correlation matrix and settings and settings per variable are generated. To run a reliability analysis, use the `run` method. This results in a `design_point`, where the reliability index, alpha values and, if specified in the settings, an overview of performed realizations and generated messages.""" def __init__(self): super().__init__() self._id = interface.Create('project') self._limit_state_function = None self._design_point = None self._fragility_curve = None self._initialized = False self._initialize_callbacks(self._id) self._set_settings(Settings()) super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['variables', 'correlation_matrix', 'limit_state_function', 'settings', 'model', 'run', 'design_point', 'validate', 'is_valid', 'total_model_runs'] @property def settings(self) -> Settings: """Settings for the algorithm performing the reliability analysis""" self._check_model() return self._settings @property def limit_state_function(self) -> LimitStateFunction: """Defines the transformation of the model output to a z-value By default, the first value in the model output is used as z-value""" if self._limit_state_function is None: lsf_id = interface.GetIdValue(self._id, 'limit_state_function') if lsf_id > 0: self._limit_state_function = LimitStateFunction(lsf_id) return self._limit_state_function
[docs] def run(self): """Performs the reliability analysis Results in a `design_point`, which is part of this project. When failed, the `design_point` is empty.""" self._design_point = None self._fragility_curve = None self._initialized = False self._run()
@property def design_point(self) -> DesignPoint: """The resulting design point of a reliability analysis, invoked by `run` Is empty when the run failed""" if self._design_point is None: designPointId = interface.GetIdValue(self._id, 'design_point') if designPointId > 0: self._design_point = DesignPoint(designPointId, self._get_variables()) return self._design_point def _get_variables(self): variables = [] variables.extend(self.variables) for variable in variables: for array_variable in variable.array_variables: if array_variable not in variables: variables.append(array_variable) return variables @property def total_model_runs(self) -> int: """Total model runs performed by the last `run`""" return interface.GetIntValue(self._id, 'total_model_runs')
[docs] class CombineProject(FrozenObject): """Project for combining design points. This is the main entry point for performing combining design points. The design points to be combined should be added to the list of `design_points`. To run the combination, use the `run` method. This results in a `design_point`, where the reliability index reflects the combined reliability index. The original `design_points` are added to the `probabilistic_library.reliability.DesignPoint.contributing_design_points` of the resulting design point. """ def __init__(self): self._id = interface.Create('combine_project') self._design_points = CallbackList(self._design_points_changed) self._settings = CombineSettings() self._correlation_matrix = SelfCorrelationMatrix() self._design_point_correlation_matrix = CorrelationMatrix() self._design_point = None super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['design_points', 'settings', 'correlation_matrix', 'run', 'design_point_correlation_matrix', 'design_point'] def _design_points_changed(self): variables = [] for design_point in self._design_points: variables.extend(design_point.get_variables()) self._correlation_matrix._set_variables(variables) self._design_point_correlation_matrix._set_variables(variables) @property def design_points(self) -> list[DesignPoint]: """List of design points to be combined""" return self._design_points @property def settings(self) -> CombineSettings: """Settings for the combination algorithm""" return self._settings @property def correlation_matrix(self) -> SelfCorrelationMatrix: """Auto correlation matrix, holds correlations between same named variables in the `design_points`""" return self._correlation_matrix def _update(self): interface.SetArrayIntValue(self._id, 'design_points', [design_point._id for design_point in self._design_points]) interface.SetIntValue(self._id, 'settings', self._settings._id) interface.SetIntValue(self._id, 'correlation_matrix', self._correlation_matrix._id) interface.SetIntValue(self._id, 'design_point_correlation_matrix', self._design_point_correlation_matrix._id)
[docs] def is_valid(self) -> bool: """Indicates whether the combine project is valid""" self._update() return interface.GetBoolValue(self._id, 'is_valid')
[docs] def validate(self): """Prints the validity of the combine project""" self._update() id_ = interface.GetIdValue(self._id, 'validate') if id_ > 0: validation_report = ValidationReport(id_) validation_report.print()
[docs] def run(self): """Performs the combination of the design point. Results in a `design_point`, which is part of this project. When failed, the `design_point` is empty.""" self._design_point = None # update performed by is_valid if (self.is_valid()): interface.Execute(self._id, 'run') else: print('run not executed, input is not valid')
@property def design_point(self) -> DesignPoint: """The resulting combined design point, invoked by `run` Is empty when the run failed. The original `design_points` are added to the `probabilistic_library.reliability.DesignPoint.contributing_design_point` """ if self._design_point is None: designPointId = interface.GetIdValue(self._id, 'design_point') if designPointId > 0: variables = [] for design_point in self._design_points: variables.extend(design_point.get_variables()) self._design_point = DesignPoint(designPointId, variables, self._design_points) return self._design_point
[docs] class ExcludingCombineProject(FrozenObject): """Project for combining design points exclusively or, otherwise stated, design points calculated for a scenario. This is the main entry point for this operation. Excluding design points refer to design points generated for a scenario. Each scenario has a probability too, but scenarios are mutually exclusive. Probabilities of scenarios should add up to 1. The design points to be combined should be added to the list of `design_points`. The list of `scenarios` should correspond with the list of `design_points`. To run the combination, use the `run` method. This results in a `design_point`, where the reliability index reflects the combined reliability index. The original `design_points` are added to the `probabilistic_library.reliability.DesignPoint.contributing_design_points` of the resulting design point. """ def __init__(self): self._id = interface.Create('excluding_combine_project') self._design_points = CallbackList(self._design_points_changed) self._scenarios = CallbackList(self._scenarios_changed) self._settings = ExcludingCombineSettings() self._design_point = None self._synchronizing = False self._dirty = True interface.SetIntValue(self._id, 'settings', self._settings._id) super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['design_points', 'scenarios', 'settings', 'is_valid', 'validate', 'run', 'design_point'] def _design_points_changed(self): if not self._synchronizing: self._dirty = True def _scenarios_changed(self): if not self._synchronizing: # replace floats by Scenario self._synchronizing = True for i in range(len(self._scenarios)): if isinstance(self._scenarios[i], int) or isinstance(self._scenarios[i], float): val = self._scenarios[i] self._scenarios[i] = Scenario() self._scenarios[i].probability = val self._synchronizing = False self._dirty = True def _update(self): if self._dirty: interface.SetArrayIntValue(self._id, 'design_points', [design_point._id for design_point in self._design_points]) interface.SetArrayIntValue(self._id, 'scenarios', [scenario._id for scenario in self._scenarios]) self._dirty = False @property def design_points(self) -> list[DesignPoint]: """List of design points to be combined""" return self._design_points @property def scenarios(self) -> list[Scenario]: """List of scenarios corresponding with design points to be combined""" return self._scenarios @property def settings(self) -> ExcludingCombineSettings: """Settings for the combine algorithm""" return self._settings
[docs] def is_valid(self) -> bool: """Indicates whether the excluding combine project is valid""" self._update() return interface.GetBoolValue(self._id, 'is_valid')
[docs] def validate(self): """Prints the validity of the excluding combine project""" self._update() id_ = interface.GetIdValue(self._id, 'validate') if id_ > 0: validation_report = ValidationReport(id_) validation_report.print()
[docs] def run(self): """Performs the excluding combination of the design point. Results in a design point, which is part of this project. When failed, the `design_point` is empty.""" self._update() self._design_point = None if (self.is_valid()): interface.Execute(self._id, 'run') else: # print validation messages self.validate()
@property def design_point(self): """The resulting excluding combined design point, invoked by `run`. Is empty when the run failed. The original `design_points` are added to the `contributing_design_points`""" if self._design_point is None: design_point_id = interface.GetIdValue(self._id, 'design_point') if design_point_id > 0: variables = [] for design_point in self._design_points: variables.extend(design_point.get_variables()) self._design_point = DesignPoint(design_point_id, variables, self._design_points) return self._design_point
[docs] class LengthEffectProject(FrozenObject): """Project for applying the length effect to a design point, also known as upscaling in space. This is the main entry point for applying the length effect. When a design point is valid for a certain section or cross section, it can be useful to make it applicable to a longer section. This operation takes into account the length effect or upscaling. Each input variable is valid for a certain length. These lengths are stored in the `correlation_lengths` list. The length effect applied to the design point results in a design point valid for the requested `length`. To run the combination, use the `LengthEffectProject.run` method. This results in a `design_point`, where the reliability index reflects the length effect applied reliability index. The original `LengthEffectProject.design_point_cross_section` is added to the `probabilistic_library.reliability.DesignPoint.contributing_design_points` of the resulting design point. ```mermaid classDiagram class LengthEffectProject{ +design_point_cross_section DesignPoint +design_point DesignPoint +run() } class Stochast{} class SelfCorrelationMatrix{} class Stochast{} DesignPoint "input" <-- LengthEffectProject DesignPoint "result" <-- LengthEffectProject SelfCorrelationMatrix <-- LengthEffectProject Stochast "*" <-- SelfCorrelationMatrix ``` """ def __init__(self): self._id = interface.Create('length_effect_project') self._design_point_cross_section = DesignPoint() self._correlation_matrix = SelfCorrelationMatrix() self._design_point = None super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['design_point_cross_section', 'correlation_lengths' 'length', 'correlation_matrix', 'run', 'design_point'] @property def design_point_cross_section(self) -> DesignPoint: """The design point to which the length effect will be applied""" return self._design_point_cross_section @design_point_cross_section.setter def design_point_cross_section(self, value: DesignPoint): self._design_point_cross_section = value variables = value.get_variables() self._correlation_matrix._set_variables(variables) @property def correlation_matrix(self) -> SelfCorrelationMatrix: """Auto correlation matrix, holds correlations of variables in the `design_point_cross_section`""" return self._correlation_matrix @property def correlation_lengths(self) -> list[float]: """List of lengths of the input variables, should match with the input variables""" return interface.GetArrayValue(self._id, 'correlation_lengths') @correlation_lengths.setter def correlation_lengths(self, values : list[float]): interface.SetArrayValue(self._id, 'correlation_lengths', values) @property def length(self) -> float: """Length to be applied to the design point. The resulting design point will be applicable to this length""" return interface.GetValue(self._id, 'length') @length.setter def length(self, value: float): interface.SetValue(self._id, 'length', value)
[docs] def run(self): """Applies the length effect to the `design_point_cross_section`. Results is a design point, which is part of this project. When failed, the `design_point` is empty.""" self._design_point = None interface.SetIntValue(self._id, 'design_point_cross_section', self._design_point_cross_section._id) interface.SetIntValue(self._id, 'correlation_matrix', self._correlation_matrix._id) interface.Execute(self._id, 'run')
@property def design_point(self) -> DesignPoint: """The length effect applied design point, invoked by `run` Is empty when the run failed. The original `design_point_cross_section` is added to the `DesignPoint.contributing_design_point`""" if self._design_point is None: designPointId = interface.GetIdValue(self._id, 'design_point') if designPointId > 0: variables = self._design_point_cross_section.get_variables() self._design_point = DesignPoint(designPointId, variables, self._design_point_cross_section) return self._design_point