Source code for probabilistic_library.reliability

# 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
from math import isnan
import matplotlib.pyplot as plt
import sys
from enum import Enum

from .utils import FrozenObject, FrozenList, PrintUtils, CallbackList
from .statistic import Stochast, FragilityValue
from .logging import Message, Evaluation, ValidationReport
from . import interface

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

[docs] class ReliabilityMethod(Enum): """Enumeration which defines the algorithm to perform a reliability analysis""" form = 'form' numerical_integration = 'numerical_integration' crude_monte_carlo = 'crude_monte_carlo' importance_sampling = 'importance_sampling' adaptive_importance_sampling = 'adaptive_importance_sampling' directional_sampling = 'directional_sampling' subset_simulation = 'subset_simulation' numerical_bisection = 'numerical_bisection' latin_hypercube = 'latin_hypercube' cobyla_reliability = 'cobyla_reliability' form_then_directional_sampling = 'form_then_directional_sampling' directional_sampling_then_form = 'directional_sampling_then_form' def __str__(self): return str(self.value)
[docs] class DesignPointMethod(Enum): """Enumeration which defines how the design point is determined""" nearest_to_mean = 'nearest_to_mean' center_of_gravity = 'center_of_gravity' center_of_angles = 'center_of_angles' def __str__(self): return str(self.value)
[docs] class StartMethod(Enum): """Enumeration which defines the starting point of some reliability algorithms""" fixed_value = 'fixed_value' one = 'one' ray_search = 'ray_search' sphere_search = 'sphere_search' sensitivity_search = 'sensitivity_search' def __str__(self): return str(self.value)
[docs] class GradientType(Enum): """Enumeration which defines how a gradient is calculated""" single = 'single' double = 'double' def __str__(self): return str(self.value)
[docs] class SampleMethod(Enum): """Enumeration which defines how samples are generated in the subset simulation algorithm""" markov_chain = 'markov_chain' adaptive_conditional = 'adaptive_conditional' def __str__(self): return str(self.value)
[docs] class CombinerMethod(Enum): """Enumeration which defines the algorithm to combine design points""" hohenbichler = 'hohenbichler' importance_sampling = 'importance_sampling' directional_sampling = 'directional_sampling' hohenbichler_form = 'hohenbichler_form' def __str__(self): return str(self.value)
[docs] class ExcludingCombinerMethod(Enum): """Enumeration which defines the algorithm to combine excluding design points""" weighted_sum = 'weighted_sum' hohenbichler = 'hohenbichler_excluding' def __str__(self): return str(self.value)
[docs] class CombineType(Enum): """Enumeration which defines how design points are combined""" series = 'series' """OR port, the result of the combination is the probability that one of the failure conditions will happen""" parallel = 'parallel' """AND port, the result of the combination is the probability that all of the failure conditions will happen.""" def __str__(self): return str(self.value)
[docs] class MessageType(Enum): """Indicates the severity of a message Only in case of message type 'error' a calculation is not allowed or has failed""" debug = 'debug' info = 'info' warning = 'warning' error = 'error' def __str__(self): return str(self.value)
[docs] class Settings(FrozenObject): """Settings of a reliability algorithm Settings of all reliability algorithms are combined in this class. Often settings only apply to a selected number of algorithms. Settings per variable are listed in `stochast_settings`. The settings are divided into the following categories: Algorithm settings: Settings which are used to control the algorithm, such as the number of allowed samples and the convergence criterion. Runtime settings: Settings to control how model executions take place and which additional output to produce. These settings do not influence the calculated reliability index and alpha values.""" def __init__(self, id_ = None): if id_ is None: self._id = interface.Create('settings') else: self._id = id_ self._stochast_settings = FrozenList() super()._freeze() interface.SetBoolValue(self._id, 'use_openmp_in_reliability', False) def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['max_parallel_processes', 'save_realizations', 'save_convergence', 'save_messages', 'reuse_calculations', 'reliability_method', 'design_point_method', 'start_method', 'all_quadrants', 'max_steps_sphere_search', 'is_repeatable_random', 'random_seed', 'sample_method' 'minimum_samples', 'maximum_samples', 'minimum_iterations', 'maximum_iterations', 'minimum_directions', 'maximum_directions', 'epsilon_beta', 'step_size', 'gradient_type', 'relaxation_factor', 'relaxation_loops', 'minimum_variance_loops', 'maximum_variance_loops', 'variation_coefficient', 'fraction_failed', 'stochast_settings', 'is_valid', 'validate'] @property def max_parallel_processes(self) -> int: """The number of parallel executions of model evaluations""" return interface.GetIntValue(self._id, 'max_parallel_processes') @max_parallel_processes.setter def max_parallel_processes(self, value : int): interface.SetIntValue(self._id, 'max_parallel_processes', value) @property def save_realizations(self) -> bool: """Indicates whether samples should be saved If saved, the samples will be part of the design point""" return interface.GetBoolValue(self._id, 'save_realizations') @save_realizations.setter def save_realizations(self, value : bool): interface.SetBoolValue(self._id, 'save_realizations', value) @property def save_convergence(self) -> bool: """Indicates whether information about convergence of the reliability analysis should be saved If saved, convergence data will be part of the design point""" return interface.GetBoolValue(self._id, 'save_convergence') @save_convergence.setter def save_convergence(self, value : bool): interface.SetBoolValue(self._id, 'save_convergence', value) @property def save_messages(self) -> bool: """Indicates whether messages generated by the reliability analysis will be saved If saved, messages will be part of the design point""" return interface.GetBoolValue(self._id, 'save_messages') @save_messages.setter def save_messages(self, value : bool): interface.SetBoolValue(self._id, 'save_messages', value) @property def reuse_calculations(self) -> bool: """Indicates whether prior model results will be reused by the reliability analysis. This will speed up calculations when several analyses are performed, for which the same realizations will have to be executed, for example a Crude Monte Carlo analysis with different limit state definitions. But when a modification to the model is made, which is beyond the scope of the model definition, this leads to undesired results""" return interface.GetBoolValue(self._id, 'reuse_calculations') @reuse_calculations.setter def reuse_calculations(self, value : bool): interface.SetBoolValue(self._id, 'reuse_calculations', value) @property def reliability_method(self) -> ReliabilityMethod: """Defines the reliability algorithm""" return ReliabilityMethod[interface.GetStringValue(self._id, 'reliability_method')] @reliability_method.setter def reliability_method(self, value : ReliabilityMethod): interface.SetStringValue(self._id, 'reliability_method', str(value)) @property def design_point_method(self) -> DesignPointMethod: """Defines the algorithm how the design point is derived""" return DesignPointMethod[interface.GetStringValue(self._id, 'design_point_method')] @design_point_method.setter def design_point_method(self, value : DesignPointMethod): interface.SetStringValue(self._id, 'design_point_method', str(value)) @property def start_method(self) -> StartMethod: """Defines the starting point of the algorithm""" return StartMethod[interface.GetStringValue(self._id, 'start_method')] @start_method.setter def start_method(self, value : StartMethod): interface.SetStringValue(self._id, 'start_method', str(value)) @property def all_quadrants(self) -> bool: """Indicates whether all quadrants should be evaluated, only used by sphere search""" return interface.GetBoolValue(self._id, 'all_quadrants') @all_quadrants.setter def all_quadrants(self, value : bool): interface.SetBoolValue(self._id, 'all_quadrants', value) @property def max_steps_sphere_search(self) -> int: """The maximum number of steps in sphere search to determine the starting point""" return interface.GetIntValue(self._id, 'max_steps_sphere_search') @max_steps_sphere_search.setter def max_steps_sphere_search(self, value : int): interface.SetIntValue(self._id, 'max_steps_sphere_search', value) @property def is_repeatable_random(self) -> bool: """Indicates whether in each run the same random samples will be generated""" return interface.GetBoolValue(self._id, 'is_repeatable_random') @is_repeatable_random.setter def is_repeatable_random(self, value : bool): interface.SetBoolValue(self._id, 'is_repeatable_random', value) @property def random_seed(self) -> int: """Seed number for the random generator""" return interface.GetIntValue(self._id, 'random_seed') @random_seed.setter def random_seed(self, value : int): interface.SetIntValue(self._id, 'random_seed', value) @property def sample_method(self) -> SampleMethod: """The way samples are generated for the subset simulation algorithm""" return SampleMethod[interface.GetStringValue(self._id, 'sample_method')] @sample_method.setter def sample_method(self, value : SampleMethod): interface.SetStringValue(self._id, 'sample_method', str(value)) @property def minimum_samples(self) -> int: """The minimum number of samples to be used""" return interface.GetIntValue(self._id, 'minimum_samples') @minimum_samples.setter def minimum_samples(self, value : int): interface.SetIntValue(self._id, 'minimum_samples', value) @property def maximum_samples(self) -> int: """The maximum number of samples to be used""" return interface.GetIntValue(self._id, 'maximum_samples') @maximum_samples.setter def maximum_samples(self, value : int): interface.SetIntValue(self._id, 'maximum_samples', value) @property def minimum_iterations(self) -> int: """The minimum number of iterations to be used""" return interface.GetIntValue(self._id, 'minimum_iterations') @minimum_iterations.setter def minimum_iterations(self, value : int): interface.SetIntValue(self._id, 'minimum_iterations', value) @property def maximum_iterations(self) -> int: """The maximum number of iterations to be used""" return interface.GetIntValue(self._id, 'maximum_iterations') @maximum_iterations.setter def maximum_iterations(self, value : int): interface.SetIntValue(self._id, 'maximum_iterations', value) @property def minimum_directions(self) -> int: """The minimum number of directions to be used, only for directional sampling""" return interface.GetIntValue(self._id, 'minimum_directions') @minimum_directions.setter def minimum_directions(self, value : int): interface.SetIntValue(self._id, 'minimum_directions', value) @property def maximum_directions(self) -> int: """The maximum number of directions to be used, only for directional sampling""" return interface.GetIntValue(self._id, 'maximum_directions') @maximum_directions.setter def maximum_directions(self, value : int): interface.SetIntValue(self._id, 'maximum_directions', value) @property def epsilon_beta(self) -> float: """Convergence criterion for FORM, using the maximum allowed predicted uncertainty in reliability index""" return interface.GetValue(self._id, 'epsilon_beta') @epsilon_beta.setter def epsilon_beta(self, value : float): interface.SetValue(self._id, 'epsilon_beta', value) @property def step_size(self) -> float: """Step size in finding the gradient of a model, defined in u-space""" return interface.GetValue(self._id, 'step_size') @step_size.setter def step_size(self, value : float): interface.SetValue(self._id, 'step_size', value) @property def gradient_type(self) -> GradientType: """Method to determine the gradient of a model""" return GradientType[interface.GetStringValue(self._id, 'gradient_type')] @gradient_type.setter def gradient_type(self, value : GradientType): interface.SetStringValue(self._id, 'gradient_type', str(value)) @property def relaxation_factor(self) -> float: """Relaxation factor, used by FORM""" return interface.GetValue(self._id, 'relaxation_factor') @relaxation_factor.setter def relaxation_factor(self, value : float): interface.SetValue(self._id, 'relaxation_factor', value) @property def relaxation_loops(self) -> int: """Number of relaxation loops. In case of no convergence, the relaxation factor is decreased until convergence is found, used by FORM""" return interface.GetIntValue(self._id, 'relaxation_loops') @relaxation_loops.setter def relaxation_loops(self, value): interface.SetIntValue(self._id, 'relaxation_loops', value) @property def maximum_variance_loops(self) -> int: """Maximum number of importance sampling loops, used by adaptive importance sampling""" return interface.GetIntValue(self._id, 'maximum_variance_loops') @maximum_variance_loops.setter def maximum_variance_loops(self, value : int): interface.SetIntValue(self._id, 'maximum_variance_loops', value) @property def minimum_variance_loops(self) -> int: """Minimum number of importance sampling loops, used by adaptive importance sampling""" return interface.GetIntValue(self._id, 'minimum_variance_loops') @minimum_variance_loops.setter def minimum_variance_loops(self, value : int): interface.SetIntValue(self._id, 'minimum_variance_loops', value) @property def variation_coefficient(self) -> float: """Convergence criterion, used by Monte Carlo family algorithms""" return interface.GetValue(self._id, 'variation_coefficient') @variation_coefficient.setter def variation_coefficient(self, value : float): interface.SetValue(self._id, 'variation_coefficient', value) @property def fraction_failed(self) -> float: """Indicates the fraction of failed samples. Criterion for adaptive importance sampling whether to perform another importance sampling loop""" return interface.GetValue(self._id, 'fraction_failed') @fraction_failed.setter def fraction_failed(self, value : float): interface.SetValue(self._id, 'fraction_failed', value) @property def stochast_settings(self) -> list[StochastSettings]: """List of settings specified per stochastic variable""" return self._stochast_settings def _set_variables(self, variables): new_stochast_settings = [] for variable in variables: stochast_setting = self._stochast_settings[str(variable)] if stochast_setting is None: stochast_setting = StochastSettings(variable) new_stochast_settings.append(stochast_setting) self._stochast_settings = FrozenList(new_stochast_settings) interface.SetArrayIntValue(self._id, 'stochast_settings', [stochast_setting._id for stochast_setting in self._stochast_settings])
[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()
[docs] class StochastSettings(FrozenObject): """Defines reliability or uncertainty settings for a stochastic variable These settings are part of reliability settings or uncertainty settings. When using a project, these settings are generated automatically.""" def __init__(self, variable): self._id = interface.Create('stochast_settings') self._variable = variable if not variable is None: interface.SetIntValue(self._id, 'variable', self._variable._id) super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['min_value', 'max_value', 'start_value', 'variance_factor', 'is_initialization_allowed', 'is_variance_allowed', 'intervals'] def __str__(self): if self._variable is None: return '' else: return self._variable.name @property def variable(self) -> Stochast: """The stochastic variable to which these settings apply This value cannot be set. A project generates these settings automatically when a model is set to the project.""" if self._variable is None: id_ = interface.GetIdValue(self._id, 'variable') if id_ > 0: self._variable = Stochast(id_) return self._variable @property def min_value(self) -> float: """The minimum value which will be assigned to the stochastic variable in an analysis""" return interface.GetValue(self._id, 'min_value') @min_value.setter def min_value(self, value : float): interface.SetValue(self._id, 'min_value', value) @property def max_value(self) -> float: """The maximum value which will be assigned to the stochastic variable in an analysis""" return interface.GetValue(self._id, 'max_value') @max_value.setter def max_value(self, value : float): interface.SetValue(self._id, 'max_value', value) @property def start_value(self) -> float: """The value of the starting point for this stochastic variable in an analysis""" return interface.GetValue(self._id, 'start_value') @start_value.setter def start_value(self, value : float): interface.SetValue(self._id, 'start_value', value) @property def intervals(self) -> int: """The number of intervals for this stochastic variable, used by numerical integration""" return interface.GetIntValue(self._id, 'intervals') @intervals.setter def intervals(self, value : int): interface.SetIntValue(self._id, 'intervals', value) @property def variance_factor(self) -> float: """The variance factor for this stochastic variable, used by importance sampling""" return interface.GetValue(self._id, 'variance_factor') @variance_factor.setter def variance_factor(self, value : float): interface.SetValue(self._id, 'variance_factor', value) @property def is_initialization_allowed(self) -> bool: """Indicates whether the value in the starting point can be generated (true) or that the start_value is used (false)""" return interface.GetBoolValue(self._id, 'is_initialization_allowed') @is_initialization_allowed.setter def is_initialization_allowed(self, value : bool): interface.SetBoolValue(self._id, 'is_initialization_allowed', value) @property def is_variance_allowed(self) -> bool: """Indicates whether the starting point is updated for this stochastic variable, used by adaptive importance sampling""" return interface.GetBoolValue(self._id, 'is_variance_allowed') @is_variance_allowed.setter def is_variance_allowed(self, value: bool): interface.SetBoolValue(self._id, 'is_variance_allowed', value)
[docs] class CompareType(Enum): """Enumeration which defines a comparison type""" less_than = 'less_than' greater_than = 'greater_than' def __str__(self): return str(self.value)
[docs] class LimitStateFunction(FrozenObject): """Defines how model output is transformed to a z-value, which is used by reliability analyses. A reliability algorithm only uses a z-value, where a value < 0 indicates failure and a value >= 0 indicates no failure. A model produces several output values in the form of an array, where each array value is related to an output parameter of the model. This class allows the user to select an output parameter and compare it against a critical value. A limit state function should be added to a reliability project. If not added, the reliability algorithm uses the first value of the model output array and interprets it as the z-value.""" def __init__(self, id = None): if id is None: self._id = interface.Create('limit_state_function') else: self._id = id super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['parameter', 'compare_type', 'critical_value'] def __str__(self): return self.parameter + ' ' + str(self.compare_type) + ' ' + str(self.critical_value) @property def parameter(self) -> str: """The output `probabilistic_library.project.ModelParameter` to be used""" return interface.GetStringValue(self._id, 'parameter') @parameter.setter def parameter(self, value : str | ModelParameter): interface.SetStringValue(self._id, 'parameter', str(value)) @property def compare_type(self) -> CompareType: """The way the output parameter is compared with a critical value (greater than, less than)""" return CompareType[interface.GetStringValue(self._id, 'compare_type')] @compare_type.setter def compare_type(self, value : CompareType): interface.SetStringValue(self._id, 'compare_type', str(value)) @property def critical_value(self) -> float: """The critical value with which the output parameter is compared This can be a numerical value or another model parameter (input or output)""" if interface.GetBoolValue(self._id, 'use_compare_parameter'): return interface.GetStringValue(self._id, 'compare_parameter') else: return interface.GetValue(self._id, 'critical_value') @critical_value.setter def critical_value(self, value : float | ModelParameter | str): if type(value) is float or type(value) is int: interface.SetBoolValue(self._id, 'use_compare_parameter', False) interface.SetValue(self._id, 'critical_value', value) else: interface.SetBoolValue(self._id, 'use_compare_parameter', True) interface.SetStringValue(self._id, 'compare_parameter', str(value))
[docs] class DesignPointIds(FrozenObject): """Base class for additional design point information""" def __init__(self): pass # empty method as it is in a abstract class
[docs] class DesignPoint(FrozenObject): """Result of a reliability analysis, containing the reliability index and contributions of stochastic variables. The main result of a reliability analysis is a reliability index, which can be transformed to a probability of failure. Also contributions of stochastic variables are given in the list of alphas. Convergence information, number of samples, iterations or directions are given too. Depending on settings, samples, messages and convergence information during the calculation are reported too. If intermediate design points were generated, for example by adaptive importance sampling, they are reported in the contributing design points list. The design point can further be used to combine it with other design points or upscale it to make a section design point applicable to a system""" def __init__(self, id = None, known_variables = None, known_design_points = None): if id is None: self._id = interface.Create('design_point') else: self._id = id self._alphas = None self._contributing_design_points = None self._messages = None self._realizations = None self._reliability_results = None self._ids = None self._known_variables = known_variables self._known_design_points = known_design_points super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['identifier', 'ids', 'reliability_index', 'probability_failure', 'alphas', 'contributing_design_points', 'convergence', 'is_converged', 'total_directions', 'total_iterations', 'total_model_runs', 'realizations', 'reliability_results', 'messages', 'print', 'plot_alphas', 'plot_realizations', 'plot_convergence', 'get_plot_alphas', 'get_plot_realizations', 'get_plot_convergence'] def __str__(self): return self.identifier @property def identifier(self) -> str: """Identifying text of the design point, generated by the reliability algorithm""" return interface.GetStringValue(self._id, 'identifier') @identifier.setter def identifier(self, value : str): interface.SetStringValue(self._id, 'identifier', value) @property def ids(self) -> DesignPointIds: """Additional information""" return self._ids @ids.setter def ids(self, value : DesignPointIds): self._ids = value interface.SetIntValue(self._id, 'ids', value._id) @property def reliability_index(self) -> float: """Reliability index, calculated by the reliability algorithm""" return interface.GetValue(self._id, 'reliability_index') # testing method def _set_reliability_index(self, reliability_index_value): interface.SetValue(self._id, 'reliability_index', reliability_index_value) # testing method def _add_alpha(self, variable, alpha_value): alpha = Alpha(); alpha._set_alpha(variable, alpha_value, self.reliability_index); alphas = [] alphas.extend(self.alphas) alphas.append(alpha) self._alphas = FrozenList(alphas) values = [a._id for a in self._alphas] interface.SetArrayIntValue(self._id, 'alphas', values) @property def probability_failure(self) -> float: """Probability of failure, related to the reliability index""" return interface.GetValue(self._id, 'probability_failure') @property def convergence(self) -> float: """Achieved convergence with the reliability algorithm""" return interface.GetValue(self._id, 'convergence') @property def is_converged(self) -> bool: """Indicates whether convergence is reached""" return interface.GetBoolValue(self._id, 'is_converged') @property def total_directions(self) -> int: """The total number of directions, used by the directional sampling algorithm""" return interface.GetIntValue(self._id, 'total_directions') @property def total_iterations(self) -> int: """The total number of iterations, used by FORM""" return interface.GetIntValue(self._id, 'total_iterations') @property def total_model_runs(self) -> int: """The total number of samples""" return interface.GetIntValue(self._id, 'total_model_runs') @property def alphas(self) -> list[Alpha]: """List of contributions per stochastic variable to this design point The alphas indicate the contribution of the uncertainty of the stochastic variable to the design point. They also contain the value of the variable in the design point. For each variable in the reliability project variables an alpha variable is present in this list. In case of an array variable, for each array entry an alpha value is present.""" if self._alphas is None: alphas = [] alpha_ids = interface.GetArrayIdValue(self._id, 'alphas') for alpha_id in alpha_ids: alphas.append(Alpha(alpha_id, self._known_variables)) self._alphas = FrozenList(alphas) return self._alphas @property def contributing_design_points(self) -> list[DesignPoint]: """List of intermediate design points They can refer to loops in importance sampling or in FORM, when several relaxation loops are allowed. When a starting point was used and generated, this will also be part of the contributing design point.""" if self._contributing_design_points is None: contributing_design_points = [] design_point_ids = interface.GetArrayIdValue(self._id, 'contributing_design_points') for design_point_id in design_point_ids: if design_point_id > 0: added = False if not self._known_design_points is None: for design_point in self._known_design_points: if design_point._id == design_point_id: contributing_design_points.append(design_point) added = True if not added: contributing_design_points.append(DesignPoint(design_point_id, self._known_variables, self._known_design_points)) self._contributing_design_points = FrozenList(contributing_design_points) return self._contributing_design_points @property def realizations(self) -> list[Evaluation]: """List of samples calculated by the reliability algorithm. Depends on the setting `Settings.save_realizations` whether this list is provided""" if self._realizations is None: realizations = [] realization_ids = interface.GetArrayIdValue(self._id, 'evaluations') for realization_id in realization_ids: realizations.append(Evaluation(realization_id)) self._realizations = FrozenList(realizations) return self._realizations @property def reliability_results(self) -> list[ReliabilityResult]: """List of convergence reports during the reliability analysis. Depends on the setting `Settings.save_convergence` whether this list is provided""" if self._reliability_results is None: reliability_results = [] reliability_result_ids = interface.GetArrayIdValue(self._id, 'reliability_results') for reliability_result_id in reliability_result_ids: reliability_results.append(ReliabilityResult(reliability_result_id)) self._reliability_results = FrozenList(reliability_results) return self._reliability_results @property def messages(self) -> list[Message]: """List of messages generated by the reliability algorithm. Depends on the setting `Settings.save_messages` whether this list is provided""" if self._messages is None: messages = [] message_ids = interface.GetArrayIdValue(self._id, 'messages') for message_id in message_ids: messages.append(Message(message_id)) self._messages = FrozenList(messages) return self._messages
[docs] def get_variables(self) -> list[Stochast]: """Gets a list of all stochastic variables in this design point or contributing design points""" variables = [] for alpha in self.alphas: variables.append(alpha.variable) for contributing_design_point in self.contributing_design_points: variables.extend(contributing_design_point.get_variables()) return frozenset(variables)
[docs] def print(self, decimals = 4): """Prints the design point, including convergence, list of alphas and contributing design points Parameters ---------- decimals : int, optional The number of decimals to print""" self._print(0, decimals)
def _print(self, indent : int, decimals = 4): pre = PrintUtils.get_space_from_indent(indent) pre_indexed = pre + ' ' if self.identifier == '': print(pre + 'Reliability:') else: print(pre + f'Reliability ({self.identifier})') print(pre_indexed + f'Reliability index = {self.reliability_index:.{decimals}g}') print(pre_indexed + f'Probability of failure = {self.probability_failure:.{decimals}g}') if not isnan(self.convergence): if self.is_converged: print(pre_indexed + f'Convergence = {self.convergence:.{decimals}g} (converged)') else: print(pre_indexed + f'Convergence = {self.convergence:.{decimals}g} (not converged)') print(pre_indexed + f'Model runs = {self.total_model_runs}') print(pre + 'Alpha values:') for alpha in self.alphas: print(pre_indexed + f'{alpha.identifier}: alpha = {alpha.alpha:.{decimals}g}, x = {alpha.x:.{decimals}g}') print('') if len(self.contributing_design_points) > 0: print(pre + 'Contributing design points:') for design_point in self.contributing_design_points: design_point._print(indent + 1, decimals)
[docs] def plot_alphas(self): """Shows a plot of the alphas in the form a pie-diagram""" self.get_plot_alphas().show()
[docs] def get_plot_alphas(self) -> plt: """Gets a plot object of the alphas in the form a pie-diagram""" import numpy as np alphas = [alpha.influence_factor for alpha in self.alphas if alpha.influence_factor > 0.0001] names = [f'{alpha.identifier} ({round(100*alpha.influence_factor)} %)' for alpha in self.alphas if alpha.influence_factor > 0.0001] plt.close() plt.figure() plt.pie(alphas, labels=names) plt.title("Squared alpha values", fontsize=14, fontweight='bold') return plt
[docs] def plot_realizations(self, var_x : str | Stochast = None, var_y : str | Stochast = None): """Shows a scatter-plot of realizations performed by the reliability analysis. The color indicates failure or non-failure. The x-y coordinates correspond with realization input values. Only available when `Settings.save_realizations` was set in the settings. Parameters ---------- var_x : str | Stochast, optional The stochastic variable to use for the x-axis, if omitted the variable with the greatest influence factor is used var_y : str | Stochast, optional The stochastic variable to use for the y-axis, if omitted the variable with the one but greatest influence factor is used""" self.get_plot_realizations(var_x, var_y).show()
[docs] def get_plot_realizations(self, var_x : str | Stochast = None, var_y : str | Stochast = None) -> plt: """Gets a plot object of a scatter-plot of realizations performed by the reliability analysis. The color indicates failure or non-failure. The x-y coordinates correspond with realization input values. Only available when `Settings.save_realizations` was set in the settings. Parameters ---------- var_x : str | Stochast, optional The stochastic variable to use for the x-axis, if omitted the variable with the greatest influence factor is used var_y : str | Stochast, optional The stochastic variable to use for the y-axis, if omitted the variable with the one but greatest influence factor is used""" if len(self.realizations) == 0: print ("No realizations were saved, run again with settings.save_realizations = True") if len(self.alphas) < 2: print ("Not enough variables to plot realizations") import numpy as np # 2 variables with the highest alpha alphas = [alpha.influence_factor for alpha in self.alphas] index_last_two = np.argsort(np.abs(alphas))[-2:] if var_x == None and var_y == None: index_x = int(index_last_two[0]) index_y = int(index_last_two[1]) if var_x != None and var_y == None: index_x = self.alphas.index(var_x) index_y = int(index_last_two[0]) if var_x == None and var_y != None: index_x = int(index_last_two[0]) index_y = self.alphas.index(var_y) if var_x != None and var_y != None: index_x = self.alphas.index(var_x) index_y = self.alphas.index(var_y) if index_x < 0 or index_y < 0: print ("Variables could not be found") x_values = [realization.input_values[index_x] for realization in self.realizations] y_values = [realization.input_values[index_y] for realization in self.realizations] z = [realization.z for realization in self.realizations] colors = ["r" if val < 0 else "g" for val in z] plt.close() # plot realizations plt.figure() plt.grid(True) plt.scatter(x_values, y_values, color=colors, alpha=0.5) plt.scatter(self.alphas[index_x].x, self.alphas[index_y].x, label='design point' if self.identifier == '' else self.identifier, color="black") plt.xlabel(self.alphas[index_x].identifier) plt.ylabel(self.alphas[index_y].identifier) plt.legend() plt.title('Realizations: Red = Failure, Green = No Failure', fontsize=14, fontweight='bold') return plt
[docs] def plot_convergence(self): """Shows a plot of the convergence against the iteration or sample index. Only available when `Settings.save_convergence` was set""" self.get_plot_convergence().show()
[docs] def get_plot_convergence(self) -> plt: """Gets a plot object of the convergence against the iteration or sample index. Only available when `Settings.save_convergence` was set""" if len(self.reliability_results) == 0: print ("No convergence data were saved, run again with settings.save_convergence = True") import numpy as np index = [x.index for x in self.reliability_results] beta = [x.reliability_index for x in self.reliability_results] conv = [x.convergence for x in self.reliability_results] plt.close() ax1 = plt.subplot() color = "tab:blue" ax1.set_xlabel("index [-]") ax1.set_ylabel("reliability index [-]", color=color) ax1.plot(index, beta) ax1.tick_params(axis="y", labelcolor=color) ax2 = ax1.twinx() color = "tab:red" ax2.set_ylabel("convergence [-]", color=color) ax2.plot(index, conv, "r--", label="convergence") ax2.tick_params(axis="y", labelcolor=color) plt.title('Convergence', fontsize=14, fontweight='bold') return plt
[docs] class Alpha(FrozenObject): """Contribution of a stochastic variable to a design point The contribution consists of two indicators, the influence of the uncertainty of the stochastic variable to the design point and the physical value of the variable in the design point.""" def __init__(self, id = None, known_variables = None): if id is None: self._id = interface.Create('alpha') else: self._id = id self._variable = None self._known_variables = known_variables super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['variable', 'identifier', 'alpha', 'alpha_correlated', 'influence_factor', 'index', 'x', 'u'] @property def variable(self) -> Stochast: """Stochastic variable to which this alpha value refers""" if self._variable is None: variable_id = interface.GetIdValue(self._id, 'variable') if variable_id > 0: if not self._known_variables is None: for variable in self._known_variables: if variable._id == variable_id: self._variable = variable if self._variable is None: self._variable = Stochast(variable_id); return self._variable def __str__(self): return self.identifier @property def identifier(self) -> str: """Identifying string of the variable or, in case of an array variable, accompanied with its index number""" return interface.GetStringValue(self._id, 'identifier') # internal method def _set_variable(self, variable): self._variable = variable # testing method def _set_alpha(self, variable, alpha_value, beta): self._variable = variable interface.SetIntValue(self._id, 'variable', variable._id) interface.SetValue(self._id, 'alpha', alpha_value) u = - beta * alpha_value interface.SetValue(self._id, 'u',- beta * alpha_value) interface.SetValue(self._id, 'x', variable.get_x_from_u(u)) @property def alpha(self) -> float: """Alpha value, the contribution of the uncertainty of the variable""" return interface.GetValue(self._id, 'alpha') @property def alpha_correlated(self) -> float: """Alpha value after transformation for correlation, used to determine the physical value 'x'""" return interface.GetValue(self._id, 'alpha_correlated') @property def influence_factor(self) -> float: """Influence factor of the uncertainty of the variable This value equals alpha squared. All influence factors of a design point sum up to 1.""" return interface.GetValue(self._id, 'influence_factor') @property def index(self) -> int: """Index number of the variable, if it is defined as an array variable""" return interface.GetIntValue(self._id, 'index') @property def u(self) -> float: """U-value of the variable in the design point""" return interface.GetValue(self._id, 'u') @property def x(self) -> float: """Physical value of the variable in the design point""" return interface.GetValue(self._id, 'x')
[docs] class FragilityCurve(FrozenObject): """Curve with a number of x-values, for which a reliability is defined A fragility curve is similar to a `probabilistic_library.statistic.Stochast` with distribution type cdf_curve. A fragility curve has an additional method `integrate` to calculate the reliability, when the distribution of the x-values is known. ```mermaid classDiagram class FragilityValue{ +x: float +reliability_index: float +design_point : DesignPoint } class DesignPoint{ } class FragilityCurve{ +"derived properties" : float +integrand : Stochast +integrate() : DesignPoint } class FragilityCurveProject{ +integrand Stochast +fragility_curve FragilityCurve +run() } FragilityValue "*" <-- FragilityCurve DesignPoint <-- FragilityValue DesignPoint <-- FragilityCurveProject FragilityCurve <-- FragilityCurveProject ``` """ def __init__(self, id = None): if id is None: self._id = interface.Create('fragility_curve') else: self._id = id self._fragility_values = None self._synchronizing = False super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['name', 'mean', 'deviation', 'variation', 'fragility_values', 'copy_from', 'get_quantile', 'get_x_from_u', 'get_u_from_x', 'get_pdf', 'get_cdf', 'get_special_values', 'integrate'] @property def name(self) -> str: """Identifying name of the fragility curve""" return interface.GetStringValue(self._id, 'name') @name.setter def name(self, value : str): interface.SetStringValue(self._id, 'name', value) def __str__(self): return self.name @property def mean(self) -> float: """Gets the mean of the fragility curve""" return interface.GetValue(self._id, 'mean') @property def deviation(self) -> float: """Gets the standard deviation of the fragility curve""" return interface.GetValue(self._id, 'deviation') @property def variation(self) -> float: """Gets the variation coefficient of the fragility curve""" return interface.GetValue(self._id, 'variation') @property def fragility_values(self) -> list[FragilityValue]: """List of fragility values. Each fragility value defines a reliability index for an x-value. Preferably the design point from which the reliability index is taken should be specified too, which improves the `integrate` calculation""" if self._fragility_values is None: self._synchronizing = True self._fragility_values = CallbackList(self._fragility_values_changed) fragility_ids = interface.GetArrayIdValue(self._id, 'fragility_values') for fragility_id in fragility_ids: self._fragility_values.append(FragilityValue(fragility_id)) self._synchronizing = False return self._fragility_values def _fragility_values_changed(self): if not self._synchronizing: interface.SetArrayIntValue(self._id, 'fragility_values', [fragility_value._id for fragility_value in self._fragility_values])
[docs] def get_quantile(self, quantile : float) -> float: """Gets the value belonging to a given quantile Parameters ---------- quantile : float. Quantile for which the x-value is requested, must be between 0 and 1 (exclusive)""" return interface.GetArgValue(self._id, 'quantile', quantile)
[docs] def get_x_from_u(self, u : float) -> float: """Gets the x-value at a given u-value of the fragility curve Parameters ---------- u : float. U-value for which the x-value is requested""" return interface.GetArgValue(self._id, 'x_from_u', u)
[docs] def get_u_from_x(self, x : float) -> float: """Gets the u-value at a given x-value of the fragility curve Parameters ---------- x : float X-value for which the u-value is requested""" return interface.GetArgValue(self._id, 'u_from_x', x)
[docs] def get_pdf(self, x : float) -> float: """Gets the PDF value of the fragility curve Parameters ---------- x : float X-value for which the PDF is requested""" return interface.GetArgValue(self._id, 'pdf', x)
[docs] def get_cdf(self, x : float) -> float: """Gets the CDF value of the fragility curve Parameters ---------- x : float X-value for which the CDF is requested""" return interface.GetArgValue(self._id, 'cdf', x)
[docs] def get_special_values(self) -> list[float]: """Gets a list of special x-values, which is useful for plotting""" return interface.GetArrayValue(self._id, 'special_values')
[docs] def copy_from(self, source): """Copies the properties from a source fragility curve. Parameters ---------- source : FragilityCurve The fragility curve to copy the properties from""" if source is FragilityCurve: interface.SetIntValue(self._id, 'copy_from', source._id) self._fragility_values = None
[docs] def integrate(self, integrand : Stochast) -> DesignPoint: """Calculates the reliability index of the fragility curve if the distribution of the x-values in the `fragility_values` is given. Results in a design point. The calculation is performed by integration over the possible x-values. If the design points in the fragility values are given, the alpha values in the resulting design point are based on the design points in the `fragility_values`. Parameters ---------- integrand : Stochast. Stochastic variable describing the distribution of the x-values in the 'fragility_values'""" project = FragilityCurveProject() project.integrand = integrand project.fragility_curve = self project.run() return project.design_point
[docs] class FragilityCurveProject(FrozenObject): """Project to calculate the reliability of a fragility curve when the distribution of the x-values of the fragility_valus of the fragility curve is known. This class is used internally by the `FragilityCurve.integrate` method.""" def __init__(self): self._id = interface.Create('fragility_curve_project') self._integrand = None self._fragility_curve = None self._design_point = None super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['integrand', 'fragility_curve', 'design_point'] @property def integrand(self) -> Stochast: """Stochastic variable describing the distribution of the x-values in the `FragilityCurve.fragility_values`""" return self._integrand @integrand.setter def integrand(self, value : Stochast): self._integrand = value interface.SetIntValue(self._id, 'integrand', value._id) @property def fragility_curve(self) -> FragilityCurve: """fragility curve to be integrated""" return self._fragility_curve @fragility_curve.setter def fragility_curve(self, value : FragilityCurve): self._fragility_curve = value interface.SetIntValue(self._id, 'fragility_curve', value._id) @property def design_point(self) -> DesignPoint: """Resulting design point""" return self._design_point
[docs] def run(self): """Performs the integration""" self._design_point = None interface.Execute(self._id, 'run') design_point_id = interface.GetIdValue(self._id, 'design_point') if design_point_id > 0: known_variables = self._get_variables() self._design_point = DesignPoint(design_point_id, known_variables)
def _get_variables(self): variables = [] variables.append(self.integrand) variables.append(self.fragility_curve) for fragility_value in self.fragility_curve.fragility_values: if isinstance(fragility_value.design_point, DesignPoint): for alpha in fragility_value.design_point.alphas: if alpha.variable is not None and alpha.variable not in variables: variables.append(alpha.variable) for array_variable in alpha.variable.array_variables: if array_variable not in variables: variables.append(array_variable) return variables
[docs] class CombineSettings(FrozenObject): """Settings for combining design points These settings are used by the `probabilistic_library.project.CombineProject`""" def __init__(self): self._id = interface.Create('combine_settings') super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['combiner_method', 'combine_type'] @property def combiner_method(self) -> CombinerMethod: """Algorithm which performs the combination of design points""" return CombinerMethod[interface.GetStringValue(self._id, 'combiner_method')] @combiner_method.setter def combiner_method(self, value : CombinerMethod): interface.SetStringValue(self._id, 'combiner_method', str(value)) @property def combine_type(self) -> CombineType: """Type of combining design points (series, parallel)""" return CombineType[interface.GetStringValue(self._id, 'combine_type')] @combine_type.setter def combine_type(self, value : CombineType): interface.SetStringValue(self._id, 'combine_type', str(value))
[docs] class ExcludingCombineSettings(FrozenObject): """Settings for combining design points exclusively These settings are used by the `probabilistic_library.project.ExcludingCombineProject`""" def __init__(self): self._id = interface.Create('excluding_combine_settings') super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['combiner_method'] @property def combiner_method(self) -> ExcludingCombinerMethod: """Algorithm which performs the exclusive combination of design points""" return ExcludingCombinerMethod[interface.GetStringValue(self._id, 'combiner_method')] @combiner_method.setter def combiner_method(self, value : ExcludingCombinerMethod): interface.SetStringValue(self._id, 'combiner_method', str(value))
[docs] class ReliabilityResult(FrozenObject): """Contains the intermediate reliability index and convergence during a reliability analysis""" def __init__(self, id = None): if id == None: self._id = interface.Create('reliability_result') else: self._id = id super()._freeze() def __del__(self): interface.Destroy(self._id) def __dir__(self): return ['index', 'reliability_index', 'convergence'] @property def index(self) -> int: """Index of a realization or iteration in the reliability analysis to which the reliability index and convergence belong""" return interface.GetIntValue(self._id, 'index') @property def reliability_index(self) -> float: """Intermediate reliability index in the reliability analysis""" return interface.GetValue(self._id, 'reliability_index') @property def convergence(self) -> float: """Intermediate convergence index in the reliability analysis""" return interface.GetValue(self._id, 'convergence')