-
Notifications
You must be signed in to change notification settings - Fork 6
Add ensemble functionality #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
80b8c3d
5406030
09728fe
64e17e1
d6460fd
3ea5837
63513d5
7ae579e
f406c0d
bdf7357
30b2b1a
0349137
ba0a5a6
e2af071
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -459,60 +459,89 @@ def fetch_clearsky_with_fallback( | |
|
|
||
|
|
||
| def save_forecast( | ||
| forecast: np.ndarray, | ||
| forecast: np.ndarray | dict[str, np.ndarray], | ||
| time_step: datetime, | ||
| n_steps: int, | ||
| latitudes: np.ndarray, | ||
| longitudes: np.ndarray, | ||
| dataset_name: str, | ||
| nowcast_config: NowcastConfig, | ||
| model_version: str, | ||
| output_mode: str, | ||
| run_mode: str = "files", | ||
| s3_config: S3Config | None = None, | ||
| ) -> str: | ||
| """Save forecast array to a CF-compliant NetCDF4 file. | ||
| """Save forecast data to a CF-compliant NetCDF4 file. | ||
|
|
||
| Writes the probabilistic advection forecast to either a local file or S3, | ||
| depending on `run_mode`. The time coordinate is stored as CF-convention | ||
| numeric values (float64, minutes since the forecast reference time). | ||
|
|
||
| Args: | ||
| forecast: Forecast array, shape [time, lat, lon] or | ||
| [ensemble, time, lat, lon]. | ||
| forecast: Forecast array with shape [ensemble, time, lat, lon], | ||
| or mapping of statistic name to arrays with the same shape. | ||
| time_step: Forecast reference time (start of the forecast window). | ||
| n_steps: Number of forecast time steps to write. | ||
| latitudes: 1-D array of latitude values (degrees). | ||
| longitudes: 1-D array of longitude values (degrees). | ||
| dataset_name: Name of the source dataset (options: KNMI, DWD). | ||
| nowcast_config: NowcastConfig object supplying output directory, | ||
| ensemble size, and input data frequency. | ||
| nowcast_config: NowcastConfig object supplying output directory | ||
| and input data frequency. | ||
| model_version: Model version string written as a global attribute. | ||
| output_mode: Output aggregation mode label written to global | ||
| NetCDF attrs (expected: 'deterministic', 'ensemble_statistics', | ||
| or 'full_ensemble'). | ||
| run_mode: One of 'files' (local) or 's3'. Defaults to 'files'. | ||
| s3_config: S3Config object; required when run_mode is 's3'. | ||
|
|
||
| Returns: | ||
| Filename (basename only) of the written NetCDF file. | ||
| """ | ||
| input_data_frequency_minutes = nowcast_config.input_data_frequency_minutes | ||
| ens_members = nowcast_config.ens_members | ||
| filename = f"SolarNowcast_{time_step.strftime('%Y%m%d%H%M')}.nc" | ||
|
|
||
| # Add ensemble dimension if needed (forecast should be [ensemble, time, lat, lon]) | ||
| if forecast.ndim == 3: | ||
| forecast = forecast[np.newaxis, :, :, :] # Now [1, time, lat, lon] | ||
| data_vars: dict[str, tuple[list[str], np.ndarray, dict[str, str]]] | ||
| statistics_attr = "" | ||
|
|
||
| # Build time coordinate (CF-convention: minutes since forecast reference time) | ||
| if isinstance(forecast, dict): | ||
| if not forecast: | ||
| raise ValueError("forecast statistics mapping cannot be empty") | ||
|
|
||
| time_step_naive = time_step.replace(tzinfo=None) | ||
| _time_units = f"minutes since {time_step_naive.strftime('%Y-%m-%d %H:%M:%S')}" | ||
| time_datetimes = [ | ||
| time_step_naive + timedelta(minutes=input_data_frequency_minutes * i) | ||
| for i in range(0, n_steps) | ||
| ] | ||
| data_vars = {} | ||
| first_shape: tuple[int, ...] | None = None | ||
| for statistic, values in forecast.items(): | ||
| if values.ndim != 4: | ||
| raise ValueError( | ||
| "Each statistic array must have shape " "(ensemble, time, lat, lon)." | ||
| ) | ||
|
|
||
| ds = xr.Dataset( | ||
| { | ||
| "probabilistic_advection": ( | ||
| if first_shape is None: | ||
| first_shape = values.shape | ||
| elif values.shape != first_shape: | ||
| raise ValueError("All statistic arrays must share the same shape") | ||
|
|
||
| variable_name = f"GHI_probabilistic_advection_{statistic}" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion to put {statistic} first in the name, so it reads: mean_GHI_probabilistic_advection This makes it easier when making a fast visualization with ncview 😄 Otherwise they're all called GHI_probabilistic_advection in the viewer |
||
| data_vars[variable_name] = ( | ||
| ["ensemble", "time", "lat", "lon"], | ||
| values, | ||
| { | ||
| "description": ( | ||
| f"Probabilistic advection solar forecast ({statistic})" | ||
| ), | ||
| "long_name": "Surface downwelling solar radiation", | ||
| "units": "W m-2", | ||
| }, | ||
| ) | ||
|
|
||
| ens_members = first_shape[0] if first_shape is not None else 0 | ||
| statistics_attr = ",".join(forecast.keys()) | ||
| else: | ||
| if forecast.ndim != 4: | ||
| raise ValueError("forecast must have shape (ensemble, time, lat, lon).") | ||
|
|
||
| ens_members = forecast.shape[0] | ||
| data_vars = { | ||
| "GHI_probabilistic_advection": ( | ||
| ["ensemble", "time", "lat", "lon"], | ||
| forecast, | ||
| { | ||
|
|
@@ -521,7 +550,19 @@ def save_forecast( | |
| "units": "W m-2", | ||
| }, | ||
| ), | ||
| }, | ||
| } | ||
|
|
||
| # Build time coordinate (CF-convention: minutes since forecast reference time) | ||
|
|
||
| time_step_naive = time_step.replace(tzinfo=None) | ||
| _time_units = f"minutes since {time_step_naive.strftime('%Y-%m-%d %H:%M:%S')}" | ||
| time_datetimes = [ | ||
| time_step_naive + timedelta(minutes=input_data_frequency_minutes * i) | ||
| for i in range(0, n_steps) | ||
| ] | ||
|
|
||
| ds = xr.Dataset( | ||
| data_vars, | ||
| coords={ | ||
| "time": ( | ||
| ["time"], | ||
|
|
@@ -549,11 +590,13 @@ def save_forecast( | |
| f"Simple Probabilistic Advection solar forecast " | ||
| f"using {dataset_name} data" | ||
| ), | ||
| "output_mode": output_mode, | ||
| "history": ( | ||
| f"Created " | ||
| f"{datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')} UTC" | ||
| ), | ||
| "model_version": model_version, | ||
| **({"statistics": statistics_attr} if statistics_attr else {}), | ||
| }, | ||
| ) | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion to make sure that the shape of the forecast matches the ensemble members chosen in the config: