Skip to content
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

Include the linopy version of the transport tutorial #101

Open
wants to merge 50 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
f1fec0d
Make name column a mixin
glatterf42 Apr 18, 2024
66ebd6a
Make optimization columns mixins
glatterf42 Apr 18, 2024
2c67afd
Inherit mixin requirements directly
glatterf42 Apr 18, 2024
d358641
Include optimization parameter basis (#79)
glatterf42 Jun 28, 2024
53fb371
Introduce optimization.Variable
glatterf42 Jul 2, 2024
c5dfe30
Streamline naming in tests
glatterf42 Jul 3, 2024
28fd7f3
Make constrained_to_indexset optional for scalar Variables
glatterf42 Jul 5, 2024
0fa58ab
Enable import of typing.Never on Python 3.10
glatterf42 Jul 5, 2024
7b8db2a
Introduce optimization.Equation
glatterf42 Jul 3, 2024
181dc75
Streamline naming in tests
glatterf42 Jul 3, 2024
34154e5
Validate var/equ data only for non-empty data
glatterf42 Jul 10, 2024
521a963
TEMPORARY Add all missing db migrations
glatterf42 Jul 10, 2024
58a064f
Add transport tutorial
glatterf42 Jul 10, 2024
b47dbdc
Add required dependencies
glatterf42 Jul 10, 2024
fcccc68
Remove outdated comments
glatterf42 Jul 10, 2024
14b302f
Rename read_solution and other review suggestions
glatterf42 Jul 11, 2024
e27d0a6
Rename model file
glatterf42 Jul 11, 2024
045fd64
Remove superfluous auxiliary function
glatterf42 Jul 11, 2024
93ec91f
Refactor to only passing run for interface simplicity
glatterf42 Jul 11, 2024
5a79467
Revert back to parameter.units and values as they are required
glatterf42 Jul 11, 2024
5efa1cb
Remove superfluous variable
glatterf42 Jul 11, 2024
04e60bc
Add tests for transport tutorial
glatterf42 Jul 11, 2024
c380dee
Install tutorial dependencies for this PR
glatterf42 Jul 24, 2024
53925dd
Allow error tolerance to pass test
glatterf42 Jul 24, 2024
4f65e6b
Allow linopy version with numpy pin :(
glatterf42 Jul 24, 2024
fcf866b
Exclude untyped linopy from mypy
glatterf42 Aug 2, 2024
38806cf
Rename transport file for consistency
glatterf42 Aug 2, 2024
a7fa6ab
Shorten util function
glatterf42 Aug 12, 2024
9581ec5
Introduce equation.remove_data()
glatterf42 Aug 27, 2024
f21879e
Introduce variable.remove_data()
glatterf42 Aug 27, 2024
5643bbe
Prefer None over Never for type hints
glatterf42 Aug 28, 2024
d9f5903
Make run.get_by_id() available in all backends
glatterf42 Aug 28, 2024
3543e10
Introduce run.opt.remove_solution()
glatterf42 Aug 28, 2024
1ad1251
Remove superfluous check for scalar.unit
glatterf42 Aug 29, 2024
41ccf98
Introduce core.runs.clone()
glatterf42 Aug 29, 2024
eb1b0fc
Remove superfluous session.add() for table, indexset
glatterf42 Aug 29, 2024
1a4c439
Remove superfluous session.add() for equation
glatterf42 Aug 29, 2024
3b8f45f
Remove superfluous session.add() for variable
glatterf42 Aug 29, 2024
b009acf
Allow clone of runs without iamc data
glatterf42 Aug 29, 2024
0c13ae1
Create helper create_default_dantzig_run()
glatterf42 Aug 29, 2024
103fca5
Improve linopy tutorial and helper functions
glatterf42 Aug 29, 2024
4e02ecb
Add second part of transport tutorial
glatterf42 Aug 29, 2024
087b52c
Lock dependencies after rebase
glatterf42 Oct 3, 2024
4c8ac88
Adapt tests to fixtures and own errors
glatterf42 Oct 3, 2024
7bc37d0
Adapt linopy tutorial tests to fixtures and own errors
glatterf42 Oct 3, 2024
fb52421
Clarify core docstrings
glatterf42 Oct 3, 2024
d4e187d
Bump pandas version in poetry.lock
glatterf42 Oct 3, 2024
a367338
Fix np.strings comparison
glatterf42 Oct 3, 2024
31498ce
Set new variable.data index only for non-scalars
glatterf42 Oct 3, 2024
8b55f96
Reconcile alembic migrations
glatterf42 Oct 7, 2024
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: 2 additions & 2 deletions .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ jobs:
#------------------------------------------------
- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction --no-root --with dev,server
run: poetry install --no-interaction --no-root --with dev,server,tutorial

- name: Install PyArrow
if: ${{ matrix.with-pyarrow }}
Expand Down Expand Up @@ -174,7 +174,7 @@ jobs:
#------------------------------------------------
- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction --no-root --with dev,server
run: poetry install --no-interaction --no-root --with dev,server,tutorial

#------------------------
# install root project
Expand Down
2 changes: 1 addition & 1 deletion ixmp4/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
from .model import Model as Model
from .optimization.equation import Equation as Equation
from .optimization.indexset import IndexSet as IndexSet
from .optimization.parameter import Parameter as Parameter
from .optimization.scalar import Scalar as Scalar
from .optimization.table import Table as Table
from .optimization.parameter import Parameter as Parameter

# TODO Is this really the name we want to use?
from .optimization.variable import Variable as OptimizationVariable
Expand Down
6 changes: 6 additions & 0 deletions ixmp4/core/optimization/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@ def __init__(self, *args, run: Run, **kwargs) -> None:
self.scalars = ScalarRepository(_backend=self.backend, _run=run)
self.tables = TableRepository(_backend=self.backend, _run=run)
self.variables = VariableRepository(_backend=self.backend, _run=run)

def remove_solution(self) -> None:
for equation in self.equations.list():
equation.remove_data()
for variable in self.variables.list():
variable.remove_data()
4 changes: 2 additions & 2 deletions ixmp4/core/optimization/equation.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def data(self) -> dict[str, Any]:
return self._model.data

def add(self, data: dict[str, Any] | pd.DataFrame) -> None:
"""Adds data to an existing Equation."""
"""Adds data to the Equation."""
self.backend.optimization.equations.add_data(
equation_id=self._model.id, data=data
)
Expand All @@ -41,7 +41,7 @@ def add(self, data: dict[str, Any] | pd.DataFrame) -> None:
).data

def remove_data(self) -> None:
"""Removes data from an existing Equation."""
"""Removes all data from the Equation."""
self.backend.optimization.equations.remove_data(equation_id=self._model.id)
self._model.data = self.backend.optimization.equations.get(
run_id=self._model.run__id, name=self._model.name
Expand Down
2 changes: 1 addition & 1 deletion ixmp4/core/optimization/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def data(self) -> dict[str, Any]:
return self._model.data

def add(self, data: dict[str, Any] | pd.DataFrame) -> None:
"""Adds data to an existing Parameter."""
"""Adds data to the Parameter."""
self.backend.optimization.parameters.add_data(
parameter_id=self._model.id, data=data
)
Expand Down
4 changes: 1 addition & 3 deletions ixmp4/core/optimization/scalar.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ def unit(self):

@unit.setter
def unit(self, unit: str | Unit):
if isinstance(unit, Unit):
unit = unit
else:
if not isinstance(unit, Unit):
unit_model = self.backend.units.get(unit)
unit = Unit(_backend=self.backend, _model=unit_model)
self._model = self.backend.optimization.scalars.update(
Expand Down
2 changes: 1 addition & 1 deletion ixmp4/core/optimization/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def data(self) -> dict[str, Any]:
return self._model.data

def add(self, data: dict[str, Any] | pd.DataFrame) -> None:
"""Adds data to an existing Table."""
"""Adds data to an the Table."""
self.backend.optimization.tables.add_data(table_id=self._model.id, data=data)
self._model.data = self.backend.optimization.tables.get(
run_id=self._model.run__id, name=self._model.name
Expand Down
4 changes: 2 additions & 2 deletions ixmp4/core/optimization/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def data(self) -> dict[str, Any]:
return self._model.data

def add(self, data: dict[str, Any] | pd.DataFrame) -> None:
"""Adds data to an existing Variable."""
"""Adds data to the Variable."""
self.backend.optimization.variables.add_data(
variable_id=self._model.id, data=data
)
Expand All @@ -41,7 +41,7 @@ def add(self, data: dict[str, Any] | pd.DataFrame) -> None:
).data

def remove_data(self) -> None:
"""Removes data from an existing Variable."""
"""Removes all data from the Variable."""
self.backend.optimization.variables.remove_data(variable_id=self._model.id)
self._model.data = self.backend.optimization.variables.get(
run_id=self._model.run__id, name=self._model.name
Expand Down
62 changes: 62 additions & 0 deletions ixmp4/core/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,68 @@ def tabulate(self, default_only: bool = True, **kwargs) -> pd.DataFrame:
runs["scenario"] = runs["scenario__id"].map(self.backend.scenarios.map())
return runs[["id", "model", "scenario", "version", "is_default"]]

def clone(
self,
run_id: int,
model: str | None = None,
scenario: str | None = None,
keep_solution: bool = True,
) -> Run:
base_run = Run(
_backend=self.backend, _model=self.backend.runs.get_by_id(run_id)
)
run = Run(
_backend=self.backend,
_model=self.backend.runs.create(
model if model else base_run.model.name,
scenario if scenario else base_run.scenario.name,
),
)
datapoints = base_run.iamc.tabulate()
if not datapoints.empty:
run.iamc.add(df=datapoints)
for scalar in base_run.optimization.scalars.list():
run.optimization.scalars.create(
name=scalar.name,
value=scalar.value,
unit=self.backend.units.get(scalar.unit.name).name,
)
for indexset in base_run.optimization.indexsets.list():
run.optimization.indexsets.create(name=indexset.name).add(
elements=indexset.elements
)
for table in base_run.optimization.tables.list():
run.optimization.tables.create(
name=table.name,
constrained_to_indexsets=table.constrained_to_indexsets,
column_names=[column.name for column in table.columns],
).add(data=table.data)
for parameter in base_run.optimization.parameters.list():
run.optimization.parameters.create(
name=parameter.name,
constrained_to_indexsets=parameter.constrained_to_indexsets,
column_names=[column.name for column in parameter.columns],
).add(data=parameter.data)
for equation in base_run.optimization.equations.list():
cloned_equation = run.optimization.equations.create(
name=equation.name,
constrained_to_indexsets=equation.constrained_to_indexsets,
column_names=[column.name for column in equation.columns],
)
if keep_solution:
cloned_equation.add(data=equation.data)
for variable in base_run.optimization.variables.list():
cloned_variable = run.optimization.variables.create(
name=variable.name,
constrained_to_indexsets=variable.constrained_to_indexsets,
column_names=[column.name for column in variable.columns]
if variable.columns
else None,
)
if keep_solution:
cloned_variable.add(data=variable.data)
return run


class RunMetaFacade(BaseFacade, UserDict):
run: RunModel
Expand Down
20 changes: 20 additions & 0 deletions ixmp4/data/abstract/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,26 @@ def get_default_version(
"""
...

def get_by_id(self, id: int) -> Run:
"""Retrieves a Run by its id.

Parameters
----------
id : int
Unique integer id.

Raises
------
:class:`ixmp4.data.abstract.Run.NotFound`.
If the Run with `id` does not exist.

Returns
-------
:class:`ixmp4.data.abstract.Run`:
The retrieved Run.
"""
...

def list(
self,
*,
Expand Down
4 changes: 4 additions & 0 deletions ixmp4/data/api/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ def get(self, model_name: str, scenario_name: str, version: int) -> Run:
is_default=None,
)

def get_by_id(self, id: int) -> Run:
res = self._get_by_id(id)
return Run(**res)

def enumerate(self, **kwargs) -> list[Run] | pd.DataFrame:
return super().enumerate(**kwargs)

Expand Down
11 changes: 6 additions & 5 deletions ixmp4/data/db/optimization/equation/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ def validate_data(self, key, data: dict[str, Any]):
data_to_validate = copy.deepcopy(data)
del data_to_validate["levels"]
del data_to_validate["marginals"]
_ = utils.validate_data(
host=self,
data=data_to_validate,
columns=self.columns,
)
if data_to_validate != {}:
_ = utils.validate_data(
host=self,
data=data_to_validate,
columns=self.columns,
)
return data

__table_args__ = (db.UniqueConstraint("name", "run__id"),)
1 change: 0 additions & 1 deletion ixmp4/data/db/optimization/indexset/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,4 @@ def add_elements(
else:
indexset.elements = indexset.elements + elements

self.session.add(indexset)
self.session.commit()
1 change: 0 additions & 1 deletion ixmp4/data/db/optimization/table/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,5 +159,4 @@ def add_data(self, table_id: int, data: dict[str, Any] | pd.DataFrame) -> None:
orient="list"
)

self.session.add(table)
self.session.commit()
7 changes: 2 additions & 5 deletions ixmp4/data/db/optimization/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,10 @@

def collect_indexsets_to_check(
columns: list["Column"],
) -> dict[str, Any]:
) -> dict[str, list[float | int | str]]:
"""Creates a {key:value} dict from linked Column.names and their
IndexSet.elements."""
collection: dict[str, Any] = {}
for column in columns:
collection[column.name] = column.indexset.elements
return collection
return {column.name: column.indexset.elements for column in columns}


def validate_data(host: base.BaseModel, data: dict[str, Any], columns: list["Column"]):
Expand Down
11 changes: 6 additions & 5 deletions ixmp4/data/db/optimization/variable/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ def validate_data(self, key, data: dict[str, Any]):
data_to_validate = copy.deepcopy(data)
del data_to_validate["levels"]
del data_to_validate["marginals"]
_ = utils.validate_data(
host=self,
data=data_to_validate,
columns=self.columns,
)
if data_to_validate != {}:
_ = utils.validate_data(
host=self,
data=data_to_validate,
columns=self.columns,
)
return data

__table_args__ = (db.UniqueConstraint("name", "run__id"),)
15 changes: 10 additions & 5 deletions ixmp4/data/db/optimization/variable/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,18 @@ def add_data(self, variable_id: int, data: dict[str, Any] | pd.DataFrame) -> Non
f"{', '.join(missing_columns)}!"
)

# TODO Somehow, this got to main without the if index_list checks
# -> Do we need/have a test for add_data() to a scalar variable?
index_list = [column.name for column in variable.columns]
existing_data = pd.DataFrame(variable.data)
if not existing_data.empty:
existing_data.set_index(index_list, inplace=True)
variable.data = (
data.set_index(index_list).combine_first(existing_data).reset_index()
).to_dict(orient="list")
if index_list:
data = data.set_index(index_list)
if not existing_data.empty:
existing_data.set_index(index_list, inplace=True)
data = data.combine_first(existing_data)
if index_list:
data = data.reset_index()
variable.data = data.to_dict(orient="list")

self.session.commit()

Expand Down
Loading
Loading