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}