Skip to content

Commit

Permalink
Include optimization parameter basis (#79)
Browse files Browse the repository at this point in the history
* Make Column generic enough for multiple parents
* Introduce optimization.Parameter
* Add tests for add_data
* Enable remaining parameter tests (#86)
* Enable remaining parameter tests
* Include optimization parameter api layer (#89)
* Bump several dependency versions
* Let api/column handle both tables and parameters
* Make api-layer tests pass
* Include optimization parameter core layer (#90)
* Enable parameter core layer and test it
* Fix things after rebase
* Ensure all intended changes survive the rebase
* Adapt data validation function for parameters
* Allow tests to pass again
  • Loading branch information
glatterf42 committed Aug 22, 2024
1 parent 3f541f5 commit 6a32dcc
Show file tree
Hide file tree
Showing 41 changed files with 1,609 additions and 60 deletions.
8 changes: 4 additions & 4 deletions ixmp4/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# flake8: noqa
import importlib.metadata

from ixmp4.core import IndexSet as IndexSet
from ixmp4.core import Model as Model
from ixmp4.core import Parameter as Parameter
from ixmp4.core import Platform as Platform
from ixmp4.core import Region as Region
from ixmp4.core import Run as Run
from ixmp4.core import Scalar as Scalar
from ixmp4.core import Scenario as Scenario
from ixmp4.core import Table as Table
from ixmp4.core import Unit as Unit
from ixmp4.core import Variable as Variable
from ixmp4.core import IndexSet as IndexSet
from ixmp4.core import Scalar as Scalar
from ixmp4.core import Table as Table
from ixmp4.core.exceptions import InconsistentIamcType as InconsistentIamcType
from ixmp4.core.exceptions import IxmpError as IxmpError
from ixmp4.core.exceptions import NotFound as NotFound
Expand Down
1 change: 1 addition & 0 deletions ixmp4/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .optimization.indexset import IndexSet as IndexSet
from .optimization.scalar import Scalar as Scalar
from .optimization.table import Table as Table
from .optimization.parameter import Parameter as Parameter
from .platform import Platform as Platform
from .region import Region as Region
from .run import Run as Run
Expand Down
3 changes: 3 additions & 0 deletions ixmp4/core/optimization/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from ..base import BaseFacade
from .indexset import IndexSetRepository
from .parameter import ParameterRepository
from .scalar import ScalarRepository
from .table import TableRepository

Expand All @@ -11,11 +12,13 @@ class OptimizationData(BaseFacade):
IndexSet, Table, Variable, etc."""

indexsets: IndexSetRepository
parameters: ParameterRepository
scalars: ScalarRepository
tables: TableRepository

def __init__(self, *args, run: Run, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.indexsets = IndexSetRepository(_backend=self.backend, _run=run)
self.parameters = ParameterRepository(_backend=self.backend, _run=run)
self.scalars = ScalarRepository(_backend=self.backend, _run=run)
self.tables = TableRepository(_backend=self.backend, _run=run)
131 changes: 131 additions & 0 deletions ixmp4/core/optimization/parameter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
from datetime import datetime
from typing import Any, ClassVar, Iterable

import pandas as pd

from ixmp4.core.base import BaseFacade, BaseModelFacade
from ixmp4.data.abstract import Docs as DocsModel
from ixmp4.data.abstract import Parameter as ParameterModel
from ixmp4.data.abstract import Run
from ixmp4.data.abstract.optimization import Column


class Parameter(BaseModelFacade):
_model: ParameterModel
NotFound: ClassVar = ParameterModel.NotFound
NotUnique: ClassVar = ParameterModel.NotUnique

@property
def id(self) -> int:
return self._model.id

@property
def name(self) -> str:
return self._model.name

@property
def run_id(self) -> int:
return self._model.run__id

@property
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."""
self.backend.optimization.parameters.add_data(
parameter_id=self._model.id, data=data
)
self._model.data = self.backend.optimization.parameters.get(
run_id=self._model.run__id, name=self._model.name
).data

@property
def values(self) -> list:
return self._model.data.get("values", [])

@property
def units(self) -> list:
return self._model.data.get("units", [])

@property
def constrained_to_indexsets(self) -> list[str]:
return [column.indexset.name for column in self._model.columns]

@property
def columns(self) -> list[Column]:
return self._model.columns

@property
def created_at(self) -> datetime | None:
return self._model.created_at

@property
def created_by(self) -> str | None:
return self._model.created_by

@property
def docs(self):
try:
return self.backend.optimization.parameters.docs.get(self.id).description
except DocsModel.NotFound:
return None

@docs.setter
def docs(self, description):
if description is None:
self.backend.optimization.parameters.docs.delete(self.id)
else:
self.backend.optimization.parameters.docs.set(self.id, description)

@docs.deleter
def docs(self):
try:
self.backend.optimization.parameters.docs.delete(self.id)
# TODO: silently failing
except DocsModel.NotFound:
return None

def __str__(self) -> str:
return f"<Parameter {self.id} name={self.name}>"


class ParameterRepository(BaseFacade):
_run: Run

def __init__(self, _run: Run, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._run = _run

def create(
self,
name: str,
constrained_to_indexsets: list[str],
column_names: list[str] | None = None,
) -> Parameter:
model = self.backend.optimization.parameters.create(
name=name,
run_id=self._run.id,
constrained_to_indexsets=constrained_to_indexsets,
column_names=column_names,
)
return Parameter(_backend=self.backend, _model=model)

def get(self, name: str) -> Parameter:
model = self.backend.optimization.parameters.get(run_id=self._run.id, name=name)
return Parameter(_backend=self.backend, _model=model)

def list(self, name: str | None = None) -> Iterable[Parameter]:
parameters = self.backend.optimization.parameters.list(
run_id=self._run.id, name=name
)
return [
Parameter(
_backend=self.backend,
_model=i,
)
for i in parameters
]

def tabulate(self, name: str | None = None) -> pd.DataFrame:
return self.backend.optimization.parameters.tabulate(name=name)
3 changes: 2 additions & 1 deletion ixmp4/data/abstract/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
This module holds a shared datastructure and interface for normalization
between the database and api data models and repositories.
"""
# flake8: noqa

from .base import (
BaseMeta,
Expand Down Expand Up @@ -32,6 +31,8 @@
from .optimization import (
IndexSet,
IndexSetRepository,
Parameter,
ParameterRepository,
Scalar,
ScalarRepository,
Table,
Expand Down
1 change: 1 addition & 0 deletions ixmp4/data/abstract/optimization/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .column import Column
from .indexset import IndexSet, IndexSetRepository
from .parameter import Parameter, ParameterRepository
from .scalar import Scalar, ScalarRepository
from .table import Table, TableRepository
4 changes: 3 additions & 1 deletion ixmp4/data/abstract/optimization/column.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ class Column(base.BaseModel, Protocol):
"""Unique name of the Column."""
dtype: types.String
"""Type of the Column's data."""
table__id: types.Integer
table__id: types.Mapped[int | None]
"""Foreign unique integer id of a Table."""
parameter__id: types.Mapped[int | None]
"""Foreign unique integer id of a Parameter."""
indexset: types.Mapped[IndexSet]
"""Associated IndexSet."""
constrained_to_indexset: types.Integer
Expand Down
Loading

0 comments on commit 6a32dcc

Please sign in to comment.