Skip to content

Commit

Permalink
Include optimization parameter (#94)
Browse files Browse the repository at this point in the history
* Include optimization parameter basis (#79)
* Fix references to DB filters in docs
* Streamline naming in tests
* Fix and test parameter list and tabulate for specific runs
* Make indexset-creation a test utility
* Incorporate changes from #110
* Use pandas for updated add_data behaviour
* Raise minimum pandas version to enable add_data upsert
* Generalize UsageError for more optimization items
* Use generalized UsageError for Table
* Use own errors for Parameter
  • Loading branch information
glatterf42 authored Oct 3, 2024
1 parent 353b744 commit 769a198
Show file tree
Hide file tree
Showing 48 changed files with 1,713 additions and 65 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ jobs:
postgres-version: "16"
backend: "sqlite,rest-sqlite"
pandas-version: "2.1.3"
# pandas 2.0.0
# pandas 2.1.0
- python-version: "3.10"
with-pyarrow: true
postgres-version: "16"
backend: "sqlite,rest-sqlite"
pandas-version: "2.0.0"
pandas-version: "2.1.0"

name: py${{ matrix.python-version }} | backend=${{ matrix.backend }} | with-pyarrow=${{ matrix.with-pyarrow }} | pgsql=${{ matrix.postgres-version }} | pandas=${{ matrix.pandas-version }}
runs-on: ubuntu-latest
Expand Down
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
7 changes: 2 additions & 5 deletions ixmp4/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,6 @@ class OptimizationDataValidationError(IxmpError):
http_error_name = "optimization_data_validation_error"


# == Optimization.Table ==


class OptimizationTableUsageError(IxmpError):
class OptimizationItemUsageError(IxmpError):
http_status_code = 422
http_error_name = "optimization_table_usage_error"
http_error_name = "optimization_item_usage_error"
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)
133 changes: 133 additions & 0 deletions ixmp4/core/optimization/parameter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
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(
run_id=self._run.id, 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
4 changes: 2 additions & 2 deletions ixmp4/data/abstract/optimization/indexset.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def list(self, *, name: str | None = None, **kwargs) -> list[IndexSet]:
# TODO: Update kwargs
\*\*kwargs: any
More filter parameters as specified in
`ixmp4.data.db.iamc.variable.filters.VariableFilter`.
`ixmp4.data.db.optimization.indexset.filter.OptimizationIndexSetFilter`.
Returns
-------
Expand All @@ -112,7 +112,7 @@ def tabulate(self, *, name: str | None = None, **kwargs) -> pd.DataFrame:
# TODO: Update kwargs
\*\*kwargs: any
More filter parameters as specified in
`ixmp4.data.db.iamc.variable.filters.VariableFilter`.
`ixmp4.data.db.optimization.indexset.filter.OptimizationIndexSetFilter`.
Returns
-------
Expand Down
Loading

0 comments on commit 769a198

Please sign in to comment.