pyepwmorph 3.1.0__tar.gz → 3.3.0__tar.gz

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.
Files changed (28) hide show
  1. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/.gitignore +4 -0
  2. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/PKG-INFO +25 -2
  3. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/README.md +24 -1
  4. pyepwmorph-3.3.0/pyepwmorph/data/amy_diffuse_table.parquet +0 -0
  5. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/models/ch2025.py +2 -4
  6. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/morph/procedures.py +11 -2
  7. pyepwmorph-3.3.0/pyepwmorph/tools/amy.py +867 -0
  8. pyepwmorph-3.3.0/pyepwmorph/tools/amy_meteoswiss.py +123 -0
  9. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/tools/configuration.py +53 -20
  10. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/tools/io.py +75 -5
  11. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyproject.toml +1 -1
  12. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/LICENSE +0 -0
  13. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/__init__.py +0 -0
  14. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/data/__init__.py +0 -0
  15. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/data/ch2025_monthly.parquet +0 -0
  16. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/data/ch2025_stations.parquet +0 -0
  17. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/models/__init__.py +0 -0
  18. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/models/access.py +0 -0
  19. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/models/assemble.py +0 -0
  20. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/models/coordinate.py +0 -0
  21. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/models/custom.py +0 -0
  22. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/morph/__init__.py +0 -0
  23. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/tools/__init__.py +0 -0
  24. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/tools/cache.py +0 -0
  25. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/tools/psychrometrics.py +0 -0
  26. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/tools/solar.py +0 -0
  27. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/tools/utilities.py +0 -0
  28. {pyepwmorph-3.1.0 → pyepwmorph-3.3.0}/pyepwmorph/tools/workflow.py +0 -0
@@ -159,3 +159,7 @@ cython_debug/
159
159
  # and can be added to the global gitignore or merged into this file. For a more nuclear
160
160
  # option (not recommended) you can uncomment the following to ignore the entire idea folder.
161
161
  .idea/
162
+
163
+ # Example inputs and outputs of examples/run_amy_meteoswiss.py
164
+ examples/amy_data/
165
+ examples/amy_result/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyepwmorph
3
- Version: 3.1.0
3
+ Version: 3.3.0
4
4
  Summary: A python package to enable simple and easy gathering of climate model data and morphing of EPW files
5
5
  Project-URL: Homepage, https://github.com/justinfmccarty/pyepwmorph
6
6
  Project-URL: Issues, https://github.com/justinfmccarty/pyepwmorph/issues
@@ -148,10 +148,33 @@ morphed = results["gwl2.0"]["50"]
148
148
 
149
149
  The site is matched to the nearest station that has the requested variables (or, with `ch2025_full_coverage=True`, the nearest station that has every CH2025 variable), with an elevation penalty so a much higher station is not chosen just because it is close on the map. Supported variables are temperature, humidity, dew point, wind, and radiation (global, diffuse, and direct). Pressure and cloud cover are not in CH2025; requesting them raises an error. Sky cover is left at the EPW's baseline values when radiation is morphed, so longwave sky temperature in EnergyPlus does not follow the shortwave change.
150
150
 
151
- The signal is the change from the 1991-2020 reference climate to the chosen warming level. Each model chain's change is computed first, over the chains that reach that warming level, and the percentiles are taken of those changes. If the EPW's years fall outside 1991-2020, a `UserWarning` recommends a TMY built from 1991-2020 data and the signal is still applied to the file as it stands. The same caveats are listed in `MorphConfig.ch2025_notes` and the matched station in `MorphConfig.ch2025_station`.
151
+ The signal is the change from the 1991-2020 reference climate to the chosen warming level. Each model chain's change is computed first, over the chains that reach that warming level, and the percentiles are taken of those changes. The EPW's period is read from the `Period of Record` in its header. If that is not exactly 1991-2020, a `UserWarning` recommends a TMY built from 1991-2020 data. If the header states no period, a second warning says the period could not be detected, and the mismatch warning still fires if the data rows' years fall outside 1991-2020. The signal is applied to the file as it stands either way. `tools.io.write_period_of_record` adds the statement to files built without one. The same caveats are listed in `MorphConfig.ch2025_notes` and the matched station in `MorphConfig.ch2025_station`.
152
152
 
153
153
  CH2025 data: MeteoSwiss & ETH Zurich (2025), Climate CH2025 - Daily Datasets, CC-BY 4.0, https://doi.org/10.18751/climate/scenarios/ch2025/data/1.0/
154
154
 
155
+ ### Measured data (actual meteorological year)
156
+
157
+ Turn one real year of station measurements, hourly or 10-minute, into an EPW
158
+ for calibration. The table needs fixed column names and hour-ending
159
+ timestamps; `pyepwmorph.tools.amy.describe_columns()` lists them.
160
+
161
+ ```python
162
+ from pyepwmorph.tools import amy, amy_meteoswiss
163
+
164
+ table = amy_meteoswiss.read_meteoswiss_ogd("ogd-smn_sma_t_historical_2020-2029.csv") # UTC, hour-ending
165
+ location = amy_meteoswiss.meteoswiss_location("ogd-smn_meta_stations.csv", "SMA")
166
+ report = amy.station_table_to_epw(
167
+ table, location, 2025, "zurich_fluntern_2025.epw",
168
+ source_name="MeteoSwiss Zurich-Fluntern", attribution=amy_meteoswiss.ATTRIBUTION,
169
+ )
170
+ print(report.to_text())
171
+ ```
172
+
173
+ Other networks only need a table with the documented columns and
174
+ `timestamp_label` / `table_utc_offset` set to match their clock. Direct and
175
+ diffuse irradiance and sky cover are derived when the station does not measure
176
+ them (the report and the EPW header say how).
177
+
155
178
  ## Climate scenarios
