meteosynth 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meteosynth/__init__.py +15 -0
- meteosynth/generators.py +190 -0
- meteosynth/markov_chain_simulator_kde2d.py +655 -0
- meteosynth/markov_implementations/markov_chain_simulator_continuous.py +287 -0
- meteosynth/markov_implementations/markov_chain_simulator_discrete.py +170 -0
- meteosynth/met_data_processor.py +90 -0
- meteosynth/metdata_pvgis.py +134 -0
- meteosynth/py.typed +0 -0
- meteosynth/sup_plot_lib.py +148 -0
- meteosynth-0.1.0.dist-info/METADATA +176 -0
- meteosynth-0.1.0.dist-info/RECORD +13 -0
- meteosynth-0.1.0.dist-info/WHEEL +4 -0
- meteosynth-0.1.0.dist-info/licenses/LICENSE +21 -0
meteosynth/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from meteosynth.markov_chain_simulator_kde2d import MarkovChainSimulator2dKDE
|
|
2
|
+
from meteosynth.markov_implementations.markov_chain_simulator_continuous import ContinuousMarkovChainSimulator
|
|
3
|
+
from meteosynth.markov_implementations.markov_chain_simulator_discrete import MarkovChainSimulatorDiscrete
|
|
4
|
+
from meteosynth.met_data_processor import MetDataProcessor, convert_to_daily_wide
|
|
5
|
+
from meteosynth.generators import EnvSeriesGenerator, generate_hr_simulator
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"MarkovChainSimulator2dKDE",
|
|
9
|
+
"ContinuousMarkovChainSimulator",
|
|
10
|
+
"MarkovChainSimulatorDiscrete",
|
|
11
|
+
"MetDataProcessor",
|
|
12
|
+
"convert_to_daily_wide",
|
|
13
|
+
"EnvSeriesGenerator",
|
|
14
|
+
"generate_hr_simulator",
|
|
15
|
+
]
|
meteosynth/generators.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Daily synthetic-series generators built on top of the Markov-chain simulators.
|
|
3
|
+
|
|
4
|
+
The main entry point is :class:`EnvSeriesGenerator`, which manages 24 hourly
|
|
5
|
+
:class:`~meteosynth.markov_chain_simulator_kde2d.MarkovChainSimulator2dKDE`
|
|
6
|
+
models (one per hour of the day) and stitches their transitions together into a
|
|
7
|
+
complete 24-hour profile of a meteorological attribute (e.g. plane-of-array
|
|
8
|
+
direct irradiance ``poa_direct``, air temperature, wind speed).
|
|
9
|
+
|
|
10
|
+
This is the piece intended for Monte-Carlo studies of energy-project output and
|
|
11
|
+
requirements: call :meth:`EnvSeriesGenerator.generate_ensemble` to draw many
|
|
12
|
+
independent synthetic days.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
import pandas as pd
|
|
22
|
+
|
|
23
|
+
from .markov_chain_simulator_kde2d import MarkovChainSimulator2dKDE
|
|
24
|
+
|
|
25
|
+
# Meteorological attributes available in a processed PVGIS hourly frame.
|
|
26
|
+
MET_DATA_COLS = [
|
|
27
|
+
"poa_direct",
|
|
28
|
+
"poa_sky_diffuse",
|
|
29
|
+
"poa_ground_diffuse",
|
|
30
|
+
"solar_elevation",
|
|
31
|
+
"temp_air",
|
|
32
|
+
"wind_speed",
|
|
33
|
+
"Int",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
HOURS_PER_DAY = 24
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def generate_hr_simulator(
|
|
40
|
+
met_data: pd.DataFrame,
|
|
41
|
+
hour: int,
|
|
42
|
+
attr_str: str,
|
|
43
|
+
bandwidth: Optional[float] = 0.1,
|
|
44
|
+
) -> MarkovChainSimulator2dKDE:
|
|
45
|
+
"""
|
|
46
|
+
Build a Markov-chain simulator for the transition into a specific hour.
|
|
47
|
+
|
|
48
|
+
The current hour's values (``current``) are conditioned on the previous
|
|
49
|
+
hour's values (``previous``), so the resulting simulator models the
|
|
50
|
+
hour-to-hour transition ``hour-1 -> hour``.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
met_data : pd.DataFrame
|
|
55
|
+
Processed meteorological data containing at least an ``hour`` column
|
|
56
|
+
and the requested ``attr_str`` column.
|
|
57
|
+
hour : int
|
|
58
|
+
Target hour of the day (0-23) for which to build the transition model.
|
|
59
|
+
attr_str : str
|
|
60
|
+
Meteorological attribute to model (one of :data:`MET_DATA_COLS`).
|
|
61
|
+
bandwidth : float, optional
|
|
62
|
+
KDE bandwidth passed to the simulator. Defaults to ``0.1`` for smooth
|
|
63
|
+
transitions; use ``None`` for Scott's rule.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
MarkovChainSimulator2dKDE
|
|
68
|
+
A fitted simulator for the ``hour-1 -> hour`` transition.
|
|
69
|
+
"""
|
|
70
|
+
if attr_str not in MET_DATA_COLS:
|
|
71
|
+
raise ValueError(f"attr_str must be one of {MET_DATA_COLS}, got {attr_str!r}")
|
|
72
|
+
|
|
73
|
+
hour_prev = (hour - 1) % HOURS_PER_DAY
|
|
74
|
+
cur_hour = met_data[met_data["hour"] == hour]
|
|
75
|
+
prev_hour = met_data[met_data["hour"] == hour_prev]
|
|
76
|
+
|
|
77
|
+
# Combine previous and current hour values into a single transition frame.
|
|
78
|
+
transition_data = pd.DataFrame(
|
|
79
|
+
{
|
|
80
|
+
"previous": prev_hour[attr_str].values,
|
|
81
|
+
"current": cur_hour[attr_str].values,
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return MarkovChainSimulator2dKDE(transition_data, bandwidth=bandwidth)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class EnvSeriesGenerator:
|
|
89
|
+
"""
|
|
90
|
+
Generate synthetic 24-hour daily profiles of a meteorological attribute.
|
|
91
|
+
|
|
92
|
+
The generator fits one hourly transition simulator per hour of the day and
|
|
93
|
+
chains them: starting from ``start_value`` at hour 0, each hour's value is
|
|
94
|
+
sampled from the conditional distribution given the previous hour's value.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
met_data : pd.DataFrame
|
|
99
|
+
Processed meteorological data (typically a single month subset) with
|
|
100
|
+
``hour`` and ``attr_str`` columns.
|
|
101
|
+
attr_str : str
|
|
102
|
+
Meteorological attribute to model (one of :data:`MET_DATA_COLS`).
|
|
103
|
+
bandwidth : float, optional
|
|
104
|
+
KDE bandwidth forwarded to every hourly simulator.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
met_data: pd.DataFrame,
|
|
110
|
+
attr_str: str,
|
|
111
|
+
bandwidth: Optional[float] = 0.1,
|
|
112
|
+
) -> None:
|
|
113
|
+
self.attr_str = attr_str
|
|
114
|
+
self.met_data = met_data
|
|
115
|
+
self._dic_simulator: dict[int, MarkovChainSimulator2dKDE] = {}
|
|
116
|
+
|
|
117
|
+
logging.info("Initializing %d hourly simulators for attribute '%s'...",
|
|
118
|
+
HOURS_PER_DAY, attr_str)
|
|
119
|
+
for hour in range(HOURS_PER_DAY):
|
|
120
|
+
self._dic_simulator[hour] = generate_hr_simulator(
|
|
121
|
+
met_data, hour, attr_str=attr_str, bandwidth=bandwidth
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def simulators(self) -> dict[int, MarkovChainSimulator2dKDE]:
|
|
126
|
+
"""The dictionary of hourly simulators, keyed by hour of day (0-23)."""
|
|
127
|
+
return self._dic_simulator
|
|
128
|
+
|
|
129
|
+
def gen_day_sequence(self, start_value: float = 0.0, seed: Optional[int] = None) -> np.ndarray:
|
|
130
|
+
"""
|
|
131
|
+
Generate a single synthetic 24-hour profile.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
start_value : float
|
|
136
|
+
The value at hour 0 (e.g. ``0.0`` irradiance at midnight).
|
|
137
|
+
seed : int, optional
|
|
138
|
+
If provided, seeds the generator once so the whole day is
|
|
139
|
+
reproducible while each hour still transitions stochastically.
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
np.ndarray
|
|
144
|
+
Array of shape ``(24,)`` with one value per hour.
|
|
145
|
+
"""
|
|
146
|
+
if seed is not None:
|
|
147
|
+
np.random.seed(seed)
|
|
148
|
+
|
|
149
|
+
seq_vals = np.zeros(HOURS_PER_DAY)
|
|
150
|
+
current_val = start_value
|
|
151
|
+
for k in range(HOURS_PER_DAY):
|
|
152
|
+
sim_k = self._dic_simulator[k]
|
|
153
|
+
# Do not forward the seed to each step: that would reseed to the
|
|
154
|
+
# same value every hour and yield an identical draw. Seeding once
|
|
155
|
+
# above already makes the whole day reproducible.
|
|
156
|
+
current_val = sim_k.get_next_state(current_val)
|
|
157
|
+
seq_vals[k] = current_val
|
|
158
|
+
return seq_vals
|
|
159
|
+
|
|
160
|
+
def generate_ensemble(
|
|
161
|
+
self,
|
|
162
|
+
n_days: int,
|
|
163
|
+
start_value: float = 0.0,
|
|
164
|
+
seed: Optional[int] = None,
|
|
165
|
+
) -> np.ndarray:
|
|
166
|
+
"""
|
|
167
|
+
Generate an ensemble of independent synthetic days for Monte-Carlo use.
|
|
168
|
+
|
|
169
|
+
Parameters
|
|
170
|
+
----------
|
|
171
|
+
n_days : int
|
|
172
|
+
Number of synthetic daily profiles to draw.
|
|
173
|
+
start_value : float
|
|
174
|
+
The value at hour 0 for every profile.
|
|
175
|
+
seed : int, optional
|
|
176
|
+
If provided, seeds the whole ensemble once so the full draw is
|
|
177
|
+
reproducible.
|
|
178
|
+
|
|
179
|
+
Returns
|
|
180
|
+
-------
|
|
181
|
+
np.ndarray
|
|
182
|
+
Array of shape ``(n_days, 24)``; row ``i`` is the ``i``-th
|
|
183
|
+
synthetic day.
|
|
184
|
+
"""
|
|
185
|
+
if seed is not None:
|
|
186
|
+
np.random.seed(seed)
|
|
187
|
+
|
|
188
|
+
return np.vstack(
|
|
189
|
+
[self.gen_day_sequence(start_value=start_value) for _ in range(n_days)]
|
|
190
|
+
)
|