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

1"""This module contains a SensorModule which reads a .dat file 

2from the Deutsche Wetterdienst (DWD).""" 

3 

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) 

16 

17 

18class TRYSensorConfig(BaseModuleConfig): 

19 """Define parameters for the TRYSensor""" 

20 

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 ] 

71 

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"] 

81 

82 

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: 

87 

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 """ 

100 

101 config: TRYSensorConfig 

102 

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 ) 

119 

120 @property 

121 def filename(self): 

122 """Return the filename.""" 

123 return self.config.filename 

124 

125 @property 

126 def t_sample(self): 

127 """Return the sample rate.""" 

128 return self.config.t_sample 

129 

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) 

137 

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] 

149 

150 def register_callbacks(self): 

151 """Dont do anything as this BaseModule is not event-triggered""" 

152 

153 

154def read_dat_file(dat_file): 

155 """ 

156 Read a .dat file from the DWD's TRY data from the given .dat-file 

157 

158 Args: 

159 dat_file (str): The file to read 

160 Returns: 

161 

162 """ 

163 # pylint: disable=raise-missing-from 

164 _sep = r"\s+" 

165 

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.") 

178 

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