156
179
 
157
180
  | Scenario | SSP | Description | Expected warming |
@@ -109,10 +109,33 @@ morphed = results["gwl2.0"]["50"]
109
109
 
110
110
  The site is matched to the nearest station that has the requested variables (or, with `ch2025_full_coverage=True`, the nearest station that has every CH2025 variable), with an elevation penalty so a much higher station is not chosen just because it is close on the map. Supported variables are temperature, humidity, dew point, wind, and radiation (global, diffuse, and direct). Pressure and cloud cover are not in CH2025; requesting them raises an error. Sky cover is left at the EPW's baseline values when radiation is morphed, so longwave sky temperature in EnergyPlus does not follow the shortwave change.
111
111
 
112
- The signal is the change from the 1991-2020 reference climate to the chosen warming level. Each model chain's change is computed first, over the chains that reach that warming level, and the percentiles are taken of those changes. If the EPW's years fall outside 1991-2020, a `UserWarning` recommends a TMY built from 1991-2020 data and the signal is still applied to the file as it stands. The same caveats are listed in `MorphConfig.ch2025_notes` and the matched station in `MorphConfig.ch2025_station`.
112
+ The signal is the change from the 1991-2020 reference climate to the chosen warming level. Each model chain's change is computed first, over the chains that reach that warming level, and the percentiles are taken of those changes. The EPW's period is read from the `Period of Record` in its header. If that is not exactly 1991-2020, a `UserWarning` recommends a TMY built from 1991-2020 data. If the header states no period, a second warning says the period could not be detected, and the mismatch warning still fires if the data rows' years fall outside 1991-2020. The signal is applied to the file as it stands either way. `tools.io.write_period_of_record` adds the statement to files built without one. The same caveats are listed in `MorphConfig.ch2025_notes` and the matched station in `MorphConfig.ch2025_station`.
113
113
 
114
114
  CH2025 data: MeteoSwiss & ETH Zurich (2025), Climate CH2025 - Daily Datasets, CC-BY 4.0, https://doi.org/10.18751/climate/scenarios/ch2025/data/1.0/
115
115
 
116
+ ### Measured data (actual meteorological year)
117
+
118
+ Turn one real year of station measurements, hourly or 10-minute, into an EPW
119
+ for calibration. The table needs fixed column names and hour-ending
120
+ timestamps; `pyepwmorph.tools.amy.describe_columns()` lists them.
121
+
122
+ ```python
123
+ from pyepwmorph.tools import amy, amy_meteoswiss
124
+
125
+ table = amy_meteoswiss.read_meteoswiss_ogd("ogd-smn_sma_t_historical_2020-2029.csv") # UTC, hour-ending
126
+ location = amy_meteoswiss.meteoswiss_location("ogd-smn_meta_stations.csv", "SMA")
127
+ report = amy.station_table_to_epw(
128
+ table, location, 2025, "zurich_fluntern_2025.epw",
129
+ source_name="MeteoSwiss Zurich-Fluntern", attribution=amy_meteoswiss.ATTRIBUTION,
130
+ )
131
+ print(report.to_text())
132
+ ```
133
+
134
+ Other networks only need a table with the documented columns and
135
+ `timestamp_label` / `table_utc_offset` set to match their clock. Direct and
136
+ diffuse irradiance and sky cover are derived when the station does not measure
137
+ them (the report and the EPW header say how).
138
+
116
139
  ## Climate scenarios
117
140
 
118
141
  | Scenario | SSP | Description | Expected warming |
@@ -93,10 +93,8 @@ def reference_key(state: str) -> str:
93
93
 
94
94
 
95
95
  def baseline_matches(baseline_range) -> bool:
96
- """Return whether an EPW baseline lies inside the CH2025 reference period."""
97
- start, end = (int(year) for year in baseline_range)
98
- ref_start, ref_end = CH2025_BASELINE_RANGE
99
- return ref_start <= start and end <= ref_end
96
+ """Return whether an EPW baseline is exactly the CH2025 reference period."""
97
+ return tuple(int(year) for year in baseline_range) == CH2025_BASELINE_RANGE
100
98
 
101
99
 
102
100
  def in_switzerland(latitude: float, longitude: float) -> bool:
@@ -7,6 +7,8 @@ Container for all of the individual morphing calculations which can be traced ba
7
7
  55 514–524 ISSN 0960-1481 URL https://www.sciencedirect.com/science/article/pii/S0960148113000232
8
8
 
9
9
  """
10
+ import calendar
11
+
10
12
  import numpy as np
11
13
  import pandas as pd
12
14
 
@@ -33,10 +35,17 @@ def _index_of(*candidates):
33
35
 
34
36
 
35
37
  def _year_of(index):
36
- """Return the year of a DatetimeIndex, or the default solar year."""
38
+ """Return a non-leap year to build solar geometry for the data in *index*.
39
+
40
+ EPW files always hold 8760 hours with no 29 February, so their calendar
41
+ is a non-leap year's even when the first data row says 2016. Building
42
+ daily series for a leap year would add an empty 29 February and shift
43
+ every later day by one, so a leap year is replaced by the year before.
44
+ """
37
45
  if index is None or len(index) == 0:
38
46
  return morph_solar_utils.DEFAULT_SOLAR_YEAR
39
- return int(index[0].year)
47
+ year = int(index[0].year)
48
+ return year - 1 if calendar.isleap(year) else year
40
49
 
41
50
 
42
51
  def _as_series(values, index, name, decimals=2):