Coverage for agentlib/modules/utils/try_sensor.py: 0%
57 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-04-07 16:27 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2025-04-07 16:27 +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, validator
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