Skip to content

Commit 1664649

Browse files
Copilotclaude
andauthored
Rename investment group input tables to explicit investment_* names (#1621)
* Initial plan * Rename investment group input tables Rename group_asset/group_asset_membership to investment_group_asset/investment_group_asset_membership across schema, model building, validation, tests, fixtures, and user docs. Co-Authored-By: Claude Code (claude-sonnet-4-6) <noreply@anthropic.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Claude Code (claude-sonnet-4-6) <noreply@anthropic.com>
1 parent 25a0751 commit 1664649

12 files changed

Lines changed: 100 additions & 84 deletions

docs/src/20-user-guide/20-how-to-use.md

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -544,8 +544,8 @@ Groups are useful to represent several common constraints.
544544

545545
In order to define the groups in the model, the following steps are necessary:
546546

547-
1. Create a group file by defining the `name` property and its parameters in the `group_asset` table (or CSV file).
548-
2. Assign assets to the group by adding entries to the `group_asset_membership` table (or CSV file).
547+
1. Create a group file by defining the `name` property and its parameters in the `investment_group_asset` table (or CSV file).
548+
2. Assign assets to the group by adding entries to the `investment_group_asset_membership` table (or CSV file).
549549

550550
#### [Group Investment constraints](@id investment-group-setup)
551551

@@ -556,32 +556,37 @@ $\sum_{a \in G} v^{\text{inv}}_a \times \text{coefficient}_a \left\{\begin{array
556556
i.e., a sum-product of all investment variables listed in the group multiplied by given coefficients related by a "right hand side".
557557
An example of group investment are the maximum and minimum investment limits for group of investment variables.
558558
The mathematical formulation of these constraints is available [here](@ref investment-group-constraints).
559-
They can be achieved using group investment constraints by adding rows in `group_asset` such that:
560559

561-
- Each row in table `group_asset`
560+
!!! info
561+
At the moment, group constraints are only supported for investment variables through `investment_group_asset` and `investment_group_asset_membership`.
562+
If you need additional constraints involving other variables, add them manually to the JuMP model as shown in the [Bids tutorial](@ref bids-tutorial).
563+
564+
They can be achieved using group investment constraints by adding rows in `investment_group_asset` such that:
565+
566+
- Each row in table `investment_group_asset`
562567
- `name` is the name of the group, and unique identifier.
563568
- `milestone_year` is the year for which the group is defined.
564569
- `invest_method = true`. This parameter enables the model to use the investment group constraints.
565570
- `constraint_sense` is either `<=` for maximum and `>=` for minimum.
566571
- `rhs` is the corresponding value.
567-
- Each row in table `group_asset_membership`
568-
- `group_name` should match `group_asset.name`.
572+
- Each row in table `investment_group_asset_membership`
573+
- `group_name` should match `investment_group_asset.name`.
569574
- `asset` is the name of the asset.
570-
- `milestone_year` should match `group_asset.milestone_year` and `asset.milestone_year`.
575+
- `milestone_year` should match `investment_group_asset.milestone_year` and `asset.milestone_year`.
571576
- `coefficient` should be the capacity value for the investment limit.
572577

573578
!!! warning
574-
Notice that only one constraint is created per row in `group_asset`, which means that if both the minimum and maximum investment limits are desired, two rows are required in `group_asset`, one with `constraint_sense = '<='` and one with `constraint_sense = '>='`. In this case, the names of the groups must be different, from instance `ccgt_max` and `ccgt_min`.
575-
Similarly, the elements in `group_asset_membership` will need to be duplicated, one for each group.
579+
Notice that only one constraint is created per row in `investment_group_asset`, which means that if both the minimum and maximum investment limits are desired, two rows are required in `investment_group_asset`, one with `constraint_sense = '<='` and one with `constraint_sense = '>='`. In this case, the names of the groups must be different, from instance `ccgt_max` and `ccgt_min`.
580+
Similarly, the elements in `investment_group_asset_membership` will need to be duplicated, one for each group.
576581

577582
#### Example: Group of Assets
578583

579-
Let's explore how the groups are set up in the test case called [Norse](https://github.com/TulipaEnergy/TulipaEnergyModel.jl/tree/main/test/inputs/Norse). First, let's take a look at the `group-asset.csv` file:
584+
Let's explore how the groups are set up in the test case called [Norse](https://github.com/TulipaEnergy/TulipaEnergyModel.jl/tree/main/test/inputs/Norse). First, let's take a look at the `investment-group-asset.csv` file:
580585

581586
```@example display-group-setup
582587
using DataFrames # hide
583588
using CSV # hide
584-
input_asset_file = "../../../test/inputs/Norse/group-asset.csv" # hide
589+
input_asset_file = "../../../test/inputs/Norse/investment-group-asset.csv" # hide
585590
assets = CSV.read(input_asset_file, DataFrame, header = 1) # hide
586591
```
587592

@@ -590,8 +595,8 @@ In the given data, there are two groups: `renewables` and `ccgt`. Both groups ha
590595
Let's now explore which assets are in each group. To do so, we can take a look at the `asset.csv` file:
591596

592597
```@example display-group-setup
593-
input_file = "../../../test/inputs/Norse/group-asset-membership.csv" # hide
594-
group_asset_membership = CSV.read(input_file, DataFrame) # hide
598+
input_file = "../../../test/inputs/Norse/investment-group-asset-membership.csv" # hide
599+
investment_group_asset_membership = CSV.read(input_file, DataFrame) # hide
595600
```
596601

597602
Here we can see that the assets `Asgard_Solar` and `Midgard_Wind` belong to the `renewables` group, while the assets `Asgard_CCGT` and `Midgard_CCGT` belong to the `ccgt` group.

docs/src/20-user-guide/54-input-table-schemas.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ The input data must follow the table schemas below to correctly build a system i
55
The schemas below are in [`input-schemas.json`](https://github.com/TulipaEnergy/TulipaEnergyModel.jl/blob/main/src/input-schemas.json). You can also view the schemas after loading the package by typing `TulipaEnergyModel.schema` in the Julia console.
66

77
!!! info "Optional tables/files and their defaults"
8-
The following tables/files are allowed to be missing: "assets\_rep\_periods\_partitions", "assets\_timeframe\_partitions", "assets\_timeframe\_profiles", "flows\_rep\_periods\_partitions", "group\_asset", "group\_asset\_membership", "profiles\_timeframe". These tables that are allowed to be missing are tables allowed to be empty, i.e., to have 0 rows, during model creation.
8+
The following tables/files are allowed to be missing: "assets\_rep\_periods\_partitions", "assets\_timeframe\_partitions", "assets\_timeframe\_profiles", "flows\_rep\_periods\_partitions", "investment\_group\_asset", "investment\_group\_asset\_membership", "profiles\_timeframe". These tables that are allowed to be missing are tables allowed to be empty, i.e., to have 0 rows, during model creation.
99
- For the partitions tables/files, the default value are `specification = uniform` and `partition = 1` for each asset/flow and year
1010
- For the profiles tables/files, the default value is a flat profile of value 1.0 p.u.
1111
- If no group table/file is available there will be no group constraints in the model

src/constraints/investment_group.jl

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,18 @@ function _append_group_data_to_indices!(connection, cons_table_name)
4747
"""
4848
WITH cte_group_expression AS (
4949
SELECT
50-
group_asset.name AS name,
51-
group_asset.milestone_year AS milestone_year,
50+
investment_group_asset.name AS name,
51+
investment_group_asset.milestone_year AS milestone_year,
5252
ARRAY_AGG(var.id) AS var_assets_investment_ids,
53-
ARRAY_AGG(group_asset_membership.coefficient) AS coefficients,
54-
FROM group_asset
55-
LEFT JOIN group_asset_membership
56-
ON group_asset.name = group_asset_membership.group_name
57-
AND group_asset.milestone_year = group_asset_membership.milestone_year
53+
ARRAY_AGG(investment_group_asset_membership.coefficient) AS coefficients,
54+
FROM investment_group_asset
55+
LEFT JOIN investment_group_asset_membership
56+
ON investment_group_asset.name = investment_group_asset_membership.group_name
57+
AND investment_group_asset.milestone_year = investment_group_asset_membership.milestone_year
5858
LEFT JOIN var_assets_investment AS var
59-
ON group_asset_membership.asset = var.asset
60-
AND group_asset.milestone_year = var.milestone_year
61-
GROUP BY group_asset.name, group_asset.milestone_year
59+
ON investment_group_asset_membership.asset = var.asset
60+
AND investment_group_asset.milestone_year = var.milestone_year
61+
GROUP BY investment_group_asset.name, investment_group_asset.milestone_year
6262
)
6363
SELECT
6464
cons.*,

src/data-validation.jl

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ function _validate_no_duplicate_rows!(error_messages, connection)
154154
("flow_milestone", [:from_asset, :to_asset, :milestone_year]),
155155
("flows_profiles", [:from_asset, :to_asset, :milestone_year, :profile_type]),
156156
("flows_rep_periods_partitions", [:from_asset, :to_asset, :milestone_year, :rep_period]),
157-
("group_asset", [:name, :milestone_year]),
157+
("investment_group_asset", [:name, :milestone_year]),
158158
("profiles_rep_periods", [:profile_name, :milestone_year, :rep_period, :timestep]),
159159
("profiles_timeframe", [:profile_name, :milestone_year, :period]),
160160
("rep_periods_data", [:milestone_year, :rep_period]),
@@ -279,15 +279,15 @@ end
279279
function _validate_assets_in_investment_groups_are_investable!(error_messages, connection)
280280
query = """
281281
SELECT
282-
group_asset_membership.group_name,
283-
group_asset_membership.asset,
284-
group_asset.milestone_year,
285-
FROM group_asset_membership
286-
LEFT JOIN group_asset
287-
ON group_asset_membership.group_name = group_asset.name
282+
investment_group_asset_membership.group_name,
283+
investment_group_asset_membership.asset,
284+
investment_group_asset.milestone_year,
285+
FROM investment_group_asset_membership
286+
LEFT JOIN investment_group_asset
287+
ON investment_group_asset_membership.group_name = investment_group_asset.name
288288
LEFT JOIN asset_milestone
289-
ON group_asset_membership.asset = asset_milestone.asset
290-
AND group_asset.milestone_year = asset_milestone.milestone_year
289+
ON investment_group_asset_membership.asset = asset_milestone.asset
290+
AND investment_group_asset.milestone_year = asset_milestone.milestone_year
291291
WHERE NOT asset_milestone.investable
292292
"""
293293
for row in DuckDB.query(connection, query)
@@ -301,40 +301,40 @@ function _validate_assets_in_investment_groups_are_investable!(error_messages, c
301301
end
302302

303303
function _validate_group_consistency!(error_messages, connection)
304-
# Check the values in the table `group_asset_membership`:
305-
# - `group_name` comes from `group_asset.name`
304+
# Check the values in the table `investment_group_asset_membership`:
305+
# - `group_name` comes from `investment_group_asset.name`
306306
# - `asset` comes from `asset.asset`
307307
_validate_foreign_key!(
308308
error_messages,
309309
connection,
310-
"group_asset_membership",
310+
"investment_group_asset_membership",
311311
:group_name,
312-
"group_asset",
312+
"investment_group_asset",
313313
:name,
314314
)
315315
_validate_foreign_key!(
316316
error_messages,
317317
connection,
318-
"group_asset_membership",
318+
"investment_group_asset_membership",
319319
:asset,
320320
"asset",
321321
:asset,
322322
)
323323

324-
# Check that the groups created in `group_asset` have at least one entry in `group_asset_membership`
324+
# Check that the groups created in `investment_group_asset` have at least one entry in `investment_group_asset_membership`
325325
for row in DuckDB.query(
326326
connection,
327327
"FROM (
328-
SELECT group_asset.name, COUNT(group_asset_membership.asset) AS count_group_members
329-
FROM group_asset
330-
LEFT JOIN group_asset_membership
331-
ON group_asset.name = group_asset_membership.group_name
332-
GROUP BY group_asset.name
328+
SELECT investment_group_asset.name, COUNT(investment_group_asset_membership.asset) AS count_group_members
329+
FROM investment_group_asset
330+
LEFT JOIN investment_group_asset_membership
331+
ON investment_group_asset.name = investment_group_asset_membership.group_name
332+
GROUP BY investment_group_asset.name
333333
) WHERE count_group_members = 0",
334334
)
335335
push!(
336336
error_messages,
337-
"Group '$(row.name)' in 'group_asset' has no members in 'group_asset_membership'",
337+
"Group '$(row.name)' in 'investment_group_asset' has no members in 'investment_group_asset_membership'",
338338
)
339339
end
340340

src/input-schemas.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -788,7 +788,7 @@
788788
"type": "VARCHAR"
789789
}
790790
},
791-
"group_asset": {
791+
"investment_group_asset": {
792792
"constraint_sense": {
793793
"constraints": {
794794
"oneOf": [
@@ -817,7 +817,7 @@
817817
"type": "DOUBLE"
818818
}
819819
},
820-
"group_asset_membership": {
820+
"investment_group_asset_membership": {
821821
"asset": {
822822
"description": "One of the assets in this group.",
823823
"type": "VARCHAR"

src/io.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ const tables_allowed_to_be_missing = [
77
"flows_profiles"
88
"flows_relationships"
99
"flows_rep_periods_partitions"
10-
"group_asset"
11-
"group_asset_membership"
10+
"investment_group_asset"
11+
"investment_group_asset_membership"
1212
"profiles_rep_periods"
1313
"profiles_timeframe"
1414
"stochastic_scenario"

src/sql/create-constraints.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,7 @@ select
668668
ga.constraint_sense,
669669
ga.rhs,
670670
from
671-
group_asset as ga
671+
investment_group_asset as ga
672672
where
673673
ga.invest_method
674674
;
File renamed without changes.
File renamed without changes.

test/test-constraint-investment-groups.jl

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,22 @@
1717
DuckDB.query(
1818
connection,
1919
"""
20-
CREATE OR REPLACE TABLE group_asset (name VARCHAR, milestone_year INT, constraint_sense VARCHAR, rhs DOUBLE, invest_method BOOL);
21-
INSERT INTO group_asset VALUES ('group1', 2030, '<=', 7700, true);
22-
INSERT INTO group_asset VALUES ('group1', 2050, '>=', 3300, true);
23-
INSERT INTO group_asset VALUES ('group2', 2030, '==', 1234, true);
24-
INSERT INTO group_asset VALUES ('group2', 2050, '==', 4321, false);
20+
CREATE OR REPLACE TABLE investment_group_asset (name VARCHAR, milestone_year INT, constraint_sense VARCHAR, rhs DOUBLE, invest_method BOOL);
21+
INSERT INTO investment_group_asset VALUES ('group1', 2030, '<=', 7700, true);
22+
INSERT INTO investment_group_asset VALUES ('group1', 2050, '>=', 3300, true);
23+
INSERT INTO investment_group_asset VALUES ('group2', 2030, '==', 1234, true);
24+
INSERT INTO investment_group_asset VALUES ('group2', 2050, '==', 4321, false);
2525
""",
2626
)
2727
DuckDB.query(
2828
connection,
2929
"""
30-
CREATE OR REPLACE TABLE group_asset_membership (group_name VARCHAR, milestone_year INT, asset VARCHAR, coefficient DOUBLE);
31-
INSERT INTO group_asset_membership VALUES ('group1', 2030, 'producer1', 3.14);
32-
INSERT INTO group_asset_membership VALUES ('group1', 2030, 'producer2', 6.66);
33-
INSERT INTO group_asset_membership VALUES ('group1', 2050, 'producer2', 2.51);
34-
INSERT INTO group_asset_membership VALUES ('group2', 2030, 'producer1', 0.73);
35-
INSERT INTO group_asset_membership VALUES ('group2', 2050, 'producer2', 3.45);
30+
CREATE OR REPLACE TABLE investment_group_asset_membership (group_name VARCHAR, milestone_year INT, asset VARCHAR, coefficient DOUBLE);
31+
INSERT INTO investment_group_asset_membership VALUES ('group1', 2030, 'producer1', 3.14);
32+
INSERT INTO investment_group_asset_membership VALUES ('group1', 2030, 'producer2', 6.66);
33+
INSERT INTO investment_group_asset_membership VALUES ('group1', 2050, 'producer2', 2.51);
34+
INSERT INTO investment_group_asset_membership VALUES ('group2', 2030, 'producer1', 0.73);
35+
INSERT INTO investment_group_asset_membership VALUES ('group2', 2050, 'producer2', 3.45);
3636
""",
3737
)
3838

0 commit comments

Comments
 (0)