Skip to content

Monte Carlo

machwave.montecarlo

MonteCarloSimulation

The MonteCarloSimulation class: - Stores data for a Monte Carlo simulation - Executes the simulation - Delegates plotting to plot_service.py

Source code in machwave/montecarlo/base.py
class MonteCarloSimulation:
    """
    The MonteCarloSimulation class:
    - Stores data for a Monte Carlo simulation
    - Executes the simulation
    - Delegates plotting to `plot_service.py`
    """

    def __init__(
        self,
        parameters: list[typing.Any],
        number_of_scenarios: int,
        simulation: type[Simulation],
    ) -> None:
        """
        Initializes a MonteCarloSimulation object.

        Args:
            parameters: list with the input parameters for a simulation class.
            number_of_scenarios: Number of scenarios to be simulated.
            simulation: Simulation class reference.
        """
        self.parameters = parameters
        self.number_of_scenarios = number_of_scenarios
        self.simulation = simulation

        self.scenarios: list = []
        self.results: list = []

        self._object_store = dict()  # for nested parameter processing

    def generate_scenario(self) -> list[typing.Any]:
        """
        Generates a Monte Carlo scenario in the form of a list of parameters.
        """
        new_scenario = []
        parameters_copy = deepcopy(self.parameters)

        for parameter in parameters_copy:
            if isinstance(parameter, MonteCarloParameter):
                parameter = parameter.get_random_value()
            else:
                self._process_nested_parameters(parameter)

            new_scenario.append(parameter)

        self.scenarios.append(new_scenario)
        return new_scenario

    def _process_nested_parameters(self, parameter: typing.Any) -> None:
        """
        Recursively processes an object's attributes to replace
        MonteCarloParameter instances with randomized values and store objects
        using UUIDs.

        Args:
            parameter: The object whose attributes will be processed.
        """
        parameter_uuid = uuid.uuid4()
        self._object_store[parameter_uuid] = parameter
        search_tree = {parameter_uuid: get_object_dict(parameter)}

        i = 0
        while search_tree and i < SEARCH_TREE_DEPTH_LIMIT:
            i += 1
            new_search_tree = {}
            for param_uuid, sub_params in search_tree.items():
                param = self._object_store[param_uuid]
                for name, attr in sub_params.items():
                    object_uuid = uuid.uuid4()

                    if isinstance(attr, MonteCarloParameter):
                        setattr(param, name, attr.get_random_value())
                    elif isinstance(attr, list):
                        for item in attr:
                            if isinstance(item, dict):
                                continue
                            self._object_store[object_uuid] = item
                            new_search_tree[object_uuid] = get_object_dict(item)
                    else:
                        object_uuid = uuid.uuid4()
                        self._object_store[object_uuid] = attr
                        new_search_tree[object_uuid] = get_object_dict(attr)

            search_tree = new_search_tree

    def run(self) -> None:
        """
        Executes `number_of_scenarios` runs of the underlying Simulation.
        """
        self.results = []
        for _ in range(self.number_of_scenarios):
            scenario = self.generate_scenario()
            self.results.append(self.simulation(*scenario).run())

    def retrieve_values_from_result(
        self,
        state_index: int,
        property_name: str,
    ) -> np.ndarray:
        """
        Retrieves a specific scalar property from all simulation results.
        Returns a NumPy array of length = number_of_scenarios.

        Args:
            state_index: Index of the state in the simulation result.
            property_name: Name of the property to retrieve from the results.
        Returns:
            NumPy array containing the values of the specified property
            across all simulation results.
        """
        return np.array(
            [
                getattr(sim_result[state_index], property_name)
                for sim_result in self.results
            ]
        )

    def get_property_stats(
        self, state_index: int, property_name: str
    ) -> dict[str, float]:
        """
        Compute descriptive statistics for a scalar property across all results.

        Metrics returned:
        mean, median, variance, std_dev, mode, skew
        (Fishers, unbiased), kurtosis (excess, unbiased),
        p5 (5th percentile), p95 (95th percentile).

        Args:
            state_index: Index of the state in the simulation result.
            property_name: Name of the property to retrieve from the
                results.
        Returns:
            Dictionary containing the mean, median, variance, and
            standard deviation of the specified property across all
            simulation results.
        """
        values = np.asarray(
            self.retrieve_values_from_result(state_index, property_name)
        )

        mean_val = np.mean(values)
        median_val = np.median(values)
        var_val = np.var(values)
        std_val = np.std(values)

        mode_val = float(scipy_stats.mode(values, nan_policy="omit").mode)
        skew_val = scipy_stats.skew(values, bias=False)  # unbiased Fisher skew
        kurt_val = scipy_stats.kurtosis(values, fisher=True, bias=False)
        p5, p95 = np.percentile(values, [5, 95])

        return {
            "mean": float(mean_val),
            "median": float(median_val),
            "variance": float(var_val),
            "std_dev": float(std_val),
            "mode": float(mode_val),
            "skew": float(skew_val),
            "kurtosis": float(kurt_val),
            "p5": float(p5),
            "p95": float(p95),
        }

    def plot_histogram(
        self,
        state_index: int,
        property_name: str,
        x_axes_title: str = "x",
        **plotly_kwargs,
    ) -> None:
        plot_service.plot_histogram(
            self.results, state_index, property_name, x_axes_title, **plotly_kwargs
        )

    def plot_histogram_with_kde(
        self,
        state_index: int,
        property_name: str,
        x_axes_title: str = "x",
        nbins: int = 30,
        kde_points: int = 200,
        **plotly_kwargs,
    ) -> None:
        plot_service.plot_histogram_with_kde(
            self.results,
            state_index,
            property_name,
            x_axes_title,
            nbins,
            kde_points,
            **plotly_kwargs,
        )

    def plot_cdf(
        self,
        state_index: int,
        property_name: str,
        x_axes_title: str = "x",
        **plotly_kwargs,
    ) -> None:
        plot_service.plot_cdf(
            self.results, state_index, property_name, x_axes_title, **plotly_kwargs
        )

    def plot_time_series_extremes(
        self,
        state_index: int,
        time_property: str,
        series_property: str,
        x_axes_title: str = "time",
        title: str | None = None,
        **plotly_kwargs,
    ) -> None:
        plot_service.plot_time_series_extremes(
            self.results,
            state_index,
            time_property,
            series_property,
            x_axes_title,
            title,
            **plotly_kwargs,
        )

