diff --git a/ensysmod/api/endpoints/energy_models.py b/ensysmod/api/endpoints/energy_models.py index e077d89..0f19944 100644 --- a/ensysmod/api/endpoints/energy_models.py +++ b/ensysmod/api/endpoints/energy_models.py @@ -1,10 +1,12 @@ from typing import List, Union from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.responses import FileResponse from sqlalchemy.orm import Session from ensysmod import schemas, model, crud from ensysmod.api import deps +from ensysmod.core.fine_esm import generate_esm_from_model, optimize_esm router = APIRouter() @@ -66,10 +68,10 @@ def update_model(model_id: int, Update a energy model. """ # TODO Check if user has permission for model - model = crud.energy_model.get(db=db, id=model_id) - if model is None: + energy_model = crud.energy_model.get(db=db, id=model_id) + if energy_model is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"EnergyModel {model_id} not found!") - return crud.energy_model.update(db=db, db_obj=model, obj_in=request) + return crud.energy_model.update(db=db, db_obj=energy_model, obj_in=request) @router.delete("/{model_id}", response_model=schemas.EnergyModel) @@ -81,3 +83,49 @@ def remove_model(model_id: int, """ # TODO Check if user has permission for dataset return crud.energy_model.remove(db=db, id=model_id) + + +@router.get("/{model_id}/esm", response_model=schemas.EnergyModel) +def validate_model(model_id: int, + db: Session = Depends(deps.get_db), + current: model.User = Depends(deps.get_current_user)): + """ + Create FINE energy system model from model. + + Might take a while. + And return errors if dataset is not valid. + """ + # TODO Check if user has permission for dataset + energy_model = crud.energy_model.get(db=db, id=model_id) + if energy_model is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"EnergyModel {model_id} not found!") + + # TODO Check if user has permission for dataset + + generate_esm_from_model(db=db, model=energy_model) + return energy_model + + +@router.get("/{model_id}/optimize") +def optimize_model(model_id: int, + db: Session = Depends(deps.get_db), + current: model.User = Depends(deps.get_current_user)): + """ + Create FINE energy system model from model and optimizes it. + + Might take a while. + And return errors if dataset is not valid. + """ + # TODO Check if user has permission for dataset + energy_model = crud.energy_model.get(db=db, id=model_id) + if energy_model is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"EnergyModel {model_id} not found!") + + # TODO Check if user has permission for dataset + + esM = generate_esm_from_model(db=db, model=energy_model) + result_file_path = optimize_esm(esM=esM) + + return FileResponse(result_file_path, + media_type="application/vnd.openxmlformats-officedocument. spreadsheetml.sheet", + filename=f"{energy_model.name}.xlsx") diff --git a/ensysmod/core/fine_esm.py b/ensysmod/core/fine_esm.py new file mode 100644 index 0000000..485674e --- /dev/null +++ b/ensysmod/core/fine_esm.py @@ -0,0 +1,198 @@ +from datetime import datetime +from typing import Any, Dict, List, Union + +import pandas as pd +from FINE import EnergySystemModel, Storage, Sink, Transmission, Conversion, Source, writeOptimizationOutputToExcel +from sqlalchemy.orm import Session + +from ensysmod import crud +from ensysmod.model import EnergyModel, EnergyComponent, EnergySource, EnergySink, EnergyConversion, EnergyStorage, \ + EnergyTransmission, EnergyModelParameter, EnergyModelParameterOperation, EnergyModelParameterAttribute + +# Dictionary that contains internal and fine model parameter names +param_mapper: Dict[str, str] = { + 'yearly_limit': 'yearlyLimit', +} + + +def generate_esm_from_model(db: Session, model: EnergyModel) -> EnergySystemModel: + """ + Generate an ESM from a given EnergyModel. + + :param db: Database session + :param model: EnergyModel + :return: ESM + """ + regions = model.dataset.regions + region_ids = [region.id for region in regions] + commodities = model.dataset.commodities + esm_data = { + "hoursPerTimeStep": model.dataset.hours_per_time_step, + "numberOfTimeSteps": model.dataset.number_of_time_steps, + "costUnit": model.dataset.cost_unit, + "lengthUnit": model.dataset.length_unit, + "locations": set(region.name for region in regions), + "commodities": set(commodity.name for commodity in commodities), + "commodityUnitsDict": {commodity.name: commodity.unit for commodity in model.dataset.commodities}, + } + + esM = EnergySystemModel(verboseLogLevel=0, **esm_data) + + # Add all sources + for source in model.dataset.sources: + add_source(esM, db, source, region_ids, model.parameters) + + # Add all sinks + for sink in model.dataset.sinks: + add_sink(esM, db, sink, region_ids, model.parameters) + + # Add all conversions + for conversion in model.dataset.conversions: + add_conversion(esM, db, conversion, region_ids, model.parameters) + + # Add all storages + for storage in model.dataset.storages: + add_storage(esM, db, storage, region_ids, model.parameters) + + # Add all transmissions + for transmission in model.dataset.transmissions: + add_transmission(esM, db, transmission, region_ids, model.parameters) + + return esM + + +def add_source(esM: EnergySystemModel, db: Session, source: EnergySource, region_ids: List[int], + custom_parameters: List[EnergyModelParameter]) -> None: + esm_source = component_to_dict(db, source.component, region_ids) + esm_source["commodity"] = source.commodity.name + if source.commodity_cost is not None: + esm_source["commodityCost"] = source.commodity_cost + esm_source = override_parameters(esm_source, custom_parameters) + esM.add(Source(esM=esM, **esm_source)) + + +def add_sink(esM: EnergySystemModel, db: Session, sink: EnergySink, region_ids: List[int], + custom_parameters: List[EnergyModelParameter]) -> None: + esm_sink = component_to_dict(db, sink.component, region_ids) + esm_sink["commodity"] = sink.commodity.name + esm_sink = override_parameters(esm_sink, custom_parameters) + esM.add(Sink(esM=esM, **esm_sink)) + + +def add_conversion(esM: EnergySystemModel, db: Session, conversion: EnergyConversion, region_ids: List[int], + custom_parameters: List[EnergyModelParameter]) -> None: + esm_conversion = component_to_dict(db, conversion.component, region_ids) + esm_conversion["physicalUnit"] = conversion.commodity_unit.unit + esm_conversion["commodityConversionFactors"] = {x.commodity.name: x.conversion_factor for x in + conversion.conversion_factors} + esm_conversion = override_parameters(esm_conversion, custom_parameters) + esM.add(Conversion(esM=esM, **esm_conversion)) + + +def add_storage(esM: EnergySystemModel, db: Session, storage: EnergyStorage, region_ids: List[int], + custom_parameters: List[EnergyModelParameter]) -> None: + esm_storage = component_to_dict(db, storage.component, region_ids) + esm_storage["commodity"] = storage.commodity.name + if storage.charge_efficiency is not None: + esm_storage["chargeEfficiency"] = storage.charge_efficiency + if storage.discharge_efficiency is not None: + esm_storage["dischargeEfficiency"] = storage.discharge_efficiency + if storage.self_discharge is not None: + esm_storage["selfDischarge"] = storage.self_discharge + if storage.cyclic_lifetime is not None: + esm_storage["cyclicLifetime"] = storage.cyclic_lifetime + if storage.charge_rate is not None: + esm_storage["chargeRate"] = storage.charge_rate + if storage.discharge_rate is not None: + esm_storage["dischargeRate"] = storage.discharge_rate + if storage.state_of_charge_min is not None: + esm_storage["stateOfChargeMin"] = storage.state_of_charge_min + if storage.state_of_charge_max is not None: + esm_storage["stateOfChargeMax"] = storage.state_of_charge_max + esm_storage = override_parameters(esm_storage, custom_parameters) + esM.add(Storage(esM=esM, **esm_storage)) + + +def add_transmission(esM: EnergySystemModel, db: Session, transmission: EnergyTransmission, + region_ids: List[int], custom_parameters: List[EnergyModelParameter]) -> None: + esm_transmission = component_to_dict(db, transmission.component, region_ids) + esm_transmission["commodity"] = transmission.commodity.name + esm_transmission["distances"] = crud.energy_transmission_distance.get_dataframe(db, transmission.ref_component, + region_ids=region_ids) + esm_transmission = override_parameters(esm_transmission, custom_parameters) + esM.add(Transmission(esM=esM, **esm_transmission)) + + +def component_to_dict(db: Session, component: EnergyComponent, region_ids: List[int]) -> Dict[str, Any]: + component_data = { + "name": component.name, + "hasCapacityVariable": component.capacity_variable, + "capacityVariableDomain": component.capacity_variable_domain.value.lower(), + "capacityPerPlantUnit": component.capacity_per_plant_unit, + "investPerCapacity": component.invest_per_capacity, + "opexPerCapacity": component.opex_per_capacity, + "interestRate": component.interest_rate, + "economicLifetime": component.economic_lifetime, + } + if component.shared_potential_id is not None: + component_data["sharedPotentialID"] = component.shared_potential_id + + if crud.capacity_max.has_data(db, component_id=component.id, region_ids=region_ids): + component_data["capacityMax"] = df_or_s(crud.capacity_max.get_dataframe(db, component_id=component.id, + region_ids=region_ids)) + + if crud.capacity_fix.has_data(db, component_id=component.id, region_ids=region_ids): + component_data["capacityFix"] = df_or_s(crud.capacity_fix.get_dataframe(db, component_id=component.id, + region_ids=region_ids)) + + if crud.operation_rate_max.has_data(db, component_id=component.id, region_ids=region_ids): + component_data["operationRateMax"] = df_or_s(crud.operation_rate_max.get_dataframe(db, + component_id=component.id, + region_ids=region_ids)) + + if crud.operation_rate_fix.has_data(db, component_id=component.id, region_ids=region_ids): + component_data["operationRateFix"] = df_or_s(crud.operation_rate_fix.get_dataframe(db, + component_id=component.id, + region_ids=region_ids)) + + return component_data + + +def override_parameters(component_dict: Dict, custom_parameters: List[EnergyModelParameter]) -> Dict: + for custom_parameter in custom_parameters: + if custom_parameter.component.name != component_dict["name"]: + continue + attribute_name = param_mapper[custom_parameter.attribute.name] + if custom_parameter.operation == EnergyModelParameterOperation.add: + component_dict[attribute_name] += custom_parameter.value + elif custom_parameter.operation == EnergyModelParameterOperation.multiply: + component_dict[attribute_name] *= custom_parameter.value + elif custom_parameter.operation == EnergyModelParameterOperation.set: + component_dict[attribute_name] = custom_parameter.value + else: + raise ValueError("Unknown operation: {}".format(custom_parameter.operation)) + if custom_parameter.attribute == EnergyModelParameterAttribute.yearly_limit: + component_dict["commodityLimitID"] = "CO2" # TODO: should be configurable + return component_dict + + +def optimize_esm(esM: EnergySystemModel): + """ + Optimize the energy system model. + """ + esM.cluster(numberOfTypicalPeriods=7) + esM.optimize(timeSeriesAggregation=True, optimizationSpecs='OptimalityTol=1e-3 method=2 cuts=0', solver='gurobi') + + time_str = datetime.now().strftime("%Y%m%d%H%M%S") + result_file_path = f"./tmp/result-{time_str}" + writeOptimizationOutputToExcel(esM=esM, + outputFileName=result_file_path, + optSumOutputLevel=2, optValOutputLevel=1) + return result_file_path + ".xlsx" + + +def df_or_s(dataframe: pd.DataFrame) -> Union[pd.DataFrame, pd.Series]: + if dataframe.shape[0] == 1: + return dataframe.squeeze(axis=0) + else: + return dataframe diff --git a/ensysmod/crud/base_depends_timeseries.py b/ensysmod/crud/base_depends_timeseries.py index a44f26c..642e739 100644 --- a/ensysmod/crud/base_depends_timeseries.py +++ b/ensysmod/crud/base_depends_timeseries.py @@ -54,16 +54,24 @@ def create(self, db: Session, *, obj_in: CreateSchemaType) -> ModelType: return super().create(db=db, obj_in=obj_in_dict) - def get_dataframe(self, db: Session, *, component_id: int, region_ids: List[id]) -> pd.DataFrame: - """ - Get dataframe for component and multiple regions. - """ - data = db.query(self.model) \ + def get_multi_by_regions(self, db: Session, *, + component_id: int, region_ids: List[id]) -> Optional[List[ModelType]]: + return db.query(self.model) \ .filter(self.model.ref_component == component_id) \ .filter(self.model.ref_region.in_(region_ids)) \ .filter(or_(self.model.ref_region_to.is_(None), self.model.ref_region_to.in_(region_ids))) \ .all() + def has_data(self, db: Session, *, component_id: int, region_ids: List[id]) -> bool: + result = self.get_multi_by_regions(db=db, component_id=component_id, region_ids=region_ids) + return result is not None and len(result) > 0 + + def get_dataframe(self, db: Session, *, component_id: int, region_ids: List[id]) -> pd.DataFrame: + """ + Get dataframe for component and multiple regions. + """ + data = self.get_multi_by_regions(db=db, component_id=component_id, region_ids=region_ids) + matrix_mode = any(d.ref_region_to is not None for d in data) if matrix_mode and any(d.ref_region_to is None for d in data): diff --git a/ensysmod/crud/energy_sink.py b/ensysmod/crud/energy_sink.py index 7eddd44..b67dc75 100644 --- a/ensysmod/crud/energy_sink.py +++ b/ensysmod/crud/energy_sink.py @@ -1,7 +1,7 @@ from sqlalchemy.orm import Session -from ensysmod import crud from ensysmod.crud.base_depends_component import CRUDBaseDependsComponent +from ensysmod.crud.energy_commodity import energy_commodity from ensysmod.model import EnergySink from ensysmod.schemas import EnergySinkCreate, EnergySinkUpdate @@ -13,8 +13,8 @@ class CRUDEnergySink(CRUDBaseDependsComponent[EnergySink, EnergySinkCreate, Ener """ def create(self, db: Session, *, obj_in: EnergySinkCreate) -> EnergySink: - commodity = crud.energy_commodity.get_by_dataset_and_name(db, name=obj_in.commodity, - dataset_id=obj_in.ref_dataset) + commodity = energy_commodity.get_by_dataset_and_name(db, name=obj_in.commodity, + dataset_id=obj_in.ref_dataset) obj_in_dict = obj_in.dict() obj_in_dict['ref_commodity'] = commodity.id return super().create(db=db, obj_in=obj_in_dict) diff --git a/ensysmod/crud/energy_source.py b/ensysmod/crud/energy_source.py index 6187127..5446330 100644 --- a/ensysmod/crud/energy_source.py +++ b/ensysmod/crud/energy_source.py @@ -1,7 +1,7 @@ from sqlalchemy.orm import Session -from ensysmod import crud from ensysmod.crud.base_depends_component import CRUDBaseDependsComponent +from ensysmod.crud.energy_commodity import energy_commodity from ensysmod.model import EnergySource from ensysmod.schemas import EnergySourceCreate, EnergySourceUpdate @@ -13,8 +13,8 @@ class CRUDEnergySource(CRUDBaseDependsComponent[EnergySource, EnergySourceCreate """ def create(self, db: Session, *, obj_in: EnergySourceCreate) -> EnergySource: - commodity = crud.energy_commodity.get_by_dataset_and_name(db, name=obj_in.commodity, - dataset_id=obj_in.ref_dataset) + commodity = energy_commodity.get_by_dataset_and_name(db, name=obj_in.commodity, + dataset_id=obj_in.ref_dataset) obj_in_dict = obj_in.dict() obj_in_dict['ref_commodity'] = commodity.id return super().create(db=db, obj_in=obj_in_dict) diff --git a/ensysmod/model/dataset.py b/ensysmod/model/dataset.py index a8d7162..2f3a77b 100644 --- a/ensysmod/model/dataset.py +++ b/ensysmod/model/dataset.py @@ -1,6 +1,17 @@ -from sqlalchemy import Column, Integer, String +from typing import List + +from sqlalchemy import Column, Integer, String, and_ +from sqlalchemy.orm import relationship, Session from ensysmod.database.base_class import Base +from ensysmod.model.energy_commodity import EnergyCommodity +from ensysmod.model.energy_component import EnergyComponent +from ensysmod.model.energy_conversion import EnergyConversion +from ensysmod.model.energy_sink import EnergySink +from ensysmod.model.energy_source import EnergySource +from ensysmod.model.energy_storage import EnergyStorage +from ensysmod.model.energy_transmission import EnergyTransmission +from ensysmod.model.region import Region class Dataset(Base): @@ -18,3 +29,46 @@ class Dataset(Base): number_of_time_steps = Column(Integer, nullable=False, default=8760) cost_unit = Column(String, nullable=False, default='1e9 Euro') length_unit = Column(String, nullable=False, default='km') + + regions: List[Region] = relationship("Region", back_populates="dataset") + commodities: List[EnergyCommodity] = relationship("EnergyCommodity", back_populates="dataset") + + @property + def sources(self) -> List[EnergySource]: + sess = Session.object_session(self) + return sess.query(EnergySource).join(EnergyComponent) \ + .filter(and_(EnergyComponent.ref_dataset == self.id, + EnergyComponent.id == EnergySource.ref_component)) \ + .all() + + @property + def sinks(self) -> List[EnergySink]: + sess = Session.object_session(self) + return sess.query(EnergySink).join(EnergyComponent) \ + .filter(and_(EnergyComponent.ref_dataset == self.id, + EnergyComponent.id == EnergySink.ref_component)) \ + .all() + + @property + def conversions(self) -> List[EnergyConversion]: + sess = Session.object_session(self) + return sess.query(EnergyConversion).join(EnergyComponent) \ + .filter(and_(EnergyComponent.ref_dataset == self.id, + EnergyComponent.id == EnergyConversion.ref_component)) \ + .all() + + @property + def storages(self) -> List[EnergyStorage]: + sess = Session.object_session(self) + return sess.query(EnergyStorage).join(EnergyComponent) \ + .filter(and_(EnergyComponent.ref_dataset == self.id, + EnergyComponent.id == EnergyStorage.ref_component)) \ + .all() + + @property + def transmissions(self) -> List[EnergyTransmission]: + sess = Session.object_session(self) + return sess.query(EnergyTransmission).join(EnergyComponent) \ + .filter(and_(EnergyComponent.ref_dataset == self.id, + EnergyComponent.id == EnergyTransmission.ref_component)) \ + .all() diff --git a/ensysmod/model/energy_component.py b/ensysmod/model/energy_component.py index e926ef4..b80437a 100644 --- a/ensysmod/model/energy_component.py +++ b/ensysmod/model/energy_component.py @@ -1,6 +1,6 @@ import enum -from sqlalchemy import Column, Integer, String, Boolean, DECIMAL, ForeignKey, Enum, UniqueConstraint +from sqlalchemy import Column, Integer, String, Boolean, ForeignKey, Enum, UniqueConstraint, Float from ensysmod.database.base_class import Base @@ -42,11 +42,11 @@ class EnergyComponent(Base): capacity_variable_domain = Column(Enum(CapacityVariableDomain), default=CapacityVariableDomain.CONTINUOUS, nullable=False) - capacity_per_plant_unit = Column(DECIMAL, nullable=True, default=1.0) + capacity_per_plant_unit = Column(Integer, nullable=True, default=1.0) - invest_per_capacity = Column(DECIMAL, nullable=False, default=0.0) - opex_per_capacity = Column(DECIMAL, nullable=False, default=0.0) - interest_rate = Column(DECIMAL, nullable=False, default=0.08) + invest_per_capacity = Column(Float, nullable=False, default=0.0) + opex_per_capacity = Column(Float, nullable=False, default=0.0) + interest_rate = Column(Float, nullable=False, default=0.08) economic_lifetime = Column(Integer, nullable=False, default=10) shared_potential_id = Column(String, nullable=True) diff --git a/ensysmod/model/energy_conversion.py b/ensysmod/model/energy_conversion.py index dfa4291..37692f2 100644 --- a/ensysmod/model/energy_conversion.py +++ b/ensysmod/model/energy_conversion.py @@ -1,7 +1,10 @@ +from typing import List + from sqlalchemy import Column, Integer, ForeignKey from sqlalchemy.orm import relationship from ensysmod.database.base_class import Base +from ensysmod.model.energy_conversion_factor import EnergyConversionFactor class EnergyConversion(Base): @@ -18,4 +21,5 @@ class EnergyConversion(Base): # Relationships component = relationship("EnergyComponent") commodity_unit = relationship("EnergyCommodity", back_populates="energy_conversions") - conversion_factors = relationship("EnergyConversionFactor", back_populates="conversion") + conversion_factors: List[EnergyConversionFactor] = relationship("EnergyConversionFactor", + back_populates="conversion") diff --git a/ensysmod/model/energy_conversion_factor.py b/ensysmod/model/energy_conversion_factor.py index 3049ff7..b6c82bb 100644 --- a/ensysmod/model/energy_conversion_factor.py +++ b/ensysmod/model/energy_conversion_factor.py @@ -1,7 +1,8 @@ -from sqlalchemy import Column, Integer, ForeignKey, DECIMAL, UniqueConstraint +from sqlalchemy import Column, Integer, ForeignKey, UniqueConstraint, Float from sqlalchemy.orm import relationship from ensysmod.database.base_class import Base +from ensysmod.model.energy_commodity import EnergyCommodity class EnergyConversionFactor(Base): @@ -13,11 +14,11 @@ class EnergyConversionFactor(Base): id = Column(Integer, primary_key=True, index=True) ref_component = Column(Integer, ForeignKey("energy_conversion.ref_component"), index=True, nullable=False) ref_commodity = Column(Integer, ForeignKey("energy_commodity.id"), index=True, nullable=False) - conversion_factor = Column(DECIMAL, nullable=False) + conversion_factor = Column(Float, nullable=False) # Relationships conversion = relationship("EnergyConversion", back_populates="conversion_factors") - commodity = relationship("EnergyCommodity") + commodity: EnergyCommodity = relationship("EnergyCommodity") # table constraints __table_args__ = ( diff --git a/ensysmod/model/energy_model.py b/ensysmod/model/energy_model.py index b727729..2ce18f2 100644 --- a/ensysmod/model/energy_model.py +++ b/ensysmod/model/energy_model.py @@ -2,6 +2,7 @@ from sqlalchemy.orm import relationship from ensysmod.database.base_class import Base +from ensysmod.model import Dataset class EnergyModel(Base): @@ -11,7 +12,7 @@ class EnergyModel(Base): description = Column(String, nullable=True) # relationships - dataset = relationship("Dataset") + dataset: Dataset = relationship("Dataset") parameters = relationship("EnergyModelParameter", back_populates="model") # table constraints diff --git a/ensysmod/model/energy_source.py b/ensysmod/model/energy_source.py index 728e923..6ffd073 100644 --- a/ensysmod/model/energy_source.py +++ b/ensysmod/model/energy_source.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, Integer, ForeignKey, DECIMAL +from sqlalchemy import Column, Integer, ForeignKey, Float from sqlalchemy.orm import relationship from ensysmod.database.base_class import Base @@ -13,7 +13,7 @@ class EnergySource(Base): ref_component = Column(Integer, ForeignKey("energy_component.id"), index=True, nullable=False, primary_key=True) ref_commodity = Column(Integer, ForeignKey("energy_commodity.id"), index=True, nullable=False) - commodity_cost = Column(DECIMAL, nullable=True) + commodity_cost = Column(Float, nullable=True) # Relationships component = relationship("EnergyComponent") diff --git a/ensysmod/model/energy_storage.py b/ensysmod/model/energy_storage.py index 577e5cb..3bf4423 100644 --- a/ensysmod/model/energy_storage.py +++ b/ensysmod/model/energy_storage.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, Integer, ForeignKey, DECIMAL +from sqlalchemy import Column, Integer, ForeignKey, Float from sqlalchemy.orm import relationship from ensysmod.database.base_class import Base @@ -13,14 +13,14 @@ class EnergyStorage(Base): ref_component = Column(Integer, ForeignKey("energy_component.id"), index=True, nullable=False, primary_key=True) ref_commodity = Column(Integer, ForeignKey("energy_commodity.id"), index=True, nullable=False) - charge_efficiency = Column(DECIMAL, nullable=True) - discharge_efficiency = Column(DECIMAL, nullable=True) - self_discharge = Column(DECIMAL, nullable=True) + charge_efficiency = Column(Float, nullable=True) + discharge_efficiency = Column(Float, nullable=True) + self_discharge = Column(Float, nullable=True) cyclic_lifetime = Column(Integer, nullable=True) - charge_rate = Column(DECIMAL, nullable=True) - discharge_rate = Column(DECIMAL, nullable=True) - state_of_charge_min = Column(DECIMAL, nullable=True) - state_of_charge_max = Column(DECIMAL, nullable=True) + charge_rate = Column(Float, nullable=True) + discharge_rate = Column(Float, nullable=True) + state_of_charge_min = Column(Float, nullable=True) + state_of_charge_max = Column(Float, nullable=True) # Relationships component = relationship("EnergyComponent") diff --git a/ensysmod/model/energy_transmission.py b/ensysmod/model/energy_transmission.py index 8336da9..563862c 100644 --- a/ensysmod/model/energy_transmission.py +++ b/ensysmod/model/energy_transmission.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, Integer, ForeignKey, DECIMAL +from sqlalchemy import Column, Integer, ForeignKey, Float from sqlalchemy.orm import relationship from ensysmod.database.base_class import Base @@ -13,7 +13,7 @@ class EnergyTransmission(Base): ref_component = Column(Integer, ForeignKey("energy_component.id"), index=True, nullable=False, primary_key=True) ref_commodity = Column(Integer, ForeignKey("energy_commodity.id"), index=True, nullable=False) - loss_per_unit = Column(DECIMAL, nullable=True) + loss_per_unit = Column(Float, nullable=True) # Relationships component = relationship("EnergyComponent") diff --git a/ensysmod/model/energy_transmission_distance.py b/ensysmod/model/energy_transmission_distance.py index 49e3c72..d3a7fb5 100644 --- a/ensysmod/model/energy_transmission_distance.py +++ b/ensysmod/model/energy_transmission_distance.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, Integer, ForeignKey, DECIMAL, UniqueConstraint +from sqlalchemy import Column, Integer, ForeignKey, UniqueConstraint, Float from sqlalchemy.orm import relationship from ensysmod.database.base_class import Base @@ -15,7 +15,7 @@ class EnergyTransmissionDistance(Base): ref_region_from = Column(Integer, ForeignKey("region.id"), index=True, nullable=False) ref_region_to = Column(Integer, ForeignKey("region.id"), index=True, nullable=False) - distance = Column(DECIMAL, nullable=True) + distance = Column(Float, nullable=True) # Relationships transmission = relationship("EnergyTransmission", back_populates="distances") diff --git a/examples/data/dataset-1/conversions/conversion-1/conversion.json b/examples/data/dataset-1/conversions/conversion-1/conversion.json index aa27336..cf89491 100644 --- a/examples/data/dataset-1/conversions/conversion-1/conversion.json +++ b/examples/data/dataset-1/conversions/conversion-1/conversion.json @@ -1,15 +1,24 @@ { "name": "GuD-Turbine (Methan)", - "description": "Stellt Gas-und Dampfturbinen für Methan dar", + "description": "Stellt Gas-und Dampfturbinen für Methan dar", "commodity_unit": "Methan", + "capacity_variable": true, "conversion_factors": [ { "commodity": "Elektrizität", - "conversion_factor": 0.6 + "conversion_factor": 1 }, { "commodity": "Methan", - "conversion_factor": 1 + "conversion_factor": -1.66666667 + }, + { + "commodity": "CO2", + "conversion_factor": 0.000335 } - ] + ], + "invest_per_capacity":0.65, + "opex_per_capacity":0.021, + "interest_rate":0.08, + "economic_lifetime":33 } \ No newline at end of file diff --git a/examples/data/dataset-1/conversions/conversion-2/conversion.json b/examples/data/dataset-1/conversions/conversion-2/conversion.json index c281824..d1043dc 100644 --- a/examples/data/dataset-1/conversions/conversion-2/conversion.json +++ b/examples/data/dataset-1/conversions/conversion-2/conversion.json @@ -1,7 +1,8 @@ { "name": "GuD-Turbine (Biogas)", - "description": "Stellt Gas-und Dampfturbinen für Biogas dar", + "description": "Stellt Gas-und Dampfturbinen für Biogas dar", "commodity_unit": "Biogas", + "capacity_variable": true, "conversion_factors": [ { "commodity": "Elektrizität", diff --git a/examples/data/dataset-1/conversions/conversion-3/conversion.json b/examples/data/dataset-1/conversions/conversion-3/conversion.json index 38c26af..af6cc91 100644 --- a/examples/data/dataset-1/conversions/conversion-3/conversion.json +++ b/examples/data/dataset-1/conversions/conversion-3/conversion.json @@ -1,7 +1,8 @@ { "name": "GuD-Turbine (Wasserstoff)", - "description": "Stellt Gas-und Dampfturbinen für Wasserstoff dar", + "description": "Stellt Gas-und Dampfturbinen für Wasserstoff dar", "commodity_unit": "Wasserstoff", + "capacity_variable": true, "conversion_factors": [ { "commodity": "Elektrizität", diff --git a/examples/data/dataset-1/conversions/conversion-4/conversion.json b/examples/data/dataset-1/conversions/conversion-4/conversion.json index af269c3..4106681 100644 --- a/examples/data/dataset-1/conversions/conversion-4/conversion.json +++ b/examples/data/dataset-1/conversions/conversion-4/conversion.json @@ -1,7 +1,8 @@ { "name": "Elektrolyseure", - "description": "Stellt Elektrolyseure dar", + "description": "Stellt Elektrolyseure dar", "commodity_unit": "Wasserstoff", + "capacity_variable": true, "conversion_factors": [ { "commodity": "Elektrizität", diff --git a/examples/data/dataset-1/conversions/conversion-5/conversion.json b/examples/data/dataset-1/conversions/conversion-5/conversion.json index b7b11eb..70a91b2 100644 --- a/examples/data/dataset-1/conversions/conversion-5/conversion.json +++ b/examples/data/dataset-1/conversions/conversion-5/conversion.json @@ -2,7 +2,8 @@ "name": "rSOEC", "description": "Stellt rSOEC dar", "commodity_unit": "Wasserstoff", - "linked_conversion_capacity_id":"rSOC", + "capacity_variable": true, + "linked_conversion_capacity_id": "rSOC", "conversion_factors": [ { "commodity": "Elektrizität", diff --git a/examples/data/dataset-1/conversions/conversion-6/conversion.json b/examples/data/dataset-1/conversions/conversion-6/conversion.json index 346b5ca..dc41fd6 100644 --- a/examples/data/dataset-1/conversions/conversion-6/conversion.json +++ b/examples/data/dataset-1/conversions/conversion-6/conversion.json @@ -1,8 +1,9 @@ { "name": "rSOFC", - "description": "Stellt rSOFC dar", + "description": "Stellt rSOFC dar", "commodity_unit": "Wasserstoff", - "linked_conversion_capacity_id":"rSOC", + "capacity_variable": true, + "linked_conversion_capacity_id": "rSOC", "conversion_factors": [ { "commodity": "Elektrizität", diff --git a/examples/data/dataset-1/sources/source-1/source.json b/examples/data/dataset-1/sources/source-1/source.json index 1fe585d..ec11f18 100644 --- a/examples/data/dataset-1/sources/source-1/source.json +++ b/examples/data/dataset-1/sources/source-1/source.json @@ -1,9 +1,10 @@ { - "name": "Wind Onshore", - "description": "Stellt die Wind Onshore-Anlagen dar", - "commodity": "Elektrizität", - "invest_per_capacity": 1.1, - "opex_per_capacity":0.022, - "interest_rate": 0.08, - "economic_lifetime": 20 + "name": "Wind Onshore", + "description": "Stellt die Wind Onshore-Anlagen dar", + "commodity": "Elektrizität", + "capacity_variable": true, + "invest_per_capacity": 1.1, + "opex_per_capacity": 0.022, + "interest_rate": 0.08, + "economic_lifetime": 20 } \ No newline at end of file diff --git a/examples/data/dataset-1/sources/source-2/source.json b/examples/data/dataset-1/sources/source-2/source.json index 4ef531a..faf4f28 100644 --- a/examples/data/dataset-1/sources/source-2/source.json +++ b/examples/data/dataset-1/sources/source-2/source.json @@ -1,9 +1,10 @@ { - "name": "Wind Offshore", - "description": "Stellt die Wind Offshore-Anlagen dar", - "commodity": "Elektrizität", - "invest_per_capacity":2.3, - "opex_per_capacity":0.046, - "interest_rate":0.08, - "economic_lifetime":20 + "name": "Wind Offshore", + "description": "Stellt die Wind Offshore-Anlagen dar", + "commodity": "Elektrizität", + "capacity_variable": true, + "invest_per_capacity": 2.3, + "opex_per_capacity": 0.046, + "interest_rate": 0.08, + "economic_lifetime": 20 } \ No newline at end of file diff --git a/examples/data/dataset-1/sources/source-3/source.json b/examples/data/dataset-1/sources/source-3/source.json index 75fd4de..f1631b4 100644 --- a/examples/data/dataset-1/sources/source-3/source.json +++ b/examples/data/dataset-1/sources/source-3/source.json @@ -1,9 +1,10 @@ { - "name": "PV", - "description": "Stellt die PV-Anlagen dar", - "commodity": "Elektrizität", - "invest_per_capacity":0.65, - "opex_per_capacity":0.013, - "interest_rate":0.08, - "economic_lifetime":25 + "name": "PV", + "description": "Stellt die PV-Anlagen dar", + "commodity": "Elektrizität", + "capacity_variable": true, + "invest_per_capacity": 0.65, + "opex_per_capacity": 0.013, + "interest_rate": 0.08, + "economic_lifetime": 25 } \ No newline at end of file diff --git a/examples/data/dataset-1/sources/source-4/source.json b/examples/data/dataset-1/sources/source-4/source.json index 0b110cc..95024a8 100644 --- a/examples/data/dataset-1/sources/source-4/source.json +++ b/examples/data/dataset-1/sources/source-4/source.json @@ -1,7 +1,8 @@ { - "name": "Run-of-river", - "description": "Stellt die Laufwasser-Kraftwerke dar", - "commodity": "Elektrizität", - "invest_per_capacity":0, - "opex_per_capacity":0.208 + "name": "Run-of-river", + "description": "Stellt die Laufwasser-Kraftwerke dar", + "commodity": "Elektrizität", + "capacity_variable": true, + "invest_per_capacity": 0, + "opex_per_capacity": 0.208 } \ No newline at end of file diff --git a/examples/data/dataset-1/sources/source-5/source.json b/examples/data/dataset-1/sources/source-5/source.json index cfea70a..1aae6b6 100644 --- a/examples/data/dataset-1/sources/source-5/source.json +++ b/examples/data/dataset-1/sources/source-5/source.json @@ -1,6 +1,7 @@ { "name": "Natural Gas Importe", "description": "Stellt die Natural Gas-Importe dar", - "commodity": "Methan", + "commodity": "Methan", + "capacity_variable": false, "commodity_cost":0.0000331 } \ No newline at end of file diff --git a/examples/data/dataset-1/sources/source-6/source.json b/examples/data/dataset-1/sources/source-6/source.json index 0568dca..c4539b4 100644 --- a/examples/data/dataset-1/sources/source-6/source.json +++ b/examples/data/dataset-1/sources/source-6/source.json @@ -1,6 +1,7 @@ { - "name": "Biogas Importe", - "description": "Stellt die Biogas-Importe dar", - "commodity": "Biogas", - "commodity_cost":0.00005409 + "name": "Biogas Importe", + "description": "Stellt die Biogas-Importe dar", + "commodity": "Biogas", + "capacity_variable": false, + "commodity_cost": 0.00005409 } \ No newline at end of file diff --git a/examples/data/dataset-1/storages/storage-1/storage.json b/examples/data/dataset-1/storages/storage-1/storage.json index df05014..f18436e 100644 --- a/examples/data/dataset-1/storages/storage-1/storage.json +++ b/examples/data/dataset-1/storages/storage-1/storage.json @@ -1,15 +1,16 @@ { "name": "Li-ion Batterien", - "description": "Stellt Batterien dar", + "description": "Stellt Batterien dar", "commodity": "Elektrizität", - "charge_efficiency":0.95, - "cyclic_lifetime":10000, - "discharge_efficiency":0.95, - "self_discharge":0.0000423036, - "charge_rate":1, - "discharge_rate":1, - "invest_per_capacity":0.151, - "opex_per_capacity":0.002, - "interest_rate":0.08, - "economic_lifetime":22 + "capacity_variable": true, + "charge_efficiency": 0.95, + "cyclic_lifetime": 10000, + "discharge_efficiency": 0.95, + "self_discharge": 0.0000423036, + "charge_rate": 1, + "discharge_rate": 1, + "invest_per_capacity": 0.151, + "opex_per_capacity": 0.002, + "interest_rate": 0.08, + "economic_lifetime": 22 } \ No newline at end of file diff --git a/examples/data/dataset-1/storages/storage-2/storage.json b/examples/data/dataset-1/storages/storage-2/storage.json index c58431c..5489acc 100644 --- a/examples/data/dataset-1/storages/storage-2/storage.json +++ b/examples/data/dataset-1/storages/storage-2/storage.json @@ -2,6 +2,7 @@ "name": "Salzkavernen (Wasserstoff)", "description": "Stellt Salzkavernen (Wasserstoff) dar", "commodity": "Wasserstoff", + "capacity_variable": true, "capacity_variable_domain": "CONTINUOUS", "capacity_per_plant_unit": 133, "charge_rate": 0.007124, diff --git a/examples/data/dataset-1/storages/storage-3/storage.json b/examples/data/dataset-1/storages/storage-3/storage.json index 685318a..1958997 100644 --- a/examples/data/dataset-1/storages/storage-3/storage.json +++ b/examples/data/dataset-1/storages/storage-3/storage.json @@ -2,6 +2,7 @@ "name": "Salzkavernen (Bio Gas)", "description": "Stellt Salzkavernen (Bio Gas) dar", "commodity": "Biogas", + "capacity_variable": true, "capacity_variable_domain": "CONTINUOUS", "capacity_per_plant_unit": 443, "charge_rate": 0.007124, diff --git a/examples/data/dataset-1/storages/storage-4/storage.json b/examples/data/dataset-1/storages/storage-4/storage.json index 7fd41e2..1be0409 100644 --- a/examples/data/dataset-1/storages/storage-4/storage.json +++ b/examples/data/dataset-1/storages/storage-4/storage.json @@ -1,12 +1,13 @@ { - "name": "Pumpspeicher-Kraftwerk", - "description": "Stellt Pumpspeicher-Kraftwerk dar", - "commodity": "Elektrizität", - "charge_efficiency":0.88, - "discharge_efficiency":0.88, - "self_discharge":0.00000521811, - "charge_rate":0.16, - "discharge_rate":0.12, - "invest_per_capacity":0, - "opex_per_capacity":0.000153 + "name": "Pumpspeicher-Kraftwerk", + "description": "Stellt Pumpspeicher-Kraftwerk dar", + "commodity": "Elektrizität", + "capacity_variable": true, + "charge_efficiency": 0.88, + "discharge_efficiency": 0.88, + "self_discharge": 0.00000521811, + "charge_rate": 0.16, + "discharge_rate": 0.12, + "invest_per_capacity": 0, + "opex_per_capacity": 0.000153 } \ No newline at end of file diff --git a/examples/data/dataset-1/transmissions/transmission-1/transmission.json b/examples/data/dataset-1/transmissions/transmission-1/transmission.json index 0dd98f4..2757546 100644 --- a/examples/data/dataset-1/transmissions/transmission-1/transmission.json +++ b/examples/data/dataset-1/transmissions/transmission-1/transmission.json @@ -1,5 +1,6 @@ { - "name": "AC Leitungen", - "description": "Stellt die AC-Leitungen dar", - "commodity": "Elektrizität" + "name": "AC Leitungen", + "description": "Stellt die AC-Leitungen dar", + "commodity": "Elektrizität", + "capacity_variable": true } \ No newline at end of file diff --git a/examples/data/dataset-1/transmissions/transmission-2/transmission.json b/examples/data/dataset-1/transmissions/transmission-2/transmission.json index 8b61b1f..f9723ff 100644 --- a/examples/data/dataset-1/transmissions/transmission-2/transmission.json +++ b/examples/data/dataset-1/transmissions/transmission-2/transmission.json @@ -1,5 +1,6 @@ { - "name": "DC-Leitungen", - "description": "Stellt die DC-Leitungen dar", - "commodity": "Elektrizität" + "name": "DC-Leitungen", + "description": "Stellt die DC-Leitungen dar", + "commodity": "Elektrizität", + "capacity_variable": true } \ No newline at end of file diff --git a/examples/data/dataset-1/transmissions/transmission-3/transmission.json b/examples/data/dataset-1/transmissions/transmission-3/transmission.json index f27713c..3c734c5 100644 --- a/examples/data/dataset-1/transmissions/transmission-3/transmission.json +++ b/examples/data/dataset-1/transmissions/transmission-3/transmission.json @@ -1,12 +1,13 @@ { - "name": "Pipeline (Bio Gas)", - "description": "Stellt die Pipeline (Bio Gas) dar", - "commodity": "Biogas", - "has_is_built_binary_variable":"True", - "big_m":300, - "shared_potential_id":"pipelines", - "invest_per_capacity":0.000037, - "invest_if_built":0.000314, - "interest_rate":0.08, - "economic_lifetime":40 + "name": "Pipeline (Bio Gas)", + "description": "Stellt die Pipeline (Bio Gas) dar", + "commodity": "Biogas", + "capacity_variable": true, + "has_is_built_binary_variable": "True", + "big_m": 300, + "shared_potential_id": "pipelines", + "invest_per_capacity": 0.000037, + "invest_if_built": 0.000314, + "interest_rate": 0.08, + "economic_lifetime": 40 } \ No newline at end of file diff --git a/examples/data/dataset-1/transmissions/transmission-4/transmission.json b/examples/data/dataset-1/transmissions/transmission-4/transmission.json index 399c6be..fb5376c 100644 --- a/examples/data/dataset-1/transmissions/transmission-4/transmission.json +++ b/examples/data/dataset-1/transmissions/transmission-4/transmission.json @@ -1,12 +1,13 @@ { - "name": "Pipeline (Wasserstoff)", - "description": "Stellt die Pipeline (Wasserstoff) dar", - "commodity": "Wasserstoff", - "has_is_built_binary_variable":"True", - "big_m":300, - "shared_potential_id":"pipelines", - "invest_per_capacity":0.000177, - "invest_if_built":0.00033, - "interest_rate":0.08, - "economic_lifetime":40 + "name": "Pipeline (Wasserstoff)", + "description": "Stellt die Pipeline (Wasserstoff) dar", + "commodity": "Wasserstoff", + "capacity_variable": true, + "has_is_built_binary_variable": "True", + "big_m": 300, + "shared_potential_id": "pipelines", + "invest_per_capacity": 0.000177, + "invest_if_built": 0.00033, + "interest_rate": 0.08, + "economic_lifetime": 40 } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b0e63c3..7853051 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,3 +16,4 @@ numpy pandas openpyxl +FINE~=2.2.1