Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/src/20-user-guide/20-how-to-use.md
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,9 @@ The unit commitment constraints are only applied to producer and conversion asse
- `unit_commitment_integer`: It determines whether the unit commitment variables are considered as integer or not (`true` or `false`)
- `min_operating_point`: Minimum operating point or minimum stable generation level defined as a portion of the capacity of asset (p.u.)

!!! info "Minimum operating point constraints without unit commitment"
Even when `unit_commitment = 'none'`, producer and conversion assets with `min_operating_point > 0` still receive a minimum output-flow constraint (for aggregated and compact profiles vintage methods). This is useful to represent must-run conditions without the full unit commitment formulation.

For more details on the constraints that apply when selecting this method, please visit the [`mathematical formulation`](@ref formulation) section.

### [Ramping constraints](@id ramping-setup)
Expand Down
8 changes: 8 additions & 0 deletions docs/src/20-user-guide/55-outputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,14 @@ Associated input parameter: `unit_commitment_integer`

- `dual_min_output_flow_with_unit_commitment`: Dual of the constraint ["minimum output flow above the minimum operating point"](https://tulipaenergy.github.io/TulipaEnergyModel.jl/stable/40-scientific-foundation/40-formulation/#Minimum-output-flow-above-the-minimum-operating-point).

### `cons_min_output_flow_without_unit_commitment_aggregated_vintage_method`

- `dual_min_output_flow_without_unit_commitment_aggregated_vintage_method`: Dual of the constraint ["minimum output constraints without unit commitment"](https://tulipaenergy.github.io/TulipaEnergyModel.jl/stable/40-scientific-foundation/40-formulation/#Minimum-Output-Constraints-Without-Unit-Commitment).

### `cons_min_output_flow_without_unit_commitment_compact_vintage_method`

- `dual_min_output_flow_without_unit_commitment_compact_vintage_method`: Dual of the constraint ["minimum output constraints without unit commitment"](https://tulipaenergy.github.io/TulipaEnergyModel.jl/stable/40-scientific-foundation/40-formulation/#Minimum-Output-Constraints-Without-Unit-Commitment).

### `cons_transport_flow_limit`

- `dual_max_transport_flow_limit`: Dual of the constraint ["maximum transport flow limit"](https://tulipaenergy.github.io/TulipaEnergyModel.jl/stable/40-scientific-foundation/40-formulation/#Maximum-Transport-Flow-Limit).
Expand Down
18 changes: 18 additions & 0 deletions docs/src/40-scientific-foundation/40-formulation.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ In addition, the following subsets represent methods for incorporating additiona

#### Extra Parameters for Unit Commitment and Ramping Constraints

The parameter $p^{\text{min operating point}}_{a,y}$ is also used for producer and conversion assets without unit commitment when `unit_commitment = 'none'` and `min_operating_point > 0`.

| Name | Domain | Domains of Indices | Description | Units |
| -------------------------------------- | ---------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -------------- |
| $p^{\text{min operating point}}_{a,y}$ | $\mathbb{R}_{+}$ | $a \in \mathcal{A^{\text{uc}}}_y$ | Minimum operating point or minimum stable generation level defined as a portion of the capacity of asset $a$ at year $y$ | [p.u.] |
Expand Down Expand Up @@ -572,6 +574,22 @@ v^{\text{on}}_{a, k_y, b_{k_y}} - v^{\text{on}}_{a, k_y, (b_{k_y} - 1)} = v^{\te
\\ \\ \forall y \in \mathcal{Y}, \forall a \in \mathcal{A}^{\text{uc 3var}}_y, \forall k_y \in \mathcal{K}_y,\forall b_{k_y} \in \mathcal{B}_{k_y}
```

### [Minimum Output Constraints Without Unit Commitment](@id min-output-constraints-without-unit-commitment)

For producer and conversion assets without unit commitment, a minimum output flow can still be enforced using `min_operating_point > 0`. This is useful for modeling assets that have a minimum output requirement but do not need to be turned on or off, such as run-of-river hydro plants or certain types of chemical processes or must-run units.

The formulation applies only to assets with `unit_commitment = 'none'`. It uses the sum of outgoing flows weighted by $p^{\text{capacity coefficient}}_{f,y}$, so output flows that do not contribute to productive capacity (for example, byproduct emissions with coefficient 0) do not tighten the minimum-output requirement.

```math
\sum_{f \in \mathcal{F}^{\text{out}}_{a,y}} p^{\text{capacity coefficient}}_{f,y} \cdot v^{\text{flow}}_{f,k_y,b_{k_y}} \geq p^{\text{min operating point}}_{a,y} \cdot p^{\text{capacity}}_{a} \cdot v^{\text{available units aggregated}}_{a,y} \quad
\\ \\ \forall y \in \mathcal{Y}, \forall a \in \mathcal{A}^{\text{aggregated}} \cap (\mathcal{A}^{\text{p}} \cup \mathcal{A}^{\text{cv}}) \setminus \mathcal{A}^{\text{uc}}_y \;\text{with}\; p^{\text{min operating point}}_{a,y} > 0, \forall k_y \in \mathcal{K}_y, \forall b_{k_y} \in \mathcal{B}_{k_y}
```

```math
\sum_{f \in \mathcal{F}^{\text{out}}_{a,y}} p^{\text{capacity coefficient}}_{f,y} \cdot v^{\text{flow}}_{f,k_y,b_{k_y}} \geq p^{\text{min operating point}}_{a,y} \cdot p^{\text{capacity}}_{a} \cdot \sum_{v \in \mathcal{V} \mid (a,y,v) \in \mathcal{D}^{\text{compact profiles}}} v^{\text{available units compact}}_{a,y,v} \quad
\\ \\ \forall y \in \mathcal{Y}, \forall a \in \mathcal{A}^{\text{compact profiles}} \cap (\mathcal{A}^{\text{p}} \cup \mathcal{A}^{\text{cv}}) \setminus \mathcal{A}^{\text{uc}}_y \;\text{with}\; p^{\text{min operating point}}_{a,y} > 0, \forall k_y \in \mathcal{K}_y, \forall b_{k_y} \in \mathcal{B}_{k_y}
```

### [Ramping Constraints](@id ramp-constraints)

Ramping constraints restrict the rate at which the output flow of a production or conversion asset can change. If the asset is part of the unit commitment set (e.g., $\mathcal{A}^{\text{uc}}_y$), the ramping limits apply to the flow above the minimum output, but if it is not, the ramping limits apply to the total output flow.
Expand Down
2 changes: 2 additions & 0 deletions src/constraints/create.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ function compute_constraints_indices(connection)
:limit_units_on_compact_vintage_method,
:limit_units_on_aggregated_vintage_method,
:min_output_flow_with_unit_commitment,
:min_output_flow_without_unit_commitment_aggregated_vintage_method,
:min_output_flow_without_unit_commitment_compact_vintage_method,
:max_output_flow_with_basic_unit_commitment,
:max_ramp_with_unit_commitment,
:max_ramp_without_unit_commitment,
Expand Down
131 changes: 131 additions & 0 deletions src/constraints/min-output-flow.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""
add_min_output_flow_without_unit_commitment_constraints!(
connection, model, expressions, constraints
)

Adds the minimum output flow constraints for producer and conversion assets without
unit commitment and a positive `min_operating_point`.

The constraint enforces:
```
sum(capacity_coefficient * outgoing flows) >= min_operating_point [p.u.] * capacity [MW] * available_units [# of units]
```

This activates only when `unit_commitment = 'none'` and `min_operating_point > 0`.
Assets with unit commitment already enforce a minimum output flow through the
`min_output_flow_with_unit_commitment` constraint.

Note that the `capacity_coefficient` ensures that when output flows are scaled down in the capacity constraints,
they are also reflected in the minimum output flow constraint.
This is particularly useful for outputs like CO2 emissions,
which have a coefficient of zero because they do not contribute to the energy output flow and are regarded as byproducts.
"""
function add_min_output_flow_without_unit_commitment_constraints!(
connection,
model,
expressions,
constraints,
)
expr_avail_aggregated_vintage_method =
expressions[:available_asset_units_aggregated_vintage_method].expressions[:assets]
expr_avail_compact_method =
expressions[:available_asset_units_compact_vintage_method].expressions[:assets]

# - Aggregated vintage method
let table_name = :min_output_flow_without_unit_commitment_aggregated_vintage_method,
cons = constraints[table_name]

indices = _append_min_output_flow_data_to_indices_aggregated_vintage_method(
connection,
table_name,
)
attach_constraint!(
model,
cons,
table_name,
[
@constraint(
model,
outgoing_flow ≥
row.min_operating_point *
row.capacity *
expr_avail_aggregated_vintage_method[row.avail_id],
base_name = "min_output_flow_without_unit_commitment_aggregated_vintage_method[$(row.asset),$(row.milestone_year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]"
) for (row, outgoing_flow) in zip(indices, cons.expressions[:outgoing])
],
)
end

# - Compact profiles vintage method
let table_name = :min_output_flow_without_unit_commitment_compact_vintage_method,
cons = constraints[table_name]

indices = _append_min_output_flow_data_to_indices_compact_method(connection, table_name)
attach_constraint!(
model,
cons,
table_name,
[
@constraint(
model,
outgoing_flow ≥
row.min_operating_point *
row.capacity *
sum(expr_avail_compact_method[avail_id] for avail_id in row.avail_indices),
base_name = "min_output_flow_without_unit_commitment_compact_vintage_method[$(row.asset),$(row.milestone_year),$(row.rep_period),$(row.time_block_start):$(row.time_block_end)]"
) for (row, outgoing_flow) in zip(indices, cons.expressions[:outgoing])
],
)
end

return
end

function _append_min_output_flow_data_to_indices_aggregated_vintage_method(connection, table_name)
return DuckDB.query(
connection,
"SELECT
cons.id,
cons.asset,
cons.milestone_year,
cons.rep_period,
cons.time_block_start,
cons.time_block_end,
asset.capacity,
asset.min_operating_point,
expr_avail.id AS avail_id,
FROM cons_$table_name AS cons
LEFT JOIN asset
ON cons.asset = asset.asset
LEFT JOIN expr_available_asset_units_aggregated_vintage_method AS expr_avail
ON cons.asset = expr_avail.asset
AND cons.milestone_year = expr_avail.milestone_year
ORDER BY cons.id
",
)
end

function _append_min_output_flow_data_to_indices_compact_method(connection, table_name)
return DuckDB.query(
connection,
"SELECT
cons.id,
ANY_VALUE(cons.asset) AS asset,
ANY_VALUE(cons.milestone_year) AS milestone_year,
ANY_VALUE(cons.rep_period) AS rep_period,
ANY_VALUE(cons.time_block_start) AS time_block_start,
ANY_VALUE(cons.time_block_end) AS time_block_end,
ANY_VALUE(asset.capacity) AS capacity,
ANY_VALUE(asset.min_operating_point) AS min_operating_point,
ARRAY_AGG(expr_avail.id ORDER BY expr_avail.id) AS avail_indices,
FROM cons_$table_name AS cons
LEFT JOIN asset
ON cons.asset = asset.asset
LEFT JOIN expr_available_asset_units_compact_vintage_method AS expr_avail
ON cons.asset = expr_avail.asset
AND cons.milestone_year = expr_avail.milestone_year
GROUP BY cons.id
ORDER BY cons.id
",
Comment thread
datejada marked this conversation as resolved.
Outdated
)
end
7 changes: 7 additions & 0 deletions src/create-model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,13 @@ function create_model(
profiles,
)

@timeit to "add_min_output_flow_without_unit_commitment_constraints!" add_min_output_flow_without_unit_commitment_constraints!(
connection,
model,
expressions,
constraints,
)

@timeit to "add_flows_relationships_constraints!" add_flows_relationships_constraints!(
connection,
model,
Expand Down
16 changes: 16 additions & 0 deletions src/model-preparation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -711,9 +711,25 @@ function add_expressions_to_constraints!(connection, variables, constraints)
workspace;
use_highest_resolution = true,
multiply_by_duration = false,
multiply_by_capacity_coefficient = true,
add_min_outgoing_flow_duration = true,
)
end

for table_name in (
:min_output_flow_without_unit_commitment_aggregated_vintage_method,
:min_output_flow_without_unit_commitment_compact_vintage_method,
)
@timeit to "add_expression_terms_rep_period_constraints! for $table_name" add_expression_terms_rep_period_constraints!(
connection,
constraints[table_name],
variables[:flow],
workspace;
use_highest_resolution = true,
multiply_by_duration = false,
multiply_by_capacity_coefficient = true,
)
end
@timeit to "add_expression_terms_inter_period_storage_constraints!" add_expression_terms_inter_period_storage_constraints!(
connection,
constraints[:balance_storage_inter_period],
Expand Down
46 changes: 46 additions & 0 deletions src/sql/create-constraints.sql
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,52 @@ drop sequence id
create sequence id start 1
;

drop table if exists cons_min_output_flow_without_unit_commitment_aggregated_vintage_method
;

create table cons_min_output_flow_without_unit_commitment_aggregated_vintage_method as
select
nextval('id') as id,
t_high.*
from
t_highest_out_flows as t_high
left join asset on t_high.asset = asset.asset
where
asset.type in ('producer', 'conversion')
and asset.unit_commitment = 'none'
and asset.min_operating_point > 0
and asset.vintage_method = 'aggregated'
;

drop sequence id
;

create sequence id start 1
;

drop table if exists cons_min_output_flow_without_unit_commitment_compact_vintage_method
;

create table cons_min_output_flow_without_unit_commitment_compact_vintage_method as
select
nextval('id') as id,
t_high.*
from
t_highest_out_flows as t_high
left join asset on t_high.asset = asset.asset
where
asset.type in ('producer', 'conversion')
and asset.unit_commitment = 'none'
and asset.min_operating_point > 0
and asset.vintage_method = 'compact_profiles'
;

drop sequence id
;

create sequence id start 1
;

drop table if exists cons_max_output_flow_with_basic_unit_commitment
;

Expand Down
Loading
Loading