__init__(parameters, number_of_scenarios, simulation)

Initializes a MonteCarloSimulation object.

Parameters:

Name Type Description Default
parameters list[Any]

list with the input parameters for a simulation class.

required
number_of_scenarios int

Number of scenarios to be simulated.

required
simulation type[Simulation]

Simulation class reference.

required
Source code in machwave/montecarlo/base.py
def __init__(
    self,
    parameters: list[typing.Any],
    number_of_scenarios: int,
    simulation: type[Simulation],
) -> None:
    """
    Initializes a MonteCarloSimulation object.

    Args:
        parameters: list with the input parameters for a simulation class.
        number_of_scenarios: Number of scenarios to be simulated.
        simulation: Simulation class reference.
    """
    self.parameters = parameters
    self.number_of_scenarios = number_of_scenarios
    self.simulation = simulation

    self.scenarios: list = []
    self.results: list = []

    self._object_store = dict()  # for nested parameter processing

generate_scenario()

Generates a Monte Carlo scenario in the form of a list of parameters.

Source code in machwave/montecarlo/base.py
def generate_scenario(self) -> list[typing.Any]:
    """
    Generates a Monte Carlo scenario in the form of a list of parameters.
    """
    new_scenario = []
    parameters_copy = deepcopy(self.parameters)

    for parameter in parameters_copy:
        if isinstance(parameter, MonteCarloParameter):
            parameter = parameter.get_random_value()
        else:
            self._process_nested_parameters(parameter)

        new_scenario.append(parameter)

    self.scenarios.append(new_scenario)
    return new_scenario

get_property_stats(state_index, property_name)

Compute descriptive statistics for a scalar property across all results.

Metrics returned: mean, median, variance, std_dev, mode, skew (Fishers, unbiased), kurtosis (excess, unbiased), p5 (5th percentile), p95 (95th percentile).

Parameters:

Name Type Description Default
state_index int

Index of the state in the simulation result.

required
property_name str

Name of the property to retrieve from the results.

required

Returns: Dictionary containing the mean, median, variance, and standard deviation of the specified property across all simulation results.

Source code in machwave/montecarlo/base.py
def get_property_stats(
    self, state_index: int, property_name: str
) -> dict[str, float]:
    """
    Compute descriptive statistics for a scalar property across all results.

    Metrics returned:
    mean, median, variance, std_dev, mode, skew
    (Fishers, unbiased), kurtosis (excess, unbiased),
    p5 (5th percentile), p95 (95th percentile).

    Args:
        state_index: Index of the state in the simulation result.
        property_name: Name of the property to retrieve from the
            results.
    Returns:
        Dictionary containing the mean, median, variance, and
        standard deviation of the specified property across all
        simulation results.
    """
    values = np.asarray(
        self.retrieve_values_from_result(state_index, property_name)
    )

    mean_val = np.mean(values)
    median_val = np.median(values)
    var_val = np.var(values)
    std_val = np.std(values)

    mode_val = float(scipy_stats.mode(values, nan_policy="omit").mode)
    skew_val = scipy_stats.skew(values, bias=False)  # unbiased Fisher skew
    kurt_val = scipy_stats.kurtosis(values, fisher=True, bias=False)
    p5, p95 = np.percentile(values, [5, 95])

    return {
        "mean": float(mean_val),
        "median": float(median_val),
        "variance": float(var_val),
        "std_dev": float(std_val),
        "mode": float(mode_val),
        "skew": float(skew_val),
        "kurtosis": float(kurt_val),
        "p5": float(p5),
        "p95": float(p95),
    }

retrieve_values_from_result(state_index, property_name)

Retrieves a specific scalar property from all simulation results. Returns a NumPy array of length = number_of_scenarios.

Parameters:

Name Type Description Default
state_index int

Index of the state in the simulation result.

required
property_name str

Name of the property to retrieve from the results.

required

Returns: NumPy array containing the values of the specified property across all simulation results.

Source code in machwave/montecarlo/base.py
def retrieve_values_from_result(
    self,
    state_index: int,
    property_name: str,
) -> np.ndarray:
    """
    Retrieves a specific scalar property from all simulation results.
    Returns a NumPy array of length = number_of_scenarios.

    Args:
        state_index: Index of the state in the simulation result.
        property_name: Name of the property to retrieve from the results.
    Returns:
        NumPy array containing the values of the specified property
        across all simulation results.
    """
    return np.array(
        [
            getattr(sim_result[state_index], property_name)
            for sim_result in self.results
        ]
    )

run()

Executes number_of_scenarios runs of the underlying Simulation.

Source code in machwave/montecarlo/base.py
def run(self) -> None:
    """
    Executes `number_of_scenarios` runs of the underlying Simulation.
    """
    self.results = []
    for _ in range(self.number_of_scenarios):
        scenario = self.generate_scenario()
        self.results.append(self.simulation(*scenario).run())