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 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
+ ]
@@ -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
+ )