"""This file contains all the settings for the optimization problem and should
be modified and adapted to each case through a .yaml file (see examples)."""
import os
import warnings
from typing import List, Union
from pydantic import BaseModel, Field, model_validator
import yaml
[docs]
class Water(BaseModel):
"""Water properties for the linearization of piping."""
[docs]
dynamic_viscosity: float = Field(
4.041e-4, description="Dynamic viscosity at 70°C and 1 bar")
[docs]
density: float = Field(
977.76, description="Density at 70°C and 1 bar")
[docs]
heat_capacity_cp: float = Field(
4.187e3, description="Heat capacity at constant pressure")
[docs]
class Ground(BaseModel):
"""Ground properties for the linearization of piping."""
[docs]
thermal_conductivity: float = Field(
1.2, description="Thermal conductivity of the ground")
[docs]
class Temperatures(BaseModel):
"""Temperatures for the linearization of piping, calculation of
postprocessing."""
[docs]
ambient: float = Field(-20, description="Ambient temperature in °C")
[docs]
supply: float = Field(70, description="Supply temperature in °C")
[docs]
return_: float = Field(55, description="Return temperature in °C")
[docs]
class Piping(BaseModel):
"""Piping properties for the linearization of piping."""
[docs]
diameter: List[float] = Field(
default_factory=lambda: [
0.0291, 0.0372, 0.0431, 0.0545, 0.0703,
0.0825, 0.1071, 0.1325, 0.1603, 0.2101,
0.263, 0.3127, 0.3444, 0.3938, 0.4444,
0.4954, 0.5958
],
description="List of all inner diameters of the available pipe sizes"
)
[docs]
middle_diameter: List[float] = Field(
default_factory=lambda: [
0.0337, 0.0424, 0.0483, 0.0603, 0.0761,
0.0889, 0.1143, 0.1397, 0.1683, 0.2191,
0.273, 0.3239, 0.3556, 0.4064, 0.457,
0.508, 0.61
],
description="List of all middle diameters of the available pipe sizes"
)
[docs]
outer_diameter: List[float] = Field(
default_factory=lambda: [
0.09, 0.11, 0.11, 0.125, 0.14,
0.16, 0.2, 0.225, 0.25, 0.315,
0.4, 0.45, 0.5, 0.56, 0.63,
0.71, 0.8
],
description="List of all outer diameters of the available pipe sizes"
)
[docs]
cost: List[float] = Field(
default_factory=lambda: [
718, 763, 786, 880, 907,
1061, 1090, 1256, 1332, 1836,
2036, 2183, 2651, 2902, 3345,
3580, 4507
],
description="Cost of pipes"
)
[docs]
number_diameters: int = Field(
17, description="Number of discrete diameters")
[docs]
max_pr_loss: float = Field(
250., description="Assumed pressure loss in Pa per meter")
[docs]
roughness: float = Field(
0.01e-3, description="Pipe roughness factor")
[docs]
thermal_conductivity: float = Field(
0.024, description="Pipe thermal conductivity in W/mK")
[docs]
depth: float = Field(
2, description="Depth of buried pipes in m (measured from surface to the middle of pipe)")
@model_validator(mode='after')
[docs]
def check_length(self):
"""Check if the length of diameter, outer_diameter, and cost is
consistent with the defined number diameters.
"""
if len(self.diameter) != self.number_diameters:
raise ValueError(
f"""Length of diameter {len(self.diameter)} is not equal to
number_diameters {self.number_diameters}""")
if len(self.outer_diameter) != self.number_diameters:
raise ValueError(
f"""Length of outer_diameter {len(self.outer_diameter)} is
not equal to number_diameters {self.number_diameters}""")
if len(self.cost) != self.number_diameters:
raise ValueError(
f"""Length of cost {len(self.cost)} is not equal to
number_diameters {self.number_diameters}""")
return self
[docs]
class Solver(BaseModel):
"""Solver properties for the optimization problem. Used for the
optimization model."""
[docs]
mip_gap: float = Field(1e-4, description="MIP gap")
[docs]
time_limit: int = Field(
10000, description="Time limit for the optimization in seconds")
[docs]
log: str = Field("solver.log", description="Log file for the solver")
# @TODO: add more solver options, pass them to the solver flexibly
[docs]
class Economics(BaseModel):
"""Economic properties for the optimization problem. Used for the
optimization model."""
[docs]
source_price: List[List[float]] = Field(
default_factory=lambda: [[80e-3]],
description="Variable price for one kW of heat production at supply in €/kW")
[docs]
source_c_inv: List[float] = Field(
default_factory=lambda: [0.],
description="Investment costs for each source in €/kW")
[docs]
source_c_irr: List[float] = Field(
default_factory=lambda: [0.08],
description="Interest rate for sources")
[docs]
source_lifetime: List[float] = Field(
default_factory=lambda: [20.],
description="Lifetime for source investments in years")
[docs]
source_max_power: List[float] = Field(
default_factory=lambda: [1e6],
description="Maximum installed power for sources in kW")
[docs]
source_min_power: List[float] = Field(
default_factory=lambda: [0],
description="Maximum installed power for sources in kW")
[docs]
pipes_c_irr: float = Field(
0.08, description="Interest rate for pipes")
[docs]
heat_price: float = Field(
120e-3, description="Selling price for heat in €/kW")
[docs]
pipes_lifetime: float = Field(
50.0, description="Lifetime for piping investments in years")
[docs]
class Settings(BaseModel):
"""Class for the settings of the optimization problem which is passed
to the regression, optimization model, and postprocessing."""
[docs]
temperatures: Temperatures
def __init__(self,
water: Water = Water(),
ground: Ground = Ground(),
temperatures: Temperatures = Temperatures(),
piping: Piping = Piping(),
solver: Solver = Solver(),
economics: Economics = Economics()) -> None:
super().__init__(water=water,
ground=ground,
temperatures=temperatures,
piping=piping, solver=solver,
economics=economics)
[docs]
def load(file_path: Union[str, os.PathLike[str]]) -> Settings:
"""Load the settings from a yaml file."""
with open(file_path, 'r', encoding='utf-8') as file:
data = yaml.safe_load(file)
# check if there are keys which are not in the model
for key in data.keys():
if key not in Settings.model_fields.keys():
warnings.warn(f"Key {key} is not in the model.")
return Settings(**data)