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

[SMS-263] commodity construction #42

Merged
merged 33 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
bdd4704
tests: base oseomosys construction
Lkruitwagen Feb 15, 2024
ddc4fcd
feat: improvement to base abstraction
Lkruitwagen Feb 15, 2024
fbd498c
feat: add validation for time_defintion
Lkruitwagen Feb 15, 2024
687192f
tests: for time_definition
Lkruitwagen Feb 15, 2024
02fd7e3
add test_otoole_roundtrip pytest
edwardxtg Feb 16, 2024
3a14fca
[SMS-239] cleanup noqa test data (#36)
Lkruitwagen Feb 12, 2024
69e21ba
Refactor root_validator to model_validator (#37)
edwardxtg Feb 13, 2024
ab29544
tests: otoole_roundtrip
Lkruitwagen Feb 17, 2024
d0fd45e
feat: fitler pandas=3. dep warning
Lkruitwagen Feb 17, 2024
18373eb
tests: roundtrip otoole timedefn
Lkruitwagen Feb 17, 2024
b42bb07
Merge branch 'main' into sms-241-time-defn-composition
Lkruitwagen Feb 17, 2024
e5e6cae
delint
Lkruitwagen Feb 17, 2024
9e982ca
Merge remote-tracking branch 'origin/main' into refactor/tests_as_pyt…
edwardxtg Feb 20, 2024
050b9d1
otoole_roundtrip as pytest
edwardxtg Feb 20, 2024
b1aaf50
Merge branch 'refactor/tests_as_pytests' into sms-241-time-defn-compo…
Lkruitwagen Feb 20, 2024
d5fbb22
fix: rename otoole-csv paths
Lkruitwagen Feb 20, 2024
bc3764c
fix: make long_name and description optional
Lkruitwagen Feb 20, 2024
4005199
tests: skip full otoole construction for now
Lkruitwagen Feb 20, 2024
cd342f9
tests: test region construction
Lkruitwagen Feb 20, 2024
12657ff
feat: make base osemosys data built from args[0]
Lkruitwagen Feb 20, 2024
8a999c9
fix: rm composable assumptions and targets for now
Lkruitwagen Feb 20, 2024
b0ccbf6
fix: accidental rename
Lkruitwagen Feb 20, 2024
8d07812
fix: accident rename test case
Lkruitwagen Feb 20, 2024
147c1ea
tests: commodity construction and compatability
Lkruitwagen Feb 21, 2024
9557441
feat: begin refactor to /compat
Lkruitwagen Feb 21, 2024
001ef3c
feat: add 'isnumeric' helper util
Lkruitwagen Feb 21, 2024
c477ded
refactor: defaults to initial import, don't import pydatnic schemas
Lkruitwagen Feb 21, 2024
c8a333f
refactor: defaults and compat
Lkruitwagen Feb 21, 2024
85c5407
feat: commodity schema
Lkruitwagen Feb 21, 2024
b547b8b
More descriptive commodity validation error message
edwardxtg Feb 22, 2024
8255c2d
Add descriptions to commodity failing test data
edwardxtg Feb 22, 2024
d4cd850
Merge branch 'main' into sms-263-commodity-construction
Lkruitwagen Feb 23, 2024
b944e92
tests: cleanup
Lkruitwagen Feb 23, 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
91 changes: 16 additions & 75 deletions feo/osemosys/defaults.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import os
from typing import Dict, List, Union
from typing import Dict

import yaml
from pydantic import Field
from pydantic_settings import BaseSettings

from feo.osemosys.schemas.base import OSeMOSYSBase, OSeMOSYSData

class Defaults(BaseSettings):
equals_one_tolerance: float = Field(0.01)


class DefaultsLinopy(BaseSettings):
Expand All @@ -15,83 +15,24 @@ class DefaultsLinopy(BaseSettings):

otoole_name_defaults: Dict = Field(
default={
"AvailabilityFactor": OSeMOSYSData(data=1),
"CapacityFactor": OSeMOSYSData(data=1),
"CapacityToActivityUnit": OSeMOSYSData(data=1),
"DepreciationMethod": OSeMOSYSData(data="straight-line"),
"DiscountRate": OSeMOSYSData(data=0.1),
"ResidualCapacity": OSeMOSYSData(data=0),
"SpecifiedAnnualDemand": OSeMOSYSData(data=0),
"AvailabilityFactor": 1,
"CapacityFactor": 1,
"CapacityToActivityUnit": 1,
"DepreciationMethod": "straight-line",
"DiscountRate": 0.1,
"ResidualCapacity": 0,
"SpecifiedAnnualDemand": 0,
}
)

otoole_name_storage_defaults: Dict = Field(
default={
"DiscountRateStorage": OSeMOSYSData(data=0.1),
"ResidualStorageCapacity": OSeMOSYSData(data=0),
"StorageLevelStart": OSeMOSYSData(data=0),
"DiscountRateStorage": 0.1,
"ResidualStorageCapacity": 0,
"StorageLevelStart": 0,
}
)


defaults = DefaultsLinopy()


class DefaultsOtoole(OSeMOSYSBase):
"""
Class to contain all data from from otoole config yaml, including default values
"""

values: Dict[str, Dict[str, Union[str, int, float, List]]]

@classmethod
def from_otoole_yaml(cls, root_dir):
"""Instantiate a single DefaultsOtoole dict from config.yaml file

Contains information for each parameter on:
indices - column names
type - param
dtype - data type
default - default values
(short_name - optional shortened parameter name)

And information for each set on:
dtype - data type
type - set

Args:
root_dir (str): Path to the root of the otoole csv directory

Returns:
DefaultsOtoole: A single DefaultsOtoole instance
"""

# ###########
# Load Data #
# ###########

# Find otoole config yaml file in root_dir
yaml_files = [
file for file in os.listdir(root_dir) if file.endswith(".yaml") or file.endswith(".yml")
]
if len(yaml_files) == 0:
return None
elif len(yaml_files) > 1:
raise ValueError(">1 otoole config YAML files found in the directory, only 1 required")
yaml_file = yaml_files[0]

# Read in otoole config yaml data
with open(os.path.join(root_dir, yaml_file)) as file:
yaml_data = yaml.safe_load(file)

# #######################
# Define class instance #
# #######################

return cls(
id="DefaultValues",
# TODO
long_name=None,
description=None,
values=yaml_data,
)
defaults = Defaults()
defaults_linopy = DefaultsLinopy()
87 changes: 77 additions & 10 deletions feo/osemosys/schemas/base.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,77 @@
from typing import Dict, Union
from typing import Annotated, Dict, Mapping, Union

from pydantic import BaseModel
import numpy as np
import pandas as pd
from pydantic import AfterValidator, BaseModel, BeforeValidator, model_validator

from feo.osemosys.defaults import defaults
from feo.osemosys.utils import isnumeric

# ####################
# ### BASE CLASSES ###
# ####################


def values_sum_one(values: Mapping) -> bool:
assert sum(values.values()) == 1.0, "Mapping values must sum to 1.0."
return values


def nested_sum_one(values: Mapping) -> bool:
# breakpoint()
if isinstance(values, OSeMOSYSData):
data = values.data
elif isinstance(values, dict):
data = values

if data is None:
return values

# check for single value
if isnumeric(data):
if float(data) == 1.0:
return values

# else use pandas json_normalize to make flat dataframe of nested dictionary
df = pd.json_normalize(data).T
assert (
df.index.str.split(".").str.len().unique().size == 1
), "Nested dictionary must have consistent depth"
cols = [f"L{ii}" for ii in range(1, max(df.index.str.split(".").str.len()) + 1)]
df[cols] = pd.DataFrame(df.index.str.split(".").to_list(), index=df.index)

if not np.allclose(
df.groupby(cols[:-1])[0].sum(),
1.0,
atol=defaults.equals_one_tolerance,
):
raise ValueError("Nested data must sum to 1.0 along the last indexing level.")
return values


MappingSumOne = Annotated[Mapping, AfterValidator(values_sum_one)]
DataVar = float | int | str
IdxVar = str | int


class OSeMOSYSBase(BaseModel):
"""
This base class forces all objects to have a string id;
a long, human-readable name; and a description.
"""

id: str
long_name: str | None
description: str | None
long_name: str
description: str


DataVar = float | int | str
IdxVar = str | int
@model_validator(mode="before")
@classmethod
def backfill_missing(cls, values):
if "long_name" not in values:
values["long_name"] = values["id"]
if "description" not in values:
values["description"] = "No description provided."
return values


class OSeMOSYSData(BaseModel):
Expand Down Expand Up @@ -65,10 +117,11 @@ class OSeMOSYSData(BaseModel):

To define different



"""

def __init__(self, *args, **data):
super().__init__(data=args[0] if args else data)

data: Union[
DataVar, # {data: 6.}
Dict[IdxVar, DataVar],
Expand All @@ -79,7 +132,10 @@ class OSeMOSYSData(BaseModel):
]


class OSeMOSYSDataInt(BaseModel):
OSeMOSYSData_SumOne = Annotated[OSeMOSYSData, BeforeValidator(nested_sum_one)]


class OSeMOSYSData_Int(BaseModel):
data: Union[
int, # {data: 6.}
Dict[IdxVar, int],
Expand All @@ -88,3 +144,14 @@ class OSeMOSYSDataInt(BaseModel):
Dict[IdxVar, Dict[IdxVar, Dict[IdxVar, Dict[IdxVar, int]]]],
Dict[IdxVar, Dict[IdxVar, Dict[IdxVar, Dict[IdxVar, Dict[IdxVar, int]]]]],
]


class OSeMOSYSData_Bool(BaseModel):
data: Union[
bool, # {data: 6.}
Dict[IdxVar, bool],
Dict[IdxVar, Dict[IdxVar, bool]],
Dict[IdxVar, Dict[IdxVar, Dict[IdxVar, bool]]],
Dict[IdxVar, Dict[IdxVar, Dict[IdxVar, Dict[IdxVar, bool]]]],
Dict[IdxVar, Dict[IdxVar, Dict[IdxVar, Dict[IdxVar, Dict[IdxVar, bool]]]]],
]
Loading
Loading