Skip to main content

cobre_io/config/
modeling.rs

1//! Modeling option types for `config.json → modeling`.
2
3use serde::{Deserialize, Serialize};
4
5/// Method string for inflow non-negativity enforcement.
6///
7/// Accepted values in `config.json → modeling.inflow_non_negativity.method`:
8///
9/// - `"none"` — no enforcement; PAR(p) inflows may be negative.
10/// - `"truncation"` — clamp negative PAR(p) inflows to zero before LP patching.
11/// - `"penalty"` — add slack columns with `inflow_nonnegativity_cost` objective
12///   coefficient (sourced from `penalties.json → hydro.inflow_nonnegativity_cost`).
13/// - `"truncation_with_penalty"` — combine both: clamp noise *and* add slack columns.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
15#[serde(rename_all = "snake_case")]
16#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
17pub enum InflowNonNegativityMethod {
18    /// No inflow non-negativity enforcement.
19    None,
20    /// Truncation-based enforcement only (no slack columns).
21    Truncation,
22    /// Penalty-based enforcement via slack columns.
23    ///
24    /// Objective coefficient is `penalties.json → hydro.inflow_nonnegativity_cost`.
25    #[default]
26    Penalty,
27    /// Combined truncation and penalty enforcement.
28    TruncationWithPenalty,
29}
30
31/// Modeling options (`config.json → modeling`).
32#[derive(Debug, Clone, Deserialize, Serialize, Default)]
33#[serde(deny_unknown_fields)]
34#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
35pub struct ModelingConfig {
36    /// Strategy for handling non-negative inflow constraints.
37    #[serde(default)]
38    pub inflow_non_negativity: InflowNonNegativityConfig,
39
40    /// Divisor applied to every non-theta objective coefficient at template
41    /// build time, multiplied back at every cost-domain reporting boundary.
42    /// Default `1_000_000.0` — the value every golden parity baseline is pinned at.
43    ///
44    /// Objective conditioning only — results are identical in exact arithmetic;
45    /// this does not alter the model, unlike `modeling`'s other fields. The
46    /// effective dual tolerance in currency units is
47    /// `dual_feasibility_tolerance × this factor`: raising the factor without
48    /// lowering `dual_feasibility_tolerance` proportionally loosens optimality
49    /// in currency terms even though the configured tolerance value is
50    /// unchanged — the inverse-direction trap a name cannot carry.
51    ///
52    /// Absent uses the default. Must be finite and `> 0`; a value outside
53    /// `[1.0, 1e12]` is accepted but logs an advisory warning.
54    #[serde(default)]
55    pub cost_scale_factor: Option<f64>,
56}
57
58/// Inflow non-negativity treatment settings.
59#[derive(Debug, Clone, Deserialize, Serialize)]
60#[serde(default, deny_unknown_fields)]
61#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
62pub struct InflowNonNegativityConfig {
63    /// Method: `"none"`, `"truncation"`, `"penalty"`, or `"truncation_with_penalty"`.
64    ///
65    /// Default: `"penalty"`. The penalty objective coefficient is always sourced from
66    /// `penalties.json → hydro.inflow_nonnegativity_cost` (default 1000.0 when absent).
67    pub method: InflowNonNegativityMethod,
68}
69
70impl Default for InflowNonNegativityConfig {
71    fn default() -> Self {
72        Self {
73            method: InflowNonNegativityMethod::Penalty,
74        }
75    }
76}