pywr / pywr-next

An experimental repository exploring ideas for a major revision to Pywr using Rust as a backend.
6 stars 4 forks source link

Add binary and/or integer variables #187

Open jetuk opened 1 month ago

jetuk commented 1 month ago

Link to the Pywr v1.x issue: https://github.com/pywr/pywr/issues/702

Following use cases

Discrete flow

The flow in a given edge should only be a discrete value. For example, 0 or 10.0 (but no other value). This will require adding a binary (or integer) value and relating it to the capacity constraints.

Current max capacity constraint: $x_e \le b_e^{max}$

With discrete variable: $x_e - x_e^b b_e^{max} = 0$

The min flow constraints will be similar.

The user should be able to associate a cost with the variable. However, the units might get confusing.

Mutually exclusive flows

There can only be flow in one edge from a given set. This will require two new constraints to be added. The first is the same as above where the flow capacity is constrained by a new variable $xe^b$. Then a second constraint is added which ensures that only one of the binaries can "on": $\sum{e \in E} x_e \le 1$. An alternative could be an equality constraint to require one of the set to be active.

If this is implemented it would allow a more correct minimum flow to be set when configuring a bi-directional transfer.

Schema

How would this look to the user? Ideally we would have a DiscreteLink or similar node that defined the discrete flow requirement. The mutual exclusivity one is similar to an aggregated node, and perhaps could be an optional field on AggregatedNode that, if defined, would create the additional constraints.

Implementation challenges

jetuk commented 3 hours ago

The mutual exclusivity one is similar to an aggregated node, and perhaps could be an optional field on AggregatedNode that, if defined, would create the additional constraints.

See below for a suggestion on this. Here we use the Factors enum to specify that the nodes in this AggregatedNode are mutually exclusive. This implies (I think correctly) that you can't have regular flow factors across mutually nodes. @Batch21 @s-simoncelli any thoughts on this?

#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, JsonSchema, PywrVisitAll)]
#[serde(tag = "type")]
pub enum Factors {
    Proportion {
        factors: Vec<Metric>,
    },
    Ratio {
        factors: Vec<Metric>,
    },
    /// Flows are exclusive with at least `min` and at most `max` nodes active.
    Exclusive {
        min: Option<usize>,
        max: Option<usize>,
    },
}

#[derive(serde::Deserialize, serde::Serialize, Clone, Default, Debug, JsonSchema, PywrVisitAll)]
pub struct AggregatedNode {
    #[serde(flatten)]
    pub meta: NodeMeta,
    pub nodes: Vec<String>,
    pub max_flow: Option<Metric>,
    pub min_flow: Option<Metric>,
    pub factors: Option<Factors>,
}