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 optimization scalar #28

Merged
merged 34 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
25e3aca
Add data-tests for IndexSets
glatterf42 Oct 31, 2023
c38207a
Add get_by_id for Units
glatterf42 Nov 8, 2023
067785c
Update/expand IndexSet tests
glatterf42 Nov 8, 2023
087b7cf
Introduce Optimization.Scalar
glatterf42 Nov 8, 2023
3f3d98d
Correct Indexset UniqueConstraint and streamline core syntax
glatterf42 Nov 9, 2023
b7a4c9c
Update Optimization.Scalar as requested
glatterf42 Nov 9, 2023
46f3b15
Fix forgotten pytest.raises() in scalar tests
glatterf42 Nov 9, 2023
483b710
Refine Optimization.Scalar facade syntax
glatterf42 Nov 10, 2023
603f3de
Clean up leftover comments
glatterf42 Nov 10, 2023
3d07cbc
Fix scalar.value core setter
glatterf42 Nov 10, 2023
ba08b3c
Include DB migration
glatterf42 Nov 14, 2023
02c6c8c
Update/expand IndexSet tests
glatterf42 Nov 8, 2023
077fe76
Introduce Optimization.Scalar
glatterf42 Nov 8, 2023
6ffcd82
Correct Indexset UniqueConstraint and streamline core syntax
glatterf42 Nov 9, 2023
e20ed67
Update Optimization.Scalar as requested
glatterf42 Nov 9, 2023
ab92492
Fix forgotten pytest.raises() in scalar tests
glatterf42 Nov 9, 2023
ba39254
Refine Optimization.Scalar facade syntax
glatterf42 Nov 10, 2023
7711510
Clean up leftover comments
glatterf42 Nov 10, 2023
f3ea529
Fix scalar.value core setter
glatterf42 Nov 10, 2023
08b89c9
Implement suggestions in data layer
glatterf42 Dec 1, 2023
267a2a4
Update tests according to suggestions
glatterf42 Dec 1, 2023
aaa611e
adjust ScalarRepository.update
meksor Dec 5, 2023
e005e10
adjust tests
meksor Dec 5, 2023
bafd216
Allow passing in Unit, unit.name or None for Scalar creation
glatterf42 Dec 6, 2023
4b01f4b
Reduce ruff line-length, enable checking tests/
glatterf42 Dec 6, 2023
3366c3c
Adapt long comment lines to new ruff line length
glatterf42 Dec 6, 2023
5418689
Converge IndexSet and Scalar get and create methods
glatterf42 Dec 6, 2023
8585ec1
Reapply formatting lost during rebase
glatterf42 Jan 11, 2024
673e1dc
Use updated syntax for run creation
glatterf42 Jan 11, 2024
587e827
Use updated syntax for run creation
glatterf42 Jan 11, 2024
a5b63dc
Update black,ruff versions for pre-commit
glatterf42 Jan 11, 2024
a257adb
Delete accidental blank line
glatterf42 Jan 11, 2024
ad5d2ac
Rename unit_or_name to name
glatterf42 Jan 17, 2024
c422573
Remove unused imports
glatterf42 Jan 22, 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 .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ repos:
entry: bash -c "poetry run mypy ."
language: system
- repo: https://github.com/psf/black
rev: 23.10.0
rev: 23.12.1
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.0
rev: v0.1.11
hooks:
- id: ruff
2 changes: 2 additions & 0 deletions ixmp4/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from ixmp4.core import Scenario as Scenario
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.exceptions import InconsistentIamcType as InconsistentIamcType
from ixmp4.core.exceptions import IxmpError as IxmpError
from ixmp4.core.exceptions import NotFound as NotFound
Expand Down
5 changes: 4 additions & 1 deletion ixmp4/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ def login(
user = auth.get_user()
utils.good(f"Successfully authenticated as user '{user.username}'.")
if typer.confirm(
"Are you sure you want to save your credentials in plain-text for future use?"
text=(
"Are you sure you want to save your credentials in plain-text for "
"future use?"
)
):
settings.credentials.set("default", username, password)

Expand Down
19 changes: 13 additions & 6 deletions ixmp4/cli/platforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ def prompt_sqlite_dsn(name: str):
dsn = sqlite.get_dsn(path)
if path.exists():
if typer.confirm(
f"A file at the standard filesystem location for name '{name}' already exists. "
"Do you want to add the existing file to the platform registry?"
f"A file at the standard filesystem location for name '{name}' already "
"exists. Do you want to add the existing file to the platform registry?"
):
return dsn
else:
Expand All @@ -66,7 +66,7 @@ def add(
),
dsn: Optional[str] = typer.Option(
None,
help="A data source name. Can be a http(s) URl or a database connection string.",
help="Data source name. Can be a http(s) URL or a database connection string.",
callback=validate_dsn,
),
):
Expand Down Expand Up @@ -114,7 +114,8 @@ def remove(
raise typer.BadParameter(f"Platform '{name}' does not exist.")

if typer.confirm(
f"Are you sure you want to remove the platform '{platform.name}' with dsn '{platform.dsn}'?"
f"Are you sure you want to remove the platform '{platform.name}' with dsn "
f"'{platform.dsn}'?"
):
if platform.dsn.startswith("sqlite://"):
prompt_sqlite_removal(platform.dsn)
Expand Down Expand Up @@ -155,7 +156,10 @@ def list_():


@app.command(
help="Migrates all database platforms from your local toml file to the newest revision."
help=(
"Migrates all database platforms from your local toml file to the newest "
"revision."
)
)
def upgrade():
for c in settings.toml.list_platforms():
Expand All @@ -167,7 +171,10 @@ def upgrade():


@app.command(
help="Stamps all database platforms from your local toml file with the given revision."
help=(
"Stamps all database platforms from your local toml file with the given "
"revision."
)
)
def stamp(revision: str) -> None:
for c in settings.toml.list_platforms():
Expand Down
1 change: 1 addition & 0 deletions ixmp4/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from .iamc.variable import Variable as Variable
from .model import Model as Model
from .optimization.indexset import IndexSet as IndexSet
from .optimization.scalar import Scalar as Scalar
from .platform import Platform as Platform
from .region import Region as Region
from .run import Run as Run
Expand Down
9 changes: 3 additions & 6 deletions ixmp4/core/optimization/data.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
from functools import partial

from ixmp4.data.abstract import Run

from ..base import BaseFacade
from .indexset import IndexSet as IndexSetModel
from .indexset import IndexSetRepository
from .scalar import ScalarRepository


class OptimizationData(BaseFacade):
"""An optimization data instance, which provides access to optimization data such as
IndexSet, Table, Variable, etc."""

IndexSet: partial[IndexSetModel]

indexsets: IndexSetRepository
scalars: ScalarRepository

def __init__(self, *args, run: Run, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.IndexSet = partial(IndexSetModel, _backend=self.backend, _run=run)
self.indexsets = IndexSetRepository(_backend=self.backend, _run=run)
self.scalars = ScalarRepository(_backend=self.backend, _run=run)
40 changes: 15 additions & 25 deletions ixmp4/core/optimization/indexset.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,9 @@

class IndexSet(BaseModelFacade):
_model: IndexSetModel
_run: Run
NotFound: ClassVar = IndexSetModel.NotFound
NotUnique: ClassVar = IndexSetModel.NotUnique

def __init__(
self,
name: str,
_run: Run,
**kwargs,
) -> None:
super().__init__(**kwargs)
self._run = _run
if getattr(self, "_model", None) is None:
try:
self._model = self.backend.optimization.indexsets.get(
run_id=self._run.id, name=name
)
# TODO: provide logging information if IndexSet already exists
except IndexSetModel.NotFound:
self._model = self.backend.optimization.indexsets.create(
run_id=self._run.id,
name=name,
)

@property
def id(self) -> int:
return self._model.id
Expand All @@ -53,12 +32,12 @@ def add(self, elements: int | list[int | str] | str) -> None:
indexset_id=self._model.id, elements=elements
)
self._model.elements = self.backend.optimization.indexsets.get(
run_id=self._run.id, name=self._model.name
run_id=self._model.run__id, name=self._model.name
).elements

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

@property
def created_at(self) -> datetime | None:
Expand Down Expand Up @@ -101,14 +80,25 @@ def __init__(self, _run: Run, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._run = _run

def create(self, name: str) -> IndexSet:
indexset = self.backend.optimization.indexsets.create(
run_id=self._run.id,
name=name,
)
return IndexSet(_backend=self.backend, _model=indexset)

def get(self, name: str) -> IndexSet:
indexset = self.backend.optimization.indexsets.get(
run_id=self._run.id, name=name
)
return IndexSet(_backend=self.backend, _model=indexset)

def list(self, name: str | None = None) -> Iterable[IndexSet]:
indexsets = self.backend.optimization.indexsets.list(name=name)
return [
IndexSet(
_backend=self.backend,
_model=i,
_run=self._run,
name=i.name,
)
for i in indexsets
]
Expand Down
140 changes: 140 additions & 0 deletions ixmp4/core/optimization/scalar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
from datetime import datetime
from typing import ClassVar, Iterable

import pandas as pd

from ixmp4.core.base import BaseFacade, BaseModelFacade
from ixmp4.core.unit import Unit
from ixmp4.data.abstract import Docs as DocsModel
from ixmp4.data.abstract import Run
from ixmp4.data.abstract import Scalar as ScalarModel


class Scalar(BaseModelFacade):
_model: ScalarModel
NotFound: ClassVar = ScalarModel.NotFound
NotUnique: ClassVar = ScalarModel.NotUnique

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

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

@property
def value(self) -> float:
"""Associated value."""
return self._model.value

@value.setter
def value(self, value: float):
self._model.value = value
self.backend.optimization.scalars.update(
id=self._model.id,
value=self._model.value,
unit_id=self._model.unit.id,
)

@property
def unit(self):
"""Associated unit."""
return self._model.unit

@unit.setter
def unit(self, unit: str | Unit):
if isinstance(unit, Unit):
unit = unit
else:
unit_model = self.backend.units.get(unit)
unit = Unit(_backend=self.backend, _model=unit_model)
self._model = self.backend.optimization.scalars.update(
id=self._model.id,
value=self._model.value,
unit_id=unit.id,
)

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

@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.scalars.docs.get(self.id).description
except DocsModel.NotFound:
return None

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

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

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


class ScalarRepository(BaseFacade):
_run: Run

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

def create(self, name: str, value: float, unit: str | Unit | None = None) -> Scalar:
if isinstance(unit, Unit):
unit_name = unit.name
elif isinstance(unit, str):
unit_name = unit
else:
# TODO: provide logging information about None-units being converted
# to dimensionless
dimensionless_unit = self.backend.units.create(name="dimensionless")
unit_name = dimensionless_unit.name

try:
model = self.backend.optimization.scalars.create(
name=name, value=value, unit_name=unit_name, run_id=self._run.id
)
except Scalar.NotUnique as e:
raise Scalar.NotUnique(
message=f"Scalar '{name}' already exists! Did you mean to call "
"run.optimization.scalars.update()?"
) from e
return Scalar(_backend=self.backend, _model=model)

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

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

def tabulate(self, name: str | None = None) -> pd.DataFrame:
return self.backend.optimization.scalars.tabulate(name=name)
3 changes: 2 additions & 1 deletion ixmp4/core/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ def meta(self, meta):
self._meta._set(meta)

def set_as_default(self):
"""Sets this run as default version for this `model - scenario` combination."""
"""Sets this run as the default version for its `model` + `scenario`
combination."""
self.backend.runs.set_as_default_version(self._model.id)

def unset_as_default(self):
Expand Down
2 changes: 1 addition & 1 deletion ixmp4/data/abstract/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
)
from .meta import MetaValue, RunMetaEntry, RunMetaEntryRepository, StrictMetaValue
from .model import Model, ModelRepository
from .optimization import IndexSet, IndexSetRepository
from .optimization import IndexSet, IndexSetRepository, Scalar, ScalarRepository
from .region import Region, RegionRepository
from .run import Run, RunRepository
from .scenario import Scenario, ScenarioRepository
Expand Down
9 changes: 5 additions & 4 deletions ixmp4/data/abstract/docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Docs(base.BaseModel, Protocol):
dimension: types.Mapped
"The documented object."

# This doesn't work since each dimension has a different self.dimension object as of now
# This doesn't work since each dimension has a different self.dimension object atm
# def __str__(self) -> str:
# return (
# f"<Documentation for {self.dimension} {self.dimension.name}>"
Expand Down Expand Up @@ -74,8 +74,8 @@ def delete(self, dimension_id: int) -> None:
Parameters
----------
dimension_id : int
The unique id of the object whose documentation should be deleted in its dimension's
table.
The unique id of the object whose documentation should be deleted in its
dimension's table.

Raises
------
Expand All @@ -94,7 +94,8 @@ def list(
Parameters
----------
dimension_id : int
The id of an object in any dimension. If supplied only one result will be returned.
The id of an object in any dimension. If supplied only one result will be
returned.

Returns
-------
Expand Down
Loading
Loading