Skip to content

Add config obj replace schemas #717

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fe421a5
Update to using pydantic for config
brynpickering Oct 30, 2024
1f96c47
Update config to have operate and spores as subdicts; fix use of conf…
brynpickering Nov 6, 2024
4f81684
Minor cleanup
brynpickering Nov 7, 2024
e59e5e7
Removed mode redundancy to simplify the configuration
irm-codebase Nov 15, 2024
e033b74
ruff fixes
irm-codebase Nov 15, 2024
e2a051a
Simplify config schema extraction
irm-codebase Nov 15, 2024
56e1126
Add schemas folder
irm-codebase Nov 18, 2024
46ae5a0
Add data table schema w/ tests
irm-codebase Nov 19, 2024
3186660
Use Annotated for regex strings
irm-codebase Nov 19, 2024
e35ee0e
Add config obj: simplification suggestions (#711)
irm-codebase Nov 20, 2024
3a71e97
Merge branch 'add-config-obj' into add-config-obj-replace-schemas
irm-codebase Nov 20, 2024
2c9a36d
fix yaml call merge bug
irm-codebase Nov 21, 2024
a88c18d
Fix attr dict call to yaml
irm-codebase Nov 21, 2024
750acaa
Merge branch 'main' into add-config-obj-replace-schemas
brynpickering Jan 21, 2025
e0c232c
Fix tests
brynpickering Jan 21, 2025
51991ac
Move config schema to schema folder
irm-codebase Feb 3, 2025
9e4b2ba
Add schema attributes file
irm-codebase Feb 3, 2025
a3ffe8f
Add indexed parameter schema.
irm-codebase Feb 4, 2025
5c5e7a1
Add dimension schemas
irm-codebase Feb 6, 2025
5f80b49
Change / to / to avoid protected term collisions in python.
irm-codebase Feb 7, 2025
5b1d425
Initial addition of tests for config and dimensions
irm-codebase Feb 7, 2025
132e69b
Add tests for all model definition levels.
irm-codebase Feb 10, 2025
0379cdb
Replace model_def schema with pydantic model schema in Calliope model…
irm-codebase Feb 11, 2025
2168ad5
Fix urban example model MILP case definition. Remove config_schema.yaml.
irm-codebase Feb 11, 2025
77eaea6
Testing fixes: remove unused technology nat. example, improve timeste…
irm-codebase Feb 12, 2025
385a422
Fix tests, remove Techs base_tech validation to avoid false negatives…
irm-codebase Feb 12, 2025
372bbef
Remove data table schema.
irm-codebase Feb 12, 2025
03c5279
Add model math schema.
irm-codebase Feb 13, 2025
bc02208
Modify CHANGELOG, fix data table title
irm-codebase Feb 13, 2025
3fc1a0c
Improve test coverage.
irm-codebase Feb 13, 2025
2e06021
PR suggestions and fixes
irm-codebase Feb 20, 2025
11dd2f8
Fix dead xarray link.
irm-codebase Feb 20, 2025
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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ This change has occurred to avoid confusion between data "sources" and model ene

### Internal changes

|changed| Model configuration now uses `pydantic`.
|changed| updated transmission technologies to/from -> link_to/link_from to avoid conflicts with protected `python` terminology.

|changed| Model configuration, data tables, techs/nodes data, math and general model definition now uses `pydantic`.

|changed| Model definition reading is now defined in a single place (preprocess/model_definition.py).

Expand Down
6 changes: 3 additions & 3 deletions docs/advanced/constraints.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,14 @@ To force unidirectionality for a given technology along a given link, you have t
```yaml
techs:
region1_to_region2:
from: region1
to: region2
link_from: region1
link_to: region2
base_tech: transmission
one_way: true
```

This will only allow transmission from `region1` to `region2`.
To swap the direction, `to` and `from` must be swapped.
To swap the direction, `link_to` and `link_from` must be swapped.

## Per-distance transmission constraints

Expand Down
2 changes: 1 addition & 1 deletion docs/creating/nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ In the above example, `node_flow_out_max` at `region1` could be used to create [
## Understanding node-level parameters

`techs` is the only required parameter in a node.
This can be an empty dictionary (`techs: {}`), which you may use if your node is just a junction for transmission technologies (which you [**do not define in the `techs` of a node**](techs.md#transmission-technologies) - rather, you define them as separate technologies that connect `from` one node `to` another node).
This can be an empty dictionary (`techs: {}`), which you may use if your node is just a junction for transmission technologies (which you [**do not define in the `techs` of a node**](techs.md#transmission-technologies) - rather, you define them as separate technologies that connect a `link_from` node with a `link_to` node).

!!! info "See also"

Expand Down
10 changes: 5 additions & 5 deletions docs/creating/techs.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@ Instead, you associate transmission technologies with nodes in `techs`:
```yaml
techs:
ac_transmission:
from: region1 # (1)!
to: region2
link_from: region1 # (1)!
link_to: region2
flow_cap_max: 100
...
```

1. The region you specify in `from` or `to` is interchangeable unless you set the parameter `one_way: true`.
In that case, flow along the transmission line is only allowed from the `from` region to the `to` region.
1. The region you specify in `link_from` or `link_to` is interchangeable unless you set the parameter `one_way: true`.
In that case, flow along the transmission line is only allowed from the `link_from` node to the `link_to` node.

## Understanding tech-level parameters

Expand All @@ -103,7 +103,7 @@ There are _required_ parameters according to the technology `base_tech`:
* `supply`: `base_tech` and `carrier_out`.
* `demand`: `base_tech` and `carrier_in`.
* `storage`: `base_tech` and `carrier_out` and `carrier_in`.
* `transmission`: `base_tech` and `carrier_out`, `carrier_in`, `to`, and `from`.
* `transmission`: `base_tech` and `carrier_out`, `carrier_in`, `link_to`, and `link_from`.
* `conversion`: `base_tech` and `carrier_out` and `carrier_in`.

For `storage` and `transmission`, it may seem like unnecessary repetition to define both `carrier_out` and `carrier_in` as they are likely the same value.
Expand Down
20 changes: 10 additions & 10 deletions docs/examples/loading_tabular_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@
# base_tech: transmission
# carrier_in: electricity
# carrier_out: electricity
# from: A
# to: B
# link_from: A
# link_to: B
# flow_cap_max: 8
#
# nodes:
Expand Down Expand Up @@ -136,8 +136,8 @@
base_tech: transmission
carrier_in: electricity
carrier_out: electricity
from: A
to: B
link_from: A
link_to: B
flow_cap_max: 8

nodes:
Expand Down Expand Up @@ -215,8 +215,8 @@
"demand_tech": {"base_tech": "demand"},
"transmission_tech": {
"base_tech": "transmission",
"from": "A",
"to": "B",
"link_from": "A",
"link_to": "B",
"flow_cap_max": 8,
},
}
Expand Down Expand Up @@ -483,8 +483,8 @@
# base_tech: transmission
# carrier_in: electricity
# carrier_out: electricity
# from: A
# to: B
# link_from: A
# link_to: B
# flow_cap_max: 8
#
# nodes:
Expand Down Expand Up @@ -532,8 +532,8 @@
# base_tech: transmission
# carrier_in: electricity
# carrier_out: electricity
# from: A
# to: B
# link_from: A
# link_to: B
# flow_cap_max: 8
#
# nodes:
Expand Down
7 changes: 5 additions & 2 deletions docs/hooks/dummy_model/model.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ nodes:

techs:
tech_transmission:
from: A
to: B
base_tech: transmission
carrier_in: foo
carrier_out: foo
link_from: A
link_to: B

data_tables:
techs:
Expand Down
6 changes: 3 additions & 3 deletions docs/hooks/dummy_model/techs.csv
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ transmission_tech,lifetime,25
transmission_tech,one_way,1.0
transmission_tech,flow_cap_max,1.0
transmission_tech,flow_cap_min,1.0
transmission_tech,from,A
transmission_tech,to,B
transmission_tech,link_from,A
transmission_tech,link_to,B
demand_tech,sink_unit,per_cap
conversion_tech,base_tech,conversion
conversion_tech,cap_method,integer
Expand All @@ -64,4 +64,4 @@ supply_tech,cap_method,integer
supply_tech,source_unit,per_area
storage_tech,base_tech,storage
transmission_tech,base_tech,transmission
demand_tech,base_tech,demand
demand_tech,base_tech,demand
9 changes: 5 additions & 4 deletions docs/hooks/generate_readable_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@
import jsonschema2md
from mkdocs.structure.files import File

from calliope import config
from calliope.schemas import config_schema, data_table_schema, math_schema
from calliope.util import schema

TEMPDIR = tempfile.TemporaryDirectory()

# FIXME: should only use pydantic models instead of YAML
SCHEMAS = {
"config_schema": config.CalliopeConfig().model_no_ref_schema(),
"config_schema": config_schema.CalliopeConfig.model_no_ref_schema(),
"model_schema": schema.MODEL_SCHEMA,
"math_schema": schema.MATH_SCHEMA,
"data_table_schema": schema.DATA_TABLE_SCHEMA,
"math_schema": math_schema.CalliopeMathDef.model_no_ref_schema(),
"data_table_schema": data_table_schema.CalliopeDataTable.model_no_ref_schema(),
}


Expand Down
2 changes: 1 addition & 1 deletion docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Calliope is tested on Linux, macOS, and Windows.
Running Calliope requires four things:

1. The Python programming language, version {{ min_python_version }} to {{ max_python_version }}.
2. A number of Python add-on modules including [Pyomo](https://www.pyomo.org/), [Pandas](https://pandas.pydata.org/) and [Xarray](https://xarray.dev/).
2. A number of Python add-on modules including [Pyomo](https://www.pyomo.org/), [Pandas](https://pandas.pydata.org/) and [Xarray](https://docs.xarray.dev/).
3. An optimisation solver: Calliope has been tested with CBC, GLPK, Gurobi, and CPLEX. Any other solver that is compatible with Pyomo should also work.
4. The Calliope software itself.

Expand Down
10 changes: 5 additions & 5 deletions docs/migrating.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ We have changed the nesting structure for defining technology costs so they are
### `links` → transmission links defined in `techs`

The top-level key `links` no longer exists.
Instead, links are defined as separate transmission technologies in `techs`, including `to`/`from` keys:
Instead, links are defined as separate transmission technologies in `techs`, including `link_to`/`link_from` keys:

=== "v0.6"

Expand Down Expand Up @@ -298,13 +298,13 @@ Instead, links are defined as separate transmission technologies in `techs`, inc
```yaml
techs:
x1_to_x2_ac_transmission:
from: X1
to: X2
link_from: X1
link_to: X2
base_tech: transmission
flow_cap_max: 10
x1_to_x2_dc_transmission:
from: X1
to: X2
link_from: X1
link_to: X2
base_tech: transmission
flow_cap_max: 5
```
Expand Down
4 changes: 2 additions & 2 deletions docs/user_defined_math/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Without a `where` string, all valid members (according to the `definition_matrix
If a value for a valid variable member is undefined in the referenced parameter, the decision variable will be unbounded for this member.
1. It can be deactivated so that it does not appear in the built optimisation problem by setting `active: false`.
1. It can take on a `default` value that will be used in math operations to avoid `NaN` values creeping in.
The default value should be set such that it has no impact on the optimisation problem if it is included (most of the time, this means setting it to zero).
The default value should be set such that it has no impact on the optimisation problem if it is included (most of the time, this means `NaN`).

## Global Expressions

Expand All @@ -63,7 +63,7 @@ Without a `where` string, all valid members (according to the `definition_matrix
The equation expressions do _not_ have comparison operators; those are reserved for [constraints](#constraints)
1. It can be deactivated so that it does not appear in the built optimisation problem by setting `active: false`.
1. It can take on a `default` value that will be used in math operations to avoid `NaN` values creeping in.
The default value should be set such that it has no impact on the optimisation problem if it is included (most of the time, this means setting it to zero).
The default value should be set such that it has no impact on the optimisation problem if it is included (most of the time, this means `NaN`).

## Constraints

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ max-complexity = 10

# Ignore `E402` (import violations) and `F401` (unused imports) in all `__init__.py` files
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["E402", "F401"]
"__init__.py" = ["E402", "F401", "D104"]
"*.ipynb" = ["E402"]
"tests/*" = ["D"]
"docs/examples/*" = ["D"]
Expand Down
4 changes: 2 additions & 2 deletions src/calliope/backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
from calliope.preprocess import CalliopeMath

if TYPE_CHECKING:
from calliope import config
from calliope.backend.backend_model import BackendModel
from calliope.schemas import config_schema


def get_model_backend(
build_config: "config.Build", data: xr.Dataset, math: CalliopeMath
build_config: "config_schema.Build", data: xr.Dataset, math: CalliopeMath
) -> "BackendModel":
"""Assign a backend using the given configuration.

Expand Down
7 changes: 4 additions & 3 deletions src/calliope/backend/backend_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@
import numpy as np
import xarray as xr

from calliope import config, exceptions
from calliope import exceptions
from calliope.attrdict import AttrDict
from calliope.backend import helper_functions, parsing
from calliope.exceptions import warn as model_warn
from calliope.io import load_config, to_yaml
from calliope.preprocess.model_math import ORDERED_COMPONENTS_T, CalliopeMath
from calliope.schemas import config_schema
from calliope.util.schema import MODEL_SCHEMA, extract_from_schema

if TYPE_CHECKING:
Expand Down Expand Up @@ -66,7 +67,7 @@ class BackendModelGenerator(ABC):
_PARAM_TYPE = extract_from_schema(MODEL_SCHEMA, "x-type")

def __init__(
self, inputs: xr.Dataset, math: CalliopeMath, build_config: config.Build
self, inputs: xr.Dataset, math: CalliopeMath, build_config: config_schema.Build
):
"""Abstract base class to build a representation of the optimisation problem.

Expand Down Expand Up @@ -606,7 +607,7 @@ def __init__(
self,
inputs: xr.Dataset,
math: CalliopeMath,
build_config: config.Build,
build_config: config_schema.Build,
instance: T,
) -> None:
"""Abstract base class to build backend models that interface with solvers.
Expand Down
4 changes: 2 additions & 2 deletions src/calliope/backend/gurobi_backend_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
import pandas as pd
import xarray as xr

from calliope import config
from calliope.backend import backend_model, parsing
from calliope.exceptions import BackendError, BackendWarning
from calliope.exceptions import warn as model_warn
from calliope.preprocess import CalliopeMath
from calliope.schemas import config_schema

if importlib.util.find_spec("gurobipy") is not None:
import gurobipy
Expand All @@ -43,7 +43,7 @@ class GurobiBackendModel(backend_model.BackendModel):
"""gurobipy-specific backend functionality."""

def __init__(
self, inputs: xr.Dataset, math: CalliopeMath, build_config: config.Build
self, inputs: xr.Dataset, math: CalliopeMath, build_config: config_schema.Build
) -> None:
"""Gurobi solver interface class.

Expand Down
4 changes: 2 additions & 2 deletions src/calliope/backend/latex_backend_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
import pandas as pd
import xarray as xr

from calliope import config
from calliope.backend import backend_model, parsing
from calliope.exceptions import ModelError
from calliope.preprocess import CalliopeMath
from calliope.schemas import config_schema

ALLOWED_MATH_FILE_FORMATS = Literal["tex", "rst", "md"]
LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -306,7 +306,7 @@ def __init__(
self,
inputs: xr.Dataset,
math: CalliopeMath,
build_config: config.Build,
build_config: config_schema.Build,
include: Literal["all", "valid"] = "all",
) -> None:
"""Interface to build a string representation of the mathematical formulation using LaTeX math notation.
Expand Down
4 changes: 2 additions & 2 deletions src/calliope/backend/pyomo_backend_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
from pyomo.opt import SolverFactory # type: ignore
from pyomo.util.model_size import build_model_size_report # type: ignore

from calliope import config
from calliope.exceptions import BackendError, BackendWarning
from calliope.exceptions import warn as model_warn
from calliope.preprocess import CalliopeMath
from calliope.schemas import config_schema
from calliope.util.logging import LogWriter

from . import backend_model, parsing
Expand Down Expand Up @@ -60,7 +60,7 @@ class PyomoBackendModel(backend_model.BackendModel):
"""Pyomo-specific backend functionality."""

def __init__(
self, inputs: xr.Dataset, math: CalliopeMath, build_config: config.Build
self, inputs: xr.Dataset, math: CalliopeMath, build_config: config_schema.Build
) -> None:
"""Pyomo solver interface class.

Expand Down
4 changes: 2 additions & 2 deletions src/calliope/backend/where_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
import xarray as xr
from typing_extensions import NotRequired, TypedDict

from calliope import config
from calliope.backend import expression_parser
from calliope.exceptions import BackendError
from calliope.schemas import config_schema
from calliope.util import tools

if TYPE_CHECKING:
Expand All @@ -35,7 +35,7 @@ class EvalAttrs(TypedDict):
helper_functions: dict[str, Callable]
apply_where: NotRequired[bool]
references: NotRequired[set]
build_config: config.Build
build_config: config_schema.Build


class EvalWhere(expression_parser.EvalToArrayStr):
Expand Down
6 changes: 5 additions & 1 deletion src/calliope/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,13 @@ def _run_setup_model(model_file, scenario, model_format, override_dict):
f'extension for "{model_file}". Set format explicitly with '
"--model_format."
)
if isinstance(override_dict, str):
overrides = io.read_rich_yaml(override_dict)
else:
overrides = io.AttrDict()

if model_format == "yaml":
model = Model(model_file, scenario=scenario, override_dict=override_dict)
model = Model(model_file, scenario=scenario, override_dict=overrides)
elif model_format == "netcdf":
if scenario is not None or override_dict is not None:
raise ValueError(
Expand Down
Loading