Coverage for agentlib/modules/utils/try_sensor.py: 0%
57 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-04-30 13:00 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2025-04-30 13:00 +0000
1"""This module contains a SensorModule which reads a .dat file
2from the Deutsche Wetterdienst (DWD)."""
4import io
5from typing import Union, List
6from pydantic import FilePath, Field
7import numpy as np
8import pandas as pd
9from agentlib.core import (
10 BaseModule,
11 Agent,
12 AgentVariable,
13 BaseModuleConfig,
14 AgentVariables,
15)
18class TRYSensorConfig(BaseModuleConfig):
19 """Define parameters for the TRYSensor"""
21 outputs: AgentVariables = [
22 AgentVariable(
23 name="T_oda", unit="K", description="Air temperature 2m over ground [K]"
24 ),
25 AgentVariable(
26 name="pressure",
27 unit="hPa",
28 description="Air pressure in standard height [hPa]",
29 ),
30 AgentVariable(
31 name="wind_direction",
32 unit="°",
33 description="Wind direction 10 m above gorund " "[Grad] {0..360;999}",
34 ),
35 AgentVariable(
36 name="wind_speed",
37 unit="m/s",
38 description="Wind speed 10 m above ground [m/s]",
39 ),
40 AgentVariable(name="coverage", unit="eighth", description="[eighth] {0..8;9}"),
41 AgentVariable(name="absolute_humidity", unit="g/kg", description="[g/kg]"),
42 AgentVariable(
43 name="relative_humidity",
44 unit="%",
45 description="Relative humidity 2 m above ground " "[%] {1..100}",
46 ),
47 AgentVariable(
48 name="beam_direct",
49 unit="W/m^2",
50 description="Direct beam of sun (hor. plane) "
51 "[W/m^2] downwards: positive",
52 ),
53 AgentVariable(
54 name="beam_diffuse",
55 unit="/m^2",
56 description="Diffuse beam of sun (hor. plane) "
57 "[W/m^2] downwards: positive",
58 ),
59 AgentVariable(
60 name="beam_atm",
61 unit="/m^2",
62 description="Beam of atmospheric heat (hor. plane) "
63 "[W/m^2] downwards: positive",
64 ),
65 AgentVariable(
66 name="beam_terr",
67 unit="/m^2",
68 description="Beam of terrestrial heat " "[W/m^2] upwards: negative",
69 ),
70 ]
72 filename: FilePath = Field(
73 title="filename", description="The filepath to the data."
74 )
75 t_sample: Union[float, int] = Field(
76 title="t_sample",
77 default=1,
78 description="Sample time of sensor. As TRY are hourly, " "default is 1 h",
79 )
80 shared_variable_fields: List[str] = ["outputs"]
83class TRYSensor(BaseModule):
84 """A module that emulates a sensor using the TRY
85 data of the DWD.
86 The module casts the following AgentVariables into the data_broker:
88 - T_oda: Air temperature 2m over ground [K]
89 - pressure: Air pressure in standard height [hPa]
90 - wind_direction: Wind direction 10 m above gorund [Grad] {0..360;999}
91 - wind_speed: Wind speed 10 m above ground [m/s]
92 - coverage: [eighth] {0..8;9}
93 - absolute_humidity: [g/kg]
94 - relative_humidity: Relative humidity 2 m above ground [%] {1..100}
95 - beam_direct: Direct beam of sun (hor. plane) [W/m^2] downwards: positive
96 - beam_diffuse: Diffuse beam of sun (hor. plane) [W/m^2] downwards: positive
97 - beam_atm: Beam of atmospheric heat (hor. plane) [W/m^2] downwards: positive
98 - beam_terr: Beam of terrestrial heat [W/m^2] upwards: negative
99 """
101 config: TRYSensorConfig
103 def __init__(self, *, config: dict, agent: Agent):
104 """Overwrite init to enable a custom default filename
105 which uses the agent_id."""
106 super().__init__(config=config, agent=agent)
107 self.one_year = 86400 * 365
108 self._data = read_dat_file(self.filename)
109 # Resample and interpolate once according to t_sample:
110 self._data = self._data.reindex(np.arange(0, self.one_year, self.t_sample))
111 self._data = self._data.interpolate()
112 # Check if outputs match the _data:
113 _names = [v.name for v in self.variables]
114 if set(self._data.columns).difference(_names):
115 raise KeyError(
116 "The internal variables differ from the "
117 "supported data in a TRY dataset."
118 )
120 @property
121 def filename(self):
122 """Return the filename."""
123 return self.config.filename
125 @property
126 def t_sample(self):
127 """Return the sample rate."""
128 return self.config.t_sample
130 def process(self):
131 """Write the current TRY values into data_broker every other t_sample"""
132 while True:
133 data = self.get_data_now()
134 for key, val in data.items():
135 self.set(name=key, value=val)
136 yield self.env.timeout(self.t_sample)
138 def get_data_now(self):
139 """Get the data at the current env time.
140 Reiterate the year if necessary.
141 """
142 now = self.env.now % self.one_year
143 if now in self._data.index:
144 return self._data.loc[now]
145 # Interpolate:
146 df = self._data.copy()
147 df.loc[now] = np.nan
148 return df.sort_index().interpolate().loc[now]
150 def register_callbacks(self):
151 """Dont do anything as this BaseModule is not event-triggered"""
154def read_dat_file(dat_file):
155 """
156 Read a .dat file from the DWD's TRY data from the given .dat-file
158 Args:
159 dat_file (str): The file to read
160 Returns:
162 """
163 # pylint: disable=raise-missing-from
164 _sep = r"\s+"
166 with open(dat_file, "r") as file:
167 # First try for 2012 dataset:
168 try:
169 data_lines = file.readlines()[32:]
170 data_lines.remove("*** \n") # Remove line "*** "
171 except ValueError:
172 # Now for 2007 dataset:
173 try:
174 data_lines = file.readlines()[36:]
175 data_lines.remove("***\n") # Remove line "***"
176 except ValueError:
177 raise TypeError("Given .dat file could not be loaded.")
179 output = io.StringIO()
180 output.writelines(data_lines)
181 output.seek(0)
182 df = pd.read_csv(output, sep=_sep, header=0)
183 # Convert the ours to seconds
184 df.index *= 3600
185 _key_map = {
186 "t": "T_oda",
187 "p": "pressure",
188 "WR": "wind_direction",
189 "WG": "wind_speed",
190 "N": "coverage",
191 "x": "absolute_humidity",
192 "RF": "relative_humidity",
193 "B": "beam_direct",
194 "D": "beam_diffuse",
195 "A:": "beam_atm",
196 "E": "beam_terr",
197 }
198 # Rename df for easier later usage.
199 df.rename(columns=_key_map, inplace=True)
200 df = df[df.columns.intersection(_key_map.values())]
201 df["T_oda"] += 273.15 # To Kelvin
202 return df