Source code for daitum_configuration.model_configuration.model_configuration

# Copyright 2026 Daitum
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
:class:`ModelConfiguration` — the decision variables, objectives, and constraints
of an optimisation problem, plus per-model solver flags.
"""

from daitum_model import Calculation, Parameter
from daitum_model.fields import DataField, Field
from daitum_model.tables import DataTable, Table
from typeguard import typechecked

from daitum_configuration._buildable import Buildable
from daitum_configuration.model_configuration.constraint import Constraint
from daitum_configuration.model_configuration.decision_variable import DecisionVariable, DVType
from daitum_configuration.model_configuration.external_configuration import (
    ExternalModelConfiguration,
)
from daitum_configuration.model_configuration.objective import Objective
from daitum_configuration.model_configuration.priority import Priority
from daitum_configuration.model_configuration.scenario_output import ScenarioOutput
from daitum_configuration.model_configuration.stochastic_configuration import (
    StochasticConfiguration,
)


# pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-instance-attributes
# pylint: disable=too-many-branches,too-few-public-methods
[docs] @typechecked class ModelConfiguration(Buildable): """ Decision variables, objectives, constraints, and solver-level flags for one model. """ def __init__(self): self.decision_variables: list[DecisionVariable] = [] self.objectives: list[Objective] = [] self.constraints: list[Constraint] = [] self.scenario_outputs: list[ScenarioOutput] = [] self.disable_seed_solution: bool = False self.calculated_seed: bool = False self.validation_enabled: bool = True self.profiling: bool = False self.stochastic_configuration: StochasticConfiguration | None = None self.hide_objective_when_infeasible: bool = False self.external_configuration: ExternalModelConfiguration | None = None self.using_custom_evaluator: bool | None = None self.custom_evaluator_version: str | None = None self.custom_evaluator_key: str | None = None
[docs] def set_disable_seed_solution(self, disable_seed_solution: bool) -> "ModelConfiguration": """Disable the seed solution loaded from input data.""" self.disable_seed_solution = disable_seed_solution return self
[docs] def set_calculated_seed(self, calculated_seed: bool) -> "ModelConfiguration": """Enable computing the seed solution from formulas instead of input data.""" self.calculated_seed = calculated_seed return self
[docs] def set_validation_enabled(self, validation_enabled: bool) -> "ModelConfiguration": """Enable or disable validators on the input data.""" self.validation_enabled = validation_enabled return self
[docs] def set_profiling(self, profiling: bool) -> "ModelConfiguration": """Enable solver-side performance profiling.""" self.profiling = profiling return self
[docs] def set_hide_objective_when_infeasible( self, hide_objective_when_infeasible: bool ) -> "ModelConfiguration": """Hide objective values from the UI when the solution is infeasible.""" self.hide_objective_when_infeasible = hide_objective_when_infeasible return self
[docs] def set_stochastic_configuration( self, stochastic_configuration: StochasticConfiguration ) -> "ModelConfiguration": """Attach a :class:`StochasticConfiguration` for multi-trial evaluation.""" self.stochastic_configuration = stochastic_configuration return self
[docs] def set_external_configuration( self, custom_evaluator_version: str, custom_evaluator_key: str, external_configuration: ExternalModelConfiguration, ) -> "ModelConfiguration": """Route evaluation through an external evaluator with the given version, key, and :class:`ExternalModelConfiguration` mappings.""" self.using_custom_evaluator = True self.custom_evaluator_version = custom_evaluator_version self.custom_evaluator_key = custom_evaluator_key self.external_configuration = external_configuration return self
[docs] def add_decision_variable( self, dv: Parameter | Field, dv_table: Table | None = None, dv_type: DVType = DVType.RANGE, ) -> DecisionVariable: """Register a decision variable. Args: dv: A model :class:`~daitum_model.Parameter` (model-level DV) or a :class:`~daitum_model.fields.DataField` (per-row DV). dv_table: The :class:`~daitum_model.tables.DataTable` for a per-row DV. ``None`` for a model-level DV. dv_type: :class:`DVType` controlling the variable's domain. Returns: The new :class:`DecisionVariable`; chain ``.set_min(...)`` and ``.set_max(...)`` to bound it. """ if not isinstance(dv, Parameter | DataField): raise ValueError(f"{dv} is not a Parameter or DataField") if not isinstance(dv_table, DataTable) and dv_table is not None: raise ValueError(f"{dv_table} is not a DataTable or None") dv_instance = DecisionVariable(dv, dv_table, dv_type) self.decision_variables.append(dv_instance) return dv_instance
[docs] def add_objective( self, objective: Calculation | Parameter, maximise: bool = False, priority: Priority = Priority.HIGH, weight: float = 1.0, name: str | None = None, ) -> Objective: """Register an :class:`Objective`. Args: objective: Numeric :class:`~daitum_model.Calculation` to optimise. maximise: Direction of optimisation. Defaults to minimise. priority: Lexicographic :class:`Priority` for multi-objective runs. weight: Objective weight when combining via a weighted comparator. name: Optional display name. """ if not isinstance(objective, Calculation): raise ValueError("Objective must be an instance of Calculation.") objective_instance = Objective(objective, maximise, priority, weight, name) self.objectives.append(objective_instance) return objective_instance
[docs] def add_constraint(self, constraint: Calculation | Parameter) -> Constraint: """Register a :class:`Constraint` over a numeric calculation. Returns the new :class:`Constraint`; chain ``.set_type(...)`` and ``.set_lower_bound(...)``/``.set_upper_bound(...)`` to configure it. """ if not isinstance(constraint, Calculation): raise ValueError("Constraint must be an instance of Calculation.") constraint_instance = Constraint(constraint) self.constraints.append(constraint_instance) return constraint_instance
[docs] def add_scenario_output( self, name: str, scenario_output_value: Calculation | Parameter | Field, scenario_output_table: Table | None = None, ) -> ScenarioOutput: """Register a :class:`ScenarioOutput` exposed in scenario comparison. Raises: ValueError: If ``name`` or the resolved cell reference duplicates an existing scenario output. """ scenario_output = ScenarioOutput( name, scenario_output_value, scenario_output_table, ) for existing in self.scenario_outputs: if existing.name == name: raise ValueError(f"Scenario output with name '{name}' already exists.") if existing.cell_reference == scenario_output.cell_reference: raise ValueError( f"Scenario output with cell reference '{scenario_output.cell_reference}' " f"already exists." ) self.scenario_outputs.append(scenario_output) return scenario_output