From a430e8c560fa0e9d267ed07a3dc0020589ccd818 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Thu, 10 Apr 2025 14:52:53 +0200 Subject: [PATCH 001/302] add basic example for regridding using scipy --- read_datasets.py | 159 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 read_datasets.py diff --git a/read_datasets.py b/read_datasets.py new file mode 100644 index 00000000..4ae11e4b --- /dev/null +++ b/read_datasets.py @@ -0,0 +1,159 @@ +from anemoi.datasets import open_dataset +import matplotlib.pyplot as plt +import cartopy.crs as ccrs +import numpy as np +from scipy.interpolate import griddata + +COSMO_PATH = '/scratch/mch/fzanetta/data/anemoi/datasets/mch-co2-an-archive-0p02-2015-2020-6h-v3-pl13.zarr' # path in Balfrin +ERA_PATH = '/scratch/mch/apennino/data/aifs-ea-an-oper-0001-mars-n320-1979-2022-6h-v6.zarr' # path in Balfrin + +# trim edge removes boundary +cosmo = open_dataset(COSMO_PATH, select="2t", trim_edge=20) # open cosmo, select only 2m-temperature +start_date = cosmo.metadata()['start_date'] # get start and end date of cosmo +end_date = cosmo.metadata()['end_date'] +era = open_dataset(ERA_PATH, select="2t", start=start_date, end=end_date) # load era5 2m-temperature in the time-range of cosmo + + +# get indeces of era5 data that is in the bounding rectangle of cosmo data - this is just for plotting +min_lat_cosmo = min(cosmo.latitudes) +max_lat_cosmo = max(cosmo.latitudes) +min_lon_cosmo = min(cosmo.longitudes) +max_lon_cosmo = max(cosmo.longitudes) +box_lat = np.logical_and(era.latitudes>=min_lat_cosmo,era.latitudes<=max_lat_cosmo) +box_lon = np.logical_and(era.longitudes>=min_lon_cosmo,era.longitudes<=max_lon_cosmo) +indeces = np.where(box_lon*box_lat) + + +#### Approach 1 ######################################################### +#### Scipy Interpolate ################################################## + +grid = np.column_stack((era.longitudes, era.latitudes)) # stack lon-lat columns of era5 points +values = np.array(era[0,0,0,:]) # get era grid 2m-temperature values on the first avaialble date-time + +interp_grid = np.column_stack((cosmo.longitudes, cosmo.latitudes)) # stack lon-lat column of cosmo points + +values_int = griddata(grid,values,interp_grid,method='linear') # interpolate era5 to cosmo grid using scipy griddata linear + + +################ plotting ################################################ + +# plot era original +fig = plt.figure() +fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) +p = ax.scatter(x=era.longitudes[indeces], y=era.latitudes[indeces], c=era[0, 0, 0, :][indeces]) +ax.coastlines() +ax.gridlines(draw_labels=True) +plt.colorbar(p, label="K", orientation="horizontal") +plt.savefig("temperature-2m-era.jpg") + +# plot cosmo original +fig = plt.figure() +fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) +p = ax.scatter(x=cosmo.longitudes, y=cosmo.latitudes, c=cosmo[0, 0, 0, :]) +ax.coastlines() +ax.gridlines(draw_labels=True) +plt.colorbar(p, label="K", orientation="horizontal") +plt.savefig("temperature-2m-cosmo.jpg") + +#plot inerpolated era5 +fig = plt.figure() +fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) +p = ax.scatter(x=cosmo.longitudes, y=cosmo.latitudes, c=values_int) +ax.coastlines() +ax.gridlines(draw_labels=True) +plt.colorbar(p, label="K", orientation="horizontal") +plt.savefig("temperature-2m-era-downscaled.jpg") + + + + + + + + + + + + + +# cosmo = xr.open_zarr(COSMO_PATH) +# print(cosmo.attrs['data_request']) +# era = xr.open_zarr(ERA_PATH) +# print(cosmo[0,0,0,0]) +# print(era) + + + + +# min_lat_cosmo = min(cosmo.latitudes) +# max_lat_cosmo = max(cosmo.latitudes) +# min_lon_cosmo = min(cosmo.longitudes) +# max_lon_cosmo = max(cosmo.longitudes) +# box_lat = np.logical_and(era.latitudes>=min_lat_cosmo,era.latitudes<=max_lat_cosmo) +# box_lon = np.logical_and(era.longitudes>=min_lon_cosmo,era.longitudes<=max_lon_cosmo) +# indeces = np.where(box_lon*box_lat) + +# ds = xr.tutorial.open_dataset( +# "air_temperature" +# ) # use xr.tutorial.load_dataset() for xarray Date: Thu, 10 Apr 2025 14:56:53 +0200 Subject: [PATCH 002/302] rename and delete comments --- read_datasets.py => interpolate_basic.py | 96 +----------------------- 1 file changed, 1 insertion(+), 95 deletions(-) rename read_datasets.py => interpolate_basic.py (50%) diff --git a/read_datasets.py b/interpolate_basic.py similarity index 50% rename from read_datasets.py rename to interpolate_basic.py index 4ae11e4b..d17ab5ae 100644 --- a/read_datasets.py +++ b/interpolate_basic.py @@ -62,98 +62,4 @@ ax.coastlines() ax.gridlines(draw_labels=True) plt.colorbar(p, label="K", orientation="horizontal") -plt.savefig("temperature-2m-era-downscaled.jpg") - - - - - - - - - - - - - -# cosmo = xr.open_zarr(COSMO_PATH) -# print(cosmo.attrs['data_request']) -# era = xr.open_zarr(ERA_PATH) -# print(cosmo[0,0,0,0]) -# print(era) - - - - -# min_lat_cosmo = min(cosmo.latitudes) -# max_lat_cosmo = max(cosmo.latitudes) -# min_lon_cosmo = min(cosmo.longitudes) -# max_lon_cosmo = max(cosmo.longitudes) -# box_lat = np.logical_and(era.latitudes>=min_lat_cosmo,era.latitudes<=max_lat_cosmo) -# box_lon = np.logical_and(era.longitudes>=min_lon_cosmo,era.longitudes<=max_lon_cosmo) -# indeces = np.where(box_lon*box_lat) - -# ds = xr.tutorial.open_dataset( -# "air_temperature" -# ) # use xr.tutorial.load_dataset() for xarray Date: Thu, 10 Apr 2025 15:07:11 +0200 Subject: [PATCH 003/302] start a branch for transfering corrdiff from nvidia-modulus --- interpolate_basic.py | 65 -------------------------------------------- 1 file changed, 65 deletions(-) delete mode 100644 interpolate_basic.py diff --git a/interpolate_basic.py b/interpolate_basic.py deleted file mode 100644 index d17ab5ae..00000000 --- a/interpolate_basic.py +++ /dev/null @@ -1,65 +0,0 @@ -from anemoi.datasets import open_dataset -import matplotlib.pyplot as plt -import cartopy.crs as ccrs -import numpy as np -from scipy.interpolate import griddata - -COSMO_PATH = '/scratch/mch/fzanetta/data/anemoi/datasets/mch-co2-an-archive-0p02-2015-2020-6h-v3-pl13.zarr' # path in Balfrin -ERA_PATH = '/scratch/mch/apennino/data/aifs-ea-an-oper-0001-mars-n320-1979-2022-6h-v6.zarr' # path in Balfrin - -# trim edge removes boundary -cosmo = open_dataset(COSMO_PATH, select="2t", trim_edge=20) # open cosmo, select only 2m-temperature -start_date = cosmo.metadata()['start_date'] # get start and end date of cosmo -end_date = cosmo.metadata()['end_date'] -era = open_dataset(ERA_PATH, select="2t", start=start_date, end=end_date) # load era5 2m-temperature in the time-range of cosmo - - -# get indeces of era5 data that is in the bounding rectangle of cosmo data - this is just for plotting -min_lat_cosmo = min(cosmo.latitudes) -max_lat_cosmo = max(cosmo.latitudes) -min_lon_cosmo = min(cosmo.longitudes) -max_lon_cosmo = max(cosmo.longitudes) -box_lat = np.logical_and(era.latitudes>=min_lat_cosmo,era.latitudes<=max_lat_cosmo) -box_lon = np.logical_and(era.longitudes>=min_lon_cosmo,era.longitudes<=max_lon_cosmo) -indeces = np.where(box_lon*box_lat) - - -#### Approach 1 ######################################################### -#### Scipy Interpolate ################################################## - -grid = np.column_stack((era.longitudes, era.latitudes)) # stack lon-lat columns of era5 points -values = np.array(era[0,0,0,:]) # get era grid 2m-temperature values on the first avaialble date-time - -interp_grid = np.column_stack((cosmo.longitudes, cosmo.latitudes)) # stack lon-lat column of cosmo points - -values_int = griddata(grid,values,interp_grid,method='linear') # interpolate era5 to cosmo grid using scipy griddata linear - - -################ plotting ################################################ - -# plot era original -fig = plt.figure() -fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) -p = ax.scatter(x=era.longitudes[indeces], y=era.latitudes[indeces], c=era[0, 0, 0, :][indeces]) -ax.coastlines() -ax.gridlines(draw_labels=True) -plt.colorbar(p, label="K", orientation="horizontal") -plt.savefig("temperature-2m-era.jpg") - -# plot cosmo original -fig = plt.figure() -fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) -p = ax.scatter(x=cosmo.longitudes, y=cosmo.latitudes, c=cosmo[0, 0, 0, :]) -ax.coastlines() -ax.gridlines(draw_labels=True) -plt.colorbar(p, label="K", orientation="horizontal") -plt.savefig("temperature-2m-cosmo.jpg") - -#plot inerpolated era5 -fig = plt.figure() -fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) -p = ax.scatter(x=cosmo.longitudes, y=cosmo.latitudes, c=values_int) -ax.coastlines() -ax.gridlines(draw_labels=True) -plt.colorbar(p, label="K", orientation="horizontal") -plt.savefig("temperature-2m-era-downscaled.jpg") \ No newline at end of file From 99d55dffe2893523a9a0c795a68604c20e81ef3e Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 11 Apr 2025 13:43:37 +0200 Subject: [PATCH 004/302] add model and loss scripts --- src/losses/loss.py | 914 +++++++++++++++++++++ src/models/layers.py | 567 ++++++++++++++ src/models/preconditioning copy.py | 1176 ++++++++++++++++++++++++++++ src/models/preconditioning.py | 1176 ++++++++++++++++++++++++++++ src/models/song_unet.py | 906 +++++++++++++++++++++ src/models/unet.py | 267 +++++++ src/models/utils.py | 66 ++ 7 files changed, 5072 insertions(+) create mode 100644 src/losses/loss.py create mode 100644 src/models/layers.py create mode 100644 src/models/preconditioning copy.py create mode 100644 src/models/preconditioning.py create mode 100644 src/models/song_unet.py create mode 100644 src/models/unet.py create mode 100644 src/models/utils.py diff --git a/src/losses/loss.py b/src/losses/loss.py new file mode 100644 index 00000000..18dde13b --- /dev/null +++ b/src/losses/loss.py @@ -0,0 +1,914 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Loss functions used in the paper +"Elucidating the Design Space of Diffusion-Based Generative Models".""" + +import random +from typing import Callable, Optional, Union + +import numpy as np +import torch + + +class VPLoss: + """ + Loss function corresponding to the variance preserving (VP) formulation. + + Parameters + ---------- + beta_d: float, optional + Coefficient for the diffusion process, by default 19.9. + beta_min: float, optional + Minimum bound, by defaults 0.1. + epsilon_t: float, optional + Small positive value, by default 1e-5. + + Note: + ----- + Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and + Poole, B., 2020. Score-based generative modeling through stochastic differential + equations. arXiv preprint arXiv:2011.13456. + + """ + + def __init__( + self, beta_d: float = 19.9, beta_min: float = 0.1, epsilon_t: float = 1e-5 + ): + self.beta_d = beta_d + self.beta_min = beta_min + self.epsilon_t = epsilon_t + + def __call__( + self, + net: torch.nn.Module, + images: torch.Tensor, + labels: torch.Tensor, + augment_pipe: Optional[Callable] = None, + ): + """ + Calculate and return the loss corresponding to the variance preserving (VP) + formulation. + + The method adds random noise to the input images and calculates the loss as the + square difference between the network's predictions and the input images. + The noise level is determined by 'sigma', which is computed as a function of + 'epsilon_t' and random values. The calculated loss is weighted based on the + inverse of 'sigma^2'. + + Parameters: + ---------- + net: torch.nn.Module + The neural network model that will make predictions. + + images: torch.Tensor + Input images to the neural network. + + labels: torch.Tensor + Ground truth labels for the input images. + + augment_pipe: callable, optional + An optional data augmentation function that takes images as input and + returns augmented images. If not provided, no data augmentation is applied. + + Returns: + ------- + torch.Tensor + A tensor representing the loss calculated based on the network's + predictions. + """ + rnd_uniform = torch.rand([images.shape[0], 1, 1, 1], device=images.device) + sigma = self.sigma(1 + rnd_uniform * (self.epsilon_t - 1)) + weight = 1 / sigma**2 + y, augment_labels = ( + augment_pipe(images) if augment_pipe is not None else (images, None) + ) + n = torch.randn_like(y) * sigma + D_yn = net(y + n, sigma, labels, augment_labels=augment_labels) + loss = weight * ((D_yn - y) ** 2) + return loss + + def sigma( + self, t: Union[float, torch.Tensor] + ): # NOTE: also exists in preconditioning + """ + Compute the sigma(t) value for a given t based on the VP formulation. + + The function calculates the noise level schedule for the diffusion process based + on the given parameters `beta_d` and `beta_min`. + + Parameters + ---------- + t : Union[float, torch.Tensor] + The timestep or set of timesteps for which to compute sigma(t). + + Returns + ------- + torch.Tensor + The computed sigma(t) value(s). + """ + t = torch.as_tensor(t) + return ((0.5 * self.beta_d * (t**2) + self.beta_min * t).exp() - 1).sqrt() + + +class VELoss: + """ + Loss function corresponding to the variance exploding (VE) formulation. + + Parameters + ---------- + sigma_min : float + Minimum supported noise level, by default 0.02. + sigma_max : float + Maximum supported noise level, by default 100.0. + + Note: + ----- + Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and + Poole, B., 2020. Score-based generative modeling through stochastic differential + equations. arXiv preprint arXiv:2011.13456. + """ + + def __init__(self, sigma_min: float = 0.02, sigma_max: float = 100.0): + self.sigma_min = sigma_min + self.sigma_max = sigma_max + + def __call__(self, net, images, labels, augment_pipe=None): + """ + Calculate and return the loss corresponding to the variance exploding (VE) + formulation. + + The method adds random noise to the input images and calculates the loss as the + square difference between the network's predictions and the input images. + The noise level is determined by 'sigma', which is computed as a function of + 'sigma_min' and 'sigma_max' and random values. The calculated loss is weighted + based on the inverse of 'sigma^2'. + + Parameters: + ---------- + net: torch.nn.Module + The neural network model that will make predictions. + + images: torch.Tensor + Input images to the neural network. + + labels: torch.Tensor + Ground truth labels for the input images. + + augment_pipe: callable, optional + An optional data augmentation function that takes images as input and + returns augmented images. If not provided, no data augmentation is applied. + + Returns: + ------- + torch.Tensor + A tensor representing the loss calculated based on the network's + predictions. + """ + rnd_uniform = torch.rand([images.shape[0], 1, 1, 1], device=images.device) + sigma = self.sigma_min * ((self.sigma_max / self.sigma_min) ** rnd_uniform) + weight = 1 / sigma**2 + y, augment_labels = ( + augment_pipe(images) if augment_pipe is not None else (images, None) + ) + n = torch.randn_like(y) * sigma + D_yn = net(y + n, sigma, labels, augment_labels=augment_labels) + loss = weight * ((D_yn - y) ** 2) + return loss + + +class EDMLoss: + """ + Loss function proposed in the EDM paper. + + Parameters + ---------- + P_mean: float, optional + Mean value for `sigma` computation, by default -1.2. + P_std: float, optional: + Standard deviation for `sigma` computation, by default 1.2. + sigma_data: float, optional + Standard deviation for data, by default 0.5. + + Note + ---- + Reference: Karras, T., Aittala, M., Aila, T. and Laine, S., 2022. Elucidating the + design space of diffusion-based generative models. Advances in Neural Information + Processing Systems, 35, pp.26565-26577. + """ + + def __init__( + self, P_mean: float = -1.2, P_std: float = 1.2, sigma_data: float = 0.5 + ): + self.P_mean = P_mean + self.P_std = P_std + self.sigma_data = sigma_data + + def __call__(self, net, images, condition=None, labels=None, augment_pipe=None): + """ + Calculate and return the loss corresponding to the EDM formulation. + + The method adds random noise to the input images and calculates the loss as the + square difference between the network's predictions and the input images. + The noise level is determined by 'sigma', which is computed as a function of + 'P_mean' and 'P_std' random values. The calculated loss is weighted as a + function of 'sigma' and 'sigma_data'. + + Parameters: + ---------- + net: torch.nn.Module + The neural network model that will make predictions. + + images: torch.Tensor + Input images to the neural network. + + labels: torch.Tensor + Ground truth labels for the input images. + + augment_pipe: callable, optional + An optional data augmentation function that takes images as input and + returns augmented images. If not provided, no data augmentation is applied. + + Returns: + ------- + torch.Tensor + A tensor representing the loss calculated based on the network's + predictions. + """ + rnd_normal = torch.randn([images.shape[0], 1, 1, 1], device=images.device) + sigma = (rnd_normal * self.P_std + self.P_mean).exp() + weight = (sigma**2 + self.sigma_data**2) / (sigma * self.sigma_data) ** 2 + y, augment_labels = ( + augment_pipe(images) if augment_pipe is not None else (images, None) + ) + n = torch.randn_like(y) * sigma + if condition is not None: + D_yn = net( + y + n, + sigma, + condition=condition, + class_labels=labels, + augment_labels=augment_labels, + ) + else: + D_yn = net(y + n, sigma, labels, augment_labels=augment_labels) + loss = weight * ((D_yn - y) ** 2) + return loss + + +class EDMLossSR: + """ + Variation of the loss function proposed in the EDM paper for Super-Resolution. + + Parameters + ---------- + P_mean: float, optional + Mean value for `sigma` computation, by default -1.2. + P_std: float, optional: + Standard deviation for `sigma` computation, by default 1.2. + sigma_data: float, optional + Standard deviation for data, by default 0.5. + + Note + ---- + Reference: Mardani, M., Brenowitz, N., Cohen, Y., Pathak, J., Chen, C.Y., + Liu, C.C.,Vahdat, A., Kashinath, K., Kautz, J. and Pritchard, M., 2023. + Generative Residual Diffusion Modeling for Km-scale Atmospheric Downscaling. + arXiv preprint arXiv:2309.15214. + """ + + def __init__( + self, P_mean: float = -1.2, P_std: float = 1.2, sigma_data: float = 0.5 + ): + self.P_mean = P_mean + self.P_std = P_std + self.sigma_data = sigma_data + + def __call__(self, net, img_clean, img_lr, labels=None, augment_pipe=None): + """ + Calculate and return the loss corresponding to the EDM formulation. + + The method adds random noise to the input images and calculates the loss as the + square difference between the network's predictions and the input images. + The noise level is determined by 'sigma', which is computed as a function of + 'P_mean' and 'P_std' random values. The calculated loss is weighted as a + function of 'sigma' and 'sigma_data'. + + Parameters: + ---------- + net: torch.nn.Module + The neural network model that will make predictions. + + images: torch.Tensor + Input images to the neural network. + + labels: torch.Tensor + Ground truth labels for the input images. + + augment_pipe: callable, optional + An optional data augmentation function that takes images as input and + returns augmented images. If not provided, no data augmentation is applied. + + Returns: + ------- + torch.Tensor + A tensor representing the loss calculated based on the network's + predictions. + """ + rnd_normal = torch.randn([img_clean.shape[0], 1, 1, 1], device=img_clean.device) + sigma = (rnd_normal * self.P_std + self.P_mean).exp() + weight = (sigma**2 + self.sigma_data**2) / (sigma * self.sigma_data) ** 2 + + # augment for conditional generaiton + img_tot = torch.cat((img_clean, img_lr), dim=1) + y_tot, augment_labels = ( + augment_pipe(img_tot) if augment_pipe is not None else (img_tot, None) + ) + y = y_tot[:, : img_clean.shape[1], :, :] + y_lr = y_tot[:, img_clean.shape[1] :, :, :] + + n = torch.randn_like(y) * sigma + D_yn = net(y + n, y_lr, sigma, labels, augment_labels=augment_labels) + loss = weight * ((D_yn - y) ** 2) + return loss + + +class RegressionLoss: + """ + Regression loss function for the U-Net for deterministic predictions. + + Parameters + ---------- + P_mean: float, optional + Mean value for `sigma` computation, by default -1.2. + P_std: float, optional: + Standard deviation for `sigma` computation, by default 1.2. + sigma_data: float, optional + Standard deviation for data, by default 0.5. + + Note + ---- + Reference: Mardani, M., Brenowitz, N., Cohen, Y., Pathak, J., Chen, C.Y., + Liu, C.C.,Vahdat, A., Kashinath, K., Kautz, J. and Pritchard, M., 2023. + Generative Residual Diffusion Modeling for Km-scale Atmospheric Downscaling. + arXiv preprint arXiv:2309.15214. + """ + + def __init__( + self, P_mean: float = -1.2, P_std: float = 1.2, sigma_data: float = 0.5 + ): + self.P_mean = P_mean + self.P_std = P_std + self.sigma_data = sigma_data + + def __call__(self, net, img_clean, img_lr, labels=None, augment_pipe=None): + """ + Calculate and return the loss for the U-Net for deterministic predictions. + + Parameters: + ---------- + net: torch.nn.Module + The neural network model that will make predictions. + + img_clean: torch.Tensor + Input images (high resolution) to the neural network. + + img_lr: torch.Tensor + Input images (low resolution) to the neural network. + + labels: torch.Tensor + Ground truth labels for the input images. + + augment_pipe: callable, optional + An optional data augmentation function that takes images as input and + returns augmented images. If not provided, no data augmentation is applied. + + Returns: + ------- + torch.Tensor + A tensor representing the loss calculated based on the network's + predictions. + """ + rnd_normal = torch.randn([img_clean.shape[0], 1, 1, 1], device=img_clean.device) + sigma = (rnd_normal * self.P_std + self.P_mean).exp() + weight = ( + 1.0 # (sigma ** 2 + self.sigma_data ** 2) / (sigma * self.sigma_data) ** 2 + ) + + img_tot = torch.cat((img_clean, img_lr), dim=1) + y_tot, augment_labels = ( + augment_pipe(img_tot) if augment_pipe is not None else (img_tot, None) + ) + y = y_tot[:, : img_clean.shape[1], :, :] + y_lr = y_tot[:, img_clean.shape[1] :, :, :] + + input = torch.zeros_like(y, device=img_clean.device) + D_yn = net(input, y_lr, sigma, labels, augment_labels=augment_labels) + loss = weight * ((D_yn - y) ** 2) + + return loss + + +class ResLoss: + """ + Mixture loss function for denoising score matching. + + Parameters + ---------- + P_mean: float, optional + Mean value for `sigma` computation, by default -1.2. + P_std: float, optional: + Standard deviation for `sigma` computation, by default 1.2. + sigma_data: float, optional + Standard deviation for data, by default 0.5. + + Note + ---- + Reference: Mardani, M., Brenowitz, N., Cohen, Y., Pathak, J., Chen, C.Y., + Liu, C.C.,Vahdat, A., Kashinath, K., Kautz, J. and Pritchard, M., 2023. + Generative Residual Diffusion Modeling for Km-scale Atmospheric Downscaling. + arXiv preprint arXiv:2309.15214. + """ + + def __init__( + self, + regression_net, + img_shape_x, + img_shape_y, + patch_shape_x, + patch_shape_y, + patch_num, + P_mean: float = 0.0, + P_std: float = 1.2, + sigma_data: float = 0.5, + hr_mean_conditioning: bool = False, + ): + self.unet = regression_net + self.P_mean = P_mean + self.P_std = P_std + self.sigma_data = sigma_data + self.img_shape_x = img_shape_x + self.img_shape_y = img_shape_y + self.patch_shape_x = patch_shape_x + self.patch_shape_y = patch_shape_y + self.patch_num = patch_num + self.hr_mean_conditioning = hr_mean_conditioning + + def __call__( + self, + net, + img_clean, + img_lr, + labels=None, + lead_time_label=None, + augment_pipe=None, + ): + """ + Calculate and return the loss for denoising score matching. + + Parameters: + ---------- + net: torch.nn.Module + The neural network model that will make predictions. + + img_clean: torch.Tensor + Input images (high resolution) to the neural network. + + img_lr: torch.Tensor + Input images (low resolution) to the neural network. + + labels: torch.Tensor + Ground truth labels for the input images. + + augment_pipe: callable, optional + An optional data augmentation function that takes images as input and + returns augmented images. If not provided, no data augmentation is applied. + + Returns: + ------- + torch.Tensor + A tensor representing the loss calculated based on the network's + predictions. + """ + + rnd_normal = torch.randn([img_clean.shape[0], 1, 1, 1], device=img_clean.device) + sigma = (rnd_normal * self.P_std + self.P_mean).exp() + weight = (sigma**2 + self.sigma_data**2) / (sigma * self.sigma_data) ** 2 + + # augment for conditional generaiton + img_tot = torch.cat((img_clean, img_lr), dim=1) + y_tot, augment_labels = ( + augment_pipe(img_tot) if augment_pipe is not None else (img_tot, None) + ) + y = y_tot[:, : img_clean.shape[1], :, :] + y_lr = y_tot[:, img_clean.shape[1] :, :, :] + y_lr_res = y_lr + + # global index + b = y.shape[0] + Nx = torch.arange(self.img_shape_x).int() + Ny = torch.arange(self.img_shape_y).int() + grid = torch.stack(torch.meshgrid(Ny, Nx, indexing="ij"), dim=0)[ + None, + ].expand(b, -1, -1, -1) + + # form residual + if lead_time_label is not None: + y_mean = self.unet( + torch.zeros_like(y, device=img_clean.device), + y_lr_res, + sigma, + labels, + lead_time_label=lead_time_label, + augment_labels=augment_labels, + ) + else: + y_mean = self.unet( + torch.zeros_like(y, device=img_clean.device), + y_lr_res, + sigma, + labels, + augment_labels=augment_labels, + ) + + y = y - y_mean + + if self.hr_mean_conditioning: + y_lr = torch.cat((y_mean, y_lr), dim=1).contiguous() + global_index = None + # patchified training + # conditioning: cat(y_mean, y_lr, input_interp, pos_embd), 4+12+100+4 + if ( + self.img_shape_x != self.patch_shape_x + or self.img_shape_y != self.patch_shape_y + ): + c_in = y_lr.shape[1] + c_out = y.shape[1] + rnd_normal = torch.randn( + [img_clean.shape[0] * self.patch_num, 1, 1, 1], device=img_clean.device + ) + sigma = (rnd_normal * self.P_std + self.P_mean).exp() + weight = (sigma**2 + self.sigma_data**2) / ( + sigma * self.sigma_data + ) ** 2 + + # global interpolation + input_interp = torch.nn.functional.interpolate( + img_lr, + (self.patch_shape_y, self.patch_shape_x), + mode="bilinear", + ) + + # patch generation from a single sample (not from random samples due to memory consumption of regression) + y_new = torch.zeros( + b * self.patch_num, + c_out, + self.patch_shape_y, + self.patch_shape_x, + device=img_clean.device, + ) + y_lr_new = torch.zeros( + b * self.patch_num, + c_in + input_interp.shape[1], + self.patch_shape_y, + self.patch_shape_x, + device=img_clean.device, + ) + global_index = torch.zeros( + b * self.patch_num, + 2, + self.patch_shape_y, + self.patch_shape_x, + dtype=torch.int, + device=img_clean.device, + ) + for i in range(self.patch_num): + rnd_x = random.randint(0, self.img_shape_x - self.patch_shape_x) + rnd_y = random.randint(0, self.img_shape_y - self.patch_shape_y) + y_new[b * i : b * (i + 1),] = y[ + :, + :, + rnd_y : rnd_y + self.patch_shape_y, + rnd_x : rnd_x + self.patch_shape_x, + ] + global_index[b * i : b * (i + 1),] = grid[ + :, + :, + rnd_y : rnd_y + self.patch_shape_y, + rnd_x : rnd_x + self.patch_shape_x, + ] + y_lr_new[b * i : b * (i + 1),] = torch.cat( + ( + y_lr[ + :, + :, + rnd_y : rnd_y + self.patch_shape_y, + rnd_x : rnd_x + self.patch_shape_x, + ], + input_interp, + ), + 1, + ) + y = y_new + y_lr = y_lr_new + latent = y + torch.randn_like(y) * sigma + + if lead_time_label is not None: + D_yn = net( + latent, + y_lr, + sigma, + labels, + global_index=global_index, + lead_time_label=lead_time_label, + augment_labels=augment_labels, + ) + else: + D_yn = net( + latent, + y_lr, + sigma, + labels, + global_index=global_index, + augment_labels=augment_labels, + ) + loss = weight * ((D_yn - y) ** 2) + + return loss + + +class VELoss_dfsr: + """ + Loss function for dfsr model, modified from class VELoss. + + Parameters + ---------- + beta_start : float + Noise level at the initial step of the forward diffusion process, by default 0.0001. + beta_end : float + Noise level at the Final step of the forward diffusion process, by default 0.02. + num_diffusion_timesteps : int + Total number of forward/backward diffusion steps, by default 1000. + + + Note: + ----- + Reference: Ho J, Jain A, Abbeel P. Denoising diffusion probabilistic models. + Advances in neural information processing systems. 2020;33:6840-51. + """ + + def __init__( + self, + beta_start: float = 0.0001, + beta_end: float = 0.02, + num_diffusion_timesteps: int = 1000, + ): + # scheduler for diffusion: + self.beta_schedule = "linear" + self.beta_start = beta_start + self.beta_end = beta_end + self.num_diffusion_timesteps = num_diffusion_timesteps + betas = self.get_beta_schedule( + beta_schedule=self.beta_schedule, + beta_start=self.beta_start, + beta_end=self.beta_end, + num_diffusion_timesteps=self.num_diffusion_timesteps, + ) + self.betas = torch.from_numpy(betas).float() + self.num_timesteps = betas.shape[0] + + def get_beta_schedule( + self, beta_schedule, *, beta_start, beta_end, num_diffusion_timesteps + ): + """ + Compute the variance scheduling parameters {beta(0), ..., beta(t), ..., beta(T)} + based on the VP formulation. + + beta_schedule: str + Method to construct the sequence of beta(t)'s. + beta_start: float + Noise level at the initial step of the forward diffusion process, e.g., beta(0) + beta_end: float + Noise level at the final step of the forward diffusion process, e.g., beta(T) + num_diffusion_timesteps: int + Total number of forward/backward diffusion steps + """ + + def sigmoid(x): + return 1 / (np.exp(-x) + 1) + + if beta_schedule == "quad": + betas = ( + np.linspace( + beta_start**0.5, + beta_end**0.5, + num_diffusion_timesteps, + dtype=np.float64, + ) + ** 2 + ) + elif beta_schedule == "linear": + betas = np.linspace( + beta_start, beta_end, num_diffusion_timesteps, dtype=np.float64 + ) + elif beta_schedule == "const": + betas = beta_end * np.ones(num_diffusion_timesteps, dtype=np.float64) + elif beta_schedule == "jsd": # 1/T, 1/(T-1), 1/(T-2), ..., 1 + betas = 1.0 / np.linspace( + num_diffusion_timesteps, 1, num_diffusion_timesteps, dtype=np.float64 + ) + elif beta_schedule == "sigmoid": + betas = np.linspace(-6, 6, num_diffusion_timesteps) + betas = sigmoid(betas) * (beta_end - beta_start) + beta_start + else: + raise NotImplementedError(beta_schedule) + if betas.shape != (num_diffusion_timesteps,): + raise ValueError( + f"Expected betas to have shape ({num_diffusion_timesteps},), " + f"but got {betas.shape}" + ) + return betas + + def __call__(self, net, images, labels, augment_pipe=None): + """ + Calculate and return the loss corresponding to the variance preserving + formulation. + + The method adds random noise to the input images and calculates the loss as the + square difference between the network's predictions and the noise samples added + to the t-th step of the diffusion process. + The noise level is determined by 'beta_t' based on the given parameters 'beta_start', + 'beta_end' and the current diffusion timestep t. + + Parameters: + ---------- + net: torch.nn.Module + The neural network model that will make predictions. + + images: torch.Tensor + Input fluid flow data samples to the neural network. + + labels: torch.Tensor + Ground truth labels for the input fluid flow data samples. Not required for dfsr. + + augment_pipe: callable, optional + An optional data augmentation function that takes images as input and + returns augmented images. If not provided, no data augmentation is applied. + + Returns: + ------- + torch.Tensor + A tensor representing the loss calculated based on the network's + predictions. + """ + t = torch.randint( + low=0, high=self.num_timesteps, size=(images.size(0) // 2 + 1,) + ).to(images.device) + t = torch.cat([t, self.num_timesteps - t - 1], dim=0)[: images.size(0)] + e = torch.randn_like(images) + b = self.betas.to(images.device) + a = (1 - b).cumprod(dim=0).index_select(0, t).view(-1, 1, 1, 1) + x = images * a.sqrt() + e * (1.0 - a).sqrt() + + output = net(x, t, labels) + loss = (e - output).square() + + return loss + + +class RegressionLossCE: + """ + A regression loss function for the GEFS-HRRR model with probability channels, adapted + from RegressionLoss. In this version, probability channels are evaluated using + CrossEntropyLoss instead of MSELoss. + + Parameters + ---------- + P_mean: float, optional + Mean value for `sigma` computation, by default -1.2. + P_std: float, optional: + Standard deviation for `sigma` computation, by default 1.2. + sigma_data: float, optional + Standard deviation for data, by default 0.5. + prob_channels: list, optional + A index list of output probability channels. + + Note + ---- + Reference: Mardani, M., Brenowitz, N., Cohen, Y., Pathak, J., Chen, C.Y., + Liu, C.C.,Vahdat, A., Kashinath, K., Kautz, J. and Pritchard, M., 2023. + Generative Residual Diffusion Modeling for Km-scale Atmospheric Downscaling. + arXiv preprint arXiv:2309.15214. + """ + + def __init__( + self, + P_mean: float = -1.2, + P_std: float = 1.2, + sigma_data: float = 0.5, + prob_channels: list = [4, 5, 6, 7, 8], + ): + self.P_mean = P_mean + self.P_std = P_std + self.sigma_data = sigma_data + self.entropy = torch.nn.CrossEntropyLoss(reduction="none") + self.prob_channels = prob_channels + + def __call__( + self, + net, + img_clean, + img_lr, + lead_time_label=None, + labels=None, + augment_pipe=None, + ): + """ + Calculate and return the loss for the U-Net for deterministic predictions. + + Parameters: + ---------- + net: torch.nn.Module + The neural network model that will make predictions. + + img_clean: torch.Tensor + Input images (high resolution) to the neural network. + + img_lr: torch.Tensor + Input images (low resolution) to the neural network. + + lead_time_label: torch.Tensor + Lead time labels for input batches. + + labels: torch.Tensor + Ground truth labels for the input images. + + augment_pipe: callable, optional + An optional data augmentation function that takes images as input and + returns augmented images. If not provided, no data augmentation is applied. + + Returns: + ------- + torch.Tensor + A tensor representing the loss calculated based on the network's + predictions. + """ + all_channels = list(range(img_clean.shape[1])) # [0, 1, 2, ..., 10] + scalar_channels = [ + item for item in all_channels if item not in self.prob_channels + ] + rnd_normal = torch.randn([img_clean.shape[0], 1, 1, 1], device=img_clean.device) + sigma = (rnd_normal * self.P_std + self.P_mean).exp() + weight = ( + 1.0 # (sigma ** 2 + self.sigma_data ** 2) / (sigma * self.sigma_data) ** 2 + ) + + img_tot = torch.cat((img_clean, img_lr), dim=1) + y_tot, augment_labels = ( + augment_pipe(img_tot) if augment_pipe is not None else (img_tot, None) + ) + y = y_tot[:, : img_clean.shape[1], :, :] + y_lr = y_tot[:, img_clean.shape[1] :, :, :] + + input = torch.zeros_like(y, device=img_clean.device) + + if lead_time_label is not None: + D_yn = net( + input, + y_lr, + sigma, + labels, + lead_time_label=lead_time_label, + augment_labels=augment_labels, + ) + else: + D_yn = net( + input, + y_lr, + sigma, + labels, + augment_labels=augment_labels, + ) + loss1 = weight * ((D_yn[:, scalar_channels] - y[:, scalar_channels]) ** 2) + loss2 = ( + weight + * self.entropy(D_yn[:, self.prob_channels], y[:, self.prob_channels])[ + :, None + ] + ) + loss = torch.cat((loss1, loss2), dim=1) + return loss diff --git a/src/models/layers.py b/src/models/layers.py new file mode 100644 index 00000000..1fb3b171 --- /dev/null +++ b/src/models/layers.py @@ -0,0 +1,567 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Model architecture layers used in the paper "Elucidating the Design Space of +Diffusion-Based Generative Models". +""" + +from typing import Any, Dict, List + +import numpy as np +import torch +from einops import rearrange +from torch.nn.functional import silu + +from physicsnemo.models.diffusion import weight_init + + +class Linear(torch.nn.Module): + """ + A fully connected (dense) layer implementation. The layer's weights and biases can + be initialized using custom initialization strategies like "kaiming_normal", + and can be further scaled by factors `init_weight` and `init_bias`. + + Parameters + ---------- + in_features : int + Size of each input sample. + out_features : int + Size of each output sample. + bias : bool, optional + The biases of the layer. If set to `None`, the layer will not learn an additive + bias. By default True. + init_mode : str, optional (default="kaiming_normal") + The mode/type of initialization to use for weights and biases. Supported modes + are: + - "xavier_uniform": Xavier (Glorot) uniform initialization. + - "xavier_normal": Xavier (Glorot) normal initialization. + - "kaiming_uniform": Kaiming (He) uniform initialization. + - "kaiming_normal": Kaiming (He) normal initialization. + By default "kaiming_normal". + init_weight : float, optional + A scaling factor to multiply with the initialized weights. By default 1. + init_bias : float, optional + A scaling factor to multiply with the initialized biases. By default 0. + """ + + def __init__( + self, + in_features: int, + out_features: int, + bias: bool = True, + init_mode: str = "kaiming_normal", + init_weight: int = 1, + init_bias: int = 0, + ): + super().__init__() + self.in_features = in_features + self.out_features = out_features + init_kwargs = dict(mode=init_mode, fan_in=in_features, fan_out=out_features) + self.weight = torch.nn.Parameter( + weight_init([out_features, in_features], **init_kwargs) * init_weight + ) + self.bias = ( + torch.nn.Parameter(weight_init([out_features], **init_kwargs) * init_bias) + if bias + else None + ) + + def forward(self, x): + x = x @ self.weight.to(x.dtype).t() + if self.bias is not None: + x = x.add_(self.bias.to(x.dtype)) + return x + + +class Conv2d(torch.nn.Module): + """ + A custom 2D convolutional layer implementation with support for up-sampling, + down-sampling, and custom weight and bias initializations. The layer's weights + and biases canbe initialized using custom initialization strategies like + "kaiming_normal", and can be further scaled by factors `init_weight` and + `init_bias`. + + Parameters + ---------- + in_channels : int + Number of channels in the input image. + out_channels : int + Number of channels produced by the convolution. + kernel : int + Size of the convolving kernel. + bias : bool, optional + The biases of the layer. If set to `None`, the layer will not learn an + additive bias. By default True. + up : bool, optional + Whether to perform up-sampling. By default False. + down : bool, optional + Whether to perform down-sampling. By default False. + resample_filter : List[int], optional + Filter to be used for resampling. By default [1, 1]. + fused_resample : bool, optional + If True, performs fused up-sampling and convolution or fused down-sampling + and convolution. By default False. + init_mode : str, optional (default="kaiming_normal") + init_mode : str, optional (default="kaiming_normal") + The mode/type of initialization to use for weights and biases. Supported modes + are: + - "xavier_uniform": Xavier (Glorot) uniform initialization. + - "xavier_normal": Xavier (Glorot) normal initialization. + - "kaiming_uniform": Kaiming (He) uniform initialization. + - "kaiming_normal": Kaiming (He) normal initialization. + By default "kaiming_normal". + init_weight : float, optional + A scaling factor to multiply with the initialized weights. By default 1.0. + init_bias : float, optional + A scaling factor to multiply with the initialized biases. By default 0.0. + """ + + def __init__( + self, + in_channels: int, + out_channels: int, + kernel: int, + bias: bool = True, + up: bool = False, + down: bool = False, + resample_filter: List[int] = [1, 1], + fused_resample: bool = False, + init_mode: str = "kaiming_normal", + init_weight: float = 1.0, + init_bias: float = 0.0, + ): + if up and down: + raise ValueError("Both 'up' and 'down' cannot be true at the same time.") + + super().__init__() + self.in_channels = in_channels + self.out_channels = out_channels + self.up = up + self.down = down + self.fused_resample = fused_resample + init_kwargs = dict( + mode=init_mode, + fan_in=in_channels * kernel * kernel, + fan_out=out_channels * kernel * kernel, + ) + self.weight = ( + torch.nn.Parameter( + weight_init([out_channels, in_channels, kernel, kernel], **init_kwargs) + * init_weight + ) + if kernel + else None + ) + self.bias = ( + torch.nn.Parameter(weight_init([out_channels], **init_kwargs) * init_bias) + if kernel and bias + else None + ) + f = torch.as_tensor(resample_filter, dtype=torch.float32) + f = f.ger(f).unsqueeze(0).unsqueeze(1) / f.sum().square() + self.register_buffer("resample_filter", f if up or down else None) + + def forward(self, x): + w = self.weight.to(x.dtype) if self.weight is not None else None + b = self.bias.to(x.dtype) if self.bias is not None else None + f = ( + self.resample_filter.to(x.dtype) + if self.resample_filter is not None + else None + ) + w_pad = w.shape[-1] // 2 if w is not None else 0 + f_pad = (f.shape[-1] - 1) // 2 if f is not None else 0 + + if self.fused_resample and self.up and w is not None: + x = torch.nn.functional.conv_transpose2d( + x, + f.mul(4).tile([self.in_channels, 1, 1, 1]), + groups=self.in_channels, + stride=2, + padding=max(f_pad - w_pad, 0), + ) + x = torch.nn.functional.conv2d(x, w, padding=max(w_pad - f_pad, 0)) + elif self.fused_resample and self.down and w is not None: + x = torch.nn.functional.conv2d(x, w, padding=w_pad + f_pad) + x = torch.nn.functional.conv2d( + x, + f.tile([self.out_channels, 1, 1, 1]), + groups=self.out_channels, + stride=2, + ) + else: + if self.up: + x = torch.nn.functional.conv_transpose2d( + x, + f.mul(4).tile([self.in_channels, 1, 1, 1]), + groups=self.in_channels, + stride=2, + padding=f_pad, + ) + if self.down: + x = torch.nn.functional.conv2d( + x, + f.tile([self.in_channels, 1, 1, 1]), + groups=self.in_channels, + stride=2, + padding=f_pad, + ) + if w is not None: + x = torch.nn.functional.conv2d(x, w, padding=w_pad) + if b is not None: + x = x.add_(b.reshape(1, -1, 1, 1)) + return x + + +class GroupNorm(torch.nn.Module): + """ + A custom Group Normalization layer implementation. + + Group Normalization (GN) divides the channels of the input tensor into groups and + normalizes the features within each group independently. It does not require the + batch size as in Batch Normalization, making itsuitable for batch sizes of any size + or even for batch-free scenarios. + + Parameters + ---------- + num_channels : int + Number of channels in the input tensor. + num_groups : int, optional + Desired number of groups to divide the input channels, by default 32. + This might be adjusted based on the `min_channels_per_group`. + min_channels_per_group : int, optional + Minimum channels required per group. This ensures that no group has fewer + channels than this number. By default 4. + eps : float, optional + A small number added to the variance to prevent division by zero, by default + 1e-5. + + Notes + ----- + If `num_channels` is not divisible by `num_groups`, the actual number of groups + might be adjusted to satisfy the `min_channels_per_group` condition. + """ + + def __init__( + self, + num_channels: int, + num_groups: int = 32, + min_channels_per_group: int = 4, + eps: float = 1e-5, + ): + super().__init__() + self.num_groups = min(num_groups, num_channels // min_channels_per_group) + self.eps = eps + self.weight = torch.nn.Parameter(torch.ones(num_channels)) + self.bias = torch.nn.Parameter(torch.zeros(num_channels)) + + def forward(self, x): + if self.training: + # Use default torch implementation of GroupNorm for training + # This does not support channels last memory format + x = torch.nn.functional.group_norm( + x, + num_groups=self.num_groups, + weight=self.weight.to(x.dtype), + bias=self.bias.to(x.dtype), + eps=self.eps, + ) + else: + # Use custom GroupNorm implementation that supports channels last + # memory layout for inference + dtype = x.dtype + x = x.float() + x = rearrange(x, "b (g c) h w -> b g c h w", g=self.num_groups) + + mean = x.mean(dim=[2, 3, 4], keepdim=True) + var = x.var(dim=[2, 3, 4], keepdim=True) + + x = (x - mean) * (var + self.eps).rsqrt() + x = rearrange(x, "b g c h w -> b (g c) h w") + + weight = rearrange(self.weight, "c -> 1 c 1 1") + bias = rearrange(self.bias, "c -> 1 c 1 1") + x = x * weight + bias + + x = x.type(dtype) + return x + + +class AttentionOp(torch.autograd.Function): + """ + Attention weight computation, i.e., softmax(Q^T * K). + Performs all computation using FP32, but uses the original datatype for + inputs/outputs/gradients to conserve memory. + """ + + @staticmethod + def forward(ctx, q, k): + w = ( + torch.einsum( + "ncq,nck->nqk", + q.to(torch.float32), + (k / torch.sqrt(torch.tensor(k.shape[1]))).to(torch.float32), + ) + .softmax(dim=2) + .to(q.dtype) + ) + ctx.save_for_backward(q, k, w) + return w + + @staticmethod + def backward(ctx, dw): + q, k, w = ctx.saved_tensors + db = torch._softmax_backward_data( + grad_output=dw.to(torch.float32), + output=w.to(torch.float32), + dim=2, + input_dtype=torch.float32, + ) + dq = torch.einsum("nck,nqk->ncq", k.to(torch.float32), db).to( + q.dtype + ) / np.sqrt(k.shape[1]) + dk = torch.einsum("ncq,nqk->nck", q.to(torch.float32), db).to( + k.dtype + ) / np.sqrt(k.shape[1]) + return dq, dk + + +class UNetBlock(torch.nn.Module): + """ + Unified U-Net block with optional up/downsampling and self-attention. Represents + the union of all features employed by the DDPM++, NCSN++, and ADM architectures. + + Parameters: + ----------- + in_channels : int + Number of input channels. + out_channels : int + Number of output channels. + emb_channels : int + Number of embedding channels. + up : bool, optional + If True, applies upsampling in the forward pass. By default False. + down : bool, optional + If True, applies downsampling in the forward pass. By default False. + attention : bool, optional + If True, enables the self-attention mechanism in the block. By default False. + num_heads : int, optional + Number of attention heads. If None, defaults to `out_channels // 64`. + channels_per_head : int, optional + Number of channels per attention head. By default 64. + dropout : float, optional + Dropout probability. By default 0.0. + skip_scale : float, optional + Scale factor applied to skip connections. By default 1.0. + eps : float, optional + Epsilon value used for normalization layers. By default 1e-5. + resample_filter : List[int], optional + Filter for resampling layers. By default [1, 1]. + resample_proj : bool, optional + If True, resampling projection is enabled. By default False. + adaptive_scale : bool, optional + If True, uses adaptive scaling in the forward pass. By default True. + init : dict, optional + Initialization parameters for convolutional and linear layers. + init_zero : dict, optional + Initialization parameters with zero weights for certain layers. By default + {'init_weight': 0}. + init_attn : dict, optional + Initialization parameters specific to attention mechanism layers. + Defaults to 'init' if not provided. + """ + + def __init__( + self, + in_channels: int, + out_channels: int, + emb_channels: int, + up: bool = False, + down: bool = False, + attention: bool = False, + num_heads: int = None, + channels_per_head: int = 64, + dropout: float = 0.0, + skip_scale: float = 1.0, + eps: float = 1e-5, + resample_filter: List[int] = [1, 1], + resample_proj: bool = False, + adaptive_scale: bool = True, + init: Dict[str, Any] = dict(), + init_zero: Dict[str, Any] = dict(init_weight=0), + init_attn: Any = None, + ): + super().__init__() + + self.in_channels = in_channels + self.out_channels = out_channels + self.emb_channels = emb_channels + self.num_heads = ( + 0 + if not attention + else num_heads + if num_heads is not None + else out_channels // channels_per_head + ) + self.dropout = dropout + self.skip_scale = skip_scale + self.adaptive_scale = adaptive_scale + + self.norm0 = GroupNorm(num_channels=in_channels, eps=eps) + self.conv0 = Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel=3, + up=up, + down=down, + resample_filter=resample_filter, + **init, + ) + self.affine = Linear( + in_features=emb_channels, + out_features=out_channels * (2 if adaptive_scale else 1), + **init, + ) + self.norm1 = GroupNorm(num_channels=out_channels, eps=eps) + self.conv1 = Conv2d( + in_channels=out_channels, out_channels=out_channels, kernel=3, **init_zero + ) + + self.skip = None + if out_channels != in_channels or up or down: + kernel = 1 if resample_proj or out_channels != in_channels else 0 + self.skip = Conv2d( + in_channels=in_channels, + out_channels=out_channels, + kernel=kernel, + up=up, + down=down, + resample_filter=resample_filter, + **init, + ) + + if self.num_heads: + self.norm2 = GroupNorm(num_channels=out_channels, eps=eps) + self.qkv = Conv2d( + in_channels=out_channels, + out_channels=out_channels * 3, + kernel=1, + **(init_attn if init_attn is not None else init), + ) + self.proj = Conv2d( + in_channels=out_channels, + out_channels=out_channels, + kernel=1, + **init_zero, + ) + + def forward(self, x, emb): + torch.cuda.nvtx.range_push("UNetBlock") + orig = x + x = self.conv0(silu(self.norm0(x))) + + params = self.affine(emb).unsqueeze(2).unsqueeze(3).to(x.dtype) + if self.adaptive_scale: + scale, shift = params.chunk(chunks=2, dim=1) + x = silu(torch.addcmul(shift, self.norm1(x), scale + 1)) + else: + x = silu(self.norm1(x.add_(params))) + + x = self.conv1( + torch.nn.functional.dropout(x, p=self.dropout, training=self.training) + ) + x = x.add_(self.skip(orig) if self.skip is not None else orig) + x = x * self.skip_scale + + if self.num_heads: + q, k, v = ( + self.qkv(self.norm2(x)) + .reshape( + x.shape[0] * self.num_heads, x.shape[1] // self.num_heads, 3, -1 + ) + .unbind(2) + ) + w = AttentionOp.apply(q, k) + a = torch.einsum("nqk,nck->ncq", w, v) + x = self.proj(a.reshape(*x.shape)).add_(x) + x = x * self.skip_scale + torch.cuda.nvtx.range_pop() + return x + + +class PositionalEmbedding(torch.nn.Module): + """ + A module for generating positional embeddings based on timesteps. + This embedding technique is employed in the DDPM++ and ADM architectures. + + Parameters: + ----------- + num_channels : int + Number of channels for the embedding. + max_positions : int, optional + Maximum number of positions for the embeddings, by default 10000. + endpoint : bool, optional + If True, the embedding considers the endpoint. By default False. + + """ + + def __init__( + self, num_channels: int, max_positions: int = 10000, endpoint: bool = False + ): + super().__init__() + self.num_channels = num_channels + self.max_positions = max_positions + self.endpoint = endpoint + + def forward(self, x): + freqs = torch.arange( + start=0, end=self.num_channels // 2, dtype=torch.float32, device=x.device + ) + freqs = freqs / (self.num_channels // 2 - (1 if self.endpoint else 0)) + freqs = (1 / self.max_positions) ** freqs + x = x.ger(freqs.to(x.dtype)) + x = torch.cat([x.cos(), x.sin()], dim=1) + return x + + +class FourierEmbedding(torch.nn.Module): + """ + Generates Fourier embeddings for timesteps, primarily used in the NCSN++ + architecture. + + This class generates embeddings by first multiplying input tensor `x` and + internally stored random frequencies, and then concatenating the cosine and sine of + the resultant. + + Parameters: + ----------- + num_channels : int + The number of channels in the embedding. The final embedding size will be + 2 * num_channels because of concatenation of cosine and sine results. + scale : int, optional + A scale factor applied to the random frequencies, controlling their range + and thereby the frequency of oscillations in the embedding space. By default 16. + """ + + def __init__(self, num_channels: int, scale: int = 16): + super().__init__() + self.register_buffer("freqs", torch.randn(num_channels // 2) * scale) + + def forward(self, x): + x = x.ger((2 * np.pi * self.freqs).to(x.dtype)) + x = torch.cat([x.cos(), x.sin()], dim=1) + return x diff --git a/src/models/preconditioning copy.py b/src/models/preconditioning copy.py new file mode 100644 index 00000000..52a16608 --- /dev/null +++ b/src/models/preconditioning copy.py @@ -0,0 +1,1176 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Preconditioning schemes used in the paper"Elucidating the Design Space of +Diffusion-Based Generative Models". +""" + +import importlib +import warnings +from dataclasses import dataclass +from typing import List, Union + +import numpy as np +import nvtx +import torch + +from physicsnemo.models.diffusion import ( + DhariwalUNet, # noqa: F401 for globals + SongUNet, # noqa: F401 for globals +) +from physicsnemo.models.meta import ModelMetaData +from physicsnemo.models.module import Module + +network_module = importlib.import_module("physicsnemo.models.diffusion") + + +@dataclass +class VPPrecondMetaData(ModelMetaData): + """VPPrecond meta data""" + + name: str = "VPPrecond" + # Optimization + jit: bool = False + cuda_graphs: bool = False + amp_cpu: bool = False + amp_gpu: bool = True + torch_fx: bool = False + # Data type + bf16: bool = False + # Inference + onnx: bool = False + # Physics informed + func_torch: bool = False + auto_grad: bool = False + + +class VPPrecond(Module): + """ + Preconditioning corresponding to the variance preserving (VP) formulation. + + Parameters + ---------- + img_resolution : int + Image resolution. + img_channels : int + Number of color channels. + label_dim : int + Number of class labels, 0 = unconditional, by default 0. + use_fp16 : bool + Execute the underlying model at FP16 precision?, by default False. + beta_d : float + Extent of the noise level schedule, by default 19.9. + beta_min : float + Initial slope of the noise level schedule, by default 0.1. + M : int + Original number of timesteps in the DDPM formulation, by default 1000. + epsilon_t : float + Minimum t-value used during training, by default 1e-5. + model_type :str + Class name of the underlying model, by default "SongUNet". + **model_kwargs : dict + Keyword arguments for the underlying model. + + Note + ---- + Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and + Poole, B., 2020. Score-based generative modeling through stochastic differential + equations. arXiv preprint arXiv:2011.13456. + """ + + def __init__( + self, + img_resolution: int, + img_channels: int, + label_dim: int = 0, + use_fp16: bool = False, + beta_d: float = 19.9, + beta_min: float = 0.1, + M: int = 1000, + epsilon_t: float = 1e-5, + model_type: str = "SongUNet", + **model_kwargs: dict, + ): + super().__init__(meta=VPPrecondMetaData) + self.img_resolution = img_resolution + self.img_channels = img_channels + self.label_dim = label_dim + self.use_fp16 = use_fp16 + self.beta_d = beta_d + self.beta_min = beta_min + self.M = M + self.epsilon_t = epsilon_t + self.sigma_min = float(self.sigma(epsilon_t)) + self.sigma_max = float(self.sigma(1)) + model_class = getattr(network_module, model_type) + self.model = model_class( + img_resolution=img_resolution, + in_channels=img_channels, + out_channels=img_channels, + label_dim=label_dim, + **model_kwargs, + ) # TODO needs better handling + + def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): + x = x.to(torch.float32) + sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) + class_labels = ( + None + if self.label_dim == 0 + else torch.zeros([1, self.label_dim], device=x.device) + if class_labels is None + else class_labels.to(torch.float32).reshape(-1, self.label_dim) + ) + dtype = ( + torch.float16 + if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") + else torch.float32 + ) + + c_skip = 1 + c_out = -sigma + c_in = 1 / (sigma**2 + 1).sqrt() + c_noise = (self.M - 1) * self.sigma_inv(sigma) + + F_x = self.model( + (c_in * x).to(dtype), + c_noise.flatten(), + class_labels=class_labels, + **model_kwargs, + ) + if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): + raise ValueError( + f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + ) + + D_x = c_skip * x + c_out * F_x.to(torch.float32) + return D_x + + def sigma(self, t: Union[float, torch.Tensor]): + """ + Compute the sigma(t) value for a given t based on the VP formulation. + + The function calculates the noise level schedule for the diffusion process based + on the given parameters `beta_d` and `beta_min`. + + Parameters + ---------- + t : Union[float, torch.Tensor] + The timestep or set of timesteps for which to compute sigma(t). + + Returns + ------- + torch.Tensor + The computed sigma(t) value(s). + """ + t = torch.as_tensor(t) + return ((0.5 * self.beta_d * (t**2) + self.beta_min * t).exp() - 1).sqrt() + + def sigma_inv(self, sigma: Union[float, torch.Tensor]): + """ + Compute the inverse of the sigma function for a given sigma. + + This function effectively calculates t from a given sigma(t) based on the + parameters `beta_d` and `beta_min`. + + Parameters + ---------- + sigma : Union[float, torch.Tensor] + The sigma(t) value or set of sigma(t) values for which to compute the + inverse. + + Returns + ------- + torch.Tensor + The computed t value(s) corresponding to the provided sigma(t). + """ + sigma = torch.as_tensor(sigma) + return ( + (self.beta_min**2 + 2 * self.beta_d * (1 + sigma**2).log()).sqrt() + - self.beta_min + ) / self.beta_d + + def round_sigma(self, sigma: Union[float, List, torch.Tensor]): + """ + Convert a given sigma value(s) to a tensor representation. + + Parameters + ---------- + sigma : Union[float list, torch.Tensor] + The sigma value(s) to convert. + + Returns + ------- + torch.Tensor + The tensor representation of the provided sigma value(s). + """ + return torch.as_tensor(sigma) + + +@dataclass +class VEPrecondMetaData(ModelMetaData): + """VEPrecond meta data""" + + name: str = "VEPrecond" + # Optimization + jit: bool = False + cuda_graphs: bool = False + amp_cpu: bool = False + amp_gpu: bool = True + torch_fx: bool = False + # Data type + bf16: bool = False + # Inference + onnx: bool = False + # Physics informed + func_torch: bool = False + auto_grad: bool = False + + +class VEPrecond(Module): + """ + Preconditioning corresponding to the variance exploding (VE) formulation. + + Parameters + ---------- + img_resolution : int + Image resolution. + img_channels : int + Number of color channels. + label_dim : int + Number of class labels, 0 = unconditional, by default 0. + use_fp16 : bool + Execute the underlying model at FP16 precision?, by default False. + sigma_min : float + Minimum supported noise level, by default 0.02. + sigma_max : float + Maximum supported noise level, by default 100.0. + model_type :str + Class name of the underlying model, by default "SongUNet". + **model_kwargs : dict + Keyword arguments for the underlying model. + + Note + ---- + Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and + Poole, B., 2020. Score-based generative modeling through stochastic differential + equations. arXiv preprint arXiv:2011.13456. + """ + + def __init__( + self, + img_resolution: int, + img_channels: int, + label_dim: int = 0, + use_fp16: bool = False, + sigma_min: float = 0.02, + sigma_max: float = 100.0, + model_type: str = "SongUNet", + **model_kwargs: dict, + ): + super().__init__(meta=VEPrecondMetaData) + self.img_resolution = img_resolution + self.img_channels = img_channels + self.label_dim = label_dim + self.use_fp16 = use_fp16 + self.sigma_min = sigma_min + self.sigma_max = sigma_max + model_class = getattr(network_module, model_type) + self.model = model_class( + img_resolution=img_resolution, + in_channels=img_channels, + out_channels=img_channels, + label_dim=label_dim, + **model_kwargs, + ) # TODO needs better handling + + def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): + x = x.to(torch.float32) + sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) + class_labels = ( + None + if self.label_dim == 0 + else torch.zeros([1, self.label_dim], device=x.device) + if class_labels is None + else class_labels.to(torch.float32).reshape(-1, self.label_dim) + ) + dtype = ( + torch.float16 + if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") + else torch.float32 + ) + + c_skip = 1 + c_out = sigma + c_in = 1 + c_noise = (0.5 * sigma).log() + + F_x = self.model( + (c_in * x).to(dtype), + c_noise.flatten(), + class_labels=class_labels, + **model_kwargs, + ) + if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): + raise ValueError( + f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + ) + + D_x = c_skip * x + c_out * F_x.to(torch.float32) + return D_x + + def round_sigma(self, sigma: Union[float, List, torch.Tensor]): + """ + Convert a given sigma value(s) to a tensor representation. + + Parameters + ---------- + sigma : Union[float list, torch.Tensor] + The sigma value(s) to convert. + + Returns + ------- + torch.Tensor + The tensor representation of the provided sigma value(s). + """ + return torch.as_tensor(sigma) + + +@dataclass +class iDDPMPrecondMetaData(ModelMetaData): + """iDDPMPrecond meta data""" + + name: str = "iDDPMPrecond" + # Optimization + jit: bool = False + cuda_graphs: bool = False + amp_cpu: bool = False + amp_gpu: bool = True + torch_fx: bool = False + # Data type + bf16: bool = False + # Inference + onnx: bool = False + # Physics informed + func_torch: bool = False + auto_grad: bool = False + + +class iDDPMPrecond(Module): + """ + Preconditioning corresponding to the improved DDPM (iDDPM) formulation. + + Parameters + ---------- + img_resolution : int + Image resolution. + img_channels : int + Number of color channels. + label_dim : int + Number of class labels, 0 = unconditional, by default 0. + use_fp16 : bool + Execute the underlying model at FP16 precision?, by default False. + C_1 : float + Timestep adjustment at low noise levels., by default 0.001. + C_2 : float + Timestep adjustment at high noise levels., by default 0.008. + M: int + Original number of timesteps in the DDPM formulation, by default 1000. + model_type :str + Class name of the underlying model, by default "DhariwalUNet". + **model_kwargs : dict + Keyword arguments for the underlying model. + + Note + ---- + Reference: Nichol, A.Q. and Dhariwal, P., 2021, July. Improved denoising diffusion + probabilistic models. In International Conference on Machine Learning + (pp. 8162-8171). PMLR. + """ + + def __init__( + self, + img_resolution, + img_channels, + label_dim=0, + use_fp16=False, + C_1=0.001, + C_2=0.008, + M=1000, + model_type="DhariwalUNet", + **model_kwargs, + ): + super().__init__(meta=iDDPMPrecondMetaData) + self.img_resolution = img_resolution + self.img_channels = img_channels + self.label_dim = label_dim + self.use_fp16 = use_fp16 + self.C_1 = C_1 + self.C_2 = C_2 + self.M = M + model_class = getattr(network_module, model_type) + self.model = model_class( + img_resolution=img_resolution, + in_channels=img_channels, + out_channels=img_channels * 2, + label_dim=label_dim, + **model_kwargs, + ) # TODO needs better handling + + u = torch.zeros(M + 1) + for j in range(M, 0, -1): # M, ..., 1 + u[j - 1] = ( + (u[j] ** 2 + 1) + / (self.alpha_bar(j - 1) / self.alpha_bar(j)).clip(min=C_1) + - 1 + ).sqrt() + self.register_buffer("u", u) + self.sigma_min = float(u[M - 1]) + self.sigma_max = float(u[0]) + + def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): + x = x.to(torch.float32) + sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) + class_labels = ( + None + if self.label_dim == 0 + else torch.zeros([1, self.label_dim], device=x.device) + if class_labels is None + else class_labels.to(torch.float32).reshape(-1, self.label_dim) + ) + dtype = ( + torch.float16 + if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") + else torch.float32 + ) + + c_skip = 1 + c_out = -sigma + c_in = 1 / (sigma**2 + 1).sqrt() + c_noise = ( + self.M - 1 - self.round_sigma(sigma, return_index=True).to(torch.float32) + ) + + F_x = self.model( + (c_in * x).to(dtype), + c_noise.flatten(), + class_labels=class_labels, + **model_kwargs, + ) + if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): + raise ValueError( + f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + ) + + D_x = c_skip * x + c_out * F_x[:, : self.img_channels].to(torch.float32) + return D_x + + def alpha_bar(self, j): + """ + Compute the alpha_bar(j) value for a given j based on the iDDPM formulation. + + Parameters + ---------- + j : Union[int, torch.Tensor] + The timestep or set of timesteps for which to compute alpha_bar(j). + + Returns + ------- + torch.Tensor + The computed alpha_bar(j) value(s). + """ + j = torch.as_tensor(j) + return (0.5 * np.pi * j / self.M / (self.C_2 + 1)).sin() ** 2 + + def round_sigma(self, sigma, return_index=False): + """ + Round the provided sigma value(s) to the nearest value(s) in a + pre-defined set `u`. + + Parameters + ---------- + sigma : Union[float, list, torch.Tensor] + The sigma value(s) to round. + return_index : bool, optional + Whether to return the index/indices of the rounded value(s) in `u` instead + of the rounded value(s) themselves, by default False. + + Returns + ------- + torch.Tensor + The rounded sigma value(s) or their index/indices in `u`, depending on the + value of `return_index`. + """ + sigma = torch.as_tensor(sigma) + index = torch.cdist( + sigma.to(self.u.device).to(torch.float32).reshape(1, -1, 1), + self.u.reshape(1, -1, 1), + ).argmin(2) + result = index if return_index else self.u[index.flatten()].to(sigma.dtype) + return result.reshape(sigma.shape).to(sigma.device) + + +@dataclass +class EDMPrecondMetaData(ModelMetaData): + """EDMPrecond meta data""" + + name: str = "EDMPrecond" + # Optimization + jit: bool = False + cuda_graphs: bool = False + amp_cpu: bool = False + amp_gpu: bool = True + torch_fx: bool = False + # Data type + bf16: bool = False + # Inference + onnx: bool = False + # Physics informed + func_torch: bool = False + auto_grad: bool = False + + +class EDMPrecond(Module): + """ + Improved preconditioning proposed in the paper "Elucidating the Design Space of + Diffusion-Based Generative Models" (EDM) + + Parameters + ---------- + img_resolution : int + Image resolution. + img_channels : int + Number of color channels (for both input and output). If your model + requires a different number of input or output chanels, + override this by passing either of the optional + img_in_channels or img_out_channels args + label_dim : int + Number of class labels, 0 = unconditional, by default 0. + use_fp16 : bool + Execute the underlying model at FP16 precision?, by default False. + sigma_min : float + Minimum supported noise level, by default 0.0. + sigma_max : float + Maximum supported noise level, by default inf. + sigma_data : float + Expected standard deviation of the training data, by default 0.5. + model_type :str + Class name of the underlying model, by default "DhariwalUNet". + img_in_channels: int + Optional setting for when number of input channels =/= number of output + channels. If set, will override img_channels for the input + This is useful in the case of additional (conditional) channels + img_out_channels: int + Optional setting for when number of input channels =/= number of output + channels. If set, will override img_channels for the output + **model_kwargs : dict + Keyword arguments for the underlying model. + + Note + ---- + Reference: Karras, T., Aittala, M., Aila, T. and Laine, S., 2022. Elucidating the + design space of diffusion-based generative models. Advances in Neural Information + Processing Systems, 35, pp.26565-26577. + """ + + def __init__( + self, + img_resolution, + img_channels, + label_dim=0, + use_fp16=False, + sigma_min=0.0, + sigma_max=float("inf"), + sigma_data=0.5, + model_type="DhariwalUNet", + img_in_channels=None, + img_out_channels=None, + **model_kwargs, + ): + super().__init__(meta=EDMPrecondMetaData) + self.img_resolution = img_resolution + if img_in_channels is not None: + img_in_channels = img_in_channels + else: + img_in_channels = img_channels + if img_out_channels is not None: + img_out_channels = img_out_channels + else: + img_out_channels = img_channels + + self.label_dim = label_dim + self.use_fp16 = use_fp16 + self.sigma_min = sigma_min + self.sigma_max = sigma_max + self.sigma_data = sigma_data + + model_class = getattr(network_module, model_type) + self.model = model_class( + img_resolution=img_resolution, + in_channels=img_in_channels, + out_channels=img_out_channels, + label_dim=label_dim, + **model_kwargs, + ) # TODO needs better handling + + def forward( + self, + x, + sigma, + condition=None, + class_labels=None, + force_fp32=False, + **model_kwargs, + ): + x = x.to(torch.float32) + sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) + class_labels = ( + None + if self.label_dim == 0 + else torch.zeros([1, self.label_dim], device=x.device) + if class_labels is None + else class_labels.to(torch.float32).reshape(-1, self.label_dim) + ) + dtype = ( + torch.float16 + if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") + else torch.float32 + ) + + c_skip = self.sigma_data**2 / (sigma**2 + self.sigma_data**2) + c_out = sigma * self.sigma_data / (sigma**2 + self.sigma_data**2).sqrt() + c_in = 1 / (self.sigma_data**2 + sigma**2).sqrt() + c_noise = sigma.log() / 4 + + arg = c_in * x + + if condition is not None: + arg = torch.cat([arg, condition], dim=1) + + F_x = self.model( + arg.to(dtype), + c_noise.flatten(), + class_labels=class_labels, + **model_kwargs, + ) + + if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): + raise ValueError( + f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + ) + D_x = c_skip * x + c_out * F_x.to(torch.float32) + return D_x + + @staticmethod + def round_sigma(sigma: Union[float, List, torch.Tensor]): + """ + Convert a given sigma value(s) to a tensor representation. + + Parameters + ---------- + sigma : Union[float list, torch.Tensor] + The sigma value(s) to convert. + + Returns + ------- + torch.Tensor + The tensor representation of the provided sigma value(s). + """ + return torch.as_tensor(sigma) + + +@dataclass +class EDMPrecondSRMetaData(ModelMetaData): + """EDMPrecondSR meta data""" + + name: str = "EDMPrecondSR" + # Optimization + jit: bool = False + cuda_graphs: bool = False + amp_cpu: bool = False + amp_gpu: bool = True + torch_fx: bool = False + # Data type + bf16: bool = False + # Inference + onnx: bool = False + # Physics informed + func_torch: bool = False + auto_grad: bool = False + + +class EDMPrecondSR(Module): + """ + Improved preconditioning proposed in the paper "Elucidating the Design Space of + Diffusion-Based Generative Models" (EDM) for super-resolution tasks + + Parameters + ---------- + img_resolution : int + Image resolution. + img_channels : int + Number of color channels. + img_in_channels : int + Number of input color channels. + img_out_channels : int + Number of output color channels. + use_fp16 : bool + Execute the underlying model at FP16 precision?, by default False. + sigma_min : float + Minimum supported noise level, by default 0.0. + sigma_max : float + Maximum supported noise level, by default inf. + sigma_data : float + Expected standard deviation of the training data, by default 0.5. + model_type :str + Class name of the underlying model, by default "SongUNetPosEmbd". + **model_kwargs : dict + Keyword arguments for the underlying model. + + Note + ---- + References: + - Karras, T., Aittala, M., Aila, T. and Laine, S., 2022. Elucidating the + design space of diffusion-based generative models. Advances in Neural Information + Processing Systems, 35, pp.26565-26577. + - Mardani, M., Brenowitz, N., Cohen, Y., Pathak, J., Chen, C.Y., + Liu, C.C.,Vahdat, A., Kashinath, K., Kautz, J. and Pritchard, M., 2023. + Generative Residual Diffusion Modeling for Km-scale Atmospheric Downscaling. + arXiv preprint arXiv:2309.15214. + """ + + def __init__( + self, + img_resolution, + img_channels, + img_in_channels, + img_out_channels, + use_fp16=False, + sigma_min=0.0, + sigma_max=float("inf"), + sigma_data=0.5, + model_type="SongUNetPosEmbd", + scale_cond_input=True, + **model_kwargs, + ): + super().__init__(meta=EDMPrecondSRMetaData) + self.img_resolution = img_resolution + self.img_channels = img_channels # TODO: this is not used, remove it + self.img_in_channels = img_in_channels + self.img_out_channels = img_out_channels + self.use_fp16 = use_fp16 + self.sigma_min = sigma_min + self.sigma_max = sigma_max + self.sigma_data = sigma_data + self.scale_cond_input = scale_cond_input + + model_class = getattr(network_module, model_type) + self.model = model_class( + img_resolution=img_resolution, + in_channels=img_in_channels + img_out_channels, + out_channels=img_out_channels, + **model_kwargs, + ) # TODO needs better handling + self.scaling_fn = self._get_scaling_fn() + + def _get_scaling_fn(self): + if self.scale_cond_input: + warnings.warn( + "scale_cond_input=True does not properly scale the conditional input. " + "(see https://github.com/NVIDIA/modulus/issues/229). " + "This setup will be deprecated. " + "Please set scale_cond_input=False.", + DeprecationWarning, + ) + return self._legacy_scaling_fn + else: + return self._scaling_fn + + @staticmethod + def _scaling_fn(x, img_lr, c_in): + return torch.cat([c_in * x, img_lr.to(x.dtype)], dim=1) + + @staticmethod + def _legacy_scaling_fn(x, img_lr, c_in): + return c_in * torch.cat([x, img_lr.to(x.dtype)], dim=1) + + @nvtx.annotate(message="EDMPrecondSR", color="orange") + def forward( + self, + x, + img_lr, + sigma, + force_fp32=False, + **model_kwargs, + ): + # Concatenate input channels + x = x.to(torch.float32) + sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) + dtype = ( + torch.float16 + if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") + else torch.float32 + ) + + c_skip = self.sigma_data**2 / (sigma**2 + self.sigma_data**2) + c_out = sigma * self.sigma_data / (sigma**2 + self.sigma_data**2).sqrt() + c_in = 1 / (self.sigma_data**2 + sigma**2).sqrt() + c_noise = sigma.log() / 4 + + if img_lr is None: + arg = c_in * x + else: + arg = self.scaling_fn(x, img_lr, c_in) + arg = arg.to(dtype) + + F_x = self.model( + arg, + c_noise.flatten(), + class_labels=None, + **model_kwargs, + ) + + if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): + raise ValueError( + f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + ) + + D_x = c_skip * x + c_out * F_x.to(torch.float32) + return D_x + + @staticmethod + def round_sigma(sigma: Union[float, List, torch.Tensor]): + """ + Convert a given sigma value(s) to a tensor representation. + See EDMPrecond.round_sigma + """ + return EDMPrecond.round_sigma(sigma) + + +class VEPrecond_dfsr(torch.nn.Module): + """ + Preconditioning for dfsr model, modified from class VEPrecond, where the input + argument 'sigma' in forward propagation function is used to receive the timestep + of the backward diffusion process. + + Parameters + ---------- + img_resolution : int + Image resolution. + img_channels : int + Number of color channels. + label_dim : int + Number of class labels, 0 = unconditional, by default 0. + use_fp16 : bool + Execute the underlying model at FP16 precision?, by default False. + sigma_min : float + Minimum supported noise level, by default 0.02. + sigma_max : float + Maximum supported noise level, by default 100.0. + model_type :str + Class name of the underlying model, by default "SongUNet". + **model_kwargs : dict + Keyword arguments for the underlying model. + + Note + ---- + Reference: Ho J, Jain A, Abbeel P. Denoising diffusion probabilistic models. + Advances in neural information processing systems. 2020;33:6840-51. + """ + + def __init__( + self, + img_resolution: int, + img_channels: int, + label_dim: int = 0, + use_fp16: bool = False, + sigma_min: float = 0.02, + sigma_max: float = 100.0, + dataset_mean: float = 5.85e-05, + dataset_scale: float = 4.79, + model_type: str = "SongUNet", + **model_kwargs: dict, + ): + super().__init__() + self.img_resolution = img_resolution + self.img_channels = img_channels + self.label_dim = label_dim + self.use_fp16 = use_fp16 + self.model = globals()[model_type]( + img_resolution=img_resolution, + in_channels=self.img_channels, + out_channels=img_channels, + label_dim=label_dim, + **model_kwargs, + ) # TODO needs better handling + + def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): + x = x.to(torch.float32) + sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) + # print("sigma: ", sigma) + class_labels = ( + None + if self.label_dim == 0 + else torch.zeros([1, self.label_dim], device=x.device) + if class_labels is None + else class_labels.to(torch.float32).reshape(-1, self.label_dim) + ) + dtype = ( + torch.float16 + if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") + else torch.float32 + ) + + c_in = 1 + c_noise = sigma # Change the definitation of c_noise to avoid -inf values for zero sigma + + F_x = self.model( + (c_in * x).to(dtype), + c_noise.flatten(), + class_labels=class_labels, + **model_kwargs, + ) + + if F_x.dtype != dtype: + raise ValueError( + f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + ) + + return F_x + + +class VEPrecond_dfsr_cond(torch.nn.Module): + """ + Preconditioning for dfsr model with physics-informed conditioning input, modified + from class VEPrecond, where the input argument 'sigma' in forward propagation function + is used to receive the timestep of the backward diffusion process. The gradient of PDE + residual with respect to the vorticity in the governing Navier-Stokes equation is computed + as the physics-informed conditioning variable and is combined with the backward diffusion + timestep before being sent to the underlying model for noise prediction. + + Parameters + ---------- + img_resolution : int + Image resolution. + img_channels : int + Number of color channels. + label_dim : int + Number of class labels, 0 = unconditional, by default 0. + use_fp16 : bool + Execute the underlying model at FP16 precision?, by default False. + sigma_min : float + Minimum supported noise level, by default 0.02. + sigma_max : float + Maximum supported noise level, by default 100.0. + model_type :str + Class name of the underlying model, by default "SongUNet". + **model_kwargs : dict + Keyword arguments for the underlying model. + + Note + ---- + Reference: + [1] Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and + Poole, B., 2020. Score-based generative modeling through stochastic differential + equations. arXiv preprint arXiv:2011.13456. + [2] Shu D, Li Z, Farimani AB. A physics-informed diffusion model for high-fidelity + flow field reconstruction. Journal of Computational Physics. 2023 Apr 1;478:111972. + """ + + def __init__( + self, + img_resolution: int, + img_channels: int, + label_dim: int = 0, + use_fp16: bool = False, + sigma_min: float = 0.02, + sigma_max: float = 100.0, + dataset_mean: float = 5.85e-05, + dataset_scale: float = 4.79, + model_type: str = "SongUNet", + **model_kwargs: dict, + ): + super().__init__() + self.img_resolution = img_resolution + self.img_channels = img_channels + self.label_dim = label_dim + self.use_fp16 = use_fp16 + self.model = globals()[model_type]( + img_resolution=img_resolution, + in_channels=model_kwargs["model_channels"] * 2, + out_channels=img_channels, + label_dim=label_dim, + **model_kwargs, + ) # TODO needs better handling + + # modules to embed residual loss + self.conv_in = torch.nn.Conv2d( + img_channels, + model_kwargs["model_channels"], + kernel_size=3, + stride=1, + padding=1, + padding_mode="circular", + ) + self.emb_conv = torch.nn.Sequential( + torch.nn.Conv2d( + img_channels, + model_kwargs["model_channels"], + kernel_size=1, + stride=1, + padding=0, + ), + torch.nn.GELU(), + torch.nn.Conv2d( + model_kwargs["model_channels"], + model_kwargs["model_channels"], + kernel_size=3, + stride=1, + padding=1, + padding_mode="circular", + ), + ) + self.dataset_mean = dataset_mean + self.dataset_scale = dataset_scale + + def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): + x = x.to(torch.float32) + sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) + class_labels = ( + None + if self.label_dim == 0 + else torch.zeros([1, self.label_dim], device=x.device) + if class_labels is None + else class_labels.to(torch.float32).reshape(-1, self.label_dim) + ) + dtype = ( + torch.float16 + if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") + else torch.float32 + ) + + c_in = 1 + c_noise = sigma + + # Compute physics-informed conditioning information using vorticity residual + dx = ( + self.voriticity_residual((x * self.dataset_scale + self.dataset_mean)) + / self.dataset_scale + ) + x = self.conv_in(x) + cond_emb = self.emb_conv(dx) + x = torch.cat((x, cond_emb), dim=1) + + F_x = self.model( + (c_in * x).to(dtype), + c_noise.flatten(), + class_labels=class_labels, + **model_kwargs, + ) + + if F_x.dtype != dtype: + raise ValueError( + f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + ) + return F_x + + def voriticity_residual(self, w, re=1000.0, dt=1 / 32): + """ + Compute the gradient of PDE residual with respect to a given vorticity w using the + spectrum method. + + Parameters + ---------- + w: torch.Tensor + The fluid flow data sample (vorticity). + re: float + The value of Reynolds number used in the governing Navier-Stokes equation. + dt: float + Time step used to compute the time-derivative of vorticity included in the governing + Navier-Stokes equation. + + Returns + ------- + torch.Tensor + The computed vorticity gradient. + """ + + # w [b t h w] + w = w.clone() + w.requires_grad_(True) + nx = w.size(2) + device = w.device + + w_h = torch.fft.fft2(w[:, 1:-1], dim=[2, 3]) + # Wavenumbers in y-direction + k_max = nx // 2 + N = nx + k_x = ( + torch.cat( + ( + torch.arange(start=0, end=k_max, step=1, device=device), + torch.arange(start=-k_max, end=0, step=1, device=device), + ), + 0, + ) + .reshape(N, 1) + .repeat(1, N) + .reshape(1, 1, N, N) + ) + k_y = ( + torch.cat( + ( + torch.arange(start=0, end=k_max, step=1, device=device), + torch.arange(start=-k_max, end=0, step=1, device=device), + ), + 0, + ) + .reshape(1, N) + .repeat(N, 1) + .reshape(1, 1, N, N) + ) + # Negative Laplacian in Fourier space + lap = k_x**2 + k_y**2 + lap[..., 0, 0] = 1.0 + psi_h = w_h / lap + + u_h = 1j * k_y * psi_h + v_h = -1j * k_x * psi_h + wx_h = 1j * k_x * w_h + wy_h = 1j * k_y * w_h + wlap_h = -lap * w_h + + u = torch.fft.irfft2(u_h[..., :, : k_max + 1], dim=[2, 3]) + v = torch.fft.irfft2(v_h[..., :, : k_max + 1], dim=[2, 3]) + wx = torch.fft.irfft2(wx_h[..., :, : k_max + 1], dim=[2, 3]) + wy = torch.fft.irfft2(wy_h[..., :, : k_max + 1], dim=[2, 3]) + wlap = torch.fft.irfft2(wlap_h[..., :, : k_max + 1], dim=[2, 3]) + advection = u * wx + v * wy + + wt = (w[:, 2:, :, :] - w[:, :-2, :, :]) / (2 * dt) + + # establish forcing term + x = torch.linspace(0, 2 * np.pi, nx + 1, device=device) + x = x[0:-1] + X, Y = torch.meshgrid(x, x) + f = -4 * torch.cos(4 * Y) + + residual = wt + (advection - (1.0 / re) * wlap + 0.1 * w[:, 1:-1]) - f + residual_loss = (residual**2).mean() + dw = torch.autograd.grad(residual_loss, w)[0] + + return dw diff --git a/src/models/preconditioning.py b/src/models/preconditioning.py new file mode 100644 index 00000000..52a16608 --- /dev/null +++ b/src/models/preconditioning.py @@ -0,0 +1,1176 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Preconditioning schemes used in the paper"Elucidating the Design Space of +Diffusion-Based Generative Models". +""" + +import importlib +import warnings +from dataclasses import dataclass +from typing import List, Union + +import numpy as np +import nvtx +import torch + +from physicsnemo.models.diffusion import ( + DhariwalUNet, # noqa: F401 for globals + SongUNet, # noqa: F401 for globals +) +from physicsnemo.models.meta import ModelMetaData +from physicsnemo.models.module import Module + +network_module = importlib.import_module("physicsnemo.models.diffusion") + + +@dataclass +class VPPrecondMetaData(ModelMetaData): + """VPPrecond meta data""" + + name: str = "VPPrecond" + # Optimization + jit: bool = False + cuda_graphs: bool = False + amp_cpu: bool = False + amp_gpu: bool = True + torch_fx: bool = False + # Data type + bf16: bool = False + # Inference + onnx: bool = False + # Physics informed + func_torch: bool = False + auto_grad: bool = False + + +class VPPrecond(Module): + """ + Preconditioning corresponding to the variance preserving (VP) formulation. + + Parameters + ---------- + img_resolution : int + Image resolution. + img_channels : int + Number of color channels. + label_dim : int + Number of class labels, 0 = unconditional, by default 0. + use_fp16 : bool + Execute the underlying model at FP16 precision?, by default False. + beta_d : float + Extent of the noise level schedule, by default 19.9. + beta_min : float + Initial slope of the noise level schedule, by default 0.1. + M : int + Original number of timesteps in the DDPM formulation, by default 1000. + epsilon_t : float + Minimum t-value used during training, by default 1e-5. + model_type :str + Class name of the underlying model, by default "SongUNet". + **model_kwargs : dict + Keyword arguments for the underlying model. + + Note + ---- + Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and + Poole, B., 2020. Score-based generative modeling through stochastic differential + equations. arXiv preprint arXiv:2011.13456. + """ + + def __init__( + self, + img_resolution: int, + img_channels: int, + label_dim: int = 0, + use_fp16: bool = False, + beta_d: float = 19.9, + beta_min: float = 0.1, + M: int = 1000, + epsilon_t: float = 1e-5, + model_type: str = "SongUNet", + **model_kwargs: dict, + ): + super().__init__(meta=VPPrecondMetaData) + self.img_resolution = img_resolution + self.img_channels = img_channels + self.label_dim = label_dim + self.use_fp16 = use_fp16 + self.beta_d = beta_d + self.beta_min = beta_min + self.M = M + self.epsilon_t = epsilon_t + self.sigma_min = float(self.sigma(epsilon_t)) + self.sigma_max = float(self.sigma(1)) + model_class = getattr(network_module, model_type) + self.model = model_class( + img_resolution=img_resolution, + in_channels=img_channels, + out_channels=img_channels, + label_dim=label_dim, + **model_kwargs, + ) # TODO needs better handling + + def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): + x = x.to(torch.float32) + sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) + class_labels = ( + None + if self.label_dim == 0 + else torch.zeros([1, self.label_dim], device=x.device) + if class_labels is None + else class_labels.to(torch.float32).reshape(-1, self.label_dim) + ) + dtype = ( + torch.float16 + if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") + else torch.float32 + ) + + c_skip = 1 + c_out = -sigma + c_in = 1 / (sigma**2 + 1).sqrt() + c_noise = (self.M - 1) * self.sigma_inv(sigma) + + F_x = self.model( + (c_in * x).to(dtype), + c_noise.flatten(), + class_labels=class_labels, + **model_kwargs, + ) + if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): + raise ValueError( + f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + ) + + D_x = c_skip * x + c_out * F_x.to(torch.float32) + return D_x + + def sigma(self, t: Union[float, torch.Tensor]): + """ + Compute the sigma(t) value for a given t based on the VP formulation. + + The function calculates the noise level schedule for the diffusion process based + on the given parameters `beta_d` and `beta_min`. + + Parameters + ---------- + t : Union[float, torch.Tensor] + The timestep or set of timesteps for which to compute sigma(t). + + Returns + ------- + torch.Tensor + The computed sigma(t) value(s). + """ + t = torch.as_tensor(t) + return ((0.5 * self.beta_d * (t**2) + self.beta_min * t).exp() - 1).sqrt() + + def sigma_inv(self, sigma: Union[float, torch.Tensor]): + """ + Compute the inverse of the sigma function for a given sigma. + + This function effectively calculates t from a given sigma(t) based on the + parameters `beta_d` and `beta_min`. + + Parameters + ---------- + sigma : Union[float, torch.Tensor] + The sigma(t) value or set of sigma(t) values for which to compute the + inverse. + + Returns + ------- + torch.Tensor + The computed t value(s) corresponding to the provided sigma(t). + """ + sigma = torch.as_tensor(sigma) + return ( + (self.beta_min**2 + 2 * self.beta_d * (1 + sigma**2).log()).sqrt() + - self.beta_min + ) / self.beta_d + + def round_sigma(self, sigma: Union[float, List, torch.Tensor]): + """ + Convert a given sigma value(s) to a tensor representation. + + Parameters + ---------- + sigma : Union[float list, torch.Tensor] + The sigma value(s) to convert. + + Returns + ------- + torch.Tensor + The tensor representation of the provided sigma value(s). + """ + return torch.as_tensor(sigma) + + +@dataclass +class VEPrecondMetaData(ModelMetaData): + """VEPrecond meta data""" + + name: str = "VEPrecond" + # Optimization + jit: bool = False + cuda_graphs: bool = False + amp_cpu: bool = False + amp_gpu: bool = True + torch_fx: bool = False + # Data type + bf16: bool = False + # Inference + onnx: bool = False + # Physics informed + func_torch: bool = False + auto_grad: bool = False + + +class VEPrecond(Module): + """ + Preconditioning corresponding to the variance exploding (VE) formulation. + + Parameters + ---------- + img_resolution : int + Image resolution. + img_channels : int + Number of color channels. + label_dim : int + Number of class labels, 0 = unconditional, by default 0. + use_fp16 : bool + Execute the underlying model at FP16 precision?, by default False. + sigma_min : float + Minimum supported noise level, by default 0.02. + sigma_max : float + Maximum supported noise level, by default 100.0. + model_type :str + Class name of the underlying model, by default "SongUNet". + **model_kwargs : dict + Keyword arguments for the underlying model. + + Note + ---- + Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and + Poole, B., 2020. Score-based generative modeling through stochastic differential + equations. arXiv preprint arXiv:2011.13456. + """ + + def __init__( + self, + img_resolution: int, + img_channels: int, + label_dim: int = 0, + use_fp16: bool = False, + sigma_min: float = 0.02, + sigma_max: float = 100.0, + model_type: str = "SongUNet", + **model_kwargs: dict, + ): + super().__init__(meta=VEPrecondMetaData) + self.img_resolution = img_resolution + self.img_channels = img_channels + self.label_dim = label_dim + self.use_fp16 = use_fp16 + self.sigma_min = sigma_min + self.sigma_max = sigma_max + model_class = getattr(network_module, model_type) + self.model = model_class( + img_resolution=img_resolution, + in_channels=img_channels, + out_channels=img_channels, + label_dim=label_dim, + **model_kwargs, + ) # TODO needs better handling + + def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): + x = x.to(torch.float32) + sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) + class_labels = ( + None + if self.label_dim == 0 + else torch.zeros([1, self.label_dim], device=x.device) + if class_labels is None + else class_labels.to(torch.float32).reshape(-1, self.label_dim) + ) + dtype = ( + torch.float16 + if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") + else torch.float32 + ) + + c_skip = 1 + c_out = sigma + c_in = 1 + c_noise = (0.5 * sigma).log() + + F_x = self.model( + (c_in * x).to(dtype), + c_noise.flatten(), + class_labels=class_labels, + **model_kwargs, + ) + if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): + raise ValueError( + f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + ) + + D_x = c_skip * x + c_out * F_x.to(torch.float32) + return D_x + + def round_sigma(self, sigma: Union[float, List, torch.Tensor]): + """ + Convert a given sigma value(s) to a tensor representation. + + Parameters + ---------- + sigma : Union[float list, torch.Tensor] + The sigma value(s) to convert. + + Returns + ------- + torch.Tensor + The tensor representation of the provided sigma value(s). + """ + return torch.as_tensor(sigma) + + +@dataclass +class iDDPMPrecondMetaData(ModelMetaData): + """iDDPMPrecond meta data""" + + name: str = "iDDPMPrecond" + # Optimization + jit: bool = False + cuda_graphs: bool = False + amp_cpu: bool = False + amp_gpu: bool = True + torch_fx: bool = False + # Data type + bf16: bool = False + # Inference + onnx: bool = False + # Physics informed + func_torch: bool = False + auto_grad: bool = False + + +class iDDPMPrecond(Module): + """ + Preconditioning corresponding to the improved DDPM (iDDPM) formulation. + + Parameters + ---------- + img_resolution : int + Image resolution. + img_channels : int + Number of color channels. + label_dim : int + Number of class labels, 0 = unconditional, by default 0. + use_fp16 : bool + Execute the underlying model at FP16 precision?, by default False. + C_1 : float + Timestep adjustment at low noise levels., by default 0.001. + C_2 : float + Timestep adjustment at high noise levels., by default 0.008. + M: int + Original number of timesteps in the DDPM formulation, by default 1000. + model_type :str + Class name of the underlying model, by default "DhariwalUNet". + **model_kwargs : dict + Keyword arguments for the underlying model. + + Note + ---- + Reference: Nichol, A.Q. and Dhariwal, P., 2021, July. Improved denoising diffusion + probabilistic models. In International Conference on Machine Learning + (pp. 8162-8171). PMLR. + """ + + def __init__( + self, + img_resolution, + img_channels, + label_dim=0, + use_fp16=False, + C_1=0.001, + C_2=0.008, + M=1000, + model_type="DhariwalUNet", + **model_kwargs, + ): + super().__init__(meta=iDDPMPrecondMetaData) + self.img_resolution = img_resolution + self.img_channels = img_channels + self.label_dim = label_dim + self.use_fp16 = use_fp16 + self.C_1 = C_1 + self.C_2 = C_2 + self.M = M + model_class = getattr(network_module, model_type) + self.model = model_class( + img_resolution=img_resolution, + in_channels=img_channels, + out_channels=img_channels * 2, + label_dim=label_dim, + **model_kwargs, + ) # TODO needs better handling + + u = torch.zeros(M + 1) + for j in range(M, 0, -1): # M, ..., 1 + u[j - 1] = ( + (u[j] ** 2 + 1) + / (self.alpha_bar(j - 1) / self.alpha_bar(j)).clip(min=C_1) + - 1 + ).sqrt() + self.register_buffer("u", u) + self.sigma_min = float(u[M - 1]) + self.sigma_max = float(u[0]) + + def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): + x = x.to(torch.float32) + sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) + class_labels = ( + None + if self.label_dim == 0 + else torch.zeros([1, self.label_dim], device=x.device) + if class_labels is None + else class_labels.to(torch.float32).reshape(-1, self.label_dim) + ) + dtype = ( + torch.float16 + if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") + else torch.float32 + ) + + c_skip = 1 + c_out = -sigma + c_in = 1 / (sigma**2 + 1).sqrt() + c_noise = ( + self.M - 1 - self.round_sigma(sigma, return_index=True).to(torch.float32) + ) + + F_x = self.model( + (c_in * x).to(dtype), + c_noise.flatten(), + class_labels=class_labels, + **model_kwargs, + ) + if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): + raise ValueError( + f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + ) + + D_x = c_skip * x + c_out * F_x[:, : self.img_channels].to(torch.float32) + return D_x + + def alpha_bar(self, j): + """ + Compute the alpha_bar(j) value for a given j based on the iDDPM formulation. + + Parameters + ---------- + j : Union[int, torch.Tensor] + The timestep or set of timesteps for which to compute alpha_bar(j). + + Returns + ------- + torch.Tensor + The computed alpha_bar(j) value(s). + """ + j = torch.as_tensor(j) + return (0.5 * np.pi * j / self.M / (self.C_2 + 1)).sin() ** 2 + + def round_sigma(self, sigma, return_index=False): + """ + Round the provided sigma value(s) to the nearest value(s) in a + pre-defined set `u`. + + Parameters + ---------- + sigma : Union[float, list, torch.Tensor] + The sigma value(s) to round. + return_index : bool, optional + Whether to return the index/indices of the rounded value(s) in `u` instead + of the rounded value(s) themselves, by default False. + + Returns + ------- + torch.Tensor + The rounded sigma value(s) or their index/indices in `u`, depending on the + value of `return_index`. + """ + sigma = torch.as_tensor(sigma) + index = torch.cdist( + sigma.to(self.u.device).to(torch.float32).reshape(1, -1, 1), + self.u.reshape(1, -1, 1), + ).argmin(2) + result = index if return_index else self.u[index.flatten()].to(sigma.dtype) + return result.reshape(sigma.shape).to(sigma.device) + + +@dataclass +class EDMPrecondMetaData(ModelMetaData): + """EDMPrecond meta data""" + + name: str = "EDMPrecond" + # Optimization + jit: bool = False + cuda_graphs: bool = False + amp_cpu: bool = False + amp_gpu: bool = True + torch_fx: bool = False + # Data type + bf16: bool = False + # Inference + onnx: bool = False + # Physics informed + func_torch: bool = False + auto_grad: bool = False + + +class EDMPrecond(Module): + """ + Improved preconditioning proposed in the paper "Elucidating the Design Space of + Diffusion-Based Generative Models" (EDM) + + Parameters + ---------- + img_resolution : int + Image resolution. + img_channels : int + Number of color channels (for both input and output). If your model + requires a different number of input or output chanels, + override this by passing either of the optional + img_in_channels or img_out_channels args + label_dim : int + Number of class labels, 0 = unconditional, by default 0. + use_fp16 : bool + Execute the underlying model at FP16 precision?, by default False. + sigma_min : float + Minimum supported noise level, by default 0.0. + sigma_max : float + Maximum supported noise level, by default inf. + sigma_data : float + Expected standard deviation of the training data, by default 0.5. + model_type :str + Class name of the underlying model, by default "DhariwalUNet". + img_in_channels: int + Optional setting for when number of input channels =/= number of output + channels. If set, will override img_channels for the input + This is useful in the case of additional (conditional) channels + img_out_channels: int + Optional setting for when number of input channels =/= number of output + channels. If set, will override img_channels for the output + **model_kwargs : dict + Keyword arguments for the underlying model. + + Note + ---- + Reference: Karras, T., Aittala, M., Aila, T. and Laine, S., 2022. Elucidating the + design space of diffusion-based generative models. Advances in Neural Information + Processing Systems, 35, pp.26565-26577. + """ + + def __init__( + self, + img_resolution, + img_channels, + label_dim=0, + use_fp16=False, + sigma_min=0.0, + sigma_max=float("inf"), + sigma_data=0.5, + model_type="DhariwalUNet", + img_in_channels=None, + img_out_channels=None, + **model_kwargs, + ): + super().__init__(meta=EDMPrecondMetaData) + self.img_resolution = img_resolution + if img_in_channels is not None: + img_in_channels = img_in_channels + else: + img_in_channels = img_channels + if img_out_channels is not None: + img_out_channels = img_out_channels + else: + img_out_channels = img_channels + + self.label_dim = label_dim + self.use_fp16 = use_fp16 + self.sigma_min = sigma_min + self.sigma_max = sigma_max + self.sigma_data = sigma_data + + model_class = getattr(network_module, model_type) + self.model = model_class( + img_resolution=img_resolution, + in_channels=img_in_channels, + out_channels=img_out_channels, + label_dim=label_dim, + **model_kwargs, + ) # TODO needs better handling + + def forward( + self, + x, + sigma, + condition=None, + class_labels=None, + force_fp32=False, + **model_kwargs, + ): + x = x.to(torch.float32) + sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) + class_labels = ( + None + if self.label_dim == 0 + else torch.zeros([1, self.label_dim], device=x.device) + if class_labels is None + else class_labels.to(torch.float32).reshape(-1, self.label_dim) + ) + dtype = ( + torch.float16 + if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") + else torch.float32 + ) + + c_skip = self.sigma_data**2 / (sigma**2 + self.sigma_data**2) + c_out = sigma * self.sigma_data / (sigma**2 + self.sigma_data**2).sqrt() + c_in = 1 / (self.sigma_data**2 + sigma**2).sqrt() + c_noise = sigma.log() / 4 + + arg = c_in * x + + if condition is not None: + arg = torch.cat([arg, condition], dim=1) + + F_x = self.model( + arg.to(dtype), + c_noise.flatten(), + class_labels=class_labels, + **model_kwargs, + ) + + if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): + raise ValueError( + f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + ) + D_x = c_skip * x + c_out * F_x.to(torch.float32) + return D_x + + @staticmethod + def round_sigma(sigma: Union[float, List, torch.Tensor]): + """ + Convert a given sigma value(s) to a tensor representation. + + Parameters + ---------- + sigma : Union[float list, torch.Tensor] + The sigma value(s) to convert. + + Returns + ------- + torch.Tensor + The tensor representation of the provided sigma value(s). + """ + return torch.as_tensor(sigma) + + +@dataclass +class EDMPrecondSRMetaData(ModelMetaData): + """EDMPrecondSR meta data""" + + name: str = "EDMPrecondSR" + # Optimization + jit: bool = False + cuda_graphs: bool = False + amp_cpu: bool = False + amp_gpu: bool = True + torch_fx: bool = False + # Data type + bf16: bool = False + # Inference + onnx: bool = False + # Physics informed + func_torch: bool = False + auto_grad: bool = False + + +class EDMPrecondSR(Module): + """ + Improved preconditioning proposed in the paper "Elucidating the Design Space of + Diffusion-Based Generative Models" (EDM) for super-resolution tasks + + Parameters + ---------- + img_resolution : int + Image resolution. + img_channels : int + Number of color channels. + img_in_channels : int + Number of input color channels. + img_out_channels : int + Number of output color channels. + use_fp16 : bool + Execute the underlying model at FP16 precision?, by default False. + sigma_min : float + Minimum supported noise level, by default 0.0. + sigma_max : float + Maximum supported noise level, by default inf. + sigma_data : float + Expected standard deviation of the training data, by default 0.5. + model_type :str + Class name of the underlying model, by default "SongUNetPosEmbd". + **model_kwargs : dict + Keyword arguments for the underlying model. + + Note + ---- + References: + - Karras, T., Aittala, M., Aila, T. and Laine, S., 2022. Elucidating the + design space of diffusion-based generative models. Advances in Neural Information + Processing Systems, 35, pp.26565-26577. + - Mardani, M., Brenowitz, N., Cohen, Y., Pathak, J., Chen, C.Y., + Liu, C.C.,Vahdat, A., Kashinath, K., Kautz, J. and Pritchard, M., 2023. + Generative Residual Diffusion Modeling for Km-scale Atmospheric Downscaling. + arXiv preprint arXiv:2309.15214. + """ + + def __init__( + self, + img_resolution, + img_channels, + img_in_channels, + img_out_channels, + use_fp16=False, + sigma_min=0.0, + sigma_max=float("inf"), + sigma_data=0.5, + model_type="SongUNetPosEmbd", + scale_cond_input=True, + **model_kwargs, + ): + super().__init__(meta=EDMPrecondSRMetaData) + self.img_resolution = img_resolution + self.img_channels = img_channels # TODO: this is not used, remove it + self.img_in_channels = img_in_channels + self.img_out_channels = img_out_channels + self.use_fp16 = use_fp16 + self.sigma_min = sigma_min + self.sigma_max = sigma_max + self.sigma_data = sigma_data + self.scale_cond_input = scale_cond_input + + model_class = getattr(network_module, model_type) + self.model = model_class( + img_resolution=img_resolution, + in_channels=img_in_channels + img_out_channels, + out_channels=img_out_channels, + **model_kwargs, + ) # TODO needs better handling + self.scaling_fn = self._get_scaling_fn() + + def _get_scaling_fn(self): + if self.scale_cond_input: + warnings.warn( + "scale_cond_input=True does not properly scale the conditional input. " + "(see https://github.com/NVIDIA/modulus/issues/229). " + "This setup will be deprecated. " + "Please set scale_cond_input=False.", + DeprecationWarning, + ) + return self._legacy_scaling_fn + else: + return self._scaling_fn + + @staticmethod + def _scaling_fn(x, img_lr, c_in): + return torch.cat([c_in * x, img_lr.to(x.dtype)], dim=1) + + @staticmethod + def _legacy_scaling_fn(x, img_lr, c_in): + return c_in * torch.cat([x, img_lr.to(x.dtype)], dim=1) + + @nvtx.annotate(message="EDMPrecondSR", color="orange") + def forward( + self, + x, + img_lr, + sigma, + force_fp32=False, + **model_kwargs, + ): + # Concatenate input channels + x = x.to(torch.float32) + sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) + dtype = ( + torch.float16 + if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") + else torch.float32 + ) + + c_skip = self.sigma_data**2 / (sigma**2 + self.sigma_data**2) + c_out = sigma * self.sigma_data / (sigma**2 + self.sigma_data**2).sqrt() + c_in = 1 / (self.sigma_data**2 + sigma**2).sqrt() + c_noise = sigma.log() / 4 + + if img_lr is None: + arg = c_in * x + else: + arg = self.scaling_fn(x, img_lr, c_in) + arg = arg.to(dtype) + + F_x = self.model( + arg, + c_noise.flatten(), + class_labels=None, + **model_kwargs, + ) + + if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): + raise ValueError( + f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + ) + + D_x = c_skip * x + c_out * F_x.to(torch.float32) + return D_x + + @staticmethod + def round_sigma(sigma: Union[float, List, torch.Tensor]): + """ + Convert a given sigma value(s) to a tensor representation. + See EDMPrecond.round_sigma + """ + return EDMPrecond.round_sigma(sigma) + + +class VEPrecond_dfsr(torch.nn.Module): + """ + Preconditioning for dfsr model, modified from class VEPrecond, where the input + argument 'sigma' in forward propagation function is used to receive the timestep + of the backward diffusion process. + + Parameters + ---------- + img_resolution : int + Image resolution. + img_channels : int + Number of color channels. + label_dim : int + Number of class labels, 0 = unconditional, by default 0. + use_fp16 : bool + Execute the underlying model at FP16 precision?, by default False. + sigma_min : float + Minimum supported noise level, by default 0.02. + sigma_max : float + Maximum supported noise level, by default 100.0. + model_type :str + Class name of the underlying model, by default "SongUNet". + **model_kwargs : dict + Keyword arguments for the underlying model. + + Note + ---- + Reference: Ho J, Jain A, Abbeel P. Denoising diffusion probabilistic models. + Advances in neural information processing systems. 2020;33:6840-51. + """ + + def __init__( + self, + img_resolution: int, + img_channels: int, + label_dim: int = 0, + use_fp16: bool = False, + sigma_min: float = 0.02, + sigma_max: float = 100.0, + dataset_mean: float = 5.85e-05, + dataset_scale: float = 4.79, + model_type: str = "SongUNet", + **model_kwargs: dict, + ): + super().__init__() + self.img_resolution = img_resolution + self.img_channels = img_channels + self.label_dim = label_dim + self.use_fp16 = use_fp16 + self.model = globals()[model_type]( + img_resolution=img_resolution, + in_channels=self.img_channels, + out_channels=img_channels, + label_dim=label_dim, + **model_kwargs, + ) # TODO needs better handling + + def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): + x = x.to(torch.float32) + sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) + # print("sigma: ", sigma) + class_labels = ( + None + if self.label_dim == 0 + else torch.zeros([1, self.label_dim], device=x.device) + if class_labels is None + else class_labels.to(torch.float32).reshape(-1, self.label_dim) + ) + dtype = ( + torch.float16 + if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") + else torch.float32 + ) + + c_in = 1 + c_noise = sigma # Change the definitation of c_noise to avoid -inf values for zero sigma + + F_x = self.model( + (c_in * x).to(dtype), + c_noise.flatten(), + class_labels=class_labels, + **model_kwargs, + ) + + if F_x.dtype != dtype: + raise ValueError( + f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + ) + + return F_x + + +class VEPrecond_dfsr_cond(torch.nn.Module): + """ + Preconditioning for dfsr model with physics-informed conditioning input, modified + from class VEPrecond, where the input argument 'sigma' in forward propagation function + is used to receive the timestep of the backward diffusion process. The gradient of PDE + residual with respect to the vorticity in the governing Navier-Stokes equation is computed + as the physics-informed conditioning variable and is combined with the backward diffusion + timestep before being sent to the underlying model for noise prediction. + + Parameters + ---------- + img_resolution : int + Image resolution. + img_channels : int + Number of color channels. + label_dim : int + Number of class labels, 0 = unconditional, by default 0. + use_fp16 : bool + Execute the underlying model at FP16 precision?, by default False. + sigma_min : float + Minimum supported noise level, by default 0.02. + sigma_max : float + Maximum supported noise level, by default 100.0. + model_type :str + Class name of the underlying model, by default "SongUNet". + **model_kwargs : dict + Keyword arguments for the underlying model. + + Note + ---- + Reference: + [1] Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and + Poole, B., 2020. Score-based generative modeling through stochastic differential + equations. arXiv preprint arXiv:2011.13456. + [2] Shu D, Li Z, Farimani AB. A physics-informed diffusion model for high-fidelity + flow field reconstruction. Journal of Computational Physics. 2023 Apr 1;478:111972. + """ + + def __init__( + self, + img_resolution: int, + img_channels: int, + label_dim: int = 0, + use_fp16: bool = False, + sigma_min: float = 0.02, + sigma_max: float = 100.0, + dataset_mean: float = 5.85e-05, + dataset_scale: float = 4.79, + model_type: str = "SongUNet", + **model_kwargs: dict, + ): + super().__init__() + self.img_resolution = img_resolution + self.img_channels = img_channels + self.label_dim = label_dim + self.use_fp16 = use_fp16 + self.model = globals()[model_type]( + img_resolution=img_resolution, + in_channels=model_kwargs["model_channels"] * 2, + out_channels=img_channels, + label_dim=label_dim, + **model_kwargs, + ) # TODO needs better handling + + # modules to embed residual loss + self.conv_in = torch.nn.Conv2d( + img_channels, + model_kwargs["model_channels"], + kernel_size=3, + stride=1, + padding=1, + padding_mode="circular", + ) + self.emb_conv = torch.nn.Sequential( + torch.nn.Conv2d( + img_channels, + model_kwargs["model_channels"], + kernel_size=1, + stride=1, + padding=0, + ), + torch.nn.GELU(), + torch.nn.Conv2d( + model_kwargs["model_channels"], + model_kwargs["model_channels"], + kernel_size=3, + stride=1, + padding=1, + padding_mode="circular", + ), + ) + self.dataset_mean = dataset_mean + self.dataset_scale = dataset_scale + + def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): + x = x.to(torch.float32) + sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) + class_labels = ( + None + if self.label_dim == 0 + else torch.zeros([1, self.label_dim], device=x.device) + if class_labels is None + else class_labels.to(torch.float32).reshape(-1, self.label_dim) + ) + dtype = ( + torch.float16 + if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") + else torch.float32 + ) + + c_in = 1 + c_noise = sigma + + # Compute physics-informed conditioning information using vorticity residual + dx = ( + self.voriticity_residual((x * self.dataset_scale + self.dataset_mean)) + / self.dataset_scale + ) + x = self.conv_in(x) + cond_emb = self.emb_conv(dx) + x = torch.cat((x, cond_emb), dim=1) + + F_x = self.model( + (c_in * x).to(dtype), + c_noise.flatten(), + class_labels=class_labels, + **model_kwargs, + ) + + if F_x.dtype != dtype: + raise ValueError( + f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + ) + return F_x + + def voriticity_residual(self, w, re=1000.0, dt=1 / 32): + """ + Compute the gradient of PDE residual with respect to a given vorticity w using the + spectrum method. + + Parameters + ---------- + w: torch.Tensor + The fluid flow data sample (vorticity). + re: float + The value of Reynolds number used in the governing Navier-Stokes equation. + dt: float + Time step used to compute the time-derivative of vorticity included in the governing + Navier-Stokes equation. + + Returns + ------- + torch.Tensor + The computed vorticity gradient. + """ + + # w [b t h w] + w = w.clone() + w.requires_grad_(True) + nx = w.size(2) + device = w.device + + w_h = torch.fft.fft2(w[:, 1:-1], dim=[2, 3]) + # Wavenumbers in y-direction + k_max = nx // 2 + N = nx + k_x = ( + torch.cat( + ( + torch.arange(start=0, end=k_max, step=1, device=device), + torch.arange(start=-k_max, end=0, step=1, device=device), + ), + 0, + ) + .reshape(N, 1) + .repeat(1, N) + .reshape(1, 1, N, N) + ) + k_y = ( + torch.cat( + ( + torch.arange(start=0, end=k_max, step=1, device=device), + torch.arange(start=-k_max, end=0, step=1, device=device), + ), + 0, + ) + .reshape(1, N) + .repeat(N, 1) + .reshape(1, 1, N, N) + ) + # Negative Laplacian in Fourier space + lap = k_x**2 + k_y**2 + lap[..., 0, 0] = 1.0 + psi_h = w_h / lap + + u_h = 1j * k_y * psi_h + v_h = -1j * k_x * psi_h + wx_h = 1j * k_x * w_h + wy_h = 1j * k_y * w_h + wlap_h = -lap * w_h + + u = torch.fft.irfft2(u_h[..., :, : k_max + 1], dim=[2, 3]) + v = torch.fft.irfft2(v_h[..., :, : k_max + 1], dim=[2, 3]) + wx = torch.fft.irfft2(wx_h[..., :, : k_max + 1], dim=[2, 3]) + wy = torch.fft.irfft2(wy_h[..., :, : k_max + 1], dim=[2, 3]) + wlap = torch.fft.irfft2(wlap_h[..., :, : k_max + 1], dim=[2, 3]) + advection = u * wx + v * wy + + wt = (w[:, 2:, :, :] - w[:, :-2, :, :]) / (2 * dt) + + # establish forcing term + x = torch.linspace(0, 2 * np.pi, nx + 1, device=device) + x = x[0:-1] + X, Y = torch.meshgrid(x, x) + f = -4 * torch.cos(4 * Y) + + residual = wt + (advection - (1.0 / re) * wlap + 0.1 * w[:, 1:-1]) - f + residual_loss = (residual**2).mean() + dw = torch.autograd.grad(residual_loss, w)[0] + + return dw diff --git a/src/models/song_unet.py b/src/models/song_unet.py new file mode 100644 index 00000000..d38484ba --- /dev/null +++ b/src/models/song_unet.py @@ -0,0 +1,906 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Model architectures used in the paper "Elucidating the Design Space of +Diffusion-Based Generative Models". +""" + +from dataclasses import dataclass +from typing import List, Union + +import numpy as np +import nvtx +import torch +from torch.nn.functional import silu +from torch.utils.checkpoint import checkpoint + +from physicsnemo.models.diffusion import ( + Conv2d, + FourierEmbedding, + GroupNorm, + Linear, + PositionalEmbedding, + UNetBlock, +) +from physicsnemo.models.meta import ModelMetaData +from physicsnemo.models.module import Module + + +@dataclass +class MetaData(ModelMetaData): + name: str = "SongUNet" + # Optimization + jit: bool = False + cuda_graphs: bool = False + amp_cpu: bool = False + amp_gpu: bool = True + torch_fx: bool = False + # Data type + bf16: bool = True + # Inference + onnx: bool = False + # Physics informed + func_torch: bool = False + auto_grad: bool = False + + +class SongUNet(Module): + """ + Reimplementation of the DDPM++ and NCSN++ architectures, U-Net variants with + optional self-attention, embeddings, and encoder-decoder components. + + This model supports conditional and unconditional setups, as well as several + options for various internal architectural choices such as encoder and decoder + type, embedding type, etc., making it flexible and adaptable to different tasks + and configurations. + + Parameters + ----------- + img_resolution : Union[List[int], int] + The resolution of the input/output image, 1 value represents a square image. + in_channels : int + Number of channels in the input image. + out_channels : int + Number of channels in the output image. + label_dim : int, optional + Number of class labels; 0 indicates an unconditional model. By default 0. + augment_dim : int, optional + Dimensionality of augmentation labels; 0 means no augmentation. By default 0. + model_channels : int, optional + Base multiplier for the number of channels across the network, by default 128. + channel_mult : List[int], optional + Per-resolution multipliers for the number of channels. By default [1,2,2,2]. + channel_mult_emb : int, optional + Multiplier for the dimensionality of the embedding vector. By default 4. + num_blocks : int, optional + Number of residual blocks per resolution. By default 4. + attn_resolutions : List[int], optional + Resolutions at which self-attention layers are applied. By default [16]. + dropout : float, optional + Dropout probability applied to intermediate activations. By default 0.10. + label_dropout : float, optional + Dropout probability of class labels for classifier-free guidance. By default 0.0. + embedding_type : str, optional + Timestep embedding type: 'positional' for DDPM++, 'fourier' for NCSN++, 'zero' for none + By default 'positional'. + channel_mult_noise : int, optional + Timestep embedding size: 1 for DDPM++, 2 for NCSN++. By default 1. + encoder_type : str, optional + Encoder architecture: 'standard' for DDPM++, 'residual' for NCSN++. By default + 'standard'. + decoder_type : str, optional + Decoder architecture: 'standard' for both DDPM++ and NCSN++. By default + 'standard'. + resample_filter : List[int], optional (default=[1,1]) + Resampling filter: [1,1] for DDPM++, [1,3,3,1] for NCSN++. + checkpoint_level : int, optional (default=0) + How many layers should use gradient checkpointing, 0 is None + additive_pos_embed: bool = False, + Set to True to add a learned position embedding after the first conv (used in StormCast) + + + Reference + ---------- + Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and + Poole, B., 2020. Score-based generative modeling through stochastic differential + equations. arXiv preprint arXiv:2011.13456. + + Note + ----- + Equivalent to the original implementation by Song et al., available at + https://github.com/yang-song/score_sde_pytorch + + Example + -------- + >>> model = SongUNet(img_resolution=16, in_channels=2, out_channels=2) + >>> noise_labels = torch.randn([1]) + >>> class_labels = torch.randint(0, 1, (1, 1)) + >>> input_image = torch.ones([1, 2, 16, 16]) + >>> output_image = model(input_image, noise_labels, class_labels) + >>> output_image.shape + torch.Size([1, 2, 16, 16]) + """ + + def __init__( + self, + img_resolution: Union[List[int], int], + in_channels: int, + out_channels: int, + label_dim: int = 0, + augment_dim: int = 0, + model_channels: int = 128, + channel_mult: List[int] = [1, 2, 2, 2], + channel_mult_emb: int = 4, + num_blocks: int = 4, + attn_resolutions: List[int] = [16], + dropout: float = 0.10, + label_dropout: float = 0.0, + embedding_type: str = "positional", + channel_mult_noise: int = 1, + encoder_type: str = "standard", + decoder_type: str = "standard", + resample_filter: List[int] = [1, 1], + checkpoint_level: int = 0, + additive_pos_embed: bool = False, + ): + valid_embedding_types = ["fourier", "positional", "zero"] + if embedding_type not in valid_embedding_types: + raise ValueError( + f"Invalid embedding_type: {embedding_type}. Must be one of {valid_embedding_types}." + ) + + valid_encoder_types = ["standard", "skip", "residual"] + if encoder_type not in valid_encoder_types: + raise ValueError( + f"Invalid encoder_type: {encoder_type}. Must be one of {valid_encoder_types}." + ) + + valid_decoder_types = ["standard", "skip"] + if decoder_type not in valid_decoder_types: + raise ValueError( + f"Invalid decoder_type: {decoder_type}. Must be one of {valid_decoder_types}." + ) + + super().__init__(meta=MetaData()) + self.label_dropout = label_dropout + self.embedding_type = embedding_type + emb_channels = model_channels * channel_mult_emb + self.emb_channels = emb_channels + noise_channels = model_channels * channel_mult_noise + init = dict(init_mode="xavier_uniform") + init_zero = dict(init_mode="xavier_uniform", init_weight=1e-5) + init_attn = dict(init_mode="xavier_uniform", init_weight=np.sqrt(0.2)) + block_kwargs = dict( + emb_channels=emb_channels, + num_heads=1, + dropout=dropout, + skip_scale=np.sqrt(0.5), + eps=1e-6, + resample_filter=resample_filter, + resample_proj=True, + adaptive_scale=False, + init=init, + init_zero=init_zero, + init_attn=init_attn, + ) + + # for compatibility with older versions that took only 1 dimension + self.img_resolution = img_resolution + if isinstance(img_resolution, int): + self.img_shape_y = self.img_shape_x = img_resolution + else: + self.img_shape_y = img_resolution[0] + self.img_shape_x = img_resolution[1] + + # set the threshold for checkpointing based on image resolution + self.checkpoint_threshold = (self.img_shape_y >> checkpoint_level) + 1 + + # Optional additive learned positition embed after the first conv + self.additive_pos_embed = additive_pos_embed + if self.additive_pos_embed: + self.spatial_emb = torch.nn.Parameter( + torch.randn(1, model_channels, self.img_shape_y, self.img_shape_x) + ) + torch.nn.init.trunc_normal_(self.spatial_emb, std=0.02) + + # Mapping. + if self.embedding_type != "zero": + self.map_noise = ( + PositionalEmbedding(num_channels=noise_channels, endpoint=True) + if embedding_type == "positional" + else FourierEmbedding(num_channels=noise_channels) + ) + self.map_label = ( + Linear(in_features=label_dim, out_features=noise_channels, **init) + if label_dim + else None + ) + self.map_augment = ( + Linear( + in_features=augment_dim, + out_features=noise_channels, + bias=False, + **init, + ) + if augment_dim + else None + ) + self.map_layer0 = Linear( + in_features=noise_channels, out_features=emb_channels, **init + ) + self.map_layer1 = Linear( + in_features=emb_channels, out_features=emb_channels, **init + ) + + # Encoder. + self.enc = torch.nn.ModuleDict() + cout = in_channels + caux = in_channels + for level, mult in enumerate(channel_mult): + res = self.img_shape_y >> level + if level == 0: + cin = cout + cout = model_channels + self.enc[f"{res}x{res}_conv"] = Conv2d( + in_channels=cin, out_channels=cout, kernel=3, **init + ) + else: + self.enc[f"{res}x{res}_down"] = UNetBlock( + in_channels=cout, out_channels=cout, down=True, **block_kwargs + ) + if encoder_type == "skip": + self.enc[f"{res}x{res}_aux_down"] = Conv2d( + in_channels=caux, + out_channels=caux, + kernel=0, + down=True, + resample_filter=resample_filter, + ) + self.enc[f"{res}x{res}_aux_skip"] = Conv2d( + in_channels=caux, out_channels=cout, kernel=1, **init + ) + if encoder_type == "residual": + self.enc[f"{res}x{res}_aux_residual"] = Conv2d( + in_channels=caux, + out_channels=cout, + kernel=3, + down=True, + resample_filter=resample_filter, + fused_resample=True, + **init, + ) + caux = cout + for idx in range(num_blocks): + cin = cout + cout = model_channels * mult + attn = res in attn_resolutions + self.enc[f"{res}x{res}_block{idx}"] = UNetBlock( + in_channels=cin, out_channels=cout, attention=attn, **block_kwargs + ) + skips = [ + block.out_channels for name, block in self.enc.items() if "aux" not in name + ] + + # Decoder. + self.dec = torch.nn.ModuleDict() + for level, mult in reversed(list(enumerate(channel_mult))): + res = self.img_shape_y >> level + if level == len(channel_mult) - 1: + self.dec[f"{res}x{res}_in0"] = UNetBlock( + in_channels=cout, out_channels=cout, attention=True, **block_kwargs + ) + self.dec[f"{res}x{res}_in1"] = UNetBlock( + in_channels=cout, out_channels=cout, **block_kwargs + ) + else: + self.dec[f"{res}x{res}_up"] = UNetBlock( + in_channels=cout, out_channels=cout, up=True, **block_kwargs + ) + for idx in range(num_blocks + 1): + cin = cout + skips.pop() + cout = model_channels * mult + attn = idx == num_blocks and res in attn_resolutions + self.dec[f"{res}x{res}_block{idx}"] = UNetBlock( + in_channels=cin, out_channels=cout, attention=attn, **block_kwargs + ) + if decoder_type == "skip" or level == 0: + if decoder_type == "skip" and level < len(channel_mult) - 1: + self.dec[f"{res}x{res}_aux_up"] = Conv2d( + in_channels=out_channels, + out_channels=out_channels, + kernel=0, + up=True, + resample_filter=resample_filter, + ) + self.dec[f"{res}x{res}_aux_norm"] = GroupNorm( + num_channels=cout, eps=1e-6 + ) + self.dec[f"{res}x{res}_aux_conv"] = Conv2d( + in_channels=cout, out_channels=out_channels, kernel=3, **init_zero + ) + + @nvtx.annotate(message="SongUNet", color="blue") + def forward(self, x, noise_labels, class_labels, augment_labels=None): + if self.embedding_type != "zero": + # Mapping. + emb = self.map_noise(noise_labels) + emb = ( + emb.reshape(emb.shape[0], 2, -1).flip(1).reshape(*emb.shape) + ) # swap sin/cos + if self.map_label is not None: + tmp = class_labels + if self.training and self.label_dropout: + tmp = tmp * ( + torch.rand([x.shape[0], 1], device=x.device) + >= self.label_dropout + ).to(tmp.dtype) + emb = emb + self.map_label(tmp * np.sqrt(self.map_label.in_features)) + if self.map_augment is not None and augment_labels is not None: + emb = emb + self.map_augment(augment_labels) + emb = silu(self.map_layer0(emb)) + emb = silu(self.map_layer1(emb)) + else: + emb = torch.zeros( + (noise_labels.shape[0], self.emb_channels), device=x.device + ) + + # Encoder. + skips = [] + aux = x + for name, block in self.enc.items(): + with nvtx.annotate(f"SongUNet encoder: {name}", color="blue"): + if "aux_down" in name: + aux = block(aux) + elif "aux_skip" in name: + x = skips[-1] = x + block(aux) + elif "aux_residual" in name: + x = skips[-1] = aux = (x + block(aux)) / np.sqrt(2) + elif "_conv" in name: + x = block(x) + if self.additive_pos_embed: + x = x + self.spatial_emb.to(dtype=x.dtype) + skips.append(x) + else: + # For UNetBlocks check if we should use gradient checkpointing + if isinstance(block, UNetBlock): + if x.shape[-1] > self.checkpoint_threshold: + x = checkpoint(block, x, emb, use_reentrant=False) + else: + x = block(x, emb) + else: + x = block(x) + skips.append(x) + + # Decoder. + aux = None + tmp = None + for name, block in self.dec.items(): + with nvtx.annotate(f"SongUNet decoder: {name}", color="blue"): + if "aux_up" in name: + aux = block(aux) + elif "aux_norm" in name: + tmp = block(x) + elif "aux_conv" in name: + tmp = block(silu(tmp)) + aux = tmp if aux is None else tmp + aux + else: + if x.shape[1] != block.in_channels: + x = torch.cat([x, skips.pop()], dim=1) + # check for checkpointing on decoder blocks and up sampling blocks + if ( + x.shape[-1] > self.checkpoint_threshold and "_block" in name + ) or ( + x.shape[-1] > (self.checkpoint_threshold / 2) and "_up" in name + ): + x = checkpoint(block, x, emb, use_reentrant=False) + else: + x = block(x, emb) + return aux + + +class SongUNetPosEmbd(SongUNet): + """ + Reimplementation of the DDPM++ and NCSN++ architectures, U-Net variants with + optional self-attention,embeddings, and encoder-decoder components. + + This model supports conditional and unconditional setups, as well as several + options for various internal architectural choices such as encoder and decoder + type, embedding type, etc., making it flexible and adaptable to different tasks + and configurations. + + Parameters + ----------- + img_resolution : Union[List[int], int] + The resolution of the input/output image, 1 value represents a square image. + in_channels : int + Number of channels in the input image. + out_channels : int + Number of channels in the output image. + label_dim : int, optional + Number of class labels; 0 indicates an unconditional model. By default 0. + augment_dim : int, optional + Dimensionality of augmentation labels; 0 means no augmentation. By default 0. + model_channels : int, optional + Base multiplier for the number of channels across the network, by default 128. + channel_mult : List[int], optional + Per-resolution multipliers for the number of channels. By default [1,2,2,2]. + channel_mult_emb : int, optional + Multiplier for the dimensionality of the embedding vector. By default 4. + num_blocks : int, optional + Number of residual blocks per resolution. By default 4. + attn_resolutions : List[int], optional + Resolutions at which self-attention layers are applied. By default [16]. + dropout : float, optional + Dropout probability applied to intermediate activations. By default 0.13. + label_dropout : float, optional + Dropout probability of class labels for classifier-free guidance. By default 0.0. + embedding_type : str, optional + Timestep embedding type: 'positional' for DDPM++, 'fourier' for NCSN++. + By default 'positional'. + channel_mult_noise : int, optional + Timestep embedding size: 1 for DDPM++, 2 for NCSN++. By default 1. + encoder_type : str, optional + Encoder architecture: 'standard' for DDPM++, 'residual' for NCSN++. By default + 'standard'. + decoder_type : str, optional + Decoder architecture: 'standard' for both DDPM++ and NCSN++. By default + 'standard'. + resample_filter : List[int], optional (default=[1,1]) + Resampling filter: [1,1] for DDPM++, [1,3,3,1] for NCSN++. + + + Reference + ---------- + Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and + Poole, B., 2020. Score-based generative modeling through stochastic differential + equations. arXiv preprint arXiv:2011.13456. + + Note + ----- + Equivalent to the original implementation by Song et al., available at + https://github.com/yang-song/score_sde_pytorch + + Example + -------- + >>> model = SongUNet(img_resolution=16, in_channels=2, out_channels=2) + >>> noise_labels = torch.randn([1]) + >>> class_labels = torch.randint(0, 1, (1, 1)) + >>> input_image = torch.ones([1, 2, 16, 16]) + >>> output_image = model(input_image, noise_labels, class_labels) + >>> output_image.shape + torch.Size([1, 2, 16, 16]) + """ + + def __init__( + self, + img_resolution: Union[List[int], int], + in_channels: int, + out_channels: int, + label_dim: int = 0, + augment_dim: int = 0, + model_channels: int = 128, + channel_mult: List[int] = [1, 2, 2, 2, 2], + channel_mult_emb: int = 4, + num_blocks: int = 4, + attn_resolutions: List[int] = [28], + dropout: float = 0.13, + label_dropout: float = 0.0, + embedding_type: str = "positional", + channel_mult_noise: int = 1, + encoder_type: str = "standard", + decoder_type: str = "standard", + resample_filter: List[int] = [1, 1], + gridtype: str = "sinusoidal", + N_grid_channels: int = 4, + checkpoint_level: int = 0, + ): + super().__init__( + img_resolution, + in_channels, + out_channels, + label_dim, + augment_dim, + model_channels, + channel_mult, + channel_mult_emb, + num_blocks, + attn_resolutions, + dropout, + label_dropout, + embedding_type, + channel_mult_noise, + encoder_type, + decoder_type, + resample_filter, + checkpoint_level, + ) + + self.gridtype = gridtype + self.N_grid_channels = N_grid_channels + self.pos_embd = self._get_positional_embedding() + + @nvtx.annotate(message="SongUNet", color="blue") + def forward( + self, x, noise_labels, class_labels, global_index=None, augment_labels=None + ): + # append positional embedding to input conditioning + if self.pos_embd is not None: + selected_pos_embd = self.positional_embedding_indexing(x, global_index) + x = torch.cat((x, selected_pos_embd), dim=1) + + return super().forward(x, noise_labels, class_labels, augment_labels) + + def positional_embedding_indexing(self, x, global_index): + if global_index is None: + selected_pos_embd = ( + self.pos_embd.to(x.dtype) + .to(x.device)[None] + .expand((x.shape[0], -1, -1, -1)) + ) + else: + B = global_index.shape[0] + X = global_index.shape[2] + Y = global_index.shape[3] + global_index = torch.reshape( + torch.permute(global_index, (1, 0, 2, 3)), (2, -1) + ) # (B, 2, X, Y) to (2, B*X*Y) + selected_pos_embd = self.pos_embd.to(x.device)[ + :, global_index[0], global_index[1] + ] # (N_pe, B*X*Y) + selected_pos_embd = ( + torch.permute( + torch.reshape(selected_pos_embd, (self.pos_embd.shape[0], B, X, Y)), + (1, 0, 2, 3), + ) + .to(x.device) + .to(x.dtype) + ) # (B, N_pe, X, Y) + return selected_pos_embd + + def _get_positional_embedding(self): + if self.N_grid_channels == 0: + return None + elif self.gridtype == "learnable": + grid = torch.nn.Parameter( + torch.randn(self.N_grid_channels, self.img_shape_y, self.img_shape_x) + ) + elif self.gridtype == "linear": + if self.N_grid_channels != 2: + raise ValueError("N_grid_channels must be set to 2 for gridtype linear") + x = np.meshgrid(np.linspace(-1, 1, self.img_shape_y)) + y = np.meshgrid(np.linspace(-1, 1, self.img_shape_x)) + grid_x, grid_y = np.meshgrid(y, x) + grid = torch.from_numpy(np.stack((grid_x, grid_y), axis=0)) + grid.requires_grad = False + elif self.gridtype == "sinusoidal" and self.N_grid_channels == 4: + # print('sinusuidal grid added ......') + x1 = np.meshgrid(np.sin(np.linspace(0, 2 * np.pi, self.img_shape_y))) + x2 = np.meshgrid(np.cos(np.linspace(0, 2 * np.pi, self.img_shape_y))) + y1 = np.meshgrid(np.sin(np.linspace(0, 2 * np.pi, self.img_shape_x))) + y2 = np.meshgrid(np.cos(np.linspace(0, 2 * np.pi, self.img_shape_x))) + grid_x1, grid_y1 = np.meshgrid(y1, x1) + grid_x2, grid_y2 = np.meshgrid(y2, x2) + grid = torch.squeeze( + torch.from_numpy( + np.expand_dims( + np.stack((grid_x1, grid_y1, grid_x2, grid_y2), axis=0), axis=0 + ) + ) + ) + grid.requires_grad = False + elif self.gridtype == "sinusoidal" and self.N_grid_channels != 4: + if self.N_grid_channels % 4 != 0: + raise ValueError("N_grid_channels must be a factor of 4") + num_freq = self.N_grid_channels // 4 + freq_bands = 2.0 ** np.linspace(0.0, num_freq, num=num_freq) + grid_list = [] + grid_x, grid_y = np.meshgrid( + np.linspace(0, 2 * np.pi, self.img_shape_x), + np.linspace(0, 2 * np.pi, self.img_shape_y), + ) + for freq in freq_bands: + for p_fn in [np.sin, np.cos]: + grid_list.append(p_fn(grid_x * freq)) + grid_list.append(p_fn(grid_y * freq)) + grid = torch.from_numpy(np.stack(grid_list, axis=0)) + grid.requires_grad = False + elif self.gridtype == "test" and self.N_grid_channels == 2: + idx_x = torch.arange(self.img_shape_y) + idx_y = torch.arange(self.img_shape_x) + mesh_x, mesh_y = torch.meshgrid(idx_x, idx_y) + grid = torch.stack((mesh_x, mesh_y), dim=0) + else: + raise ValueError("Gridtype not supported.") + return grid + + +class SongUNetPosLtEmbd(SongUNet): + """ + This model is adapated from SongUNetPosEmbd, with the incoporatation of lead-time aware + embedding for the GEFS-HRRR model. The lead-time embedding is activated by setting the + lead_time_channels and lead_time_steps parameters. + + Parameters + ----------- + img_resolution : Union[List[int], int] + The resolution of the input/output image, 1 value represents a square image. + in_channels : int + Number of channels in the input image. + out_channels : int + Number of channels in the output image. + label_dim : int, optional + Number of class labels; 0 indicates an unconditional model. By default 0. + augment_dim : int, optional + Dimensionality of augmentation labels; 0 means no augmentation. By default 0. + model_channels : int, optional + Base multiplier for the number of channels across the network, by default 128. + channel_mult : List[int], optional + Per-resolution multipliers for the number of channels. By default [1,2,2,2]. + channel_mult_emb : int, optional + Multiplier for the dimensionality of the embedding vector. By default 4. + num_blocks : int, optional + Number of residual blocks per resolution. By default 4. + attn_resolutions : List[int], optional + Resolutions at which self-attention layers are applied. By default [16]. + dropout : float, optional + Dropout probability applied to intermediate activations. By default 0.13. + label_dropout : float, optional + Dropout probability of class labels for classifier-free guidance. By default 0.0. + embedding_type : str, optional + Timestep embedding type: 'positional' for DDPM++, 'fourier' for NCSN++. + By default 'positional'. + channel_mult_noise : int, optional + Timestep embedding size: 1 for DDPM++, 2 for NCSN++. By default 1. + encoder_type : str, optional + Encoder architecture: 'standard' for DDPM++, 'residual' for NCSN++. By default + 'standard'. + decoder_type : str, optional + Decoder architecture: 'standard' for both DDPM++ and NCSN++. By default + 'standard'. + resample_filter : List[int], optional (default=[1,1]) + Resampling filter: [1,1] for DDPM++, [1,3,3,1] for NCSN++. + lead_time_channels: int, optional + Length of lead time embedding vector + lead_time_steps: int, optional + Total number of lead times + + + Reference + ---------- + Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and + Poole, B., 2020. Score-based generative modeling through stochastic differential + equations. arXiv preprint arXiv:2011.13456. + + Note + ----- + Equivalent to the original implementation by Song et al., available at + https://github.com/yang-song/score_sde_pytorch + + Example + -------- + >>> model = SongUNet(img_resolution=16, in_channels=2, out_channels=2) + >>> noise_labels = torch.randn([1]) + >>> class_labels = torch.randint(0, 1, (1, 1)) + >>> input_image = torch.ones([1, 2, 16, 16]) + >>> output_image = model(input_image, noise_labels, class_labels) + >>> output_image.shape + torch.Size([1, 2, 16, 16]) + """ + + def __init__( + self, + img_resolution: Union[List[int], int], + in_channels: int, + out_channels: int, + label_dim: int = 0, + augment_dim: int = 0, + model_channels: int = 128, + channel_mult: List[int] = [1, 2, 2, 2, 2], + channel_mult_emb: int = 4, + num_blocks: int = 4, + attn_resolutions: List[int] = [28], + dropout: float = 0.13, + label_dropout: float = 0.0, + embedding_type: str = "positional", + channel_mult_noise: int = 1, + encoder_type: str = "standard", + decoder_type: str = "standard", + resample_filter: List[int] = [1, 1], + gridtype: str = "sinusoidal", + N_grid_channels: int = 4, + lead_time_channels: int = None, + lead_time_steps: int = 9, + prob_channels: List[int] = [], + checkpoint_level: int = 0, + ): + super().__init__( + img_resolution, + in_channels, + out_channels, + label_dim, + augment_dim, + model_channels, + channel_mult, + channel_mult_emb, + num_blocks, + attn_resolutions, + dropout, + label_dropout, + embedding_type, + channel_mult_noise, + encoder_type, + decoder_type, + resample_filter, + checkpoint_level, + ) + + self.gridtype = gridtype + self.N_grid_channels = N_grid_channels + self.pos_embd = self._get_positional_embedding() + self.lead_time_channels = lead_time_channels + self.lead_time_steps = lead_time_steps + self.lt_embd = self._get_lead_time_embedding() + self.prob_channels = prob_channels + if self.prob_channels: + self.scalar = torch.nn.Parameter( + torch.ones((1, len(self.prob_channels), 1, 1)) + ) + + @nvtx.annotate(message="SongUNet", color="blue") + def forward( + self, + x, + noise_labels, + class_labels, + lead_time_label=None, + global_index=None, + augment_labels=None, + ): + # append positional embedding to input conditioning + embeds = [] + if self.pos_embd is not None: + embeds.append(self.pos_embd.to(x.device)) + if self.lt_embd is not None: + embeds.append( + torch.reshape( + self.lt_embd[lead_time_label.int()], + (self.lead_time_channels, self.img_shape_y, self.img_shape_x), + ).to(x.device) + ) + if len(embeds) > 0: + embeds = torch.cat(embeds, dim=0) + selected_pos_embd = self.positional_embedding_indexing( + x, embeds, global_index + ) + x = torch.cat((x, selected_pos_embd), dim=1) + out = super().forward(x, noise_labels, class_labels, augment_labels) + # if training mode, let crossEntropyLoss do softmax. The model outputs logits. + # if eval mode, the model outputs probability + all_channels = list(range(out.shape[1])) # [0, 1, 2, ..., 10] + scalar_channels = [ + item for item in all_channels if item not in self.prob_channels + ] + if self.prob_channels and (not self.training): + out_final = torch.cat( + ( + out[:, scalar_channels], + (out[:, self.prob_channels] * self.scalar).softmax(dim=1), + ), + dim=1, + ) + elif self.prob_channels and self.training: + out_final = torch.cat( + (out[:, scalar_channels], (out[:, self.prob_channels] * self.scalar)), + dim=1, + ) + else: + out_final = out + return out_final + + def positional_embedding_indexing(self, x, pos_embd, global_index): + if global_index is None: + selected_pos_embd = ( + pos_embd.to(x.dtype).to(x.device)[None].expand((x.shape[0], -1, -1, -1)) + ) + else: + B = global_index.shape[0] + X = global_index.shape[2] + Y = global_index.shape[3] + global_index = torch.reshape( + torch.permute(global_index, (1, 0, 2, 3)), (2, -1) + ) # (B, 2, X, Y) to (2, B*X*Y) + selected_pos_embd = pos_embd.to(x.device)[ + :, global_index[0], global_index[1] + ] # (N_pe, B*X*Y) + selected_pos_embd = ( + torch.permute( + torch.reshape(selected_pos_embd, (pos_embd.shape[0], B, X, Y)), + (1, 0, 2, 3), + ) + .to(x.device) + .to(x.dtype) + ) # (B, N_pe, X, Y) + return selected_pos_embd + + def _get_positional_embedding(self): + if self.N_grid_channels == 0: + return None + elif self.gridtype == "learnable": + grid = torch.nn.Parameter( + torch.randn(self.N_grid_channels, self.img_shape_y, self.img_shape_x) + ) + elif self.gridtype == "linear": + if self.N_grid_channels != 2: + raise ValueError("N_grid_channels must be set to 2 for gridtype linear") + x = np.meshgrid(np.linspace(-1, 1, self.img_shape_y)) + y = np.meshgrid(np.linspace(-1, 1, self.img_shape_x)) + grid_x, grid_y = np.meshgrid(y, x) + grid = torch.from_numpy(np.stack((grid_x, grid_y), axis=0)) + grid.requires_grad = False + elif self.gridtype == "sinusoidal" and self.N_grid_channels == 4: + # print('sinusuidal grid added ......') + x1 = np.meshgrid(np.sin(np.linspace(0, 2 * np.pi, self.img_shape_y))) + x2 = np.meshgrid(np.cos(np.linspace(0, 2 * np.pi, self.img_shape_y))) + y1 = np.meshgrid(np.sin(np.linspace(0, 2 * np.pi, self.img_shape_x))) + y2 = np.meshgrid(np.cos(np.linspace(0, 2 * np.pi, self.img_shape_x))) + grid_x1, grid_y1 = np.meshgrid(y1, x1) + grid_x2, grid_y2 = np.meshgrid(y2, x2) + grid = torch.squeeze( + torch.from_numpy( + np.expand_dims( + np.stack((grid_x1, grid_y1, grid_x2, grid_y2), axis=0), axis=0 + ) + ) + ) + grid.requires_grad = False + elif self.gridtype == "sinusoidal" and self.N_grid_channels != 4: + if self.N_grid_channels % 4 != 0: + raise ValueError("N_grid_channels must be a factor of 4") + num_freq = self.N_grid_channels // 4 + freq_bands = 2.0 ** np.linspace(0.0, num_freq, num=num_freq) + grid_list = [] + grid_x, grid_y = np.meshgrid( + np.linspace(0, 2 * np.pi, self.img_shape_x), + np.linspace(0, 2 * np.pi, self.img_shape_y), + ) + for freq in freq_bands: + for p_fn in [np.sin, np.cos]: + grid_list.append(p_fn(grid_x * freq)) + grid_list.append(p_fn(grid_y * freq)) + grid = torch.from_numpy(np.stack(grid_list, axis=0)) + grid.requires_grad = False + elif self.gridtype == "test" and self.N_grid_channels == 2: + idx_x = torch.arange(self.img_shape_y) + idx_y = torch.arange(self.img_shape_x) + mesh_x, mesh_y = torch.meshgrid(idx_x, idx_y) + grid = torch.stack((mesh_x, mesh_y), dim=0) + else: + raise ValueError("Gridtype not supported.") + return grid + + def _get_lead_time_embedding(self): + if (self.lead_time_steps is None) or (self.lead_time_channels is None): + return None + grid = torch.nn.Parameter( + torch.randn( + self.lead_time_steps, + self.lead_time_channels, + self.img_shape_y, + self.img_shape_x, + ) + ) + return grid diff --git a/src/models/unet.py b/src/models/unet.py new file mode 100644 index 00000000..72706064 --- /dev/null +++ b/src/models/unet.py @@ -0,0 +1,267 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import importlib +from dataclasses import dataclass + +import torch + +from physicsnemo.models.meta import ModelMetaData +from physicsnemo.models.module import Module + +network_module = importlib.import_module("physicsnemo.models.diffusion") + + +@dataclass +class MetaData(ModelMetaData): + name: str = "UNet" + # Optimization + jit: bool = False + cuda_graphs: bool = False + amp_cpu: bool = False + amp_gpu: bool = True + torch_fx: bool = False + # Data type + bf16: bool = True + # Inference + onnx: bool = False + # Physics informed + func_torch: bool = False + auto_grad: bool = False + + +class UNet(Module): # TODO a lot of redundancy, need to clean up + """ + U-Net Wrapper for CorrDiff. + + Parameters + ----------- + img_resolution : int + The resolution of the input/output image. + img_channels : int + Number of color channels. + img_in_channels : int + Number of input color channels. + img_out_channels : int + Number of output color channels. + use_fp16: bool, optional + Execute the underlying model at FP16 precision?, by default False. + sigma_min: float, optional + Minimum supported noise level, by default 0. + sigma_max: float, optional + Maximum supported noise level, by default float('inf'). + sigma_data: float, optional + Expected standard deviation of the training data, by default 0.5. + model_type: str, optional + Class name of the underlying model, by default 'DhariwalUNet'. + **model_kwargs : dict + Keyword arguments for the underlying model. + + + References + ---------- + Mardani, M., Brenowitz, N., Cohen, Y., Pathak, J., Chen, C.Y., + Liu, C.C.,Vahdat, A., Kashinath, K., Kautz, J. and Pritchard, M., 2023. + Generative Residual Diffusion Modeling for Km-scale Atmospheric Downscaling. + arXiv preprint arXiv:2309.15214. + """ + + def __init__( + self, + img_resolution, + img_channels, + img_in_channels, + img_out_channels, + use_fp16=False, + sigma_min=0, + sigma_max=float("inf"), + sigma_data=0.5, + model_type="SongUNetPosEmbd", + **model_kwargs, + ): + super().__init__(meta=MetaData) + + self.img_channels = img_channels + + # for compatibility with older versions that took only 1 dimension + if isinstance(img_resolution, int): + self.img_shape_x = self.img_shape_y = img_resolution + else: + self.img_shape_x = img_resolution[0] + self.img_shape_y = img_resolution[1] + + self.img_in_channels = img_in_channels + self.img_out_channels = img_out_channels + + self.use_fp16 = use_fp16 + self.sigma_min = sigma_min + self.sigma_max = sigma_max + self.sigma_data = sigma_data + model_class = getattr(network_module, model_type) + self.model = model_class( + img_resolution=img_resolution, + in_channels=img_in_channels + img_out_channels, + out_channels=img_out_channels, + **model_kwargs, + ) + + def forward(self, x, img_lr, sigma, force_fp32=False, **model_kwargs): + # SR: concatenate input channels + if img_lr is not None: + x = torch.cat((x, img_lr), dim=1) + + x = x.to(torch.float32) + sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) + dtype = ( + torch.float16 + if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") + else torch.float32 + ) + + F_x = self.model( + x.to(dtype), # (c_in * x).to(dtype), + torch.zeros( + sigma.numel(), dtype=sigma.dtype, device=sigma.device + ), # c_noise.flatten() + class_labels=None, + **model_kwargs, + ) + + if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): + raise ValueError( + f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + ) + + # skip connection - for SR there's size mismatch bwtween input and output + D_x = F_x.to(torch.float32) + return D_x + + def round_sigma(self, sigma): + """ + Convert a given sigma value(s) to a tensor representation. + + Parameters + ---------- + sigma : Union[float list, torch.Tensor] + The sigma value(s) to convert. + + Returns + ------- + torch.Tensor + The tensor representation of the provided sigma value(s). + """ + return torch.as_tensor(sigma) + + +class StormCastUNet(Module): + """ + U-Net wrapper for StormCast; used so the same Song U-Net network can be re-used for this model. + + Parameters + ----------- + img_resolution : int or List[int] + The resolution of the input/output image. + img_channels : int + Number of color channels. + img_in_channels : int + Number of input color channels. + img_out_channels : int + Number of output color channels. + use_fp16: bool, optional + Execute the underlying model at FP16 precision?, by default False. + sigma_min: float, optional + Minimum supported noise level, by default 0. + sigma_max: float, optional + Maximum supported noise level, by default float('inf'). + sigma_data: float, optional + Expected standard deviation of the training data, by default 0.5. + model_type: str, optional + Class name of the underlying model, by default 'DhariwalUNet'. + **model_kwargs : dict + Keyword arguments for the underlying model. + + """ + + def __init__( + self, + img_resolution, + img_in_channels, + img_out_channels, + use_fp16=False, + sigma_min=0, + sigma_max=float("inf"), + sigma_data=0.5, + model_type="SongUNet", + **model_kwargs, + ): + super().__init__(meta=MetaData("StormCastUNet")) + + if isinstance(img_resolution, int): + self.img_shape_x = self.img_shape_y = img_resolution + else: + self.img_shape_x = img_resolution[0] + self.img_shape_y = img_resolution[1] + + self.img_in_channels = img_in_channels + self.img_out_channels = img_out_channels + + self.use_fp16 = use_fp16 + self.sigma_min = sigma_min + self.sigma_max = sigma_max + self.sigma_data = sigma_data + model_class = getattr(network_module, model_type) + self.model = model_class( + img_resolution=img_resolution, + in_channels=img_in_channels, + out_channels=img_out_channels, + **model_kwargs, + ) + + def forward(self, x, force_fp32=False, **model_kwargs): + """Run a forward pass of the StormCast regression U-Net. + + Args: + x (torch.Tensor): input to the U-Net + force_fp32 (bool, optional): force casting to fp_32 if True. Defaults to False. + + Raises: + ValueError: If input data type is a mismatch with provided options + + Returns: + D_x (torch.Tensor): Output (prediction) of the U-Net + """ + + x = x.to(torch.float32) + dtype = ( + torch.float16 + if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") + else torch.float32 + ) + + F_x = self.model( + x.to(dtype), + torch.zeros(x.shape[0], dtype=x.dtype, device=x.device), + class_labels=None, + **model_kwargs, + ) + + if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): + raise ValueError( + f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + ) + + D_x = F_x.to(torch.float32) + return D_x diff --git a/src/models/utils.py b/src/models/utils.py new file mode 100644 index 00000000..e1cde9d8 --- /dev/null +++ b/src/models/utils.py @@ -0,0 +1,66 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import numpy as np +import torch + + +def weight_init(shape: tuple, mode: str, fan_in: int, fan_out: int): + """ + Unified routine for initializing weights and biases. + This function provides a unified interface for various weight initialization + strategies like Xavier (Glorot) and Kaiming (He) initializations. + + Parameters + ---------- + shape : tuple + The shape of the tensor to initialize. It could represent weights or biases + of a layer in a neural network. + mode : str + The mode/type of initialization to use. Supported values are: + - "xavier_uniform": Xavier (Glorot) uniform initialization. + - "xavier_normal": Xavier (Glorot) normal initialization. + - "kaiming_uniform": Kaiming (He) uniform initialization. + - "kaiming_normal": Kaiming (He) normal initialization. + fan_in : int + The number of input units in the weight tensor. For convolutional layers, + this typically represents the number of input channels times the kernel height + times the kernel width. + fan_out : int + The number of output units in the weight tensor. For convolutional layers, + this typically represents the number of output channels times the kernel height + times the kernel width. + + Returns + ------- + torch.Tensor + The initialized tensor based on the specified mode. + + Raises + ------ + ValueError + If the provided `mode` is not one of the supported initialization modes. + """ + if mode == "xavier_uniform": + return np.sqrt(6 / (fan_in + fan_out)) * (torch.rand(*shape) * 2 - 1) + if mode == "xavier_normal": + return np.sqrt(2 / (fan_in + fan_out)) * torch.randn(*shape) + if mode == "kaiming_uniform": + return np.sqrt(3 / fan_in) * (torch.rand(*shape) * 2 - 1) + if mode == "kaiming_normal": + return np.sqrt(1 / fan_in) * torch.randn(*shape) + raise ValueError(f'Invalid init mode "{mode}"') From 1f404b38d32d2e6f3a187696bf5c942d14fa1bc6 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 11 Apr 2025 17:29:49 +0200 Subject: [PATCH 005/302] add utils and change imports --- src/distributed/__init__.py | 1 + src/distributed/config.py | 247 ++++++ src/distributed/manager.py | 775 ++++++++++++++++++ src/models/__init__.py | 3 + src/models/layers.py | 2 +- src/models/preconditioning copy.py | 1176 ---------------------------- src/models/preconditioning.py | 18 +- src/models/song_unet.py | 6 +- src/models/unet.py | 8 +- src/utils/capture.py | 513 ++++++++++++ src/utils/checkpoint.py | 398 ++++++++++ src/utils/console.py | 88 +++ src/utils/deterministic_sampler.py | 231 ++++++ src/utils/function_utils.py | 775 ++++++++++++++++++ src/utils/inference_utils.py | 253 ++++++ src/utils/model_utils.py | 66 ++ src/utils/stochastic_sampler.py | 533 +++++++++++++ src/utils/train_helpers.py | 107 +++ 18 files changed, 4007 insertions(+), 1193 deletions(-) create mode 100644 src/distributed/__init__.py create mode 100644 src/distributed/config.py create mode 100644 src/distributed/manager.py create mode 100644 src/models/__init__.py delete mode 100644 src/models/preconditioning copy.py create mode 100644 src/utils/capture.py create mode 100644 src/utils/checkpoint.py create mode 100644 src/utils/console.py create mode 100644 src/utils/deterministic_sampler.py create mode 100644 src/utils/function_utils.py create mode 100644 src/utils/inference_utils.py create mode 100644 src/utils/model_utils.py create mode 100644 src/utils/stochastic_sampler.py create mode 100644 src/utils/train_helpers.py diff --git a/src/distributed/__init__.py b/src/distributed/__init__.py new file mode 100644 index 00000000..0da01f3c --- /dev/null +++ b/src/distributed/__init__.py @@ -0,0 +1 @@ +from .manager import DistributedManager \ No newline at end of file diff --git a/src/distributed/config.py b/src/distributed/config.py new file mode 100644 index 00000000..c5414b4a --- /dev/null +++ b/src/distributed/config.py @@ -0,0 +1,247 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Dict, List, Optional, Union + +from treelib import Tree + + +class ProcessGroupNode: + """ + Class to store the attributes of a distributed process group + + Attributes + ---------- + name : str + Name of the process group + size : Optional[int] + Optional, number of processes in the process group + """ + + def __init__( + self, + name: str, + size: Optional[int] = None, + ): + """ + Constructor for the ProcessGroupNode class + + Parameters + ---------- + name : str + Name of the process group + size : Optional[int] + Optional, size of the process group + """ + self.name = name + self.size = size + + def __str__(self): + """ + String representation of the process group node + + Returns + ------- + str + String representation of the process group node + """ + return "ProcessGroupNode(" f"name={self.name}, " f"size={self.size}, " + + def __repr__(self): + """ + String representation of the process group node + + Returns + ------- + str + String representation of the process group node + """ + return self.__str__() + + +class ProcessGroupConfig: + """ + Class to define the configuration of a model's parallel process group structure as a + tree. Each node of the tree is of type `ProcessGroupNode`. + + Once the process group config structure (i.e, the tree structure) is set, it is + sufficient to set only the sizes for each leaf process group. Then, the size of + every parent group can be automatically computed as the product reduction of the + sub-tree of that parent group node. + + Examples + -------- + >>> from physicsnemo.distributed import ProcessGroupNode, ProcessGroupConfig + >>> + >>> # Create world group that contains all processes that are part of this job + >>> world = ProcessGroupNode("world") + >>> + >>> # Create the process group config with the highest level process group + >>> config = ProcessGroupConfig(world) + >>> + >>> # Create model and data parallel sub-groups + >>> # Sub-groups of a single node are guaranteed to be orthogonal by construction + >>> # Nodes can be added with either the name of the node or the node itself + >>> config.add_node(ProcessGroupNode("model_parallel"), parent=world) + >>> config.add_node(ProcessGroupNode("data_parallel"), parent="world") + >>> + >>> # Create spatial and channel parallel sub-groups + >>> config.add_node(ProcessGroupNode("spatial_parallel"), parent="model_parallel") + >>> config.add_node(ProcessGroupNode("channel_parallel"), parent="model_parallel") + >>> + >>> config.leaf_groups() + ['data_parallel', 'spatial_parallel', 'channel_parallel'] + >>> + >>> # Set leaf group sizes + >>> # Note: product of all leaf-node sizes should be the world size + >>> group_sizes = {"channel_parallel": 3, "spatial_parallel": 2, "data_parallel": 4} + >>> config.set_leaf_group_sizes(group_sizes) # Update all parent group sizes too + >>> config.get_node("model_parallel").size + 6 + """ + + def __init__(self, node: ProcessGroupNode): + """ + Constructor to the ProcessGroupConfig class + + Parameters + ---------- + node : ProcessGroupNode + Root node of the tree, typically would be 'world' + Note, it is generally recommended to set the child groups for 'world' + to 'model_parallel' and 'data_parallel' to aid with distributed + data parallel training unless there is a specific reason to choose a + different structure + """ + self.root = node + self.root_id = node.name + self.tree = Tree() + self.tree.create_node(node.name, node.name, data=node) + + def add_node(self, node: ProcessGroupNode, parent=Union[str, ProcessGroupNode]): + """ + Add a node to the process group config + + Parameters + ---------- + node : ProcessGroupNode + The new node to be added to the config + parent : Union[str, ProcessGroupNode] + Parent node of the node to be added. Should already be in the config. + If str, it is the name of the parent node. Otherwise, the parent + ProcessGroupNode itself. + """ + if isinstance(parent, ProcessGroupNode): + parent = parent.name + self.tree.create_node(node.name, node.name, data=node, parent=parent) + + def get_node(self, name: str) -> ProcessGroupNode: + """ + Method to get the node given the name of the node + + Parameters + ---------- + name : str + Name of the node to retrieve + + Returns + ------- + ProcessGroupNode + Node with the given name from the config + """ + return self.tree.get_node(name).data + + def update_parent_sizes(self, verbose: bool = False) -> int: + """ + Method to update parent node sizes after setting the sizes for each leaf node + + Parameters + ---------- + verbose : bool + If True, print a message each time a parent node size was updated + + Returns + ------- + int + Size of the root node + """ + return _tree_product_reduction(self.tree, self.root_id, verbose=verbose) + + def leaf_groups(self) -> List[str]: + """ + Get a list of all leaf group names + + Returns + ------- + List[str] + List of all leaf node names + """ + return [n.identifier for n in self.tree.leaves()] + + def set_leaf_group_sizes( + self, group_sizes: Dict[str, int], update_parent_sizes: bool = True + ): + """ + Set process group sizes for all leaf groups + + Parameters + ---------- + group_sizes : Dict[str, int] + Dictionary with a mapping of each leaf group name to its size + update_parent_sizes : bool + Update all parent group sizes based on the leaf group if True + If False, only set the leaf group sizes. + """ + for id, size in group_sizes.items(): + if not self.tree.contains(id): + raise AssertionError( + f"Process group {id} is not in this process group config" + ) + node = self.tree.get_node(id) + if not node.is_leaf(): + raise AssertionError(f"Process group {id} is not a leaf group") + node.data.size = size + + if update_parent_sizes: + self.update_parent_sizes() + + +def _tree_product_reduction(tree, node_id, verbose=False): + """ + Function to traverse a tree and compute the product reduction of + the sub-tree for each node starting from `node_id` + """ + children = tree.children(node_id) + node = tree.get_node(node_id) + if not children: + if node.data.size is None: + raise AssertionError("Leaf nodes should have a valid size set") + return node.data.size + + product = 1 + + for child in children: + product *= _tree_product_reduction(tree, child.identifier) + + if node.data.size != product: + if verbose: + print( + "Updating size of node " + f"{node.data.name} from {node.data.size} to {product}" + ) + node.data.size = product + + return product diff --git a/src/distributed/manager.py b/src/distributed/manager.py new file mode 100644 index 00000000..facb4664 --- /dev/null +++ b/src/distributed/manager.py @@ -0,0 +1,775 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import atexit +import os +import queue +import warnings +from typing import Optional, Tuple +from warnings import warn + +import numpy as np +import torch +import torch.distributed as dist + +from src.distributed.config import ProcessGroupConfig, ProcessGroupNode + +warnings.simplefilter("default", DeprecationWarning) + + +class UndefinedGroupError(Exception): + """Exception for querying an undefined process group using the PhysicsNeMo DistributedManager""" + + def __init__(self, name: str): + """ + + Parameters + ---------- + name : str + Name of the process group being queried. + + """ + message = ( + f"Cannot query process group '{name}' before it is explicitly created." + ) + super().__init__(message) + + +class UninitializedDistributedManagerWarning(Warning): + """Warning to indicate usage of an uninitialized DistributedManager""" + + def __init__(self): + message = ( + "A DistributedManager object is being instantiated before " + + "this singleton class has been initialized. Instantiating a manager before " + + "initialization can lead to unexpected results where processes fail " + + "to communicate. Initialize the distributed manager via " + + "DistributedManager.initialize() before instantiating." + ) + super().__init__(message) + + +class DistributedManager(object): + """Distributed Manager for setting up distributed training environment. + + This is a singleton that creates a persistance class instance for storing parallel + environment information through out the life time of the program. This should be + used to help set up Distributed Data Parallel and parallel datapipes. + + Note + ---- + One should call `DistributedManager.initialize()` prior to constructing a manager + object + + Example + ------- + >>> DistributedManager.initialize() + >>> manager = DistributedManager() + >>> manager.rank + 0 + >>> manager.world_size + 1 + """ + + _shared_state = {} + + def __new__(cls): + obj = super(DistributedManager, cls).__new__(cls) + obj.__dict__ = cls._shared_state + + # Set the defaults + if not hasattr(obj, "_rank"): + obj._rank = 0 + if not hasattr(obj, "_world_size"): + obj._world_size = 1 + if not hasattr(obj, "_local_rank"): + obj._local_rank = 0 + if not hasattr(obj, "_distributed"): + obj._distributed = False + if not hasattr(obj, "_device"): + obj._device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + if not hasattr(obj, "_cuda"): + obj._cuda = torch.cuda.is_available() + if not hasattr(obj, "_broadcast_buffers"): + obj._broadcast_buffers = False + if not hasattr(obj, "_find_unused_parameters"): + obj._find_unused_parameters = False + if not hasattr(obj, "_initialization_method"): + obj._initialization_method = "None" + if not hasattr(obj, "_groups"): + obj._groups = {} + if not hasattr(obj, "_group_ranks"): + obj._group_ranks = {} + if not hasattr(obj, "_group_names"): + obj._group_names = {} + if not hasattr(obj, "_is_initialized"): + obj._is_initialized = False + if not hasattr(obj, "_global_mesh"): + obj._global_mesh = None # Lazy initialized right when it's first needed + if not hasattr(obj, "_mesh_dims"): + obj._mesh_dims = {} # Dictionary mapping axis names to sizes + + return obj + + def __init__(self): + if not self._is_initialized: + raise UninitializedDistributedManagerWarning() + super().__init__() + + @property + def rank(self): + """Process rank""" + return self._rank + + @property + def local_rank(self): + """Process rank on local machine""" + return self._local_rank + + @property + def world_size(self): + """Number of processes in distributed environment""" + return self._world_size + + @property + def device(self): + """Process device""" + return self._device + + @property + def distributed(self): + """Distributed environment""" + return self._distributed + + @property + def cuda(self): + """If cuda is available""" + return self._cuda + + @property + def mesh_dims(self): + """Mesh Dimensions as dictionary (axis name : size)""" + return self._mesh_dims + + @property + def group_names(self): + """ + Returns a list of all named process groups created + """ + return self._groups.keys() + + @property + def global_mesh(self): + """ + Returns the global mesh. If it's not initialized, it will be created when this is called. + """ + if self._global_mesh is None: + # Fully flat mesh (1D) by default: + self.initialize_mesh(mesh_shape=(-1,), mesh_dim_names=("world",)) + + return self._global_mesh + + def mesh_names(self): + """ + Return mesh axis names + """ + return self._mesh_dims.keys() + + def mesh_sizes(self): + """ + Return mesh axis sizes + """ + return self._mesh_dims.values() + + def group(self, name=None): + """ + Returns a process group with the given name + If name is None, group is also None indicating the default process group + If named group does not exist, UndefinedGroupError exception is raised + """ + if name in self._groups.keys(): + return self._groups[name] + elif name is None: + return None + else: + raise UndefinedGroupError(name) + + def mesh(self, name=None): + """ + Return a device_mesh with the given name. + Does not initialize. If the mesh is not created + already, will raise and error + + Parameters + ---------- + name : str, optional + Name of desired mesh, by default None + """ + + if name in self._global_mesh.axis_names: + return self._global_mesh[name] + elif name is None: + return self._global_mesh + else: + raise UndefinedGroupError(f"Mesh axis {name} not defined") + + def group_size(self, name=None): + """ + Returns the size of named process group + """ + if name is None: + return self._world_size + group = self.group(name) + return dist.get_world_size(group=group) + + def group_rank(self, name=None): + """ + Returns the rank in named process group + """ + if name is None: + return self._rank + group = self.group(name) + return dist.get_rank(group=group) + + def group_name(self, group=None): + """ + Returns the name of process group + """ + if group is None: + return None + return self._group_names[group] + + @property + def broadcast_buffers(self): + """broadcast_buffers in PyTorch DDP""" + return self._broadcast_buffers + + @broadcast_buffers.setter + def broadcast_buffers(self, broadcast: bool): + """Setter for broadcast_buffers""" + self._broadcast_buffers = broadcast + + @property + def find_unused_parameters(self): + """find_unused_parameters in PyTorch DDP""" + return self._find_unused_parameters + + @find_unused_parameters.setter + def find_unused_parameters(self, find_params: bool): + """Setter for find_unused_parameters""" + if find_params: + warn( + "Setting `find_unused_parameters` in DDP to true, " + "use only if necessary." + ) + self._find_unused_parameters = find_params + + def __str__(self): + output = ( + f"Initialized process {self.rank} of {self.world_size} using " + f"method '{self._initialization_method}'. Device set to {str(self.device)}" + ) + return output + + @classmethod + def is_initialized(cls) -> bool: + """If manager singleton has been initialized""" + return cls._shared_state.get("_is_initialized", False) + + @staticmethod + def get_available_backend(): + """Get communication backend""" + if torch.cuda.is_available() and torch.distributed.is_nccl_available(): + return "nccl" + else: + return "gloo" + + @staticmethod + def initialize_env(): + """Setup method using generic initialization""" + rank = int(os.environ.get("RANK")) + world_size = int(os.environ.get("WORLD_SIZE")) + if "LOCAL_RANK" in os.environ: + local_rank = os.environ.get("LOCAL_RANK") + if local_rank is not None: + local_rank = int(local_rank) + else: + local_rank = rank % torch.cuda.device_count() + + else: + local_rank = rank % torch.cuda.device_count() + + # Read env variables + addr = os.environ.get("MASTER_ADDR") + port = os.environ.get("MASTER_PORT") + + DistributedManager.setup( + rank=rank, + world_size=world_size, + local_rank=local_rank, + addr=addr, + port=port, + backend=DistributedManager.get_available_backend(), + ) + + @staticmethod + def initialize_open_mpi(addr, port): + """Setup method using OpenMPI initialization""" + rank = int(os.environ.get("OMPI_COMM_WORLD_RANK")) + world_size = int(os.environ.get("OMPI_COMM_WORLD_SIZE")) + local_rank = int(os.environ.get("OMPI_COMM_WORLD_LOCAL_RANK")) + + DistributedManager.setup( + rank=rank, + world_size=world_size, + local_rank=local_rank, + addr=addr, + port=port, + backend=DistributedManager.get_available_backend(), + method="openmpi", + ) + + @staticmethod + def initialize_slurm(port): + """Setup method using SLURM initialization""" + rank = int(os.environ.get("SLURM_PROCID")) + world_size = int(os.environ.get("SLURM_NPROCS")) + local_rank = int(os.environ.get("SLURM_LOCALID")) + addr = os.environ.get("SLURM_LAUNCH_NODE_IPADDR") + + DistributedManager.setup( + rank=rank, + world_size=world_size, + local_rank=local_rank, + addr=addr, + port=port, + backend=DistributedManager.get_available_backend(), + method="slurm", + ) + + @staticmethod + def initialize(): + """ + Initialize distributed manager + + Current supported initialization methods are: + `ENV`: PyTorch environment variable initialization + https://pytorch.org/docs/stable/distributed.html#environment-variable-initialization + `SLURM`: Initialization on SLURM systems. + Uses `SLURM_PROCID`, `SLURM_NPROCS`, `SLURM_LOCALID` and + `SLURM_LAUNCH_NODE_IPADDR` environment variables. + `OPENMPI`: Initialization for OpenMPI launchers. + Uses `OMPI_COMM_WORLD_RANK`, `OMPI_COMM_WORLD_SIZE` and + `OMPI_COMM_WORLD_LOCAL_RANK` environment variables. + + Initialization by default is done using the first valid method in the order + listed above. Initialization method can also be explicitly controlled using the + `PHYSICSNEMO_DISTRIBUTED_INITIALIZATION_METHOD` environment variable and setting it + to one of the options above. + """ + if DistributedManager.is_initialized(): + warn("Distributed manager is already intialized") + return + + addr = os.getenv("MASTER_ADDR", "localhost") + port = os.getenv("MASTER_PORT", "12355") + # https://pytorch.org/docs/master/notes/cuda.html#id5 + # was changed in version 2.2 + if torch.__version__ < (2, 2): + os.environ["NCCL_ASYNC_ERROR_HANDLING"] = "0" + else: + os.environ["TORCH_NCCL_ASYNC_ERROR_HANDLING"] = "0" + initialization_method = os.getenv( + "PHYSICSNEMO_DISTRIBUTED_INITIALIZATION_METHOD" + ) + if initialization_method is None: + try: + DistributedManager.initialize_env() + except TypeError: + if "SLURM_PROCID" in os.environ: + DistributedManager.initialize_slurm(port) + elif "OMPI_COMM_WORLD_RANK" in os.environ: + DistributedManager.initialize_open_mpi(addr, port) + else: + warn( + "Could not initialize using ENV, SLURM or OPENMPI methods. Assuming this is a single process job" + ) + DistributedManager._shared_state["_is_initialized"] = True + elif initialization_method == "ENV": + DistributedManager.initialize_env() + elif initialization_method == "SLURM": + DistributedManager.initialize_slurm(port) + elif initialization_method == "OPENMPI": + DistributedManager.initialize_open_mpi(addr, port) + else: + raise RuntimeError( + "Unknown initialization method " + f"{initialization_method}. " + "Supported values for " + "PHYSICSNEMO_DISTRIBUTED_INITIALIZATION_METHOD are " + "ENV, SLURM and OPENMPI" + ) + + # Set per rank numpy random seed for data sampling + np.random.seed(seed=DistributedManager().rank) + + def initialize_mesh( + self, mesh_shape: Tuple[int, ...], mesh_dim_names: Tuple[str, ...] + ) -> dist.DeviceMesh: + """ + Initialize a global device mesh over the entire distributed job. + + Creates a multi-dimensional mesh of processes that can be used for distributed + operations. The mesh shape must multiply to equal the total world size, with + one dimension optionally being flexible (-1). + + Parameters + ---------- + mesh_shape : Tuple[int, ...] + Tuple of ints describing the size of each mesh dimension. Product must equal + world_size. One dimension can be -1 to be automatically calculated. + + mesh_dim_names : Tuple[str, ...] + Names for each mesh dimension. Must match length of mesh_shape. + + Returns + ------- + torch.distributed.DeviceMesh + The initialized device mesh + + Raises + ------ + RuntimeError + If mesh dimensions are invalid or don't match world size + AssertionError + If distributed environment is not available + """ + + manager = DistributedManager() + if not manager.distributed: + raise AssertionError( + "torch.distributed is unavailable. " + "Check pytorch build to ensure the distributed package is available. " + "If building PyTorch from source, set `USE_DISTRIBUTED=1` " + "to enable the distributed package" + ) + + # Assert basic properties: + if len(mesh_shape) == 0: + raise RuntimeError( + "Device Mesh requires at least one mesh dimension in `mesh_shape`" + ) + if len(mesh_shape) != len(mesh_dim_names): + raise RuntimeError( + "mesh_shape and mesh_dim_names must have the same length, but found " + f"{len(mesh_shape)} and {len(mesh_dim_names)} respectively." + ) + if len(set(mesh_dim_names)) != len(mesh_dim_names): + raise RuntimeError("Mesh dimension names must be unique") + + # Check against the total mesh shape vs. world size: + total_mesh_shape = np.prod(mesh_shape) + + # Allow one shape to be -1 + if -1 in mesh_shape: + residual_shape = int(self.world_size / (-1 * total_mesh_shape)) + + # Replace -1 with the computed size: + mesh_shape = [residual_shape if m == -1 else m for m in mesh_shape] + # Recompute total shape: + total_mesh_shape = np.prod(mesh_shape) + + if total_mesh_shape != self.world_size: + raise RuntimeError( + "Device Mesh num elements must equal world size of " + f"{total_mesh_shape} but was configured by user with " + f"global size of {self.world_size}." + ) + + # Actually create the mesh: + self._global_mesh = dist.init_device_mesh( + "cuda" if self.cuda else "cpu", + mesh_shape, + mesh_dim_names=mesh_dim_names, + ) + + # Finally, upon success, cache the mesh dimensions: + self._mesh_dims = {key: val for key, val in zip(mesh_dim_names, mesh_shape)} + + return self._global_mesh + + @staticmethod + def setup( + rank=0, + world_size=1, + local_rank=None, + addr="localhost", + port="12355", + backend="nccl", + method="env", + ): + """Set up PyTorch distributed process group and update manager attributes""" + os.environ["MASTER_ADDR"] = addr + os.environ["MASTER_PORT"] = str(port) + + DistributedManager._shared_state["_is_initialized"] = True + manager = DistributedManager() + + manager._distributed = torch.distributed.is_available() + if manager._distributed: + # Update rank and world_size if using distributed + manager._rank = rank + manager._world_size = world_size + if local_rank is None: + manager._local_rank = rank % torch.cuda.device_count() + else: + manager._local_rank = local_rank + + manager._device = torch.device( + f"cuda:{manager.local_rank}" if torch.cuda.is_available() else "cpu" + ) + + if manager._distributed: + # Setup distributed process group + try: + dist.init_process_group( + backend, + rank=manager.rank, + world_size=manager.world_size, + device_id=manager.device, + ) + except TypeError: + # device_id only introduced in PyTorch 2.3 + dist.init_process_group( + backend, + rank=manager.rank, + world_size=manager.world_size, + ) + + if torch.cuda.is_available(): + # Set device for this process and empty cache to optimize memory usage + torch.cuda.set_device(manager.device) + torch.cuda.device(manager.device) + torch.cuda.empty_cache() + + manager._initialization_method = method + + @staticmethod + def create_process_subgroup( + name: str, size: int, group_name: Optional[str] = None, verbose: bool = False + ): # pragma: no cover + """ + Create a process subgroup of a parent process group. This must be a collective + call by all processes participating in this application. + + Parameters + ---------- + name : str + Name of the process subgroup to be created. + + size : int + Size of the process subgroup to be created. This must be an integer factor of + the parent group's size. + + group_name : Optional[str] + Name of the parent process group, optional. If None, the default process group + will be used. Default None. + + verbose : bool + Print out ranks of each created process group, default False. + + """ + manager = DistributedManager() + if not manager.distributed: + raise AssertionError( + "torch.distributed is unavailable. " + "Check pytorch build to ensure the distributed package is available. " + "If building PyTorch from source, set `USE_DISTRIBUTED=1` " + "to enable the distributed package" + ) + + if name in manager._groups: + raise AssertionError(f"Group with name {name} already exists") + + # Get parent group's params + group = manager._groups[group_name] if group_name else None + group_size = dist.get_world_size(group=group) + num_groups = manager.world_size // group_size + + # Get number of sub-groups per parent group + if group_size % size != 0: + raise AssertionError( + f"Cannot divide group size {group_size} evenly into subgroups of" + f" size {size}" + ) + num_subgroups = group_size // size + + # Create all the sub-groups + # Note: all ranks in the job need to create all sub-groups in + # the same order even if a rank is not part of a sub-group + manager._group_ranks[name] = [] + for g in range(num_groups): + for i in range(num_subgroups): + # Get global ranks that are part of this sub-group + start = i * size + end = start + size + if group_name: + ranks = manager._group_ranks[group_name][g][start:end] + else: + ranks = list(range(start, end)) + # Create sub-group and keep track of ranks + tmp_group = dist.new_group(ranks=ranks) + manager._group_ranks[name].append(ranks) + if manager.rank in ranks: + # Set group in manager only if this rank is part of the group + manager._groups[name] = tmp_group + manager._group_names[tmp_group] = name + + if verbose and manager.rank == 0: + print(f"Process group '{name}':") + for grp in manager._group_ranks[name]: + print(" ", grp) + + @staticmethod + def create_orthogonal_process_group( + orthogonal_group_name: str, group_name: str, verbose: bool = False + ): # pragma: no cover + """ + Create a process group that is orthogonal to the specified process group. + + Parameters + ---------- + orthogonal_group_name : str + Name of the orthogonal process group to be created. + + group_name : str + Name of the existing process group. + + verbose : bool + Print out ranks of each created process group, default False. + + """ + manager = DistributedManager() + if not manager.distributed: + raise AssertionError( + "torch.distributed is unavailable. " + "Check pytorch build to ensure the distributed package is available. " + "If building PyTorch from source, set `USE_DISTRIBUTED=1` " + "to enable the distributed package" + ) + + if group_name not in manager._groups: + raise ValueError(f"Group with name {group_name} does not exist") + if orthogonal_group_name in manager._groups: + raise ValueError(f"Group with name {orthogonal_group_name} already exists") + + group_ranks = manager._group_ranks[group_name] + orthogonal_ranks = [list(i) for i in zip(*group_ranks)] + + for ranks in orthogonal_ranks: + tmp_group = dist.new_group(ranks=ranks) + if manager.rank in ranks: + # Set group in manager only if this rank is part of the group + manager._groups[orthogonal_group_name] = tmp_group + manager._group_names[tmp_group] = orthogonal_group_name + + manager._group_ranks[orthogonal_group_name] = orthogonal_ranks + + if verbose and manager.rank == 0: + print(f"Process group '{orthogonal_group_name}':") + for grp in manager._group_ranks[orthogonal_group_name]: + print(" ", grp) + + @staticmethod + def create_group_from_node( + node: ProcessGroupNode, + parent: Optional[str] = None, + verbose: bool = False, + ): # pragma: no cover + if node.size is None: + raise AssertionError( + "Cannot create groups from a ProcessGroupNode that is not fully" + " populated. Ensure that config.set_leaf_group_sizes is called first" + " with `update_parent_sizes = True`" + ) + + DistributedManager.create_process_subgroup( + node.name, node.size, group_name=parent, verbose=verbose + ) + # Create orthogonal process group + orthogonal_group = f"__orthogonal_to_{node.name}" + DistributedManager.create_orthogonal_process_group( + orthogonal_group, node.name, verbose=verbose + ) + return orthogonal_group + + @staticmethod + def create_groups_from_config( + config: ProcessGroupConfig, verbose: bool = False + ): # pragma: no cover + + warnings.warn( + "DistributedManager.create_groups_from_config is no longer the most simple " + "way to organize process groups. Please switch to DeviceMesh, " + "and DistributedManager.initialize_mesh", + category=DeprecationWarning, + stacklevel=2, + ) + + # Traverse process group tree in breadth first order + # to create nested process groups + q = queue.Queue() + q.put(config.root_id) + DistributedManager.create_group_from_node(config.root) + + while not q.empty(): + node_id = q.get() + if verbose: + print(f"Node ID: {node_id}") + + children = config.tree.children(node_id) + if verbose: + print(f" Children: {children}") + + parent_group = node_id + for child in children: + # Create child group and replace parent group by orthogonal group so + # that each child forms an independent block of processes + parent_group = DistributedManager.create_group_from_node( + child.data, + parent=parent_group, + ) + + # Add child ids to the queue + q.put(child.identifier) + + @atexit.register + @staticmethod + def cleanup(): + """Clean up distributed group and singleton""" + # Destroying group.WORLD is enough for all process groups to get destroyed + if ( + "_is_initialized" in DistributedManager._shared_state + and DistributedManager._shared_state["_is_initialized"] + and "_distributed" in DistributedManager._shared_state + and DistributedManager._shared_state["_distributed"] + ): + if torch.cuda.is_available(): + dist.barrier(device_ids=[DistributedManager().local_rank]) + else: + dist.barrier() + dist.destroy_process_group() + DistributedManager._shared_state = {} diff --git a/src/models/__init__.py b/src/models/__init__.py new file mode 100644 index 00000000..6b790ae4 --- /dev/null +++ b/src/models/__init__.py @@ -0,0 +1,3 @@ +from .unet import UNet +from .song_unet import SongUNet, SongUNetPosEmbd, SongUNetPosLtEmbd +from .layers import Linear, Conv2d, GroupNorm, AttentionOp, UNetBlock, PositionalEmbedding, FourierEmbedding \ No newline at end of file diff --git a/src/models/layers.py b/src/models/layers.py index 1fb3b171..d5a1ab29 100644 --- a/src/models/layers.py +++ b/src/models/layers.py @@ -26,7 +26,7 @@ from einops import rearrange from torch.nn.functional import silu -from physicsnemo.models.diffusion import weight_init +from src.utils.model_utils import weight_init class Linear(torch.nn.Module): diff --git a/src/models/preconditioning copy.py b/src/models/preconditioning copy.py deleted file mode 100644 index 52a16608..00000000 --- a/src/models/preconditioning copy.py +++ /dev/null @@ -1,1176 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. -# SPDX-FileCopyrightText: All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Preconditioning schemes used in the paper"Elucidating the Design Space of -Diffusion-Based Generative Models". -""" - -import importlib -import warnings -from dataclasses import dataclass -from typing import List, Union - -import numpy as np -import nvtx -import torch - -from physicsnemo.models.diffusion import ( - DhariwalUNet, # noqa: F401 for globals - SongUNet, # noqa: F401 for globals -) -from physicsnemo.models.meta import ModelMetaData -from physicsnemo.models.module import Module - -network_module = importlib.import_module("physicsnemo.models.diffusion") - - -@dataclass -class VPPrecondMetaData(ModelMetaData): - """VPPrecond meta data""" - - name: str = "VPPrecond" - # Optimization - jit: bool = False - cuda_graphs: bool = False - amp_cpu: bool = False - amp_gpu: bool = True - torch_fx: bool = False - # Data type - bf16: bool = False - # Inference - onnx: bool = False - # Physics informed - func_torch: bool = False - auto_grad: bool = False - - -class VPPrecond(Module): - """ - Preconditioning corresponding to the variance preserving (VP) formulation. - - Parameters - ---------- - img_resolution : int - Image resolution. - img_channels : int - Number of color channels. - label_dim : int - Number of class labels, 0 = unconditional, by default 0. - use_fp16 : bool - Execute the underlying model at FP16 precision?, by default False. - beta_d : float - Extent of the noise level schedule, by default 19.9. - beta_min : float - Initial slope of the noise level schedule, by default 0.1. - M : int - Original number of timesteps in the DDPM formulation, by default 1000. - epsilon_t : float - Minimum t-value used during training, by default 1e-5. - model_type :str - Class name of the underlying model, by default "SongUNet". - **model_kwargs : dict - Keyword arguments for the underlying model. - - Note - ---- - Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and - Poole, B., 2020. Score-based generative modeling through stochastic differential - equations. arXiv preprint arXiv:2011.13456. - """ - - def __init__( - self, - img_resolution: int, - img_channels: int, - label_dim: int = 0, - use_fp16: bool = False, - beta_d: float = 19.9, - beta_min: float = 0.1, - M: int = 1000, - epsilon_t: float = 1e-5, - model_type: str = "SongUNet", - **model_kwargs: dict, - ): - super().__init__(meta=VPPrecondMetaData) - self.img_resolution = img_resolution - self.img_channels = img_channels - self.label_dim = label_dim - self.use_fp16 = use_fp16 - self.beta_d = beta_d - self.beta_min = beta_min - self.M = M - self.epsilon_t = epsilon_t - self.sigma_min = float(self.sigma(epsilon_t)) - self.sigma_max = float(self.sigma(1)) - model_class = getattr(network_module, model_type) - self.model = model_class( - img_resolution=img_resolution, - in_channels=img_channels, - out_channels=img_channels, - label_dim=label_dim, - **model_kwargs, - ) # TODO needs better handling - - def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): - x = x.to(torch.float32) - sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) - class_labels = ( - None - if self.label_dim == 0 - else torch.zeros([1, self.label_dim], device=x.device) - if class_labels is None - else class_labels.to(torch.float32).reshape(-1, self.label_dim) - ) - dtype = ( - torch.float16 - if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") - else torch.float32 - ) - - c_skip = 1 - c_out = -sigma - c_in = 1 / (sigma**2 + 1).sqrt() - c_noise = (self.M - 1) * self.sigma_inv(sigma) - - F_x = self.model( - (c_in * x).to(dtype), - c_noise.flatten(), - class_labels=class_labels, - **model_kwargs, - ) - if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): - raise ValueError( - f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." - ) - - D_x = c_skip * x + c_out * F_x.to(torch.float32) - return D_x - - def sigma(self, t: Union[float, torch.Tensor]): - """ - Compute the sigma(t) value for a given t based on the VP formulation. - - The function calculates the noise level schedule for the diffusion process based - on the given parameters `beta_d` and `beta_min`. - - Parameters - ---------- - t : Union[float, torch.Tensor] - The timestep or set of timesteps for which to compute sigma(t). - - Returns - ------- - torch.Tensor - The computed sigma(t) value(s). - """ - t = torch.as_tensor(t) - return ((0.5 * self.beta_d * (t**2) + self.beta_min * t).exp() - 1).sqrt() - - def sigma_inv(self, sigma: Union[float, torch.Tensor]): - """ - Compute the inverse of the sigma function for a given sigma. - - This function effectively calculates t from a given sigma(t) based on the - parameters `beta_d` and `beta_min`. - - Parameters - ---------- - sigma : Union[float, torch.Tensor] - The sigma(t) value or set of sigma(t) values for which to compute the - inverse. - - Returns - ------- - torch.Tensor - The computed t value(s) corresponding to the provided sigma(t). - """ - sigma = torch.as_tensor(sigma) - return ( - (self.beta_min**2 + 2 * self.beta_d * (1 + sigma**2).log()).sqrt() - - self.beta_min - ) / self.beta_d - - def round_sigma(self, sigma: Union[float, List, torch.Tensor]): - """ - Convert a given sigma value(s) to a tensor representation. - - Parameters - ---------- - sigma : Union[float list, torch.Tensor] - The sigma value(s) to convert. - - Returns - ------- - torch.Tensor - The tensor representation of the provided sigma value(s). - """ - return torch.as_tensor(sigma) - - -@dataclass -class VEPrecondMetaData(ModelMetaData): - """VEPrecond meta data""" - - name: str = "VEPrecond" - # Optimization - jit: bool = False - cuda_graphs: bool = False - amp_cpu: bool = False - amp_gpu: bool = True - torch_fx: bool = False - # Data type - bf16: bool = False - # Inference - onnx: bool = False - # Physics informed - func_torch: bool = False - auto_grad: bool = False - - -class VEPrecond(Module): - """ - Preconditioning corresponding to the variance exploding (VE) formulation. - - Parameters - ---------- - img_resolution : int - Image resolution. - img_channels : int - Number of color channels. - label_dim : int - Number of class labels, 0 = unconditional, by default 0. - use_fp16 : bool - Execute the underlying model at FP16 precision?, by default False. - sigma_min : float - Minimum supported noise level, by default 0.02. - sigma_max : float - Maximum supported noise level, by default 100.0. - model_type :str - Class name of the underlying model, by default "SongUNet". - **model_kwargs : dict - Keyword arguments for the underlying model. - - Note - ---- - Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and - Poole, B., 2020. Score-based generative modeling through stochastic differential - equations. arXiv preprint arXiv:2011.13456. - """ - - def __init__( - self, - img_resolution: int, - img_channels: int, - label_dim: int = 0, - use_fp16: bool = False, - sigma_min: float = 0.02, - sigma_max: float = 100.0, - model_type: str = "SongUNet", - **model_kwargs: dict, - ): - super().__init__(meta=VEPrecondMetaData) - self.img_resolution = img_resolution - self.img_channels = img_channels - self.label_dim = label_dim - self.use_fp16 = use_fp16 - self.sigma_min = sigma_min - self.sigma_max = sigma_max - model_class = getattr(network_module, model_type) - self.model = model_class( - img_resolution=img_resolution, - in_channels=img_channels, - out_channels=img_channels, - label_dim=label_dim, - **model_kwargs, - ) # TODO needs better handling - - def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): - x = x.to(torch.float32) - sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) - class_labels = ( - None - if self.label_dim == 0 - else torch.zeros([1, self.label_dim], device=x.device) - if class_labels is None - else class_labels.to(torch.float32).reshape(-1, self.label_dim) - ) - dtype = ( - torch.float16 - if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") - else torch.float32 - ) - - c_skip = 1 - c_out = sigma - c_in = 1 - c_noise = (0.5 * sigma).log() - - F_x = self.model( - (c_in * x).to(dtype), - c_noise.flatten(), - class_labels=class_labels, - **model_kwargs, - ) - if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): - raise ValueError( - f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." - ) - - D_x = c_skip * x + c_out * F_x.to(torch.float32) - return D_x - - def round_sigma(self, sigma: Union[float, List, torch.Tensor]): - """ - Convert a given sigma value(s) to a tensor representation. - - Parameters - ---------- - sigma : Union[float list, torch.Tensor] - The sigma value(s) to convert. - - Returns - ------- - torch.Tensor - The tensor representation of the provided sigma value(s). - """ - return torch.as_tensor(sigma) - - -@dataclass -class iDDPMPrecondMetaData(ModelMetaData): - """iDDPMPrecond meta data""" - - name: str = "iDDPMPrecond" - # Optimization - jit: bool = False - cuda_graphs: bool = False - amp_cpu: bool = False - amp_gpu: bool = True - torch_fx: bool = False - # Data type - bf16: bool = False - # Inference - onnx: bool = False - # Physics informed - func_torch: bool = False - auto_grad: bool = False - - -class iDDPMPrecond(Module): - """ - Preconditioning corresponding to the improved DDPM (iDDPM) formulation. - - Parameters - ---------- - img_resolution : int - Image resolution. - img_channels : int - Number of color channels. - label_dim : int - Number of class labels, 0 = unconditional, by default 0. - use_fp16 : bool - Execute the underlying model at FP16 precision?, by default False. - C_1 : float - Timestep adjustment at low noise levels., by default 0.001. - C_2 : float - Timestep adjustment at high noise levels., by default 0.008. - M: int - Original number of timesteps in the DDPM formulation, by default 1000. - model_type :str - Class name of the underlying model, by default "DhariwalUNet". - **model_kwargs : dict - Keyword arguments for the underlying model. - - Note - ---- - Reference: Nichol, A.Q. and Dhariwal, P., 2021, July. Improved denoising diffusion - probabilistic models. In International Conference on Machine Learning - (pp. 8162-8171). PMLR. - """ - - def __init__( - self, - img_resolution, - img_channels, - label_dim=0, - use_fp16=False, - C_1=0.001, - C_2=0.008, - M=1000, - model_type="DhariwalUNet", - **model_kwargs, - ): - super().__init__(meta=iDDPMPrecondMetaData) - self.img_resolution = img_resolution - self.img_channels = img_channels - self.label_dim = label_dim - self.use_fp16 = use_fp16 - self.C_1 = C_1 - self.C_2 = C_2 - self.M = M - model_class = getattr(network_module, model_type) - self.model = model_class( - img_resolution=img_resolution, - in_channels=img_channels, - out_channels=img_channels * 2, - label_dim=label_dim, - **model_kwargs, - ) # TODO needs better handling - - u = torch.zeros(M + 1) - for j in range(M, 0, -1): # M, ..., 1 - u[j - 1] = ( - (u[j] ** 2 + 1) - / (self.alpha_bar(j - 1) / self.alpha_bar(j)).clip(min=C_1) - - 1 - ).sqrt() - self.register_buffer("u", u) - self.sigma_min = float(u[M - 1]) - self.sigma_max = float(u[0]) - - def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): - x = x.to(torch.float32) - sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) - class_labels = ( - None - if self.label_dim == 0 - else torch.zeros([1, self.label_dim], device=x.device) - if class_labels is None - else class_labels.to(torch.float32).reshape(-1, self.label_dim) - ) - dtype = ( - torch.float16 - if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") - else torch.float32 - ) - - c_skip = 1 - c_out = -sigma - c_in = 1 / (sigma**2 + 1).sqrt() - c_noise = ( - self.M - 1 - self.round_sigma(sigma, return_index=True).to(torch.float32) - ) - - F_x = self.model( - (c_in * x).to(dtype), - c_noise.flatten(), - class_labels=class_labels, - **model_kwargs, - ) - if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): - raise ValueError( - f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." - ) - - D_x = c_skip * x + c_out * F_x[:, : self.img_channels].to(torch.float32) - return D_x - - def alpha_bar(self, j): - """ - Compute the alpha_bar(j) value for a given j based on the iDDPM formulation. - - Parameters - ---------- - j : Union[int, torch.Tensor] - The timestep or set of timesteps for which to compute alpha_bar(j). - - Returns - ------- - torch.Tensor - The computed alpha_bar(j) value(s). - """ - j = torch.as_tensor(j) - return (0.5 * np.pi * j / self.M / (self.C_2 + 1)).sin() ** 2 - - def round_sigma(self, sigma, return_index=False): - """ - Round the provided sigma value(s) to the nearest value(s) in a - pre-defined set `u`. - - Parameters - ---------- - sigma : Union[float, list, torch.Tensor] - The sigma value(s) to round. - return_index : bool, optional - Whether to return the index/indices of the rounded value(s) in `u` instead - of the rounded value(s) themselves, by default False. - - Returns - ------- - torch.Tensor - The rounded sigma value(s) or their index/indices in `u`, depending on the - value of `return_index`. - """ - sigma = torch.as_tensor(sigma) - index = torch.cdist( - sigma.to(self.u.device).to(torch.float32).reshape(1, -1, 1), - self.u.reshape(1, -1, 1), - ).argmin(2) - result = index if return_index else self.u[index.flatten()].to(sigma.dtype) - return result.reshape(sigma.shape).to(sigma.device) - - -@dataclass -class EDMPrecondMetaData(ModelMetaData): - """EDMPrecond meta data""" - - name: str = "EDMPrecond" - # Optimization - jit: bool = False - cuda_graphs: bool = False - amp_cpu: bool = False - amp_gpu: bool = True - torch_fx: bool = False - # Data type - bf16: bool = False - # Inference - onnx: bool = False - # Physics informed - func_torch: bool = False - auto_grad: bool = False - - -class EDMPrecond(Module): - """ - Improved preconditioning proposed in the paper "Elucidating the Design Space of - Diffusion-Based Generative Models" (EDM) - - Parameters - ---------- - img_resolution : int - Image resolution. - img_channels : int - Number of color channels (for both input and output). If your model - requires a different number of input or output chanels, - override this by passing either of the optional - img_in_channels or img_out_channels args - label_dim : int - Number of class labels, 0 = unconditional, by default 0. - use_fp16 : bool - Execute the underlying model at FP16 precision?, by default False. - sigma_min : float - Minimum supported noise level, by default 0.0. - sigma_max : float - Maximum supported noise level, by default inf. - sigma_data : float - Expected standard deviation of the training data, by default 0.5. - model_type :str - Class name of the underlying model, by default "DhariwalUNet". - img_in_channels: int - Optional setting for when number of input channels =/= number of output - channels. If set, will override img_channels for the input - This is useful in the case of additional (conditional) channels - img_out_channels: int - Optional setting for when number of input channels =/= number of output - channels. If set, will override img_channels for the output - **model_kwargs : dict - Keyword arguments for the underlying model. - - Note - ---- - Reference: Karras, T., Aittala, M., Aila, T. and Laine, S., 2022. Elucidating the - design space of diffusion-based generative models. Advances in Neural Information - Processing Systems, 35, pp.26565-26577. - """ - - def __init__( - self, - img_resolution, - img_channels, - label_dim=0, - use_fp16=False, - sigma_min=0.0, - sigma_max=float("inf"), - sigma_data=0.5, - model_type="DhariwalUNet", - img_in_channels=None, - img_out_channels=None, - **model_kwargs, - ): - super().__init__(meta=EDMPrecondMetaData) - self.img_resolution = img_resolution - if img_in_channels is not None: - img_in_channels = img_in_channels - else: - img_in_channels = img_channels - if img_out_channels is not None: - img_out_channels = img_out_channels - else: - img_out_channels = img_channels - - self.label_dim = label_dim - self.use_fp16 = use_fp16 - self.sigma_min = sigma_min - self.sigma_max = sigma_max - self.sigma_data = sigma_data - - model_class = getattr(network_module, model_type) - self.model = model_class( - img_resolution=img_resolution, - in_channels=img_in_channels, - out_channels=img_out_channels, - label_dim=label_dim, - **model_kwargs, - ) # TODO needs better handling - - def forward( - self, - x, - sigma, - condition=None, - class_labels=None, - force_fp32=False, - **model_kwargs, - ): - x = x.to(torch.float32) - sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) - class_labels = ( - None - if self.label_dim == 0 - else torch.zeros([1, self.label_dim], device=x.device) - if class_labels is None - else class_labels.to(torch.float32).reshape(-1, self.label_dim) - ) - dtype = ( - torch.float16 - if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") - else torch.float32 - ) - - c_skip = self.sigma_data**2 / (sigma**2 + self.sigma_data**2) - c_out = sigma * self.sigma_data / (sigma**2 + self.sigma_data**2).sqrt() - c_in = 1 / (self.sigma_data**2 + sigma**2).sqrt() - c_noise = sigma.log() / 4 - - arg = c_in * x - - if condition is not None: - arg = torch.cat([arg, condition], dim=1) - - F_x = self.model( - arg.to(dtype), - c_noise.flatten(), - class_labels=class_labels, - **model_kwargs, - ) - - if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): - raise ValueError( - f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." - ) - D_x = c_skip * x + c_out * F_x.to(torch.float32) - return D_x - - @staticmethod - def round_sigma(sigma: Union[float, List, torch.Tensor]): - """ - Convert a given sigma value(s) to a tensor representation. - - Parameters - ---------- - sigma : Union[float list, torch.Tensor] - The sigma value(s) to convert. - - Returns - ------- - torch.Tensor - The tensor representation of the provided sigma value(s). - """ - return torch.as_tensor(sigma) - - -@dataclass -class EDMPrecondSRMetaData(ModelMetaData): - """EDMPrecondSR meta data""" - - name: str = "EDMPrecondSR" - # Optimization - jit: bool = False - cuda_graphs: bool = False - amp_cpu: bool = False - amp_gpu: bool = True - torch_fx: bool = False - # Data type - bf16: bool = False - # Inference - onnx: bool = False - # Physics informed - func_torch: bool = False - auto_grad: bool = False - - -class EDMPrecondSR(Module): - """ - Improved preconditioning proposed in the paper "Elucidating the Design Space of - Diffusion-Based Generative Models" (EDM) for super-resolution tasks - - Parameters - ---------- - img_resolution : int - Image resolution. - img_channels : int - Number of color channels. - img_in_channels : int - Number of input color channels. - img_out_channels : int - Number of output color channels. - use_fp16 : bool - Execute the underlying model at FP16 precision?, by default False. - sigma_min : float - Minimum supported noise level, by default 0.0. - sigma_max : float - Maximum supported noise level, by default inf. - sigma_data : float - Expected standard deviation of the training data, by default 0.5. - model_type :str - Class name of the underlying model, by default "SongUNetPosEmbd". - **model_kwargs : dict - Keyword arguments for the underlying model. - - Note - ---- - References: - - Karras, T., Aittala, M., Aila, T. and Laine, S., 2022. Elucidating the - design space of diffusion-based generative models. Advances in Neural Information - Processing Systems, 35, pp.26565-26577. - - Mardani, M., Brenowitz, N., Cohen, Y., Pathak, J., Chen, C.Y., - Liu, C.C.,Vahdat, A., Kashinath, K., Kautz, J. and Pritchard, M., 2023. - Generative Residual Diffusion Modeling for Km-scale Atmospheric Downscaling. - arXiv preprint arXiv:2309.15214. - """ - - def __init__( - self, - img_resolution, - img_channels, - img_in_channels, - img_out_channels, - use_fp16=False, - sigma_min=0.0, - sigma_max=float("inf"), - sigma_data=0.5, - model_type="SongUNetPosEmbd", - scale_cond_input=True, - **model_kwargs, - ): - super().__init__(meta=EDMPrecondSRMetaData) - self.img_resolution = img_resolution - self.img_channels = img_channels # TODO: this is not used, remove it - self.img_in_channels = img_in_channels - self.img_out_channels = img_out_channels - self.use_fp16 = use_fp16 - self.sigma_min = sigma_min - self.sigma_max = sigma_max - self.sigma_data = sigma_data - self.scale_cond_input = scale_cond_input - - model_class = getattr(network_module, model_type) - self.model = model_class( - img_resolution=img_resolution, - in_channels=img_in_channels + img_out_channels, - out_channels=img_out_channels, - **model_kwargs, - ) # TODO needs better handling - self.scaling_fn = self._get_scaling_fn() - - def _get_scaling_fn(self): - if self.scale_cond_input: - warnings.warn( - "scale_cond_input=True does not properly scale the conditional input. " - "(see https://github.com/NVIDIA/modulus/issues/229). " - "This setup will be deprecated. " - "Please set scale_cond_input=False.", - DeprecationWarning, - ) - return self._legacy_scaling_fn - else: - return self._scaling_fn - - @staticmethod - def _scaling_fn(x, img_lr, c_in): - return torch.cat([c_in * x, img_lr.to(x.dtype)], dim=1) - - @staticmethod - def _legacy_scaling_fn(x, img_lr, c_in): - return c_in * torch.cat([x, img_lr.to(x.dtype)], dim=1) - - @nvtx.annotate(message="EDMPrecondSR", color="orange") - def forward( - self, - x, - img_lr, - sigma, - force_fp32=False, - **model_kwargs, - ): - # Concatenate input channels - x = x.to(torch.float32) - sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) - dtype = ( - torch.float16 - if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") - else torch.float32 - ) - - c_skip = self.sigma_data**2 / (sigma**2 + self.sigma_data**2) - c_out = sigma * self.sigma_data / (sigma**2 + self.sigma_data**2).sqrt() - c_in = 1 / (self.sigma_data**2 + sigma**2).sqrt() - c_noise = sigma.log() / 4 - - if img_lr is None: - arg = c_in * x - else: - arg = self.scaling_fn(x, img_lr, c_in) - arg = arg.to(dtype) - - F_x = self.model( - arg, - c_noise.flatten(), - class_labels=None, - **model_kwargs, - ) - - if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): - raise ValueError( - f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." - ) - - D_x = c_skip * x + c_out * F_x.to(torch.float32) - return D_x - - @staticmethod - def round_sigma(sigma: Union[float, List, torch.Tensor]): - """ - Convert a given sigma value(s) to a tensor representation. - See EDMPrecond.round_sigma - """ - return EDMPrecond.round_sigma(sigma) - - -class VEPrecond_dfsr(torch.nn.Module): - """ - Preconditioning for dfsr model, modified from class VEPrecond, where the input - argument 'sigma' in forward propagation function is used to receive the timestep - of the backward diffusion process. - - Parameters - ---------- - img_resolution : int - Image resolution. - img_channels : int - Number of color channels. - label_dim : int - Number of class labels, 0 = unconditional, by default 0. - use_fp16 : bool - Execute the underlying model at FP16 precision?, by default False. - sigma_min : float - Minimum supported noise level, by default 0.02. - sigma_max : float - Maximum supported noise level, by default 100.0. - model_type :str - Class name of the underlying model, by default "SongUNet". - **model_kwargs : dict - Keyword arguments for the underlying model. - - Note - ---- - Reference: Ho J, Jain A, Abbeel P. Denoising diffusion probabilistic models. - Advances in neural information processing systems. 2020;33:6840-51. - """ - - def __init__( - self, - img_resolution: int, - img_channels: int, - label_dim: int = 0, - use_fp16: bool = False, - sigma_min: float = 0.02, - sigma_max: float = 100.0, - dataset_mean: float = 5.85e-05, - dataset_scale: float = 4.79, - model_type: str = "SongUNet", - **model_kwargs: dict, - ): - super().__init__() - self.img_resolution = img_resolution - self.img_channels = img_channels - self.label_dim = label_dim - self.use_fp16 = use_fp16 - self.model = globals()[model_type]( - img_resolution=img_resolution, - in_channels=self.img_channels, - out_channels=img_channels, - label_dim=label_dim, - **model_kwargs, - ) # TODO needs better handling - - def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): - x = x.to(torch.float32) - sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) - # print("sigma: ", sigma) - class_labels = ( - None - if self.label_dim == 0 - else torch.zeros([1, self.label_dim], device=x.device) - if class_labels is None - else class_labels.to(torch.float32).reshape(-1, self.label_dim) - ) - dtype = ( - torch.float16 - if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") - else torch.float32 - ) - - c_in = 1 - c_noise = sigma # Change the definitation of c_noise to avoid -inf values for zero sigma - - F_x = self.model( - (c_in * x).to(dtype), - c_noise.flatten(), - class_labels=class_labels, - **model_kwargs, - ) - - if F_x.dtype != dtype: - raise ValueError( - f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." - ) - - return F_x - - -class VEPrecond_dfsr_cond(torch.nn.Module): - """ - Preconditioning for dfsr model with physics-informed conditioning input, modified - from class VEPrecond, where the input argument 'sigma' in forward propagation function - is used to receive the timestep of the backward diffusion process. The gradient of PDE - residual with respect to the vorticity in the governing Navier-Stokes equation is computed - as the physics-informed conditioning variable and is combined with the backward diffusion - timestep before being sent to the underlying model for noise prediction. - - Parameters - ---------- - img_resolution : int - Image resolution. - img_channels : int - Number of color channels. - label_dim : int - Number of class labels, 0 = unconditional, by default 0. - use_fp16 : bool - Execute the underlying model at FP16 precision?, by default False. - sigma_min : float - Minimum supported noise level, by default 0.02. - sigma_max : float - Maximum supported noise level, by default 100.0. - model_type :str - Class name of the underlying model, by default "SongUNet". - **model_kwargs : dict - Keyword arguments for the underlying model. - - Note - ---- - Reference: - [1] Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and - Poole, B., 2020. Score-based generative modeling through stochastic differential - equations. arXiv preprint arXiv:2011.13456. - [2] Shu D, Li Z, Farimani AB. A physics-informed diffusion model for high-fidelity - flow field reconstruction. Journal of Computational Physics. 2023 Apr 1;478:111972. - """ - - def __init__( - self, - img_resolution: int, - img_channels: int, - label_dim: int = 0, - use_fp16: bool = False, - sigma_min: float = 0.02, - sigma_max: float = 100.0, - dataset_mean: float = 5.85e-05, - dataset_scale: float = 4.79, - model_type: str = "SongUNet", - **model_kwargs: dict, - ): - super().__init__() - self.img_resolution = img_resolution - self.img_channels = img_channels - self.label_dim = label_dim - self.use_fp16 = use_fp16 - self.model = globals()[model_type]( - img_resolution=img_resolution, - in_channels=model_kwargs["model_channels"] * 2, - out_channels=img_channels, - label_dim=label_dim, - **model_kwargs, - ) # TODO needs better handling - - # modules to embed residual loss - self.conv_in = torch.nn.Conv2d( - img_channels, - model_kwargs["model_channels"], - kernel_size=3, - stride=1, - padding=1, - padding_mode="circular", - ) - self.emb_conv = torch.nn.Sequential( - torch.nn.Conv2d( - img_channels, - model_kwargs["model_channels"], - kernel_size=1, - stride=1, - padding=0, - ), - torch.nn.GELU(), - torch.nn.Conv2d( - model_kwargs["model_channels"], - model_kwargs["model_channels"], - kernel_size=3, - stride=1, - padding=1, - padding_mode="circular", - ), - ) - self.dataset_mean = dataset_mean - self.dataset_scale = dataset_scale - - def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): - x = x.to(torch.float32) - sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) - class_labels = ( - None - if self.label_dim == 0 - else torch.zeros([1, self.label_dim], device=x.device) - if class_labels is None - else class_labels.to(torch.float32).reshape(-1, self.label_dim) - ) - dtype = ( - torch.float16 - if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") - else torch.float32 - ) - - c_in = 1 - c_noise = sigma - - # Compute physics-informed conditioning information using vorticity residual - dx = ( - self.voriticity_residual((x * self.dataset_scale + self.dataset_mean)) - / self.dataset_scale - ) - x = self.conv_in(x) - cond_emb = self.emb_conv(dx) - x = torch.cat((x, cond_emb), dim=1) - - F_x = self.model( - (c_in * x).to(dtype), - c_noise.flatten(), - class_labels=class_labels, - **model_kwargs, - ) - - if F_x.dtype != dtype: - raise ValueError( - f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." - ) - return F_x - - def voriticity_residual(self, w, re=1000.0, dt=1 / 32): - """ - Compute the gradient of PDE residual with respect to a given vorticity w using the - spectrum method. - - Parameters - ---------- - w: torch.Tensor - The fluid flow data sample (vorticity). - re: float - The value of Reynolds number used in the governing Navier-Stokes equation. - dt: float - Time step used to compute the time-derivative of vorticity included in the governing - Navier-Stokes equation. - - Returns - ------- - torch.Tensor - The computed vorticity gradient. - """ - - # w [b t h w] - w = w.clone() - w.requires_grad_(True) - nx = w.size(2) - device = w.device - - w_h = torch.fft.fft2(w[:, 1:-1], dim=[2, 3]) - # Wavenumbers in y-direction - k_max = nx // 2 - N = nx - k_x = ( - torch.cat( - ( - torch.arange(start=0, end=k_max, step=1, device=device), - torch.arange(start=-k_max, end=0, step=1, device=device), - ), - 0, - ) - .reshape(N, 1) - .repeat(1, N) - .reshape(1, 1, N, N) - ) - k_y = ( - torch.cat( - ( - torch.arange(start=0, end=k_max, step=1, device=device), - torch.arange(start=-k_max, end=0, step=1, device=device), - ), - 0, - ) - .reshape(1, N) - .repeat(N, 1) - .reshape(1, 1, N, N) - ) - # Negative Laplacian in Fourier space - lap = k_x**2 + k_y**2 - lap[..., 0, 0] = 1.0 - psi_h = w_h / lap - - u_h = 1j * k_y * psi_h - v_h = -1j * k_x * psi_h - wx_h = 1j * k_x * w_h - wy_h = 1j * k_y * w_h - wlap_h = -lap * w_h - - u = torch.fft.irfft2(u_h[..., :, : k_max + 1], dim=[2, 3]) - v = torch.fft.irfft2(v_h[..., :, : k_max + 1], dim=[2, 3]) - wx = torch.fft.irfft2(wx_h[..., :, : k_max + 1], dim=[2, 3]) - wy = torch.fft.irfft2(wy_h[..., :, : k_max + 1], dim=[2, 3]) - wlap = torch.fft.irfft2(wlap_h[..., :, : k_max + 1], dim=[2, 3]) - advection = u * wx + v * wy - - wt = (w[:, 2:, :, :] - w[:, :-2, :, :]) / (2 * dt) - - # establish forcing term - x = torch.linspace(0, 2 * np.pi, nx + 1, device=device) - x = x[0:-1] - X, Y = torch.meshgrid(x, x) - f = -4 * torch.cos(4 * Y) - - residual = wt + (advection - (1.0 / re) * wlap + 0.1 * w[:, 1:-1]) - f - residual_loss = (residual**2).mean() - dw = torch.autograd.grad(residual_loss, w)[0] - - return dw diff --git a/src/models/preconditioning.py b/src/models/preconditioning.py index 52a16608..7b621e29 100644 --- a/src/models/preconditioning.py +++ b/src/models/preconditioning.py @@ -27,13 +27,13 @@ import numpy as np import nvtx import torch +import torch.nn as nn -from physicsnemo.models.diffusion import ( +from src.models import ( DhariwalUNet, # noqa: F401 for globals SongUNet, # noqa: F401 for globals ) from physicsnemo.models.meta import ModelMetaData -from physicsnemo.models.module import Module network_module = importlib.import_module("physicsnemo.models.diffusion") @@ -58,7 +58,7 @@ class VPPrecondMetaData(ModelMetaData): auto_grad: bool = False -class VPPrecond(Module): +class VPPrecond(nn.Module): """ Preconditioning corresponding to the variance preserving (VP) formulation. @@ -241,7 +241,7 @@ class VEPrecondMetaData(ModelMetaData): auto_grad: bool = False -class VEPrecond(Module): +class VEPrecond(nn.Module): """ Preconditioning corresponding to the variance exploding (VE) formulation. @@ -370,7 +370,7 @@ class iDDPMPrecondMetaData(ModelMetaData): auto_grad: bool = False -class iDDPMPrecond(Module): +class iDDPMPrecond(nn.Module): """ Preconditioning corresponding to the improved DDPM (iDDPM) formulation. @@ -544,7 +544,7 @@ class EDMPrecondMetaData(ModelMetaData): auto_grad: bool = False -class EDMPrecond(Module): +class EDMPrecond(nn.Module): """ Improved preconditioning proposed in the paper "Elucidating the Design Space of Diffusion-Based Generative Models" (EDM) @@ -713,7 +713,7 @@ class EDMPrecondSRMetaData(ModelMetaData): auto_grad: bool = False -class EDMPrecondSR(Module): +class EDMPrecondSR(nn.Module): """ Improved preconditioning proposed in the paper "Elucidating the Design Space of Diffusion-Based Generative Models" (EDM) for super-resolution tasks @@ -861,7 +861,7 @@ def round_sigma(sigma: Union[float, List, torch.Tensor]): return EDMPrecond.round_sigma(sigma) -class VEPrecond_dfsr(torch.nn.Module): +class VEPrecond_dfsr(nn.Module): """ Preconditioning for dfsr model, modified from class VEPrecond, where the input argument 'sigma' in forward propagation function is used to receive the timestep @@ -953,7 +953,7 @@ def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs) return F_x -class VEPrecond_dfsr_cond(torch.nn.Module): +class VEPrecond_dfsr_cond(nn.Module): """ Preconditioning for dfsr model with physics-informed conditioning input, modified from class VEPrecond, where the input argument 'sigma' in forward propagation function diff --git a/src/models/song_unet.py b/src/models/song_unet.py index d38484ba..68adbdaf 100644 --- a/src/models/song_unet.py +++ b/src/models/song_unet.py @@ -27,8 +27,9 @@ import torch from torch.nn.functional import silu from torch.utils.checkpoint import checkpoint +import torch.nn as nn -from physicsnemo.models.diffusion import ( +from src.models import ( Conv2d, FourierEmbedding, GroupNorm, @@ -37,7 +38,6 @@ UNetBlock, ) from physicsnemo.models.meta import ModelMetaData -from physicsnemo.models.module import Module @dataclass @@ -58,7 +58,7 @@ class MetaData(ModelMetaData): auto_grad: bool = False -class SongUNet(Module): +class SongUNet(nn.Module): """ Reimplementation of the DDPM++ and NCSN++ architectures, U-Net variants with optional self-attention, embeddings, and encoder-decoder components. diff --git a/src/models/unet.py b/src/models/unet.py index 72706064..db8e4f86 100644 --- a/src/models/unet.py +++ b/src/models/unet.py @@ -18,11 +18,11 @@ from dataclasses import dataclass import torch +import torch.nn as nn from physicsnemo.models.meta import ModelMetaData -from physicsnemo.models.module import Module -network_module = importlib.import_module("physicsnemo.models.diffusion") +network_module = importlib.import_module("src.models") @dataclass @@ -43,7 +43,7 @@ class MetaData(ModelMetaData): auto_grad: bool = False -class UNet(Module): # TODO a lot of redundancy, need to clean up +class UNet(nn.Module): # TODO a lot of redundancy, need to clean up """ U-Net Wrapper for CorrDiff. @@ -166,7 +166,7 @@ def round_sigma(self, sigma): return torch.as_tensor(sigma) -class StormCastUNet(Module): +class StormCastUNet(nn.Module): """ U-Net wrapper for StormCast; used so the same Song U-Net network can be re-used for this model. diff --git a/src/utils/capture.py b/src/utils/capture.py new file mode 100644 index 00000000..50057f91 --- /dev/null +++ b/src/utils/capture.py @@ -0,0 +1,513 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import functools +import logging +import os +import time +from contextlib import nullcontext +from logging import Logger +from typing import Any, Callable, Dict, NewType, Optional, Union + +import torch + +from src.distributed import DistributedManager + +float16 = NewType("float16", torch.float16) +bfloat16 = NewType("bfloat16", torch.bfloat16) +optim = NewType("optim", torch.optim) + + +class _StaticCapture(object): + """Base class for StaticCapture decorator. + + This class should not be used, rather StaticCaptureTraining and StaticCaptureEvaluate + should be used instead for training and evaluation functions. + """ + + # Grad scaler and checkpoint class variables use for checkpoint saving and loading + # Since an instance of Static capture does not exist for checkpoint functions + # one must use class functions to access state dicts + _amp_scalers = {} + _amp_scaler_checkpoints = {} + _logger = logging.getLogger("capture") + + def __new__(cls, *args, **kwargs): + obj = super(_StaticCapture, cls).__new__(cls) + obj.amp_scalers = cls._amp_scalers + obj.amp_scaler_checkpoints = cls._amp_scaler_checkpoints + obj.logger = cls._logger + return obj + + def __init__( + self, + model: "physicsnemo.Module", + optim: Optional[optim] = None, + logger: Optional[Logger] = None, + use_graphs: bool = True, + use_autocast: bool = True, + use_gradscaler: bool = True, + compile: bool = False, + cuda_graph_warmup: int = 11, + amp_type: Union[float16, bfloat16] = torch.float16, + gradient_clip_norm: Optional[float] = None, + label: Optional[str] = None, + ): + self.logger = logger if logger else self.logger + # Checkpoint label (used for gradscaler) + self.label = label if label else f"scaler_{len(self.amp_scalers.keys())}" + + # DDP fix + if not isinstance(model, physicsnemo.models.Module) and hasattr( + model, "module" + ): + model = model.module + + if not isinstance(model, physicsnemo.models.Module): + self.logger.error("Model not a PhysicsNeMo Module!") + raise ValueError("Model not a PhysicsNeMo Module!") + if compile: + model = torch.compile(model) + + self.model = model + + self.optim = optim + self.eval = False + self.no_grad = False + self.gradient_clip_norm = gradient_clip_norm + + # Set up toggles for optimizations + if not (amp_type == torch.float16 or amp_type == torch.bfloat16): + raise ValueError("AMP type must be torch.float16 or torch.bfloat16") + # CUDA device + if "cuda" in str(self.model.device): + # CUDA graphs + if use_graphs and not self.model.meta.cuda_graphs: + self.logger.warning( + f"Model {model.meta.name} does not support CUDA graphs, turning off" + ) + use_graphs = False + self.cuda_graphs_enabled = use_graphs + + # AMP GPU + if not self.model.meta.amp_gpu: + self.logger.warning( + f"Model {model.meta.name} does not support AMP on GPUs, turning off" + ) + use_autocast = False + use_gradscaler = False + self.use_gradscaler = use_gradscaler + self.use_autocast = use_autocast + + self.amp_device = "cuda" + # Check if bfloat16 is suppored on the GPU + if amp_type == torch.bfloat16 and not torch.cuda.is_bf16_supported(): + self.logger.warning( + "Current CUDA device does not support bfloat16, falling back to float16" + ) + amp_type = torch.float16 + self.amp_dtype = amp_type + # Gradient Scaler + scaler_enabled = self.use_gradscaler and amp_type == torch.float16 + self.scaler = self._init_amp_scaler(scaler_enabled, self.logger) + + self.replay_stream = torch.cuda.Stream(self.model.device) + # CPU device + else: + self.cuda_graphs_enabled = False + # AMP CPU + if use_autocast and not self.model.meta.amp_cpu: + self.logger.warning( + f"Model {model.meta.name} does not support AMP on CPUs, turning off" + ) + use_autocast = False + + self.use_autocast = use_autocast + self.amp_device = "cpu" + # Only float16 is supported on CPUs + # https://pytorch.org/docs/stable/amp.html#cpu-op-specific-behavior + if amp_type == torch.float16 and use_autocast: + self.logger.warning( + "torch.float16 not supported for CPU AMP, switching to torch.bfloat16" + ) + amp_type = torch.bfloat16 + self.amp_dtype = torch.bfloat16 + # Gradient Scaler (not enabled) + self.scaler = self._init_amp_scaler(False, self.logger) + self.replay_stream = None + + if self.cuda_graphs_enabled: + self.graph = torch.cuda.CUDAGraph() + + self.output = None + self.iteration = 0 + self.cuda_graph_warmup = cuda_graph_warmup # Default for DDP = 11 + + def __call__(self, fn: Callable) -> Callable: + self.function = fn + + @functools.wraps(fn) + def decorated(*args: Any, **kwds: Any) -> Any: + """Training step decorator function""" + + with torch.no_grad() if self.no_grad else nullcontext(): + if self.cuda_graphs_enabled: + self._cuda_graph_forward(*args, **kwds) + else: + self._zero_grads() + self.output = self._amp_forward(*args, **kwds) + + if not self.eval: + # Update model parameters + self.scaler.step(self.optim) + self.scaler.update() + + return self.output + + return decorated + + def _cuda_graph_forward(self, *args: Any, **kwargs: Any) -> Any: + """Forward training step with CUDA graphs + + Returns + ------- + Any + Output of neural network forward + """ + # Graph warm up + if self.iteration < self.cuda_graph_warmup: + self.replay_stream.wait_stream(torch.cuda.current_stream()) + self._zero_grads() + with torch.cuda.stream(self.replay_stream): + output = self._amp_forward(*args, **kwargs) + self.output = output.detach() + torch.cuda.current_stream().wait_stream(self.replay_stream) + # CUDA Graphs + else: + # Graph record + if self.iteration == self.cuda_graph_warmup: + self.logger.warning(f"Recording graph of '{self.function.__name__}'") + self._zero_grads() + torch.cuda.synchronize() + if DistributedManager().distributed: + torch.distributed.barrier() + # TODO: temporary workaround till this issue is fixed: + # https://github.com/pytorch/pytorch/pull/104487#issuecomment-1638665876 + delay = os.environ.get("PHYSICSNEMO_CUDA_GRAPH_CAPTURE_DELAY", "10") + time.sleep(int(delay)) + with torch.cuda.graph(self.graph): + output = self._amp_forward(*args, **kwargs) + self.output = output.detach() + # Graph replay + self.graph.replay() + + self.iteration += 1 + return self.output + + def _zero_grads(self): + """Zero gradients + + Default to `set_to_none` since this will in general have lower memory + footprint, and can modestly improve performance. + + Note + ---- + Zeroing gradients can potentially cause an invalid CUDA memory access in another + graph. However if your graph involves gradients, you much set your gradients to none. + If there is already a graph recorded that includes these gradients, this will error. + Use the `NoGrad` version of capture to avoid this issue for inferencers / validators. + """ + # Skip zeroing if no grad is being used + if self.no_grad: + return + + try: + self.optim.zero_grad(set_to_none=True) + except Exception: + if self.optim: + self.optim.zero_grad() + # For apex optim support and eval mode (need to reset model grads) + self.model.zero_grad(set_to_none=True) + + def _amp_forward(self, *args, **kwargs) -> Any: + """Compute loss and gradients (if training) with AMP + + Returns + ------- + Any + Output of neural network forward + """ + with torch.autocast( + self.amp_device, enabled=self.use_autocast, dtype=self.amp_dtype + ): + output = self.function(*args, **kwargs) + + if not self.eval: + # In training mode output should be the loss + self.scaler.scale(output).backward() + if self.gradient_clip_norm is not None: + self.scaler.unscale_(self.optim) + torch.nn.utils.clip_grad_norm_( + self.model.parameters(), self.gradient_clip_norm + ) + + return output + + def _init_amp_scaler( + self, scaler_enabled: bool, logger: Logger + ) -> torch.cuda.amp.GradScaler: + # Create gradient scaler + scaler = torch.cuda.amp.GradScaler(enabled=scaler_enabled) + # Store scaler in class variable + self.amp_scalers[self.label] = scaler + logging.debug(f"Created gradient scaler {self.label}") + + # If our checkpoint dictionary has weights for this scaler lets load + if self.label in self.amp_scaler_checkpoints: + try: + scaler.load_state_dict(self.amp_scaler_checkpoints[self.label]) + del self.amp_scaler_checkpoints[self.label] + self.logger.info(f"Loaded grad scaler state dictionary {self.label}.") + except Exception as e: + self.logger.error( + f"Failed to load grad scaler {self.label} state dict from saved " + + "checkpoints. Did you switch the ordering of declared static captures?" + ) + raise ValueError(e) + return scaler + + @classmethod + def state_dict(cls) -> Dict[str, Any]: + """Class method for accsessing the StaticCapture state dictionary. + Use this in a training checkpoint function. + + Returns + ------- + Dict[str, Any] + Dictionary of states to save for file + """ + scaler_states = {} + for key, value in cls._amp_scalers.items(): + scaler_states[key] = value.state_dict() + + return scaler_states + + @classmethod + def load_state_dict(cls, state_dict: Dict[str, Any]) -> None: + """Class method for loading a StaticCapture state dictionary. + Use this in a training checkpoint function. + + Returns + ------- + Dict[str, Any] + Dictionary of states to save for file + """ + for key, value in state_dict.items(): + # If scaler has been created already load the weights + if key in cls._amp_scalers: + try: + cls._amp_scalers[key].load_state_dict(value) + cls._logger.info(f"Loaded grad scaler state dictionary {key}.") + except Exception as e: + cls._logger.error( + f"Failed to load grad scaler state dict with id {key}." + + " Something went wrong!" + ) + raise ValueError(e) + # Otherwise store in checkpoints for later use + else: + cls._amp_scaler_checkpoints[key] = value + + @classmethod + def reset_state(cls): + cls._amp_scalers = {} + cls._amp_scaler_checkpoints = {} + + +class StaticCaptureTraining(_StaticCapture): + """A performance optimization decorator for PyTorch training functions. + + This class should be initialized as a decorator on a function that computes the + forward pass of the neural network and loss function. The user should only call the + defind training step function. This will apply optimizations including: AMP and + Cuda Graphs. + + Parameters + ---------- + model : physicsnemo.models.Module + PhysicsNeMo Model + optim : torch.optim + Optimizer + logger : Optional[Logger], optional + PhysicsNeMo Launch Logger, by default None + use_graphs : bool, optional + Toggle CUDA graphs if supported by model, by default True + use_amp : bool, optional + Toggle AMP if supported by mode, by default True + cuda_graph_warmup : int, optional + Number of warmup steps for cuda graphs, by default 11 + amp_type : Union[float16, bfloat16], optional + Auto casting type for AMP, by default torch.float16 + gradient_clip_norm : Optional[float], optional + Threshold for gradient clipping + label : Optional[str], optional + Static capture checkpoint label, by default None + + Raises + ------ + ValueError + If the model provided is not a physicsnemo.models.Module. I.e. has no meta data. + + Example + ------- + >>> # Create model + >>> model = physicsnemo.models.mlp.FullyConnected(2, 64, 2) + >>> input = torch.rand(8, 2) + >>> output = torch.rand(8, 2) + >>> # Create optimizer + >>> optim = torch.optim.Adam(model.parameters(), lr=0.001) + >>> # Create training step function with optimization wrapper + >>> @StaticCaptureTraining(model=model, optim=optim) + ... def training_step(model, invar, outvar): + ... predvar = model(invar) + ... loss = torch.sum(torch.pow(predvar - outvar, 2)) + ... return loss + ... + >>> # Sample training loop + >>> for i in range(3): + ... loss = training_step(model, input, output) + ... + + Note + ---- + Static captures must be checkpointed when training using the `state_dict()` if AMP + is being used with gradient scaler. By default, this requires static captures to be + instantiated in the same order as when they were checkpointed. The label parameter + can be used to relax/circumvent this ordering requirement. + + Note + ---- + Capturing multiple cuda graphs in a single program can lead to potential invalid CUDA + memory access errors on some systems. Prioritize capturing training graphs when this + occurs. + """ + + def __init__( + self, + model: "physicsnemo.Module", + optim: torch.optim, + logger: Optional[Logger] = None, + use_graphs: bool = True, + use_amp: bool = True, + compile: bool = False, + cuda_graph_warmup: int = 11, + amp_type: Union[float16, bfloat16] = torch.float16, + gradient_clip_norm: Optional[float] = None, + label: Optional[str] = None, + ): + super().__init__( + model, + optim, + logger, + use_graphs, + use_amp, + use_amp, + compile, + cuda_graph_warmup, + amp_type, + gradient_clip_norm, + label, + ) + + +class StaticCaptureEvaluateNoGrad(_StaticCapture): + + """An performance optimization decorator for PyTorch no grad evaluation. + + This class should be initialized as a decorator on a function that computes run the + forward pass of the model that does not require gradient calculations. This is the + recommended method to use for inference and validation methods. + + Parameters + ---------- + model : physicsnemo.models.Module + PhysicsNeMo Model + logger : Optional[Logger], optional + PhysicsNeMo Launch Logger, by default None + use_graphs : bool, optional + Toggle CUDA graphs if supported by model, by default True + use_amp : bool, optional + Toggle AMP if supported by mode, by default True + cuda_graph_warmup : int, optional + Number of warmup steps for cuda graphs, by default 11 + amp_type : Union[float16, bfloat16], optional + Auto casting type for AMP, by default torch.float16 + label : Optional[str], optional + Static capture checkpoint label, by default None + + Raises + ------ + ValueError + If the model provided is not a physicsnemo.models.Module. I.e. has no meta data. + + Example + ------- + >>> # Create model + >>> model = physicsnemo.models.mlp.FullyConnected(2, 64, 2) + >>> input = torch.rand(8, 2) + >>> # Create evaluate function with optimization wrapper + >>> @StaticCaptureEvaluateNoGrad(model=model) + ... def eval_step(model, invar): + ... predvar = model(invar) + ... return predvar + ... + >>> output = eval_step(model, input) + >>> output.size() + torch.Size([8, 2]) + + Note + ---- + Capturing multiple cuda graphs in a single program can lead to potential invalid CUDA + memory access errors on some systems. Prioritize capturing training graphs when this + occurs. + """ + + def __init__( + self, + model: "physicsnemo.Module", + logger: Optional[Logger] = None, + use_graphs: bool = True, + use_amp: bool = True, + compile: bool = False, + cuda_graph_warmup: int = 11, + amp_type: Union[float16, bfloat16] = torch.float16, + label: Optional[str] = None, + ): + super().__init__( + model, + None, + logger, + use_graphs, + use_amp, + compile, + False, + cuda_graph_warmup, + amp_type, + None, + label, + ) + self.eval = True # No optimizer/scaler calls + self.no_grad = True # No grad context and no grad zeroing diff --git a/src/utils/checkpoint.py b/src/utils/checkpoint.py new file mode 100644 index 00000000..8ec70faa --- /dev/null +++ b/src/utils/checkpoint.py @@ -0,0 +1,398 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import glob +import re +from pathlib import Path +from typing import Any, Dict, List, NewType, Optional, Union + +import torch +from torch.cuda.amp import GradScaler +from torch.optim.lr_scheduler import _LRScheduler + +from src.distributed import DistributedManager +from src.utils.console import PythonLogger +from src.utils.capture import _StaticCapture + +optimizer = NewType("optimizer", torch.optim) +scheduler = NewType("scheduler", _LRScheduler) +scaler = NewType("scaler", GradScaler) + +checkpoint_logging = PythonLogger("checkpoint") + + +def _get_checkpoint_filename( + path: str, + base_name: str = "checkpoint", + index: Union[int, None] = None, + saving: bool = False, + model_type: str = "mdlus", +) -> str: + """Gets the file name /path of checkpoint + + This function has three different ways of providing a checkout filename: + - If supplied an index this will return the checkpoint name using that index. + - If index is None and saving is false, this will get the checkpoint with the + largest index (latest save). + - If index is None and saving is true, it will return the next valid index file name + which is calculated by indexing the largest checkpoint index found by one. + + Parameters + ---------- + path : str + Path to checkpoints + base_name: str, optional + Base file name, by default checkpoint + index : Union[int, None], optional + Checkpoint index, by default None + saving : bool, optional + Get filename for saving a new checkpoint, by default False + model_type : str + Model type, by default "mdlus" for PhysicsNeMo models and "pt" for PyTorch models + + + Returns + ------- + str + Checkpoint file name + """ + # Get model parallel rank so all processes in the first model parallel group + # can save their checkpoint. In the case without model parallelism, + # model_parallel_rank should be the same as the process rank itself and + # only rank 0 saves + if not DistributedManager.is_initialized(): + checkpoint_logging.warning( + "`DistributedManager` not initialized already. Initializing now, but this might lead to unexpected errors" + ) + DistributedManager.initialize() + manager = DistributedManager() + model_parallel_rank = ( + manager.group_rank("model_parallel") + if "model_parallel" in manager.group_names + else 0 + ) + + # Input file name + checkpoint_filename = str( + Path(path).resolve() / f"{base_name}.{model_parallel_rank}" + ) + + # File extension for PhysicsNeMo models or PyTorch models + file_extension = ".mdlus" if model_type == "mdlus" else ".pt" + + # If epoch is provided load that file + if index is not None: + checkpoint_filename = checkpoint_filename + f".{index}" + checkpoint_filename += file_extension + # Otherwise try loading the latest epoch or rolling checkpoint + else: + file_names = [ + Path(fname).name + for fname in glob.glob( + checkpoint_filename + "*" + file_extension, recursive=False + ) + ] + + if len(file_names) > 0: + # If checkpoint from a null index save exists load that + # This is the most likely line to error since it will fail with + # invalid checkpoint names + file_idx = [ + int( + re.sub( + f"^{base_name}.{model_parallel_rank}.|" + file_extension, + "", + fname, + ) + ) + for fname in file_names + ] + file_idx.sort() + # If we are saving index by 1 to get the next free file name + if saving: + checkpoint_filename = checkpoint_filename + f".{file_idx[-1]+1}" + else: + checkpoint_filename = checkpoint_filename + f".{file_idx[-1]}" + checkpoint_filename += file_extension + else: + checkpoint_filename += ".0" + file_extension + + return checkpoint_filename + + +def _unique_model_names( + models: List[torch.nn.Module], +) -> Dict[str, torch.nn.Module]: + """Util to clean model names and index if repeat names, will also strip DDP wrappers + if they exist. + + Parameters + ---------- + model : List[torch.nn.Module] + List of models to generate names for + + Returns + ------- + Dict[str, torch.nn.Module] + Dictionary of model names and respective modules + """ + # Loop through provided models and set up base names + model_dict = {} + for model0 in models: + if hasattr(model0, "module"): + # Strip out DDP layer + model0 = model0.module + # Base name of model is meta.name unless pytorch model + base_name = model0.__class__.__name__ + if isinstance(model0, physicsnemo.models.Module): + base_name = model0.meta.name + # If we have multiple models of the same name, introduce another index + if base_name in model_dict: + model_dict[base_name].append(model0) + else: + model_dict[base_name] = [model0] + + # Set up unique model names if needed + output_dict = {} + for key, model in model_dict.items(): + if len(model) > 1: + for i, model0 in enumerate(model): + output_dict[key + str(i)] = model0 + else: + output_dict[key] = model[0] + + return output_dict + + +def save_checkpoint( + path: str, + models: Union[torch.nn.Module, List[torch.nn.Module], None] = None, + optimizer: Union[optimizer, None] = None, + scheduler: Union[scheduler, None] = None, + scaler: Union[scaler, None] = None, + epoch: Union[int, None] = None, + metadata: Optional[Dict[str, Any]] = None, +) -> None: + """Training checkpoint saving utility + + This will save a training checkpoint in the provided path following the file naming + convention "checkpoint.{model parallel id}.{epoch/index}.mdlus". The load checkpoint + method in PhysicsNeMo core can then be used to read this file. + + Parameters + ---------- + path : str + Path to save the training checkpoint + models : Union[torch.nn.Module, List[torch.nn.Module], None], optional + A single or list of PyTorch models, by default None + optimizer : Union[optimizer, None], optional + Optimizer, by default None + scheduler : Union[scheduler, None], optional + Learning rate scheduler, by default None + scaler : Union[scaler, None], optional + AMP grad scaler. Will attempt to save on in static capture if none provided, by + default None + epoch : Union[int, None], optional + Epoch checkpoint to load. If none this will save the checkpoint in the next + valid index, by default None + metadata : Optional[Dict[str, Any]], optional + Additional metadata to save, by default None + """ + # Create checkpoint directory if it does not exist + if not Path(path).is_dir(): + checkpoint_logging.warning( + f"Output directory {path} does not exist, will " "attempt to create" + ) + Path(path).mkdir(parents=True, exist_ok=True) + + # == Saving model checkpoint == + if models: + if not isinstance(models, list): + models = [models] + models = _unique_model_names(models) + for name, model in models.items(): + # Get model type + model_type = ( + "mdlus" if isinstance(model, physicsnemo.models.Module) else "pt" + ) + + # Get full file path / name + file_name = _get_checkpoint_filename( + path, name, index=epoch, saving=True, model_type=model_type + ) + + # Save state dictionary + if isinstance(model, physicsnemo.models.Module): + model.save(file_name) + else: + torch.save(model.state_dict(), file_name) + checkpoint_logging.success(f"Saved model state dictionary: {file_name}") + + # == Saving training checkpoint == + checkpoint_dict = {} + # Optimizer state dict + if optimizer: + checkpoint_dict["optimizer_state_dict"] = optimizer.state_dict() + + # Scheduler state dict + if scheduler: + checkpoint_dict["scheduler_state_dict"] = scheduler.state_dict() + + # Scheduler state dict + if scaler: + checkpoint_dict["scaler_state_dict"] = scaler.state_dict() + # Static capture is being used, save its grad scaler + if _StaticCapture._amp_scalers: + checkpoint_dict["static_capture_state_dict"] = _StaticCapture.state_dict() + + # Output file name + output_filename = _get_checkpoint_filename( + path, index=epoch, saving=True, model_type="pt" + ) + if epoch: + checkpoint_dict["epoch"] = epoch + if metadata: + checkpoint_dict["metadata"] = metadata + + # Save checkpoint to memory + if bool(checkpoint_dict): + torch.save( + checkpoint_dict, + output_filename, + ) + checkpoint_logging.success(f"Saved training checkpoint: {output_filename}") + + +def load_checkpoint( + path: str, + models: Union[torch.nn.Module, List[torch.nn.Module], None] = None, + optimizer: Union[optimizer, None] = None, + scheduler: Union[scheduler, None] = None, + scaler: Union[scaler, None] = None, + epoch: Union[int, None] = None, + metadata_dict: Optional[Dict[str, Any]] = {}, + device: Union[str, torch.device] = "cpu", +) -> int: + """Checkpoint loading utility + + This loader is designed to be used with the save checkpoint utility in PhysicsNeMo + Launch. Given a path, this method will try to find a checkpoint and load state + dictionaries into the provided training objects. + + Parameters + ---------- + path : str + Path to training checkpoint + models : Union[torch.nn.Module, List[torch.nn.Module], None], optional + A single or list of PyTorch models, by default None + optimizer : Union[optimizer, None], optional + Optimizer, by default None + scheduler : Union[scheduler, None], optional + Learning rate scheduler, by default None + scaler : Union[scaler, None], optional + AMP grad scaler, by default None + epoch : Union[int, None], optional + Epoch checkpoint to load. If none is provided this will attempt to load the + checkpoint with the largest index, by default None + metadata_dict: Optional[Dict[str, Any]], optional + Dictionary to store metadata from the checkpoint, by default None + device : Union[str, torch.device], optional + Target device, by default "cpu" + + Returns + ------- + int + Loaded epoch + """ + # Check if checkpoint directory exists + if not Path(path).is_dir(): + checkpoint_logging.warning( + f"Provided checkpoint directory {path} does not exist, skipping load" + ) + return 0 + + # == Loading model checkpoint == + if models: + if not isinstance(models, list): + models = [models] + models = _unique_model_names(models) + for name, model in models.items(): + # Get model type + model_type = ( + "mdlus" if isinstance(model, physicsnemo.models.Module) else "pt" + ) + + # Get full file path / name + file_name = _get_checkpoint_filename( + path, name, index=epoch, model_type=model_type + ) + if not Path(file_name).exists(): + checkpoint_logging.error( + f"Could not find valid model file {file_name}, skipping load" + ) + continue + # Load state dictionary + if isinstance(model, physicsnemo.models.Module): + model.load(file_name) + else: + model.load_state_dict(torch.load(file_name, map_location=device)) + + checkpoint_logging.success( + f"Loaded model state dictionary {file_name} to device {device}" + ) + + # == Loading training checkpoint == + checkpoint_filename = _get_checkpoint_filename(path, index=epoch, model_type="pt") + if not Path(checkpoint_filename).is_file(): + checkpoint_logging.warning( + "Could not find valid checkpoint file, skipping load" + ) + return 0 + + checkpoint_dict = torch.load(checkpoint_filename, map_location=device) + checkpoint_logging.success( + f"Loaded checkpoint file {checkpoint_filename} to device {device}" + ) + + # Optimizer state dict + if optimizer and "optimizer_state_dict" in checkpoint_dict: + optimizer.load_state_dict(checkpoint_dict["optimizer_state_dict"]) + checkpoint_logging.success("Loaded optimizer state dictionary") + + # Scheduler state dict + if scheduler and "scheduler_state_dict" in checkpoint_dict: + scheduler.load_state_dict(checkpoint_dict["scheduler_state_dict"]) + checkpoint_logging.success("Loaded scheduler state dictionary") + + # Scaler state dict + if scaler and "scaler_state_dict" in checkpoint_dict: + scaler.load_state_dict(checkpoint_dict["scaler_state_dict"]) + checkpoint_logging.success("Loaded grad scaler state dictionary") + + if "static_capture_state_dict" in checkpoint_dict: + _StaticCapture.load_state_dict(checkpoint_dict["static_capture_state_dict"]) + checkpoint_logging.success("Loaded static capture state dictionary") + + epoch = 0 + if "epoch" in checkpoint_dict: + epoch = checkpoint_dict["epoch"] + + # Update metadata if exists and the dictionary object is provided + metadata = checkpoint_dict.get("metadata", {}) + for key, value in metadata.items(): + metadata_dict[key] = value + + return epoch diff --git a/src/utils/console.py b/src/utils/console.py new file mode 100644 index 00000000..42315769 --- /dev/null +++ b/src/utils/console.py @@ -0,0 +1,88 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import os + +from termcolor import colored + + +class PythonLogger: + """Simple console logger for DL training + This is a WIP + """ + + def __init__(self, name: str = "launch"): + self.logger = logging.getLogger(name) + + def file_logging(self, file_name: str = "launch.log"): + """Log to file""" + if os.path.exists(file_name): + try: + os.remove(file_name) + except FileNotFoundError: + # ignore if already removed (can happen with multiple processes) + pass + formatter = logging.Formatter( + "[%(asctime)s - %(name)s - %(levelname)s] %(message)s", + datefmt="%H:%M:%S", + ) + filehandler = logging.FileHandler(file_name) + filehandler.setFormatter(formatter) + filehandler.setLevel(logging.DEBUG) + self.logger.addHandler(filehandler) + + def log(self, message: str): + """Log message""" + self.logger.info(message) + + def info(self, message: str): + """Log info""" + self.logger.info(colored(message, "light_blue")) + + def success(self, message: str): + """Log success""" + self.logger.info(colored(message, "light_green")) + + def warning(self, message: str): + """Log warning""" + self.logger.warning(colored(message, "light_yellow")) + + def error(self, message: str): + """Log error""" + self.logger.error(colored(message, "light_red")) + + +class RankZeroLoggingWrapper: + """Wrapper class to only log from rank 0 process in distributed training.""" + + def __init__(self, obj, dist): + self.obj = obj + self.dist = dist + + def __getattr__(self, name): + attr = getattr(self.obj, name) + if callable(attr): + + def wrapper(*args, **kwargs): + if self.dist.rank == 0: + return attr(*args, **kwargs) + else: + return None + + return wrapper + else: + return attr diff --git a/src/utils/deterministic_sampler.py b/src/utils/deterministic_sampler.py new file mode 100644 index 00000000..4b2f32b4 --- /dev/null +++ b/src/utils/deterministic_sampler.py @@ -0,0 +1,231 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import numpy as np +import nvtx +import torch + +from src.models import EDMPrecond + +# ruff: noqa: E731 + + +@nvtx.annotate(message="deterministic_sampler", color="red") +def deterministic_sampler( + net, + latents, + img_lr, + img_shape=None, + class_labels=None, + randn_like=torch.randn_like, + num_steps=18, + sigma_min=None, + sigma_max=None, + rho=7, + solver="heun", + discretization="edm", + schedule="linear", + scaling="none", + epsilon_s=1e-3, + C_1=0.001, + C_2=0.008, + M=1000, + alpha=1, + S_churn=0, + S_min=0, + S_max=float("inf"), + S_noise=1, +): + """ + Generalized sampler, representing the superset of all sampling methods discussed + in the paper "Elucidating the Design Space of Diffusion-Based Generative Models" + """ + + # conditioning + x_lr = img_lr + + if solver not in ["euler", "heun"]: + raise ValueError(f"Unknown solver {solver}") + if discretization not in ["vp", "ve", "iddpm", "edm"]: + raise ValueError(f"Unknown discretization {discretization}") + if schedule not in ["vp", "ve", "linear"]: + raise ValueError(f"Unknown schedule {schedule}") + if scaling not in ["vp", "none"]: + raise ValueError(f"Unknown scaling {scaling}") + + # Helper functions for VP & VE noise level schedules. + vp_sigma = ( + lambda beta_d, beta_min: lambda t: ( + np.e ** (0.5 * beta_d * (t**2) + beta_min * t) - 1 + ) + ** 0.5 + ) + vp_sigma_deriv = ( + lambda beta_d, beta_min: lambda t: 0.5 + * (beta_min + beta_d * t) + * (sigma(t) + 1 / sigma(t)) + ) + vp_sigma_inv = ( + lambda beta_d, beta_min: lambda sigma: ( + (beta_min**2 + 2 * beta_d * (sigma**2 + 1).log()).sqrt() - beta_min + ) + / beta_d + ) + ve_sigma = lambda t: t.sqrt() + ve_sigma_deriv = lambda t: 0.5 / t.sqrt() + ve_sigma_inv = lambda sigma: sigma**2 + + # Select default noise level range based on the specified time step discretization. + if sigma_min is None: + vp_def = vp_sigma(beta_d=19.1, beta_min=0.1)(t=epsilon_s) + sigma_min = {"vp": vp_def, "ve": 0.02, "iddpm": 0.002, "edm": 0.002}[ + discretization + ] + if sigma_max is None: + vp_def = vp_sigma(beta_d=19.1, beta_min=0.1)(t=1) + sigma_max = {"vp": vp_def, "ve": 100, "iddpm": 81, "edm": 80}[discretization] + + # Adjust noise levels based on what's supported by the network. + sigma_min = max(sigma_min, net.sigma_min) + sigma_max = min(sigma_max, net.sigma_max) + + # Compute corresponding betas for VP. + vp_beta_d = ( + 2 + * (np.log(sigma_min**2 + 1) / epsilon_s - np.log(sigma_max**2 + 1)) + / (epsilon_s - 1) + ) + vp_beta_min = np.log(sigma_max**2 + 1) - 0.5 * vp_beta_d + + # Define time steps in terms of noise level. + step_indices = torch.arange(num_steps, dtype=torch.float64, device=latents.device) + if discretization == "vp": + orig_t_steps = 1 + step_indices / (num_steps - 1) * (epsilon_s - 1) + sigma_steps = vp_sigma(vp_beta_d, vp_beta_min)(orig_t_steps) + elif discretization == "ve": + orig_t_steps = (sigma_max**2) * ( + (sigma_min**2 / sigma_max**2) ** (step_indices / (num_steps - 1)) + ) + sigma_steps = ve_sigma(orig_t_steps) + elif discretization == "iddpm": + u = torch.zeros(M + 1, dtype=torch.float64, device=latents.device) + alpha_bar = lambda j: (0.5 * np.pi * j / M / (C_2 + 1)).sin() ** 2 + for j in torch.arange(M, 0, -1, device=latents.device): # M, ..., 1 + u[j - 1] = ( + (u[j] ** 2 + 1) / (alpha_bar(j - 1) / alpha_bar(j)).clip(min=C_1) - 1 + ).sqrt() + u_filtered = u[torch.logical_and(u >= sigma_min, u <= sigma_max)] + sigma_steps = u_filtered[ + ((len(u_filtered) - 1) / (num_steps - 1) * step_indices) + .round() + .to(torch.int64) + ] + else: + sigma_steps = ( + sigma_max ** (1 / rho) + + step_indices + / (num_steps - 1) + * (sigma_min ** (1 / rho) - sigma_max ** (1 / rho)) + ) ** rho + + # Define noise level schedule. + if schedule == "vp": + sigma = vp_sigma(vp_beta_d, vp_beta_min) + sigma_deriv = vp_sigma_deriv(vp_beta_d, vp_beta_min) + sigma_inv = vp_sigma_inv(vp_beta_d, vp_beta_min) + elif schedule == "ve": + sigma = ve_sigma + sigma_deriv = ve_sigma_deriv + sigma_inv = ve_sigma_inv + else: + sigma = lambda t: t + sigma_deriv = lambda t: 1 + sigma_inv = lambda sigma: sigma + + # Define scaling schedule. + if scaling == "vp": + s = lambda t: 1 / (1 + sigma(t) ** 2).sqrt() + s_deriv = lambda t: -sigma(t) * sigma_deriv(t) * (s(t) ** 3) + else: + s = lambda t: 1 + s_deriv = lambda t: 0 + + # Compute final time steps based on the corresponding noise levels. + t_steps = sigma_inv(net.round_sigma(sigma_steps)) + t_steps = torch.cat([t_steps, torch.zeros_like(t_steps[:1])]) # t_N = 0 + + # Main sampling loop. + t_next = t_steps[0] + x_next = latents.to(torch.float64) * (sigma(t_next) * s(t_next)) + for i, (t_cur, t_next) in enumerate(zip(t_steps[:-1], t_steps[1:])): # 0, ..., N-1 + x_cur = x_next + + # Increase noise temporarily. + gamma = ( + min(S_churn / num_steps, np.sqrt(2) - 1) + if S_min <= sigma(t_cur) <= S_max + else 0 + ) + t_hat = sigma_inv(net.round_sigma(sigma(t_cur) + gamma * sigma(t_cur))) + x_hat = s(t_hat) / s(t_cur) * x_cur + ( + sigma(t_hat) ** 2 - sigma(t_cur) ** 2 + ).clip(min=0).sqrt() * s(t_hat) * S_noise * randn_like(x_cur) + + # Euler step. + h = t_next - t_hat + if isinstance(net, EDMPrecond): + # Conditioning info is passed as keyword arg + denoised = net( + x_hat / s(t_hat), + sigma(t_hat), + condition=x_lr, + class_labels=class_labels, + ).to(torch.float64) + else: + denoised = net(x_hat / s(t_hat), x_lr, sigma(t_hat), class_labels).to( + torch.float64 + ) + d_cur = ( + sigma_deriv(t_hat) / sigma(t_hat) + s_deriv(t_hat) / s(t_hat) + ) * x_hat - sigma_deriv(t_hat) * s(t_hat) / sigma(t_hat) * denoised + x_prime = x_hat + alpha * h * d_cur + t_prime = t_hat + alpha * h + + # Apply 2nd order correction. + if solver == "euler" or i == num_steps - 1: + x_next = x_hat + h * d_cur + else: + if isinstance(net, EDMPrecond): + # Conditioning info is passed as keyword arg + denoised = net( + x_prime / s(t_prime), + sigma(t_prime), + condition=x_lr, + class_labels=class_labels, + ).to(torch.float64) + else: + denoised = net( + x_prime / s(t_prime), x_lr, sigma(t_prime), class_labels + ).to(torch.float64) + d_prime = ( + sigma_deriv(t_prime) / sigma(t_prime) + s_deriv(t_prime) / s(t_prime) + ) * x_prime - sigma_deriv(t_prime) * s(t_prime) / sigma(t_prime) * denoised + x_next = x_hat + h * ( + (1 - 1 / (2 * alpha)) * d_cur + 1 / (2 * alpha) * d_prime + ) + + return x_next diff --git a/src/utils/function_utils.py b/src/utils/function_utils.py new file mode 100644 index 00000000..dcbb127e --- /dev/null +++ b/src/utils/function_utils.py @@ -0,0 +1,775 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Miscellaneous utility classes and functions.""" + +import contextlib +import ctypes +import datetime +import fnmatch +import importlib +import inspect +import os +import re +import shutil +import sys +import types +import warnings +from typing import Any, List, Tuple, Union + +import cftime +import numpy as np +import torch + +# ruff: noqa: E722 PERF203 S110 E713 S324 + + +class EasyDict(dict): # pragma: no cover + """ + Convenience class that behaves like a dict but allows access with the attribute + syntax. + """ + + def __getattr__(self, name: str) -> Any: + try: + return self[name] + except KeyError: + raise AttributeError(name) + + def __setattr__(self, name: str, value: Any) -> None: + self[name] = value + + def __delattr__(self, name: str) -> None: + del self[name] + + +class StackedRandomGenerator: # pragma: no cover + """ + Wrapper for torch.Generator that allows specifying a different random seed + for each sample in a minibatch. + """ + + def __init__(self, device, seeds): + super().__init__() + self.generators = [ + torch.Generator(device).manual_seed(int(seed) % (1 << 32)) for seed in seeds + ] + + def randn(self, size, **kwargs): + if size[0] != len(self.generators): + raise ValueError( + f"Expected first dimension of size {len(self.generators)}, got {size[0]}" + ) + return torch.stack( + [torch.randn(size[1:], generator=gen, **kwargs) for gen in self.generators] + ) + + def randn_like(self, input): + return self.randn( + input.shape, dtype=input.dtype, layout=input.layout, device=input.device + ) + + def randint(self, *args, size, **kwargs): + if size[0] != len(self.generators): + raise ValueError( + f"Expected first dimension of size {len(self.generators)}, got {size[0]}" + ) + return torch.stack( + [ + torch.randint(*args, size=size[1:], generator=gen, **kwargs) + for gen in self.generators + ] + ) + + +def parse_int_list(s): # pragma: no cover + """ + Parse a comma separated list of numbers or ranges and return a list of ints. + Example: '1,2,5-10' returns [1, 2, 5, 6, 7, 8, 9, 10] + """ + if isinstance(s, list): + return s + ranges = [] + range_re = re.compile(r"^(\d+)-(\d+)$") + for p in s.split(","): + m = range_re.match(p) + if m: + ranges.extend(range(int(m.group(1)), int(m.group(2)) + 1)) + else: + ranges.append(int(p)) + return ranges + + +# Small util functions +# ------------------------------------------------------------------------------------- +def convert_datetime_to_cftime( + time: datetime.datetime, cls=cftime.DatetimeGregorian +) -> cftime.DatetimeGregorian: + """Convert a Python datetime object to a cftime DatetimeGregorian object.""" + return cls(time.year, time.month, time.day, time.hour, time.minute, time.second) + + +def time_range( + start_time: datetime.datetime, + end_time: datetime.datetime, + step: datetime.timedelta, + inclusive: bool = False, +): + """Like the Python `range` iterator, but with datetimes.""" + t = start_time + while (t <= end_time) if inclusive else (t < end_time): + yield t + t += step + + +def format_time(seconds: Union[int, float]) -> str: # pragma: no cover + """Convert the seconds to human readable string with days, hours, minutes and seconds.""" + s = int(np.rint(seconds)) + + if s < 60: + return "{0}s".format(s) + elif s < 60 * 60: + return "{0}m {1:02}s".format(s // 60, s % 60) + elif s < 24 * 60 * 60: + return "{0}h {1:02}m {2:02}s".format(s // (60 * 60), (s // 60) % 60, s % 60) + else: + return "{0}d {1:02}h {2:02}m".format( + s // (24 * 60 * 60), (s // (60 * 60)) % 24, (s // 60) % 60 + ) + + +def format_time_brief(seconds: Union[int, float]) -> str: # pragma: no cover + """Convert the seconds to human readable string with days, hours, minutes and seconds.""" + s = int(np.rint(seconds)) + + if s < 60: + return "{0}s".format(s) + elif s < 60 * 60: + return "{0}m {1:02}s".format(s // 60, s % 60) + elif s < 24 * 60 * 60: + return "{0}h {1:02}m".format(s // (60 * 60), (s // 60) % 60) + else: + return "{0}d {1:02}h".format(s // (24 * 60 * 60), (s // (60 * 60)) % 24) + + +def tuple_product(t: Tuple) -> Any: # pragma: no cover + """Calculate the product of the tuple elements.""" + result = 1 + + for v in t: + result *= v + + return result + + +_str_to_ctype = { + "uint8": ctypes.c_ubyte, + "uint16": ctypes.c_uint16, + "uint32": ctypes.c_uint32, + "uint64": ctypes.c_uint64, + "int8": ctypes.c_byte, + "int16": ctypes.c_int16, + "int32": ctypes.c_int32, + "int64": ctypes.c_int64, + "float32": ctypes.c_float, + "float64": ctypes.c_double, +} + + +def get_dtype_and_ctype(type_obj: Any) -> Tuple[np.dtype, Any]: # pragma: no cover + """ + Given a type name string (or an object having a __name__ attribute), return + matching Numpy and ctypes types that have the same size in bytes. + """ + type_str = None + + if isinstance(type_obj, str): + type_str = type_obj + elif hasattr(type_obj, "__name__"): + type_str = type_obj.__name__ + elif hasattr(type_obj, "name"): + type_str = type_obj.name + else: + raise RuntimeError("Cannot infer type name from input") + + if type_str not in _str_to_ctype.keys(): + raise ValueError("Unknown type name: " + type_str) + + my_dtype = np.dtype(type_str) + my_ctype = _str_to_ctype[type_str] + + if my_dtype.itemsize != ctypes.sizeof(my_ctype): + raise ValueError( + "Numpy and ctypes types for '{}' have different sizes!".format(type_str) + ) + + return my_dtype, my_ctype + + +# Functionality to import modules/objects by name, and call functions by name +# ------------------------------------------------------------------------------------- + + +def get_module_from_obj_name( + obj_name: str, +) -> Tuple[types.ModuleType, str]: # pragma: no cover + """ + Searches for the underlying module behind the name to some python object. + Returns the module and the object name (original name with module part removed). + """ + + # allow convenience shorthands, substitute them by full names + obj_name = re.sub("^np.", "numpy.", obj_name) + obj_name = re.sub("^tf.", "tensorflow.", obj_name) + + # list alternatives for (module_name, local_obj_name) + parts = obj_name.split(".") + name_pairs = [ + (".".join(parts[:i]), ".".join(parts[i:])) for i in range(len(parts), 0, -1) + ] + + # try each alternative in turn + for module_name, local_obj_name in name_pairs: + try: + module = importlib.import_module(module_name) # may raise ImportError + get_obj_from_module(module, local_obj_name) # may raise AttributeError + return module, local_obj_name + except: + pass + + # maybe some of the modules themselves contain errors? + for module_name, _local_obj_name in name_pairs: + try: + importlib.import_module(module_name) # may raise ImportError + except ImportError: + if not str(sys.exc_info()[1]).startswith( + "No module named '" + module_name + "'" + ): + raise + + # maybe the requested attribute is missing? + for module_name, local_obj_name in name_pairs: + try: + module = importlib.import_module(module_name) # may raise ImportError + get_obj_from_module(module, local_obj_name) # may raise AttributeError + except ImportError: + pass + + # we are out of luck, but we have no idea why + raise ImportError(obj_name) + + +def get_obj_from_module( + module: types.ModuleType, obj_name: str +) -> Any: # pragma: no cover + """ + Traverses the object name and returns the last (rightmost) python object. + """ + if obj_name == "": + return module + obj = module + for part in obj_name.split("."): + obj = getattr(obj, part) + return obj + + +def get_obj_by_name(name: str) -> Any: # pragma: no cover + """ + Finds the python object with the given name. + """ + module, obj_name = get_module_from_obj_name(name) + return get_obj_from_module(module, obj_name) + + +def call_func_by_name( + *args, func_name: str = None, **kwargs +) -> Any: # pragma: no cover + """ + Finds the python object with the given name and calls it as a function. + """ + if func_name is None: + raise ValueError("func_name must be specified") + func_obj = get_obj_by_name(func_name) + if not callable(func_obj): + raise ValueError(func_name + " is not callable") + return func_obj(*args, **kwargs) + + +def construct_class_by_name( + *args, class_name: str = None, **kwargs +) -> Any: # pragma: no cover + """ + Finds the python class with the given name and constructs it with the given + arguments. + """ + return call_func_by_name(*args, func_name=class_name, **kwargs) + + +def get_module_dir_by_obj_name(obj_name: str) -> str: # pragma: no cover + """ + Get the directory path of the module containing the given object name. + """ + module, _ = get_module_from_obj_name(obj_name) + return os.path.dirname(inspect.getfile(module)) + + +def is_top_level_function(obj: Any) -> bool: # pragma: no cover + """ + Determine whether the given object is a top-level function, i.e., defined at module + scope using 'def'. + """ + return callable(obj) and obj.__name__ in sys.modules[obj.__module__].__dict__ + + +def get_top_level_function_name(obj: Any) -> str: # pragma: no cover + """ + Return the fully-qualified name of a top-level function. + """ + if not is_top_level_function(obj): + raise ValueError("Object is not a top-level function") + module = obj.__module__ + if module == "__main__": + module = os.path.splitext(os.path.basename(sys.modules[module].__file__))[0] + return module + "." + obj.__name__ + + +# File system helpers +# ------------------------------------------------------------------------------------------ + + +def list_dir_recursively_with_ignore( + dir_path: str, ignores: List[str] = None, add_base_to_relative: bool = False +) -> List[Tuple[str, str]]: # pragma: no cover + """ + List all files recursively in a given directory while ignoring given file and + directory names. Returns list of tuples containing both absolute and relative paths. + """ + if not os.path.isdir(dir_path): + raise RuntimeError(f"Directory does not exist: {dir_path}") + base_name = os.path.basename(os.path.normpath(dir_path)) + + if ignores is None: + ignores = [] + + result = [] + + for root, dirs, files in os.walk(dir_path, topdown=True): + for ignore_ in ignores: + dirs_to_remove = [d for d in dirs if fnmatch.fnmatch(d, ignore_)] + + # dirs need to be edited in-place + for d in dirs_to_remove: + dirs.remove(d) + + files = [f for f in files if not fnmatch.fnmatch(f, ignore_)] + + absolute_paths = [os.path.join(root, f) for f in files] + relative_paths = [os.path.relpath(p, dir_path) for p in absolute_paths] + + if add_base_to_relative: + relative_paths = [os.path.join(base_name, p) for p in relative_paths] + + if len(absolute_paths) != len(relative_paths): + raise ValueError("Number of absolute and relative paths do not match") + result += zip(absolute_paths, relative_paths) + + return result + + +def copy_files_and_create_dirs( + files: List[Tuple[str, str]] +) -> None: # pragma: no cover + """ + Takes in a list of tuples of (src, dst) paths and copies files. + Will create all necessary directories. + """ + for file in files: + target_dir_name = os.path.dirname(file[1]) + + # will create all intermediate-level directories + if not os.path.exists(target_dir_name): + os.makedirs(target_dir_name) + + shutil.copyfile(file[0], file[1]) + + +# ---------------------------------------------------------------------------- +# Cached construction of constant tensors. Avoids CPU=>GPU copy when the +# same constant is used multiple times. + +_constant_cache = dict() + + +def constant( + value, shape=None, dtype=None, device=None, memory_format=None +): # pragma: no cover + """Cached construction of constant tensors""" + value = np.asarray(value) + if shape is not None: + shape = tuple(shape) + if dtype is None: + dtype = torch.get_default_dtype() + if device is None: + device = torch.device("cpu") + if memory_format is None: + memory_format = torch.contiguous_format + + key = ( + value.shape, + value.dtype, + value.tobytes(), + shape, + dtype, + device, + memory_format, + ) + tensor = _constant_cache.get(key, None) + if tensor is None: + tensor = torch.as_tensor(value.copy(), dtype=dtype, device=device) + if shape is not None: + tensor, _ = torch.broadcast_tensors(tensor, torch.empty(shape)) + tensor = tensor.contiguous(memory_format=memory_format) + _constant_cache[key] = tensor + return tensor + + +# ---------------------------------------------------------------------------- +# Replace NaN/Inf with specified numerical values. + +try: + nan_to_num = torch.nan_to_num # 1.8.0a0 +except AttributeError: + + def nan_to_num( + input, nan=0.0, posinf=None, neginf=None, *, out=None + ): # pylint: disable=redefined-builtin # pragma: no cover + """Replace NaN/Inf with specified numerical values""" + if not isinstance(input, torch.Tensor): + raise TypeError("input should be a Tensor") + if posinf is None: + posinf = torch.finfo(input.dtype).max + if neginf is None: + neginf = torch.finfo(input.dtype).min + if nan != 0: + raise ValueError("nan_to_num only supports nan=0") + return torch.clamp( + input.unsqueeze(0).nansum(0), min=neginf, max=posinf, out=out + ) + + +# ---------------------------------------------------------------------------- +# Symbolic assert. + +try: + symbolic_assert = torch._assert # 1.8.0a0 # pylint: disable=protected-access +except AttributeError: + symbolic_assert = torch.Assert # 1.7.0 + +# ---------------------------------------------------------------------------- +# Context manager to temporarily suppress known warnings in torch.jit.trace(). +# Note: Cannot use catch_warnings because of https://bugs.python.org/issue29672 + + +@contextlib.contextmanager +def suppress_tracer_warnings(): # pragma: no cover + """ + Context manager to temporarily suppress known warnings in torch.jit.trace(). + Note: Cannot use catch_warnings because of https://bugs.python.org/issue29672 + """ + flt = ("ignore", None, torch.jit.TracerWarning, None, 0) + warnings.filters.insert(0, flt) + yield + warnings.filters.remove(flt) + + +# ---------------------------------------------------------------------------- +# Assert that the shape of a tensor matches the given list of integers. +# None indicates that the size of a dimension is allowed to vary. +# Performs symbolic assertion when used in torch.jit.trace(). + + +def assert_shape(tensor, ref_shape): # pragma: no cover + """ + Assert that the shape of a tensor matches the given list of integers. + None indicates that the size of a dimension is allowed to vary. + Performs symbolic assertion when used in torch.jit.trace(). + """ + if tensor.ndim != len(ref_shape): + raise AssertionError( + f"Wrong number of dimensions: got {tensor.ndim}, expected {len(ref_shape)}" + ) + for idx, (size, ref_size) in enumerate(zip(tensor.shape, ref_shape)): + if ref_size is None: + pass + elif isinstance(ref_size, torch.Tensor): + with suppress_tracer_warnings(): # as_tensor results are registered as constants + symbolic_assert( + torch.equal(torch.as_tensor(size), ref_size), + f"Wrong size for dimension {idx}", + ) + elif isinstance(size, torch.Tensor): + with suppress_tracer_warnings(): # as_tensor results are registered as constants + symbolic_assert( + torch.equal(size, torch.as_tensor(ref_size)), + f"Wrong size for dimension {idx}: expected {ref_size}", + ) + elif size != ref_size: + raise AssertionError( + f"Wrong size for dimension {idx}: got {size}, expected {ref_size}" + ) + + +# ---------------------------------------------------------------------------- +# Function decorator that calls torch.autograd.profiler.record_function(). + + +def profiled_function(fn): # pragma: no cover + """Function decorator that calls torch.autograd.profiler.record_function().""" + + def decorator(*args, **kwargs): + with torch.autograd.profiler.record_function(fn.__name__): + return fn(*args, **kwargs) + + decorator.__name__ = fn.__name__ + return decorator + + +# ---------------------------------------------------------------------------- +# Sampler for torch.utils.data.DataLoader that loops over the dataset +# indefinitely, shuffling items as it goes. + + +class InfiniteSampler(torch.utils.data.Sampler): # pragma: no cover + """ + Sampler for torch.utils.data.DataLoader that loops over the dataset + indefinitely, shuffling items as it goes. + """ + + def __init__( + self, dataset, rank=0, num_replicas=1, shuffle=True, seed=0, window_size=0.5 + ): + if not len(dataset) > 0: + raise ValueError("Dataset must contain at least one item") + if not num_replicas > 0: + raise ValueError("num_replicas must be positive") + if not 0 <= rank < num_replicas: + raise ValueError("rank must be non-negative and less than num_replicas") + if not 0 <= window_size <= 1: + raise ValueError("window_size must be between 0 and 1") + super().__init__() + self.dataset = dataset + self.rank = rank + self.num_replicas = num_replicas + self.shuffle = shuffle + self.seed = seed + self.window_size = window_size + + def __iter__(self): + order = np.arange(len(self.dataset)) + rnd = None + window = 0 + if self.shuffle: + rnd = np.random.RandomState(self.seed) + rnd.shuffle(order) + window = int(np.rint(order.size * self.window_size)) + + idx = 0 + while True: + i = idx % order.size + if idx % self.num_replicas == self.rank: + yield order[i] + if window >= 2: + j = (i - rnd.randint(window)) % order.size + order[i], order[j] = order[j], order[i] + idx += 1 + + +# ---------------------------------------------------------------------------- +# Utilities for operating with torch.nn.Module parameters and buffers. + + +def params_and_buffers(module): # pragma: no cover + """Get parameters and buffers of a nn.Module""" + if not isinstance(module, torch.nn.Module): + raise TypeError("module must be a torch.nn.Module instance") + return list(module.parameters()) + list(module.buffers()) + + +def named_params_and_buffers(module): # pragma: no cover + """Get named parameters and buffers of a nn.Module""" + if not isinstance(module, torch.nn.Module): + raise TypeError("module must be a torch.nn.Module instance") + return list(module.named_parameters()) + list(module.named_buffers()) + + +@torch.no_grad() +def copy_params_and_buffers( + src_module, dst_module, require_all=False +): # pragma: no cover + """Copy parameters and buffers from a source module to target module""" + if not isinstance(src_module, torch.nn.Module): + raise TypeError("src_module must be a torch.nn.Module instance") + if not isinstance(dst_module, torch.nn.Module): + raise TypeError("dst_module must be a torch.nn.Module instance") + src_tensors = dict(named_params_and_buffers(src_module)) + for name, tensor in named_params_and_buffers(dst_module): + if not ((name in src_tensors) or (not require_all)): + raise ValueError(f"Missing source tensor for {name}") + if name in src_tensors: + tensor.copy_(src_tensors[name]) + + +# ---------------------------------------------------------------------------- +# Context manager for easily enabling/disabling DistributedDataParallel +# synchronization. + + +@contextlib.contextmanager +def ddp_sync(module, sync): # pragma: no cover + """ + Context manager for easily enabling/disabling DistributedDataParallel + synchronization. + """ + if not isinstance(module, torch.nn.Module): + raise TypeError("module must be a torch.nn.Module instance") + if sync or not isinstance(module, torch.nn.parallel.DistributedDataParallel): + yield + else: + with module.no_sync(): + yield + + +# ---------------------------------------------------------------------------- +# Check DistributedDataParallel consistency across processes. + + +def check_ddp_consistency(module, ignore_regex=None): # pragma: no cover + """Check DistributedDataParallel consistency across processes.""" + if not isinstance(module, torch.nn.Module): + raise TypeError("module must be a torch.nn.Module instance") + for name, tensor in named_params_and_buffers(module): + fullname = type(module).__name__ + "." + name + if ignore_regex is not None and re.fullmatch(ignore_regex, fullname): + continue + tensor = tensor.detach() + if tensor.is_floating_point(): + tensor = nan_to_num(tensor) + other = tensor.clone() + torch.distributed.broadcast(tensor=other, src=0) + if not (tensor == other).all(): + raise RuntimeError(f"DDP consistency check failed for {fullname}") + + +# ---------------------------------------------------------------------------- +# Print summary table of module hierarchy. + + +def print_module_summary( + module, inputs, max_nesting=3, skip_redundant=True +): # pragma: no cover + """Print summary table of module hierarchy.""" + if not isinstance(module, torch.nn.Module): + raise TypeError("module must be a torch.nn.Module instance") + if isinstance(module, torch.jit.ScriptModule): + raise TypeError("module must not be a torch.jit.ScriptModule instance") + if not isinstance(inputs, (tuple, list)): + raise TypeError("inputs must be a tuple or list") + + # Register hooks. + entries = [] + nesting = [0] + + def pre_hook(_mod, _inputs): + nesting[0] += 1 + + def post_hook(mod, _inputs, outputs): + nesting[0] -= 1 + if nesting[0] <= max_nesting: + outputs = list(outputs) if isinstance(outputs, (tuple, list)) else [outputs] + outputs = [t for t in outputs if isinstance(t, torch.Tensor)] + entries.append(EasyDict(mod=mod, outputs=outputs)) + + hooks = [mod.register_forward_pre_hook(pre_hook) for mod in module.modules()] + hooks += [mod.register_forward_hook(post_hook) for mod in module.modules()] + + # Run module. + outputs = module(*inputs) + for hook in hooks: + hook.remove() + + # Identify unique outputs, parameters, and buffers. + tensors_seen = set() + for e in entries: + e.unique_params = [t for t in e.mod.parameters() if id(t) not in tensors_seen] + e.unique_buffers = [t for t in e.mod.buffers() if id(t) not in tensors_seen] + e.unique_outputs = [t for t in e.outputs if id(t) not in tensors_seen] + tensors_seen |= { + id(t) for t in e.unique_params + e.unique_buffers + e.unique_outputs + } + + # Filter out redundant entries. + if skip_redundant: + entries = [ + e + for e in entries + if len(e.unique_params) or len(e.unique_buffers) or len(e.unique_outputs) + ] + + # Construct table. + rows = [ + [type(module).__name__, "Parameters", "Buffers", "Output shape", "Datatype"] + ] + rows += [["---"] * len(rows[0])] + param_total = 0 + buffer_total = 0 + submodule_names = {mod: name for name, mod in module.named_modules()} + for e in entries: + name = "" if e.mod is module else submodule_names[e.mod] + param_size = sum(t.numel() for t in e.unique_params) + buffer_size = sum(t.numel() for t in e.unique_buffers) + output_shapes = [str(list(t.shape)) for t in e.outputs] + output_dtypes = [str(t.dtype).split(".")[-1] for t in e.outputs] + rows += [ + [ + name + (":0" if len(e.outputs) >= 2 else ""), + str(param_size) if param_size else "-", + str(buffer_size) if buffer_size else "-", + (output_shapes + ["-"])[0], + (output_dtypes + ["-"])[0], + ] + ] + for idx in range(1, len(e.outputs)): + rows += [ + [name + f":{idx}", "-", "-", output_shapes[idx], output_dtypes[idx]] + ] + param_total += param_size + buffer_total += buffer_size + rows += [["---"] * len(rows[0])] + rows += [["Total", str(param_total), str(buffer_total), "-", "-"]] + + # Print table. + widths = [max(len(cell) for cell in column) for column in zip(*rows)] + for row in rows: + print( + " ".join( + cell + " " * (width - len(cell)) for cell, width in zip(row, widths) + ) + ) + return outputs + + +# ---------------------------------------------------------------------------- diff --git a/src/utils/inference_utils.py b/src/utils/inference_utils.py new file mode 100644 index 00000000..842bdd37 --- /dev/null +++ b/src/utils/inference_utils.py @@ -0,0 +1,253 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime + +import cftime +import nvtx +import torch +import tqdm + +from src.utils.function_utils import StackedRandomGenerator, time_range + +############################################################################ +# CorrDiff Generation Utilities # +############################################################################ + + +def regression_step( + net: torch.nn.Module, + img_lr: torch.Tensor, + latents_shape: torch.Size, + lead_time_label: torch.Tensor = None, +) -> torch.Tensor: + """ + Given a low-res input, performs a regression step to produce ensemble mean. + This function performs the regression on a single instance and then replicates + the results across the batch dimension. + + Args: + net (torch.nn.Module): U-Net model for regression. + img_lr (torch.Tensor): Low-resolution input. + latents_shape (torch.Size): Shape of the latent representation. Typically + (batch_size, out_channels, image_shape_x, image_shape_y). + + + Returns: + torch.Tensor: Predicted output at the next time step. + """ + # Create a tensor of zeros with the given shape and move it to the appropriate device + x_hat = torch.zeros(latents_shape, dtype=torch.float64, device=net.device) + t_hat = torch.tensor(1.0, dtype=torch.float64, device=net.device) + + # Perform regression on a single batch element + with torch.inference_mode(): + if lead_time_label is not None: + x = net(x_hat[0:1], img_lr, t_hat, lead_time_label=lead_time_label) + else: + x = net(x_hat[0:1], img_lr, t_hat) + + # If the batch size is greater than 1, repeat the prediction + if x_hat.shape[0] > 1: + x = x.repeat([d if i == 0 else 1 for i, d in enumerate(x_hat.shape)]) + + return x + + +def diffusion_step( # TODO generalize the module and add defaults + net: torch.nn.Module, + sampler_fn: callable, + seed_batch_size: int, + img_shape: tuple, + img_out_channels: int, + rank_batches: list, + img_lr: torch.Tensor, + rank: int, + device: torch.device, + hr_mean: torch.Tensor = None, + lead_time_label: torch.Tensor = None, +) -> torch.Tensor: + + """ + Generate images using diffusion techniques as described in the relevant paper. + + Args: + net (torch.nn.Module): The diffusion model network. + sampler_fn (callable): Function used to sample images from the diffusion model. + seed_batch_size (int): Number of seeds per batch. + img_shape (tuple): Shape of the images, (height, width). + img_out_channels (int): Number of output channels for the image. + rank_batches (list): List of batches of seeds to process. + img_lr (torch.Tensor): Low-resolution input image. + rank (int): Rank of the current process for distributed processing. + device (torch.device): Device to perform computations. + mean_hr (torch.Tensor, optional): High-resolution mean tensor, to be used as an additional input. By default None. + + Returns: + torch.Tensor: Generated images concatenated across batches. + """ + + img_lr = img_lr.to(memory_format=torch.channels_last) + + # Handling of the high-res mean + additional_args = {} + if hr_mean is not None: + additional_args["mean_hr"] = hr_mean + if lead_time_label is not None: + additional_args["lead_time_label"] = lead_time_label + additional_args["img_shape"] = img_shape + + # Loop over batches + all_images = [] + for batch_seeds in tqdm.tqdm(rank_batches, unit="batch", disable=(rank != 0)): + with nvtx.annotate(f"generate {len(all_images)}", color="rapids"): + batch_size = len(batch_seeds) + if batch_size == 0: + continue + + # Initialize random generator, and generate latents + rnd = StackedRandomGenerator(device, batch_seeds) + latents = rnd.randn( + [ + seed_batch_size, + img_out_channels, + img_shape[0], + img_shape[1], + ], + device=device, + ).to(memory_format=torch.channels_last) + + with torch.inference_mode(): + images = sampler_fn( + net, latents, img_lr, randn_like=rnd.randn_like, **additional_args + ) + all_images.append(images) + return torch.cat(all_images) + + +############################################################################ +# CorrDiff writer utilities # +############################################################################ + + +class NetCDFWriter: + """NetCDF Writer""" + + def __init__( + self, f, lat, lon, input_channels, output_channels, has_lead_time=False + ): + self._f = f + self.has_lead_time = has_lead_time + # create unlimited dimensions + f.createDimension("time") + f.createDimension("ensemble") + + if lat.shape != lon.shape: + raise ValueError("lat and lon must have the same shape") + ny, nx = lat.shape + + # create lat/lon grid + f.createDimension("x", nx) + f.createDimension("y", ny) + + v = f.createVariable("lat", "f", dimensions=("y", "x")) + # NOTE rethink this for datasets whose samples don't have constant lat-lon. + v[:] = lat + v.standard_name = "latitude" + v.units = "degrees_north" + + v = f.createVariable("lon", "f", dimensions=("y", "x")) + v[:] = lon + v.standard_name = "longitude" + v.units = "degrees_east" + + # create time dimension + if has_lead_time: + v = f.createVariable("time", "str", ("time")) + else: + v = f.createVariable("time", "i8", ("time")) + v.calendar = "standard" + v.units = "hours since 1990-01-01 00:00:00" + + self.truth_group = f.createGroup("truth") + self.prediction_group = f.createGroup("prediction") + self.input_group = f.createGroup("input") + + for variable in output_channels: + name = variable.name + variable.level + self.truth_group.createVariable(name, "f", dimensions=("time", "y", "x")) + self.prediction_group.createVariable( + name, "f", dimensions=("ensemble", "time", "y", "x") + ) + + # setup input data in netCDF + + for variable in input_channels: + name = variable.name + variable.level + self.input_group.createVariable(name, "f", dimensions=("time", "y", "x")) + + def write_input(self, channel_name, time_index, val): + """Write input data to NetCDF file.""" + self.input_group[channel_name][time_index] = val + + def write_truth(self, channel_name, time_index, val): + """Write ground truth data to NetCDF file.""" + self.truth_group[channel_name][time_index] = val + + def write_prediction(self, channel_name, time_index, ensemble_index, val): + """Write prediction data to NetCDF file.""" + self.prediction_group[channel_name][ensemble_index, time_index] = val + + def write_time(self, time_index, time): + """Write time information to NetCDF file.""" + if self.has_lead_time: + self._f["time"][time_index] = time + else: + time_v = self._f["time"] + self._f["time"][time_index] = cftime.date2num( + time, time_v.units, time_v.calendar + ) + + +############################################################################ +# CorrDiff time utilities # +############################################################################ + + +def get_time_from_range(times_range, time_format="%Y-%m-%dT%H:%M:%S"): + """Generates a list of times within a given range. + + Args: + times_range: A list containing start time, end time, and optional interval (hours). + time_format: The format of the input times (default: "%Y-%m-%dT%H:%M:%S"). + + Returns: + A list of times within the specified range. + """ + + start_time = datetime.datetime.strptime(times_range[0], time_format) + end_time = datetime.datetime.strptime(times_range[1], time_format) + interval = ( + datetime.timedelta(hours=times_range[2]) + if len(times_range) > 2 + else datetime.timedelta(hours=1) + ) + + times = [ + t.strftime(time_format) + for t in time_range(start_time, end_time, interval, inclusive=True) + ] + return times diff --git a/src/utils/model_utils.py b/src/utils/model_utils.py new file mode 100644 index 00000000..e1cde9d8 --- /dev/null +++ b/src/utils/model_utils.py @@ -0,0 +1,66 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import numpy as np +import torch + + +def weight_init(shape: tuple, mode: str, fan_in: int, fan_out: int): + """ + Unified routine for initializing weights and biases. + This function provides a unified interface for various weight initialization + strategies like Xavier (Glorot) and Kaiming (He) initializations. + + Parameters + ---------- + shape : tuple + The shape of the tensor to initialize. It could represent weights or biases + of a layer in a neural network. + mode : str + The mode/type of initialization to use. Supported values are: + - "xavier_uniform": Xavier (Glorot) uniform initialization. + - "xavier_normal": Xavier (Glorot) normal initialization. + - "kaiming_uniform": Kaiming (He) uniform initialization. + - "kaiming_normal": Kaiming (He) normal initialization. + fan_in : int + The number of input units in the weight tensor. For convolutional layers, + this typically represents the number of input channels times the kernel height + times the kernel width. + fan_out : int + The number of output units in the weight tensor. For convolutional layers, + this typically represents the number of output channels times the kernel height + times the kernel width. + + Returns + ------- + torch.Tensor + The initialized tensor based on the specified mode. + + Raises + ------ + ValueError + If the provided `mode` is not one of the supported initialization modes. + """ + if mode == "xavier_uniform": + return np.sqrt(6 / (fan_in + fan_out)) * (torch.rand(*shape) * 2 - 1) + if mode == "xavier_normal": + return np.sqrt(2 / (fan_in + fan_out)) * torch.randn(*shape) + if mode == "kaiming_uniform": + return np.sqrt(3 / fan_in) * (torch.rand(*shape) * 2 - 1) + if mode == "kaiming_normal": + return np.sqrt(1 / fan_in) * torch.randn(*shape) + raise ValueError(f'Invalid init mode "{mode}"') diff --git a/src/utils/stochastic_sampler.py b/src/utils/stochastic_sampler.py new file mode 100644 index 00000000..ddcf9cc7 --- /dev/null +++ b/src/utils/stochastic_sampler.py @@ -0,0 +1,533 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import math +from typing import Any, Callable, Optional + +import torch +from torch import Tensor + + +def image_batching( + input: Tensor, + img_shape_y: int, + img_shape_x: int, + patch_shape_y: int, + patch_shape_x: int, + batch_size: int, + overlap_pix: int, + boundary_pix: int, + input_interp: Optional[Tensor] = None, +) -> Tensor: + """ + Splits a full image into a batch of patched images. + + This function takes a full image and splits it into patches, adding padding where necessary. + It can also concatenate additional interpolated data to each patch if provided. + + Parameters + ---------- + input : Tensor + The input tensor representing the full image with shape (batch_size, channels, img_shape_x, img_shape_y). + img_shape_x : int + The width (x-dimension) of the original full image. + img_shape_y : int + The height (y-dimension) of the original full image. + patch_shape_x : int + The width (x-dimension) of each image patch. + patch_shape_y : int + The height (y-dimension) of each image patch. + batch_size : int + The original batch size before patching. + overlap_pix : int + The number of overlapping pixels between adjacent patches. + boundary_pix : int + The number of pixels to crop as a boundary from each patch. + input_interp : Optional[Tensor], optional + Optional additional data to concatenate to each patch with shape (batch_size, interp_channels, patch_shape_x, patch_shape_y). + By default None. + + Returns + ------- + Tensor + A tensor containing the image patches, with shape (total_patches * batch_size, channels [+ interp_channels], patch_shape_x, patch_shape_y). + """ + patch_num_x = math.ceil(img_shape_x / (patch_shape_x - overlap_pix - boundary_pix)) + patch_num_y = math.ceil(img_shape_y / (patch_shape_y - overlap_pix - boundary_pix)) + padded_shape_x = ( + (patch_shape_x - overlap_pix - boundary_pix) * (patch_num_x - 1) + + patch_shape_x + + boundary_pix + ) + padded_shape_y = ( + (patch_shape_y - overlap_pix - boundary_pix) * (patch_num_y - 1) + + patch_shape_y + + boundary_pix + ) + pad_x_right = padded_shape_x - img_shape_x - boundary_pix + pad_y_right = padded_shape_y - img_shape_y - boundary_pix + input_padded = torch.zeros( + input.shape[0], input.shape[1], padded_shape_y, padded_shape_x + ).to(input.device) + image_padding = torch.nn.ReflectionPad2d( + (boundary_pix, pad_x_right, boundary_pix, pad_y_right) + ).to( + input.device + ) # (padding_left,padding_right,padding_top,padding_bottom) + input_padded = image_padding(input) + patch_num = patch_num_x * patch_num_y + if input_interp is not None: + output = torch.zeros( + patch_num * batch_size, + input.shape[1] + input_interp.shape[1], + patch_shape_y, + patch_shape_x, + ).to(input.device) + else: + output = torch.zeros( + patch_num * batch_size, input.shape[1], patch_shape_y, patch_shape_x + ).to(input.device) + for x_index in range(patch_num_x): + for y_index in range(patch_num_y): + x_start = x_index * (patch_shape_x - overlap_pix - boundary_pix) + y_start = y_index * (patch_shape_y - overlap_pix - boundary_pix) + if input_interp is not None: + output[ + (x_index * patch_num_y + y_index) + * batch_size : (x_index * patch_num_y + y_index + 1) + * batch_size, + ] = torch.cat( + ( + input_padded[ + :, + :, + y_start : y_start + patch_shape_y, + x_start : x_start + patch_shape_x, + ], + input_interp, + ), + dim=1, + ) + else: + output[ + (x_index * patch_num_y + y_index) + * batch_size : (x_index * patch_num_y + y_index + 1) + * batch_size, + ] = input_padded[ + :, + :, + y_start : y_start + patch_shape_y, + x_start : x_start + patch_shape_x, + ] + return output + + +def image_fuse( + input: Tensor, + img_shape_y: int, + img_shape_x: int, + patch_shape_y: int, + patch_shape_x: int, + batch_size: int, + overlap_pix: int, + boundary_pix: int, +) -> Tensor: + """ + Reconstructs a full image from a batch of patched images. + + This function takes a batch of image patches and reconstructs the full image + by stitching the patches together. The function accounts for overlapping and + boundary pixels, ensuring that overlapping areas are averaged. + + Parameters + ---------- + input : Tensor + The input tensor containing the image patches with shape (total_patches * batch_size, channels, patch_shape_x, patch_shape_y). + img_shape_x : int + The width (x-dimension) of the original full image. + img_shape_y : int + The height (y-dimension) of the original full image. + patch_shape_x : int + The width (x-dimension) of each image patch. + patch_shape_y : int + The height (y-dimension) of each image patch. + batch_size : int + The original batch size before patching. + overlap_pix : int + The number of overlapping pixels between adjacent patches. + boundary_pix : int + The number of pixels to crop as a boundary from each patch. + + Returns + ------- + Tensor + The reconstructed full image tensor with shape (batch_size, channels, img_shape_x, img_shape_y). + + """ + patch_num_x = math.ceil(img_shape_x / (patch_shape_x - overlap_pix - boundary_pix)) + patch_num_y = math.ceil(img_shape_y / (patch_shape_y - overlap_pix - boundary_pix)) + padded_shape_x = ( + (patch_shape_x - overlap_pix - boundary_pix) * (patch_num_x - 1) + + patch_shape_x + + boundary_pix + ) + padded_shape_y = ( + (patch_shape_y - overlap_pix - boundary_pix) * (patch_num_y - 1) + + patch_shape_y + + boundary_pix + ) + pad_x_right = padded_shape_x - img_shape_x - boundary_pix + pad_y_right = padded_shape_y - img_shape_y - boundary_pix + residual_x = patch_shape_x - pad_x_right # residual pixels in the last patch + residual_y = patch_shape_y - pad_y_right # residual pixels in the last patch + output = torch.zeros( + batch_size, input.shape[1], img_shape_y, img_shape_x, device=input.device + ) + one_map = torch.ones(1, 1, input.shape[2], input.shape[3], device=input.device) + count_map = torch.zeros( + 1, 1, img_shape_y, img_shape_x, device=input.device + ) # to count the overlapping times + for x_index in range(patch_num_x): + for y_index in range(patch_num_y): + x_start = x_index * (patch_shape_x - overlap_pix - boundary_pix) + y_start = y_index * (patch_shape_y - overlap_pix - boundary_pix) + if (x_index == patch_num_x - 1) and (y_index != patch_num_y - 1): + output[ + :, :, y_start : y_start + patch_shape_y - 2 * boundary_pix, x_start: + ] += input[ + (x_index * patch_num_y + y_index) + * batch_size : (x_index * patch_num_y + y_index + 1) + * batch_size, + :, + boundary_pix : patch_shape_y - boundary_pix, + boundary_pix : residual_x + boundary_pix, + ] + count_map[ + :, :, y_start : y_start + patch_shape_y - 2 * boundary_pix, x_start: + ] += one_map[ + :, + :, + boundary_pix : patch_shape_y - boundary_pix, + boundary_pix : residual_x + boundary_pix, + ] + elif (y_index == patch_num_y - 1) and ((x_index != patch_num_x - 1)): + output[ + :, :, y_start:, x_start : x_start + patch_shape_x - 2 * boundary_pix + ] += input[ + (x_index * patch_num_y + y_index) + * batch_size : (x_index * patch_num_y + y_index + 1) + * batch_size, + :, + boundary_pix : residual_y + boundary_pix, + boundary_pix : patch_shape_x - boundary_pix, + ] + count_map[ + :, :, y_start:, x_start : x_start + patch_shape_x - 2 * boundary_pix + ] += one_map[ + :, + :, + boundary_pix : residual_y + boundary_pix, + boundary_pix : patch_shape_x - boundary_pix, + ] + elif x_index == patch_num_x - 1 and y_index == patch_num_y - 1: + output[:, :, y_start:, x_start:] += input[ + (x_index * patch_num_y + y_index) + * batch_size : (x_index * patch_num_y + y_index + 1) + * batch_size, + :, + boundary_pix : residual_y + boundary_pix, + boundary_pix : residual_x + boundary_pix, + ] + count_map[:, :, y_start:, x_start:] += one_map[ + :, + :, + boundary_pix : residual_y + boundary_pix, + boundary_pix : residual_x + boundary_pix, + ] + else: + output[ + :, + :, + y_start : y_start + patch_shape_y - 2 * boundary_pix, + x_start : x_start + patch_shape_x - 2 * boundary_pix, + ] += input[ + (x_index * patch_num_y + y_index) + * batch_size : (x_index * patch_num_y + y_index + 1) + * batch_size, + :, + boundary_pix : patch_shape_y - boundary_pix, + boundary_pix : patch_shape_x - boundary_pix, + ] + count_map[ + :, + :, + y_start : y_start + patch_shape_y - 2 * boundary_pix, + x_start : x_start + patch_shape_x - 2 * boundary_pix, + ] += one_map[ + :, + :, + boundary_pix : patch_shape_y - boundary_pix, + boundary_pix : patch_shape_x - boundary_pix, + ] + return output / count_map + + +def stochastic_sampler( + net: Any, + latents: Tensor, + img_lr: Tensor, + class_labels: Optional[Tensor] = None, + randn_like: Callable[[Tensor], Tensor] = torch.randn_like, + img_shape: int = 448, + patch_shape: int = 448, + overlap_pix: int = 4, + boundary_pix: int = 2, + mean_hr: Optional[Tensor] = None, + lead_time_label: Optional[Tensor] = None, + num_steps: int = 18, + sigma_min: float = 0.002, + sigma_max: float = 800, + rho: float = 7, + S_churn: float = 0, + S_min: float = 0, + S_max: float = float("inf"), + S_noise: float = 1, +) -> Tensor: + """ + Proposed EDM sampler (Algorithm 2) with minor changes to enable super-resolution and patch-based diffusion. + + Parameters + ---------- + net : Any + The neural network model that generates denoised images from noisy inputs. + latents : Tensor + The latent variables (e.g., noise) used as the initial input for the sampler. + img_lr : Tensor + Low-resolution input image for conditioning the super-resolution process. + class_labels : Optional[Tensor], optional + Class labels for conditional generation, if required by the model. By default None. + randn_like : Callable[[Tensor], Tensor] + Function to generate random noise with the same shape as the input tensor. + By default torch.randn_like. + img_shape : int + The height and width of the full image (assumed to be square). By default 448. + patch_shape : int + The height and width of each patch (assumed to be square). By default 448. + overlap_pix : int + Number of overlapping pixels between adjacent patches. By default 4. + boundary_pix : int + Number of pixels to be cropped as a boundary from each patch. By default 2. + mean_hr : Optional[Tensor], optional + Optional tensor containing mean high-resolution images for conditioning. By default None. + num_steps : int + Number of time steps for the sampler. By default 18. + sigma_min : float + Minimum noise level. By default 0.002. + sigma_max : float + Maximum noise level. By default 800. + rho : float + Exponent used in the time step discretization. By default 7. + S_churn : float + Churn parameter controlling the level of noise added in each step. By default 0. + S_min : float + Minimum time step for applying churn. By default 0. + S_max : float + Maximum time step for applying churn. By default float("inf"). + S_noise : float + Noise scaling factor applied during the churn step. By default 1. + + Returns + ------- + Tensor + The final denoised image produced by the sampler. + """ + + # Adjust noise levels based on what's supported by the network. + "Proposed EDM sampler (Algorithm 2) with minor changes to enable super-resolution." + sigma_min = max(sigma_min, net.sigma_min) + sigma_max = min(sigma_max, net.sigma_max) + if isinstance(img_shape, tuple): + img_shape_y, img_shape_x = img_shape + else: + img_shape_x = img_shape_y = img_shape + if patch_shape > img_shape_x or patch_shape > img_shape_y: + patch_shape = min(img_shape_x, img_shape_y) + + # Time step discretization. + step_indices = torch.arange(num_steps, dtype=torch.float64, device=latents.device) + t_steps = ( + sigma_max ** (1 / rho) + + step_indices + / (num_steps - 1) + * (sigma_min ** (1 / rho) - sigma_max ** (1 / rho)) + ) ** rho + t_steps = torch.cat( + [net.round_sigma(t_steps), torch.zeros_like(t_steps[:1])] + ) # t_N = 0 + + b = latents.shape[0] + Nx = torch.arange(img_shape_x) + Ny = torch.arange(img_shape_y) + grid = torch.stack(torch.meshgrid(Ny, Nx, indexing="ij"), dim=0)[ + None, + ].expand(b, -1, -1, -1) + + # conditioning = [mean_hr, img_lr, global_lr, pos_embd] + batch_size = img_lr.shape[0] + x_lr = img_lr + if mean_hr is not None: + x_lr = torch.cat((mean_hr.expand(x_lr.shape[0], -1, -1, -1), x_lr), dim=1) + global_index = None + + # input and position padding + patching + if patch_shape != img_shape_x or patch_shape != img_shape_y: + input_interp = torch.nn.functional.interpolate( + img_lr, (patch_shape, patch_shape), mode="bilinear" + ) + x_lr = image_batching( + x_lr, + img_shape_y, + img_shape_x, + patch_shape, + patch_shape, + batch_size, + overlap_pix, + boundary_pix, + input_interp, + ) + global_index = image_batching( + grid.float(), + img_shape_y, + img_shape_x, + patch_shape, + patch_shape, + batch_size, + overlap_pix, + boundary_pix, + ).int() + + # Main sampling loop. + x_next = latents.to(torch.float64) * t_steps[0] + for i, (t_cur, t_next) in enumerate(zip(t_steps[:-1], t_steps[1:])): # 0, ..., N-1 + x_cur = x_next + # Increase noise temporarily. + gamma = S_churn / num_steps if S_min <= t_cur <= S_max else 0 + t_hat = net.round_sigma(t_cur + gamma * t_cur) + + x_hat = x_cur + (t_hat**2 - t_cur**2).sqrt() * S_noise * randn_like(x_cur) + + # Euler step. Perform patching operation on score tensor if patch-based generation is used + # denoised = net(x_hat, t_hat, class_labels,lead_time_label=lead_time_label).to(torch.float64) #x_lr + + if patch_shape != img_shape_x or patch_shape != img_shape_y: + x_hat_batch = image_batching( + x_hat, + img_shape_y, + img_shape_x, + patch_shape, + patch_shape, + batch_size, + overlap_pix, + boundary_pix, + ) + else: + x_hat_batch = x_hat + x_hat_batch = x_hat_batch.to(latents.device) + x_lr = x_lr.to(latents.device) + if global_index is not None: + global_index = global_index.to(latents.device) + + if lead_time_label is not None: + denoised = net( + x_hat_batch, + x_lr, + t_hat, + class_labels, + lead_time_label=lead_time_label, + global_index=global_index, + ).to(torch.float64) + else: + denoised = net( + x_hat_batch, + x_lr, + t_hat, + class_labels, + global_index=global_index, + ).to(torch.float64) + if patch_shape != img_shape_x or patch_shape != img_shape_y: + + denoised = image_fuse( + denoised, + img_shape_y, + img_shape_x, + patch_shape, + patch_shape, + batch_size, + overlap_pix, + boundary_pix, + ) + d_cur = (x_hat - denoised) / t_hat + x_next = x_hat + (t_next - t_hat) * d_cur + + # Apply 2nd order correction. + if i < num_steps - 1: + if patch_shape != img_shape_x or patch_shape != img_shape_y: + x_next_batch = image_batching( + x_next, + img_shape_y, + img_shape_x, + patch_shape, + patch_shape, + batch_size, + overlap_pix, + boundary_pix, + ) + else: + x_next_batch = x_next + # ask about this fix + x_next_batch = x_next_batch.to(latents.device) + if lead_time_label is not None: + denoised = net( + x_next_batch, + x_lr, + t_next, + class_labels, + lead_time_label=lead_time_label, + global_index=global_index, + ).to(torch.float64) + else: + denoised = net( + x_next_batch, + x_lr, + t_next, + class_labels, + global_index=global_index, + ).to(torch.float64) + if patch_shape != img_shape_x or patch_shape != img_shape_y: + denoised = image_fuse( + denoised, + img_shape_y, + img_shape_x, + patch_shape, + patch_shape, + batch_size, + overlap_pix, + boundary_pix, + ) + d_prime = (x_next - denoised) / t_next + x_next = x_hat + (t_next - t_hat) * (0.5 * d_cur + 0.5 * d_prime) + return x_next diff --git a/src/utils/train_helpers.py b/src/utils/train_helpers.py new file mode 100644 index 00000000..d4529ac8 --- /dev/null +++ b/src/utils/train_helpers.py @@ -0,0 +1,107 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import torch +import numpy as np +from omegaconf import ListConfig + + +def set_patch_shape(img_shape, patch_shape): + img_shape_y, img_shape_x = img_shape + patch_shape_y, patch_shape_x = patch_shape + if (patch_shape_x is None) or (patch_shape_x > img_shape_x): + patch_shape_x = img_shape_x + if (patch_shape_y is None) or (patch_shape_y > img_shape_y): + patch_shape_y = img_shape_y + if patch_shape_x != img_shape_x or patch_shape_y != img_shape_y: + if patch_shape_x != patch_shape_y: + raise NotImplementedError("Rectangular patch not supported yet") + if patch_shape_x % 32 != 0 or patch_shape_y % 32 != 0: + raise ValueError("Patch shape needs to be a multiple of 32") + return (img_shape_y, img_shape_x), (patch_shape_y, patch_shape_x) + + +def set_seed(rank): + """ + Set seeds for NumPy and PyTorch to ensure reproducibility in distributed settings + """ + np.random.seed(rank % (1 << 31)) + torch.manual_seed(np.random.randint(1 << 31)) + + +def configure_cuda_for_consistent_precision(): + """ + Configures CUDA and cuDNN settings to ensure consistent precision by + disabling TensorFloat-32 (TF32) and reduced precision settings. + """ + torch.backends.cudnn.benchmark = True + torch.backends.cudnn.allow_tf32 = False + torch.backends.cuda.matmul.allow_tf32 = False + torch.backends.cuda.matmul.allow_fp16_reduced_precision_reduction = False + + +def compute_num_accumulation_rounds(total_batch_size, batch_size_per_gpu, world_size): + """ + Calculate the total batch size per GPU in a distributed setting, log the batch size per GPU, ensure it's within valid limits, + determine the number of accumulation rounds, and validate that the global batch size matches the expected value. + """ + batch_gpu_total = total_batch_size // world_size + batch_size_per_gpu = batch_size_per_gpu + if batch_size_per_gpu is None or batch_size_per_gpu > batch_gpu_total: + batch_size_per_gpu = batch_gpu_total + num_accumulation_rounds = batch_gpu_total // batch_size_per_gpu + if total_batch_size != batch_size_per_gpu * num_accumulation_rounds * world_size: + raise ValueError( + "total_batch_size must be equal to batch_size_per_gpu * num_accumulation_rounds * world_size" + ) + return batch_gpu_total, num_accumulation_rounds + + +def handle_and_clip_gradients(model, grad_clip_threshold=None): + """ + Handles NaNs and infinities in the gradients and optionally clips the gradients. + + Parameters: + - model (torch.nn.Module): The model whose gradients need to be processed. + - grad_clip_threshold (float, optional): The threshold for gradient clipping. If None, no clipping is performed. + """ + # Replace NaNs and infinities in gradients + for param in model.parameters(): + if param.grad is not None: + torch.nan_to_num( + param.grad, nan=0.0, posinf=1e5, neginf=-1e5, out=param.grad + ) + + # Clip gradients if a threshold is provided + if grad_clip_threshold is not None: + torch.nn.utils.clip_grad_norm_(model.parameters(), grad_clip_threshold) + + +def parse_model_args(args): + """Convert ListConfig values in args to tuples.""" + return {k: tuple(v) if isinstance(v, ListConfig) else v for k, v in args.items()} + + +def is_time_for_periodic_task( + cur_nimg, freq, done, batch_size, rank, rank_0_only=False +): + """Should we perform a task that is done every `freq` samples?""" + if rank_0_only and rank != 0: + return False + elif done: # Run periodic tasks also at the end of training + return True + else: + return cur_nimg % freq < batch_size From 3b172ccc9093ceaa3b4a18a76e26d770a6301253 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 14 Apr 2025 17:14:37 +0200 Subject: [PATCH 006/302] restructure file system and add pyproject.toml --- README.md | 0 pyproject.toml | 24 +++++++++++ src/hirad/conf/train_regression.yaml | 0 src/{ => hirad}/distributed/__init__.py | 0 src/{ => hirad}/distributed/config.py | 0 src/{ => hirad}/distributed/manager.py | 0 src/hirad/losses/__init__.py | 0 src/{ => hirad}/losses/loss.py | 0 src/{ => hirad}/models/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 185 bytes .../models/__pycache__/dummy.cpython-312.pyc | Bin 0 -> 451 bytes .../models/__pycache__/unet.cpython-312.pyc | Bin 0 -> 8966 bytes src/{ => hirad}/models/layers.py | 0 src/{ => hirad}/models/preconditioning.py | 0 src/{ => hirad}/models/song_unet.py | 0 src/{ => hirad}/models/unet.py | 0 src/{ => hirad}/models/utils.py | 0 src/hirad/training/train.py | 0 src/hirad/utils/__init__.py | 0 src/{ => hirad}/utils/capture.py | 0 src/{ => hirad}/utils/checkpoint.py | 0 src/{ => hirad}/utils/console.py | 0 .../utils/deterministic_sampler.py | 0 src/{ => hirad}/utils/function_utils.py | 0 src/{ => hirad}/utils/inference_utils.py | 0 src/{ => hirad}/utils/model_utils.py | 0 src/{ => hirad}/utils/stochastic_sampler.py | 0 src/{ => hirad}/utils/train_helpers.py | 0 src/hirad_gen.egg-info/PKG-INFO | 38 ++++++++++++++++++ src/hirad_gen.egg-info/SOURCES.txt | 28 +++++++++++++ src/hirad_gen.egg-info/dependency_links.txt | 1 + src/hirad_gen.egg-info/top_level.txt | 7 ++++ 32 files changed, 98 insertions(+) create mode 100644 README.md create mode 100644 pyproject.toml create mode 100644 src/hirad/conf/train_regression.yaml rename src/{ => hirad}/distributed/__init__.py (100%) rename src/{ => hirad}/distributed/config.py (100%) rename src/{ => hirad}/distributed/manager.py (100%) create mode 100644 src/hirad/losses/__init__.py rename src/{ => hirad}/losses/loss.py (100%) rename src/{ => hirad}/models/__init__.py (100%) create mode 100644 src/hirad/models/__pycache__/__init__.cpython-312.pyc create mode 100644 src/hirad/models/__pycache__/dummy.cpython-312.pyc create mode 100644 src/hirad/models/__pycache__/unet.cpython-312.pyc rename src/{ => hirad}/models/layers.py (100%) rename src/{ => hirad}/models/preconditioning.py (100%) rename src/{ => hirad}/models/song_unet.py (100%) rename src/{ => hirad}/models/unet.py (100%) rename src/{ => hirad}/models/utils.py (100%) create mode 100644 src/hirad/training/train.py create mode 100644 src/hirad/utils/__init__.py rename src/{ => hirad}/utils/capture.py (100%) rename src/{ => hirad}/utils/checkpoint.py (100%) rename src/{ => hirad}/utils/console.py (100%) rename src/{ => hirad}/utils/deterministic_sampler.py (100%) rename src/{ => hirad}/utils/function_utils.py (100%) rename src/{ => hirad}/utils/inference_utils.py (100%) rename src/{ => hirad}/utils/model_utils.py (100%) rename src/{ => hirad}/utils/stochastic_sampler.py (100%) rename src/{ => hirad}/utils/train_helpers.py (100%) create mode 100644 src/hirad_gen.egg-info/PKG-INFO create mode 100644 src/hirad_gen.egg-info/SOURCES.txt create mode 100644 src/hirad_gen.egg-info/dependency_links.txt create mode 100644 src/hirad_gen.egg-info/top_level.txt diff --git a/README.md b/README.md new file mode 100644 index 00000000..e69de29b diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..b2fa56c9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "hirad-gen" +version = "0.1.0" +description = "High resolution atmospheric downscaling using generative machine learning" +authors = [ + { name="Petar Stamenkovic", email="petar.stamenkovic@meteoswiss.ch" } +] +readme = "README.md" +requires-python = ">=3.12" +license = {file = "LICENSE"} + +dependencies = [ + "torch>=2.6.0" +] + +[tool.setuptools] +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +where = ["src"] \ No newline at end of file diff --git a/src/hirad/conf/train_regression.yaml b/src/hirad/conf/train_regression.yaml new file mode 100644 index 00000000..e69de29b diff --git a/src/distributed/__init__.py b/src/hirad/distributed/__init__.py similarity index 100% rename from src/distributed/__init__.py rename to src/hirad/distributed/__init__.py diff --git a/src/distributed/config.py b/src/hirad/distributed/config.py similarity index 100% rename from src/distributed/config.py rename to src/hirad/distributed/config.py diff --git a/src/distributed/manager.py b/src/hirad/distributed/manager.py similarity index 100% rename from src/distributed/manager.py rename to src/hirad/distributed/manager.py diff --git a/src/hirad/losses/__init__.py b/src/hirad/losses/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/losses/loss.py b/src/hirad/losses/loss.py similarity index 100% rename from src/losses/loss.py rename to src/hirad/losses/loss.py diff --git a/src/models/__init__.py b/src/hirad/models/__init__.py similarity index 100% rename from src/models/__init__.py rename to src/hirad/models/__init__.py diff --git a/src/hirad/models/__pycache__/__init__.cpython-312.pyc b/src/hirad/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70d5748263686e7ee01a00c9c9ffd61e4261990b GIT binary patch literal 185 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 TvYl5CEg!pf>|+M=bSA$CZ1ZH& literal 0 HcmV?d00001 diff --git a/src/models/layers.py b/src/hirad/models/layers.py similarity index 100% rename from src/models/layers.py rename to src/hirad/models/layers.py diff --git a/src/models/preconditioning.py b/src/hirad/models/preconditioning.py similarity index 100% rename from src/models/preconditioning.py rename to src/hirad/models/preconditioning.py diff --git a/src/models/song_unet.py b/src/hirad/models/song_unet.py similarity index 100% rename from src/models/song_unet.py rename to src/hirad/models/song_unet.py diff --git a/src/models/unet.py b/src/hirad/models/unet.py similarity index 100% rename from src/models/unet.py rename to src/hirad/models/unet.py diff --git a/src/models/utils.py b/src/hirad/models/utils.py similarity index 100% rename from src/models/utils.py rename to src/hirad/models/utils.py diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py new file mode 100644 index 00000000..e69de29b diff --git a/src/hirad/utils/__init__.py b/src/hirad/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/utils/capture.py b/src/hirad/utils/capture.py similarity index 100% rename from src/utils/capture.py rename to src/hirad/utils/capture.py diff --git a/src/utils/checkpoint.py b/src/hirad/utils/checkpoint.py similarity index 100% rename from src/utils/checkpoint.py rename to src/hirad/utils/checkpoint.py diff --git a/src/utils/console.py b/src/hirad/utils/console.py similarity index 100% rename from src/utils/console.py rename to src/hirad/utils/console.py diff --git a/src/utils/deterministic_sampler.py b/src/hirad/utils/deterministic_sampler.py similarity index 100% rename from src/utils/deterministic_sampler.py rename to src/hirad/utils/deterministic_sampler.py diff --git a/src/utils/function_utils.py b/src/hirad/utils/function_utils.py similarity index 100% rename from src/utils/function_utils.py rename to src/hirad/utils/function_utils.py diff --git a/src/utils/inference_utils.py b/src/hirad/utils/inference_utils.py similarity index 100% rename from src/utils/inference_utils.py rename to src/hirad/utils/inference_utils.py diff --git a/src/utils/model_utils.py b/src/hirad/utils/model_utils.py similarity index 100% rename from src/utils/model_utils.py rename to src/hirad/utils/model_utils.py diff --git a/src/utils/stochastic_sampler.py b/src/hirad/utils/stochastic_sampler.py similarity index 100% rename from src/utils/stochastic_sampler.py rename to src/hirad/utils/stochastic_sampler.py diff --git a/src/utils/train_helpers.py b/src/hirad/utils/train_helpers.py similarity index 100% rename from src/utils/train_helpers.py rename to src/hirad/utils/train_helpers.py diff --git a/src/hirad_gen.egg-info/PKG-INFO b/src/hirad_gen.egg-info/PKG-INFO new file mode 100644 index 00000000..9c4c18f0 --- /dev/null +++ b/src/hirad_gen.egg-info/PKG-INFO @@ -0,0 +1,38 @@ +Metadata-Version: 2.4 +Name: hirad-gen +Version: 0.1.0 +Summary: High resolution atmospheric downscaling using generative machine learning +Author-email: Petar Stamenkovic +License: BSD 3-Clause License + + Copyright (c) 2025, MeteoSwiss + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Requires-Python: >=3.12 +Description-Content-Type: text/markdown +License-File: LICENSE +Dynamic: license-file diff --git a/src/hirad_gen.egg-info/SOURCES.txt b/src/hirad_gen.egg-info/SOURCES.txt new file mode 100644 index 00000000..1645d32a --- /dev/null +++ b/src/hirad_gen.egg-info/SOURCES.txt @@ -0,0 +1,28 @@ +LICENSE +README.md +pyproject.toml +src/distributed/__init__.py +src/distributed/config.py +src/distributed/manager.py +src/hirad_gen.egg-info/PKG-INFO +src/hirad_gen.egg-info/SOURCES.txt +src/hirad_gen.egg-info/dependency_links.txt +src/hirad_gen.egg-info/top_level.txt +src/losses/__init__.py +src/losses/loss.py +src/models/__init__.py +src/models/layers.py +src/models/preconditioning.py +src/models/song_unet.py +src/models/unet.py +src/models/utils.py +src/utils/__init__.py +src/utils/capture.py +src/utils/checkpoint.py +src/utils/console.py +src/utils/deterministic_sampler.py +src/utils/function_utils.py +src/utils/inference_utils.py +src/utils/model_utils.py +src/utils/stochastic_sampler.py +src/utils/train_helpers.py \ No newline at end of file diff --git a/src/hirad_gen.egg-info/dependency_links.txt b/src/hirad_gen.egg-info/dependency_links.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/hirad_gen.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/hirad_gen.egg-info/top_level.txt b/src/hirad_gen.egg-info/top_level.txt new file mode 100644 index 00000000..b7786910 --- /dev/null +++ b/src/hirad_gen.egg-info/top_level.txt @@ -0,0 +1,7 @@ +distributed +evaluation +losses +metrics +models +training +utils From 05e3a08a75e41c5c6a5c63422c1516dab6cdb091 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic <56728083+PetarStam@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:15:40 +0200 Subject: [PATCH 007/302] Delete src/hirad_gen.egg-info directory --- src/hirad_gen.egg-info/PKG-INFO | 38 --------------------- src/hirad_gen.egg-info/SOURCES.txt | 28 --------------- src/hirad_gen.egg-info/dependency_links.txt | 1 - src/hirad_gen.egg-info/top_level.txt | 7 ---- 4 files changed, 74 deletions(-) delete mode 100644 src/hirad_gen.egg-info/PKG-INFO delete mode 100644 src/hirad_gen.egg-info/SOURCES.txt delete mode 100644 src/hirad_gen.egg-info/dependency_links.txt delete mode 100644 src/hirad_gen.egg-info/top_level.txt diff --git a/src/hirad_gen.egg-info/PKG-INFO b/src/hirad_gen.egg-info/PKG-INFO deleted file mode 100644 index 9c4c18f0..00000000 --- a/src/hirad_gen.egg-info/PKG-INFO +++ /dev/null @@ -1,38 +0,0 @@ -Metadata-Version: 2.4 -Name: hirad-gen -Version: 0.1.0 -Summary: High resolution atmospheric downscaling using generative machine learning -Author-email: Petar Stamenkovic -License: BSD 3-Clause License - - Copyright (c) 2025, MeteoSwiss - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Requires-Python: >=3.12 -Description-Content-Type: text/markdown -License-File: LICENSE -Dynamic: license-file diff --git a/src/hirad_gen.egg-info/SOURCES.txt b/src/hirad_gen.egg-info/SOURCES.txt deleted file mode 100644 index 1645d32a..00000000 --- a/src/hirad_gen.egg-info/SOURCES.txt +++ /dev/null @@ -1,28 +0,0 @@ -LICENSE -README.md -pyproject.toml -src/distributed/__init__.py -src/distributed/config.py -src/distributed/manager.py -src/hirad_gen.egg-info/PKG-INFO -src/hirad_gen.egg-info/SOURCES.txt -src/hirad_gen.egg-info/dependency_links.txt -src/hirad_gen.egg-info/top_level.txt -src/losses/__init__.py -src/losses/loss.py -src/models/__init__.py -src/models/layers.py -src/models/preconditioning.py -src/models/song_unet.py -src/models/unet.py -src/models/utils.py -src/utils/__init__.py -src/utils/capture.py -src/utils/checkpoint.py -src/utils/console.py -src/utils/deterministic_sampler.py -src/utils/function_utils.py -src/utils/inference_utils.py -src/utils/model_utils.py -src/utils/stochastic_sampler.py -src/utils/train_helpers.py \ No newline at end of file diff --git a/src/hirad_gen.egg-info/dependency_links.txt b/src/hirad_gen.egg-info/dependency_links.txt deleted file mode 100644 index 8b137891..00000000 --- a/src/hirad_gen.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/hirad_gen.egg-info/top_level.txt b/src/hirad_gen.egg-info/top_level.txt deleted file mode 100644 index b7786910..00000000 --- a/src/hirad_gen.egg-info/top_level.txt +++ /dev/null @@ -1,7 +0,0 @@ -distributed -evaluation -losses -metrics -models -training -utils From dbf101ef0c50e1dff50ba140b85124a6d407f965 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 14 Apr 2025 17:17:57 +0200 Subject: [PATCH 008/302] add gitignore --- .gitignore | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c514c5d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,171 @@ +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json \ No newline at end of file From 5de83bc5f7bb25f8bff91ffda69b2eedd97738fd Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 16 Apr 2025 11:57:45 +0200 Subject: [PATCH 009/302] add train script with missing parts --- .../{train_regression.yaml => training.yaml} | 0 src/hirad/distributed/manager.py | 6 +- src/hirad/losses/__init__.py | 1 + src/hirad/models/__init__.py | 1 + src/hirad/training/train.py | 462 ++++++++++++++++++ src/hirad/utils/capture.py | 2 +- 6 files changed, 468 insertions(+), 4 deletions(-) rename src/hirad/conf/{train_regression.yaml => training.yaml} (100%) diff --git a/src/hirad/conf/train_regression.yaml b/src/hirad/conf/training.yaml similarity index 100% rename from src/hirad/conf/train_regression.yaml rename to src/hirad/conf/training.yaml diff --git a/src/hirad/distributed/manager.py b/src/hirad/distributed/manager.py index facb4664..e80ce138 100644 --- a/src/hirad/distributed/manager.py +++ b/src/hirad/distributed/manager.py @@ -25,7 +25,7 @@ import torch import torch.distributed as dist -from src.distributed.config import ProcessGroupConfig, ProcessGroupNode +from hirad.distributed.config import ProcessGroupConfig, ProcessGroupNode warnings.simplefilter("default", DeprecationWarning) @@ -393,7 +393,7 @@ def initialize(): else: os.environ["TORCH_NCCL_ASYNC_ERROR_HANDLING"] = "0" initialization_method = os.getenv( - "PHYSICSNEMO_DISTRIBUTED_INITIALIZATION_METHOD" + "DISTRIBUTED_INITIALIZATION_METHOD" ) if initialization_method is None: try: @@ -419,7 +419,7 @@ def initialize(): "Unknown initialization method " f"{initialization_method}. " "Supported values for " - "PHYSICSNEMO_DISTRIBUTED_INITIALIZATION_METHOD are " + "DISTRIBUTED_INITIALIZATION_METHOD are " "ENV, SLURM and OPENMPI" ) diff --git a/src/hirad/losses/__init__.py b/src/hirad/losses/__init__.py index e69de29b..185527b6 100644 --- a/src/hirad/losses/__init__.py +++ b/src/hirad/losses/__init__.py @@ -0,0 +1 @@ +from .loss import ResLoss, RegressionLoss, RegressionLossCE \ No newline at end of file diff --git a/src/hirad/models/__init__.py b/src/hirad/models/__init__.py index 6b790ae4..3b494c6c 100644 --- a/src/hirad/models/__init__.py +++ b/src/hirad/models/__init__.py @@ -1,3 +1,4 @@ from .unet import UNet from .song_unet import SongUNet, SongUNetPosEmbd, SongUNetPosLtEmbd +from .preconditioning import EDMPrecondSR from .layers import Linear, Conv2d, GroupNorm, AttentionOp, UNetBlock, PositionalEmbedding, FourierEmbedding \ No newline at end of file diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index e69de29b..a47910b3 100644 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -0,0 +1,462 @@ +import os +import time + +import psutil +import hydra +from omegaconf import DictConfig, OmegaConf +import torch +from hydra.utils import to_absolute_path +from torch.utils.tensorboard import SummaryWriter +from torch.nn.parallel import DistributedDataParallel + +from hirad.distributed import DistributedManager +from hirad.utils.console import PythonLogger, RankZeroLoggingWrapper +from hirad.utils.train_helpers import set_seed, configure_cuda_for_consistent_precision, \ + set_patch_shape, compute_num_accumulation_rounds, \ + is_time_for_periodic_task, handle_and_clip_gradients +from hirad.models import UNet, EDMPrecondSR +from hirad.losses import ResLoss, RegressionLoss, RegressionLossCE + +@hydra.main(version_base=None, config_path="conf", config_name="training") +def main(cfg: DictConfig) -> None: + + # Initialize distributed environment for training + DistributedManager.initialize() + dist = DistributedManager() + + if dist.rank==0: + writer = SummaryWriter(log_dir='tensorboard') + logger = PythonLogger("main") # general logger + logger0 = RankZeroLoggingWrapper(logger, dist) # rank 0 logger + + OmegaConf.resolve(cfg) + dataset_cfg = OmegaConf.to_container(cfg.dataset) + if hasattr(cfg, "validation"): + train_test_split = True + validation_dataset_cfg = OmegaConf.to_container(cfg.validation) + else: + train_test_split = False + validation_dataset_cfg = None + fp_optimizations = cfg.training.perf.fp_optimizations + songunet_checkpoint_level = cfg.training.perf.songunet_checkpoint_level + fp16 = fp_optimizations == "fp16" + enable_amp = fp_optimizations.startswith("amp") + amp_dtype = torch.float16 if (fp_optimizations == "amp-fp16") else torch.bfloat16 + logger.info(f"Saving the outputs in {os.getcwd()}") + checkpoint_dir = os.path.join( + cfg.training.io.get("checkpoint_dir", "."), f"checkpoints_{cfg.model.name}" + ) + if cfg.training.hp.batch_size_per_gpu == "auto": + cfg.training.hp.batch_size_per_gpu = ( + cfg.training.hp.total_batch_size // dist.world_size + ) + + set_seed(dist.rank) + configure_cuda_for_consistent_precision() + + ### Write our own dataloader ### + ( + dataset, + dataset_iterator, + validation_dataset, + validation_dataset_iterator + ) = None, None, None, None + + dataset_channels = None #len(dataset.input_channels()) + img_in_channels = None #dataset_channels + img_shape = None #dataset.image_shape() + img_out_channels = None #len(dataset.output_channels()) + + prob_channels = None + + # Parse the patch shape + if ( + cfg.model.name == "patched_diffusion" + or cfg.model.name == "lt_aware_patched_diffusion" + ): + patch_shape_x = cfg.training.hp.patch_shape_x + patch_shape_y = cfg.training.hp.patch_shape_y + else: + patch_shape_x = None + patch_shape_y = None + patch_shape = (patch_shape_y, patch_shape_x) + img_shape, patch_shape = set_patch_shape(img_shape, patch_shape) + if patch_shape != img_shape: + logger0.info("Patch-based training enabled") + else: + logger0.info("Patch-based training disabled") + # interpolate global channel if patch-based model is used + if img_shape[1] != patch_shape[1]: + img_in_channels += dataset_channels + + + # Instantiate the model and move to device. + if cfg.model.name not in ( + "regression", + "lt_aware_ce_regression", + "diffusion", + "patched_diffusion", + "lt_aware_patched_diffusion", + ): + raise ValueError("Invalid model") + model_args = { # default parameters for all networks + "img_out_channels": img_out_channels, + "img_resolution": list(img_shape), + "use_fp16": fp16, + } + standard_model_cfgs = { # default parameters for different network types + "regression": { + "img_channels": 4, + "N_grid_channels": 4, + "embedding_type": "zero", + "checkpoint_level": songunet_checkpoint_level, + }, + "lt_aware_ce_regression": { + "img_channels": 4, + "N_grid_channels": 4, + "embedding_type": "zero", + "lead_time_channels": 4, + "lead_time_steps": 9, + "prob_channels": prob_channels, + "checkpoint_level": songunet_checkpoint_level, + "model_type": "SongUNetPosLtEmbd", + }, + "diffusion": { + "img_channels": img_out_channels, + "gridtype": "sinusoidal", + "N_grid_channels": 4, + "checkpoint_level": songunet_checkpoint_level, + }, + "patched_diffusion": { + "img_channels": img_out_channels, + "gridtype": "learnable", + "N_grid_channels": 100, + "checkpoint_level": songunet_checkpoint_level, + }, + "lt_aware_patched_diffusion": { + "img_channels": img_out_channels, + "gridtype": "learnable", + "N_grid_channels": 100, + "lead_time_channels": 20, + "lead_time_steps": 9, + "checkpoint_level": songunet_checkpoint_level, + "model_type": "SongUNetPosLtEmbd", + }, + } + + + model_args.update(standard_model_cfgs[cfg.model.name]) + if cfg.model.name in ( + "diffusion", + "patched_diffusion", + "lt_aware_patched_diffusion", + ): + model_args["scale_cond_input"] = cfg.model.scale_cond_input + if hasattr(cfg.model, "model_args"): # override defaults from config file + model_args.update(OmegaConf.to_container(cfg.model.model_args)) + if cfg.model.name == "regression": + model = UNet( + img_in_channels=img_in_channels + model_args["N_grid_channels"], + **model_args, + ) + elif cfg.model.name == "lt_aware_ce_regression": + model = UNet( + img_in_channels=img_in_channels + + model_args["N_grid_channels"] + + model_args["lead_time_channels"], + **model_args, + ) + elif cfg.model.name == "lt_aware_patched_diffusion": + model = EDMPrecondSR( + img_in_channels=img_in_channels + + model_args["N_grid_channels"] + + model_args["lead_time_channels"], + **model_args, + ) + else: # diffusion or patched diffusion + model = EDMPrecondSR( + img_in_channels=img_in_channels + model_args["N_grid_channels"], + **model_args, + ) + + model.train().requires_grad_(True).to(dist.device) + + # Enable distributed data parallel if applicable + if dist.world_size > 1: + model = DistributedDataParallel( + model, + device_ids=[dist.local_rank], + broadcast_buffers=True, + output_device=dist.device, + find_unused_parameters=dist.find_unused_parameters, + ) + + # Load the regression checkpoint if applicable + if hasattr(cfg.training.io, "regression_checkpoint_path"): + regression_checkpoint_path = to_absolute_path( + cfg.training.io.regression_checkpoint_path + ) + if not os.path.exists(regression_checkpoint_path): + raise FileNotFoundError( + f"Expected this regression checkpoint but not found: {regression_checkpoint_path}" + ) + regression_net = torch.nn.Module() #Module.from_checkpoint(regression_checkpoint_path) figure out how to save and load models, also, some basic functions like num_params, device + regression_net.eval().requires_grad_(False).to(dist.device) + logger0.success("Loaded the pre-trained regression model") + + # Instantiate the loss function + patch_num = getattr(cfg.training.hp, "patch_num", 1) + if cfg.model.name in ( + "diffusion", + "patched_diffusion", + "lt_aware_patched_diffusion", + ): + loss_fn = ResLoss( + regression_net=regression_net, + img_shape_x=img_shape[1], + img_shape_y=img_shape[0], + patch_shape_x=patch_shape[1], + patch_shape_y=patch_shape[0], + patch_num=patch_num, + hr_mean_conditioning=cfg.model.hr_mean_conditioning, + ) + elif cfg.model.name == "regression": + loss_fn = RegressionLoss() + elif cfg.model.name == "lt_aware_ce_regression": + loss_fn = RegressionLossCE(prob_channels=prob_channels) + + # Instantiate the optimizer + optimizer = torch.optim.Adam( + params=model.parameters(), lr=cfg.training.hp.lr, betas=[0.9, 0.999], eps=1e-8 + ) + + # Record the current time to measure the duration of subsequent operations. + start_time = time.time() + + # Compute the number of required gradient accumulation rounds + # It is automatically used if batch_size_per_gpu * dist.world_size < total_batch_size + batch_gpu_total, num_accumulation_rounds = compute_num_accumulation_rounds( + cfg.training.hp.total_batch_size, + cfg.training.hp.batch_size_per_gpu, + dist.world_size, + ) + batch_size_per_gpu = cfg.training.hp.batch_size_per_gpu + logger0.info(f"Using {num_accumulation_rounds} gradient accumulation rounds") + + + ## Resume training from previous checkpoints if exists + if dist.world_size > 1: + torch.distributed.barrier() + try: + cur_nimg = 0 + # Fix loading and saving checkpoint + #load_checkpoint( + # path=checkpoint_dir, + # models=model, + # optimizer=optimizer, + # device=dist.device, + # ) + except: + cur_nimg = 0 + + ############################################################################ + # MAIN TRAINING LOOP # + ############################################################################ + + logger0.info(f"Training for {cfg.training.hp.training_duration} images...") + done = False + + # init variables to monitor running mean of average loss since last periodic + average_loss_running_mean = 0 + n_average_loss_running_mean = 1 + + + while not done: + tick_start_nimg = cur_nimg + tick_start_time = time.time() + # Compute & accumulate gradients + optimizer.zero_grad(set_to_none=True) + loss_accum = 0 + for _ in range(num_accumulation_rounds): + img_clean, img_lr, labels, *lead_time_label = next(dataset_iterator) # what are labels and lead_time_label + img_clean = img_clean.to(dist.device).to(torch.float32).contiguous() + img_lr = img_lr.to(dist.device).to(torch.float32).contiguous() + labels = labels.to(dist.device).contiguous() + loss_fn_kwargs = { + "net": model, + "img_clean": img_clean, + "img_lr": img_lr, + "labels": labels, + "augment_pipe": None, + } + if lead_time_label: + lead_time_label = lead_time_label[0].to(dist.device).contiguous() + loss_fn_kwargs.update({"lead_time_label": lead_time_label}) + else: + lead_time_label = None + with torch.autocast("cuda", dtype=amp_dtype, enabled=enable_amp): + loss = loss_fn(**loss_fn_kwargs) + loss = loss.sum() / batch_size_per_gpu + loss_accum += loss / num_accumulation_rounds + loss.backward() + + + loss_sum = torch.tensor([loss_accum], device=dist.device) + if dist.world_size > 1: + torch.distributed.barrier() + torch.distributed.all_reduce(loss_sum, op=torch.distributed.ReduceOp.SUM) + average_loss = (loss_sum / dist.world_size).cpu().item() + + # update running mean of average loss since last periodic task + average_loss_running_mean += ( + average_loss - average_loss_running_mean + ) / n_average_loss_running_mean + n_average_loss_running_mean += 1 + + if dist.rank == 0: + writer.add_scalar("training_loss", average_loss, cur_nimg) + writer.add_scalar( + "training_loss_running_mean", average_loss_running_mean, cur_nimg + ) + + ptt = is_time_for_periodic_task( + cur_nimg, + cfg.training.io.print_progress_freq, + done, + cfg.training.hp.total_batch_size, + dist.rank, + rank_0_only=True, + ) + if ptt: + # reset running mean of average loss + average_loss_running_mean = 0 + n_average_loss_running_mean = 1 + + # Update weights. + lr_rampup = cfg.training.hp.lr_rampup # ramp up the learning rate + for g in optimizer.param_groups: + if lr_rampup > 0: + g["lr"] = cfg.training.hp.lr * min(cur_nimg / lr_rampup, 1) + if cur_nimg >= lr_rampup: + g["lr"] *= cfg.training.hp.lr_decay ** ((cur_nimg - lr_rampup) // 5e6) + current_lr = g["lr"] + if dist.rank == 0: + writer.add_scalar("learning_rate", current_lr, cur_nimg) + handle_and_clip_gradients( + model, grad_clip_threshold=cfg.training.hp.grad_clip_threshold + ) + optimizer.step() + + cur_nimg += cfg.training.hp.total_batch_size + done = cur_nimg >= cfg.training.hp.training_duration + + # Validation + if validation_dataset_iterator is not None: + valid_loss_accum = 0 + if is_time_for_periodic_task( + cur_nimg, + cfg.training.io.validation_freq, + done, + cfg.training.hp.total_batch_size, + dist.rank, + ): + with torch.no_grad(): + for _ in range(cfg.training.io.validation_steps): + img_clean_valid, img_lr_valid, labels_valid = next( + validation_dataset_iterator + ) + + img_clean_valid = ( + img_clean_valid.to(dist.device) + .to(torch.float32) + .contiguous() + ) + img_lr_valid = ( + img_lr_valid.to(dist.device).to(torch.float32).contiguous() + ) + labels_valid = labels_valid.to(dist.device).contiguous() + loss_valid = loss_fn( + net=model, + img_clean=img_clean_valid, + img_lr=img_lr_valid, + labels=labels_valid, + augment_pipe=None, + ) + loss_valid = ( + (loss_valid.sum() / batch_size_per_gpu).cpu().item() + ) + valid_loss_accum += ( + loss_valid / cfg.training.io.validation_steps + ) + valid_loss_sum = torch.tensor( + [valid_loss_accum], device=dist.device + ) + if dist.world_size > 1: + torch.distributed.barrier() + torch.distributed.all_reduce( + valid_loss_sum, op=torch.distributed.ReduceOp.SUM + ) + average_valid_loss = valid_loss_sum / dist.world_size + if dist.rank == 0: + writer.add_scalar( + "validation_loss", average_valid_loss, cur_nimg + ) + + if is_time_for_periodic_task( + cur_nimg, + cfg.training.io.print_progress_freq, + done, + cfg.training.hp.total_batch_size, + dist.rank, + rank_0_only=True, + ): + # Print stats if we crossed the printing threshold with this batch + tick_end_time = time.time() + fields = [] + fields += [f"samples {cur_nimg:<9.1f}"] + fields += [f"training_loss {average_loss:<7.2f}"] + fields += [f"training_loss_running_mean {average_loss_running_mean:<7.2f}"] + fields += [f"learning_rate {current_lr:<7.8f}"] + fields += [f"total_sec {(tick_end_time - start_time):<7.1f}"] + fields += [f"sec_per_tick {(tick_end_time - tick_start_time):<7.1f}"] + fields += [ + f"sec_per_sample {((tick_end_time - tick_start_time) / (cur_nimg - tick_start_nimg)):<7.2f}" + ] + fields += [ + f"cpu_mem_gb {(psutil.Process(os.getpid()).memory_info().rss / 2**30):<6.2f}" + ] + fields += [ + f"peak_gpu_mem_gb {(torch.cuda.max_memory_allocated(dist.device) / 2**30):<6.2f}" + ] + fields += [ + f"peak_gpu_mem_reserved_gb {(torch.cuda.max_memory_reserved(dist.device) / 2**30):<6.2f}" + ] + logger0.info(" ".join(fields)) + torch.cuda.reset_peak_memory_stats() + + # Save checkpoints + if dist.world_size > 1: + torch.distributed.barrier() + if is_time_for_periodic_task( + cur_nimg, + cfg.training.io.save_checkpoint_freq, + done, + cfg.training.hp.total_batch_size, + dist.rank, + rank_0_only=True, + ): + # figure out how to do save and load checkpoint + #save_checkpoint( + # path=checkpoint_dir, + # models=model, + # optimizer=optimizer, + # epoch=cur_nimg, + # ) + pass + + # Done. + logger0.info("Training Completed.") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/hirad/utils/capture.py b/src/hirad/utils/capture.py index 50057f91..9c38d5aa 100644 --- a/src/hirad/utils/capture.py +++ b/src/hirad/utils/capture.py @@ -24,7 +24,7 @@ import torch -from src.distributed import DistributedManager +from hirad.distributed import DistributedManager float16 = NewType("float16", torch.float16) bfloat16 = NewType("bfloat16", torch.bfloat16) From 134ed835f7cfde077dddb5d8eeee2313cf994611 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 16 Apr 2025 14:33:48 +0200 Subject: [PATCH 010/302] add abstract dataset class --- src/hirad/datasets/base.py | 85 ++++++++++++++++++++++++++ src/hirad/datasets/dataset.py | 111 ++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 src/hirad/datasets/base.py create mode 100644 src/hirad/datasets/dataset.py diff --git a/src/hirad/datasets/base.py b/src/hirad/datasets/base.py new file mode 100644 index 00000000..22b00d25 --- /dev/null +++ b/src/hirad/datasets/base.py @@ -0,0 +1,85 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import ABC, abstractmethod +from dataclasses import dataclass +from typing import List, Tuple + +import numpy as np +import torch + + +@dataclass +class ChannelMetadata: + """Metadata describing a data channel.""" + + name: str + level: str = "" + auxiliary: bool = False + + +class DownscalingDataset(torch.utils.data.Dataset, ABC): + """An abstract class that defines the interface for downscaling datasets.""" + + @abstractmethod + def longitude(self) -> np.ndarray: + """Get longitude values from the dataset.""" + pass + + @abstractmethod + def latitude(self) -> np.ndarray: + """Get latitude values from the dataset.""" + pass + + @abstractmethod + def input_channels(self) -> List[ChannelMetadata]: + """Metadata for the input channels. A list of ChannelMetadata, one for each channel""" + pass + + @abstractmethod + def output_channels(self) -> List[ChannelMetadata]: + """Metadata for the output channels. A list of ChannelMetadata, one for each channel""" + pass + + @abstractmethod + def time(self) -> List: + """Get time values from the dataset.""" + pass + + @abstractmethod + def image_shape(self) -> Tuple[int, int]: + """Get the (height, width) of the data (same for input and output).""" + pass + + def normalize_input(self, x: np.ndarray) -> np.ndarray: + """Convert input from physical units to normalized data.""" + return x + + def denormalize_input(self, x: np.ndarray) -> np.ndarray: + """Convert input from normalized data to physical units.""" + return x + + def normalize_output(self, x: np.ndarray) -> np.ndarray: + """Convert output from physical units to normalized data.""" + return x + + def denormalize_output(self, x: np.ndarray) -> np.ndarray: + """Convert output from normalized data to physical units.""" + return x + + def info(self) -> dict: + """Get information about the dataset.""" + return {} diff --git a/src/hirad/datasets/dataset.py b/src/hirad/datasets/dataset.py new file mode 100644 index 00000000..2a26630b --- /dev/null +++ b/src/hirad/datasets/dataset.py @@ -0,0 +1,111 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Iterable, Tuple, Union +import copy +import torch + +from hirad.utils.function_utils import InfiniteSampler +from hirad.distributed import DistributedManager + +from . import base, cwb, hrrrmini, gefs_hrrr + + +# this maps all known dataset types to the corresponding init function +known_datasets = { + "cwb": cwb.get_zarr_dataset, + "hrrr_mini": hrrrmini.HRRRMiniDataset, + "gefs_hrrr": gefs_hrrr.HrrrForecastGEFSDataset, +} + + +def init_train_valid_datasets_from_config( + dataset_cfg: dict, + dataloader_cfg: Union[dict, None] = None, + batch_size: int = 1, + seed: int = 0, + validation_dataset_cfg: Union[dict, None] = None, + train_test_split: bool = True, +) -> Tuple[ + base.DownscalingDataset, + Iterable, + Union[base.DownscalingDataset, None], + Union[Iterable, None], +]: + """ + A wrapper function for managing the train-test split for the CWB dataset. + + Parameters: + - dataset_cfg (dict): Configuration for the dataset. + - dataloader_cfg (dict, optional): Configuration for the dataloader. Defaults to None. + - batch_size (int): The number of samples in each batch of data. Defaults to 1. + - seed (int): The random seed for dataset shuffling. Defaults to 0. + - train_test_split (bool): A flag to determine whether to create a validation dataset. Defaults to True. + + Returns: + - Tuple[base.DownscalingDataset, Iterable, Optional[base.DownscalingDataset], Optional[Iterable]]: A tuple containing the training dataset and iterator, and optionally the validation dataset and iterator if train_test_split is True. + """ + + config = copy.deepcopy(dataset_cfg) + (dataset, dataset_iter) = init_dataset_from_config( + config, dataloader_cfg, batch_size=batch_size, seed=seed + ) + if train_test_split: + valid_dataset_cfg = copy.deepcopy(config) + if validation_dataset_cfg: + valid_dataset_cfg.update(validation_dataset_cfg) + (valid_dataset, valid_dataset_iter) = init_dataset_from_config( + valid_dataset_cfg, dataloader_cfg, batch_size=batch_size, seed=seed + ) + else: + valid_dataset = valid_dataset_iter = None + + return dataset, dataset_iter, valid_dataset, valid_dataset_iter + + +def init_dataset_from_config( + dataset_cfg: dict, + dataloader_cfg: Union[dict, None] = None, + batch_size: int = 1, + seed: int = 0, +) -> Tuple[base.DownscalingDataset, Iterable]: + dataset_cfg = copy.deepcopy(dataset_cfg) + dataset_type = dataset_cfg.pop("type", "cwb") + if "train_test_split" in dataset_cfg: + # handled by init_train_valid_datasets_from_config + del dataset_cfg["train_test_split"] + dataset_init_func = known_datasets[dataset_type] + + dataset_obj = dataset_init_func(**dataset_cfg) + if dataloader_cfg is None: + dataloader_cfg = {} + + dist = DistributedManager() + dataset_sampler = InfiniteSampler( + dataset=dataset_obj, rank=dist.rank, num_replicas=dist.world_size, seed=seed + ) + + dataset_iterator = iter( + torch.utils.data.DataLoader( + dataset=dataset_obj, + sampler=dataset_sampler, + batch_size=batch_size, + worker_init_fn=None, + **dataloader_cfg, + ) + ) + + return (dataset_obj, dataset_iterator) From de4e7c55374b45d637754f5bf8be4f6726e762c7 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 23 Apr 2025 16:09:28 +0200 Subject: [PATCH 011/302] adapt checkpoint saving and loading --- src/hirad/utils/checkpoint.py | 51 ++++++++++------------------------- 1 file changed, 14 insertions(+), 37 deletions(-) diff --git a/src/hirad/utils/checkpoint.py b/src/hirad/utils/checkpoint.py index 8ec70faa..5ce194a1 100644 --- a/src/hirad/utils/checkpoint.py +++ b/src/hirad/utils/checkpoint.py @@ -23,11 +23,11 @@ from torch.cuda.amp import GradScaler from torch.optim.lr_scheduler import _LRScheduler -from src.distributed import DistributedManager -from src.utils.console import PythonLogger -from src.utils.capture import _StaticCapture +from hirad.distributed import DistributedManager +from hirad.utils.console import PythonLogger +from hirad.utils.capture import _StaticCapture -optimizer = NewType("optimizer", torch.optim) +optimizer = NewType("optimizer", torch.optim.Optimizer) scheduler = NewType("scheduler", _LRScheduler) scaler = NewType("scaler", GradScaler) @@ -39,7 +39,7 @@ def _get_checkpoint_filename( base_name: str = "checkpoint", index: Union[int, None] = None, saving: bool = False, - model_type: str = "mdlus", + model_type: str = "pt", ) -> str: """Gets the file name /path of checkpoint @@ -91,7 +91,7 @@ def _get_checkpoint_filename( ) # File extension for PhysicsNeMo models or PyTorch models - file_extension = ".mdlus" if model_type == "mdlus" else ".pt" + file_extension = "."+model_type # If epoch is provided load that file if index is not None: @@ -157,8 +157,6 @@ def _unique_model_names( model0 = model0.module # Base name of model is meta.name unless pytorch model base_name = model0.__class__.__name__ - if isinstance(model0, physicsnemo.models.Module): - base_name = model0.meta.name # If we have multiple models of the same name, introduce another index if base_name in model_dict: model_dict[base_name].append(model0) @@ -189,8 +187,8 @@ def save_checkpoint( """Training checkpoint saving utility This will save a training checkpoint in the provided path following the file naming - convention "checkpoint.{model parallel id}.{epoch/index}.mdlus". The load checkpoint - method in PhysicsNeMo core can then be used to read this file. + convention "checkpoint.{model parallel id}.{epoch/index}.pt". The load checkpoint + method can then be used to read this file. Parameters ---------- @@ -224,21 +222,13 @@ def save_checkpoint( models = [models] models = _unique_model_names(models) for name, model in models.items(): - # Get model type - model_type = ( - "mdlus" if isinstance(model, physicsnemo.models.Module) else "pt" - ) - # Get full file path / name file_name = _get_checkpoint_filename( - path, name, index=epoch, saving=True, model_type=model_type + path, name, index=epoch, saving=True, model_type="pt" ) # Save state dictionary - if isinstance(model, physicsnemo.models.Module): - model.save(file_name) - else: - torch.save(model.state_dict(), file_name) + torch.save(model.state_dict(), file_name) checkpoint_logging.success(f"Saved model state dictionary: {file_name}") # == Saving training checkpoint == @@ -251,12 +241,9 @@ def save_checkpoint( if scheduler: checkpoint_dict["scheduler_state_dict"] = scheduler.state_dict() - # Scheduler state dict + # Scaler state dict if scaler: checkpoint_dict["scaler_state_dict"] = scaler.state_dict() - # Static capture is being used, save its grad scaler - if _StaticCapture._amp_scalers: - checkpoint_dict["static_capture_state_dict"] = _StaticCapture.state_dict() # Output file name output_filename = _get_checkpoint_filename( @@ -288,8 +275,7 @@ def load_checkpoint( ) -> int: """Checkpoint loading utility - This loader is designed to be used with the save checkpoint utility in PhysicsNeMo - Launch. Given a path, this method will try to find a checkpoint and load state + This loader is designed to be used with the save checkpoint utility. Given a path, this method will try to find a checkpoint and load state dictionaries into the provided training objects. Parameters @@ -331,9 +317,7 @@ def load_checkpoint( models = _unique_model_names(models) for name, model in models.items(): # Get model type - model_type = ( - "mdlus" if isinstance(model, physicsnemo.models.Module) else "pt" - ) + model_type = "pt" # Get full file path / name file_name = _get_checkpoint_filename( @@ -345,10 +329,7 @@ def load_checkpoint( ) continue # Load state dictionary - if isinstance(model, physicsnemo.models.Module): - model.load(file_name) - else: - model.load_state_dict(torch.load(file_name, map_location=device)) + model.load_state_dict(torch.load(file_name, map_location=device)) checkpoint_logging.success( f"Loaded model state dictionary {file_name} to device {device}" @@ -382,10 +363,6 @@ def load_checkpoint( scaler.load_state_dict(checkpoint_dict["scaler_state_dict"]) checkpoint_logging.success("Loaded grad scaler state dictionary") - if "static_capture_state_dict" in checkpoint_dict: - _StaticCapture.load_state_dict(checkpoint_dict["static_capture_state_dict"]) - checkpoint_logging.success("Loaded static capture state dictionary") - epoch = 0 if "epoch" in checkpoint_dict: epoch = checkpoint_dict["epoch"] From cff9da486d60d77764e2996632ba12d4642fc47f Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 25 Apr 2025 16:51:17 +0200 Subject: [PATCH 012/302] adapt checkpoint loading and saving --- src/hirad/models/__init__.py | 5 +- src/hirad/training/train.py | 105 ++++++++++++++++++++++--------- src/hirad/utils/checkpoint.py | 115 +++++++++++----------------------- 3 files changed, 115 insertions(+), 110 deletions(-) diff --git a/src/hirad/models/__init__.py b/src/hirad/models/__init__.py index 3b494c6c..f17e5cef 100644 --- a/src/hirad/models/__init__.py +++ b/src/hirad/models/__init__.py @@ -1,4 +1,5 @@ from .unet import UNet from .song_unet import SongUNet, SongUNetPosEmbd, SongUNetPosLtEmbd -from .preconditioning import EDMPrecondSR -from .layers import Linear, Conv2d, GroupNorm, AttentionOp, UNetBlock, PositionalEmbedding, FourierEmbedding \ No newline at end of file +from .preconditioning import EDMPrecondSR, EDMPrecond +from .layers import Linear, Conv2d, GroupNorm, AttentionOp, UNetBlock, PositionalEmbedding, FourierEmbedding +from .meta import ModelMetaData \ No newline at end of file diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index a47910b3..d6fe5630 100644 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -4,6 +4,7 @@ import psutil import hydra from omegaconf import DictConfig, OmegaConf +import json import torch from hydra.utils import to_absolute_path from torch.utils.tensorboard import SummaryWriter @@ -14,8 +15,10 @@ from hirad.utils.train_helpers import set_seed, configure_cuda_for_consistent_precision, \ set_patch_shape, compute_num_accumulation_rounds, \ is_time_for_periodic_task, handle_and_clip_gradients +from hirad.utils.checkpoint import load_checkpoint, save_checkpoint from hirad.models import UNet, EDMPrecondSR from hirad.losses import ResLoss, RegressionLoss, RegressionLossCE +from hirad.datasets import init_train_valid_datasets_from_config @hydra.main(version_base=None, config_path="conf", config_name="training") def main(cfg: DictConfig) -> None: @@ -54,20 +57,38 @@ def main(cfg: DictConfig) -> None: set_seed(dist.rank) configure_cuda_for_consistent_precision() - ### Write our own dataloader ### + # Instantiate the dataset + data_loader_kwargs = { + "pin_memory": True, + "num_workers": cfg.training.perf.dataloader_workers, + "prefetch_factor": 2, + } ( - dataset, - dataset_iterator, - validation_dataset, - validation_dataset_iterator - ) = None, None, None, None + dataset, + dataset_iterator, + validation_dataset, + validation_dataset_iterator, + ) = init_train_valid_datasets_from_config( + dataset_cfg, + data_loader_kwargs, + batch_size=cfg.training.hp.batch_size_per_gpu, + seed=0, + validation_dataset_cfg=validation_dataset_cfg, + train_test_split=train_test_split, + ) - dataset_channels = None #len(dataset.input_channels()) - img_in_channels = None #dataset_channels - img_shape = None #dataset.image_shape() - img_out_channels = None #len(dataset.output_channels()) + # Parse image configuration & update model args + dataset_channels = len(dataset.input_channels()) + img_in_channels = dataset_channels + img_shape = dataset.image_shape() + img_out_channels = len(dataset.output_channels()) + if cfg.model.hr_mean_conditioning: + img_in_channels += img_out_channels - prob_channels = None + if cfg.model.name == "lt_aware_ce_regression": + prob_channels = dataset.get_prob_channel_index() + else: + prob_channels = None # Parse the patch shape if ( @@ -181,6 +202,10 @@ def main(cfg: DictConfig) -> None: model.train().requires_grad_(True).to(dist.device) + if not os.path.exists(os.path.join(checkpoint_dir, 'model_args.json')): + with open(os.path.join(checkpoint_dir, 'model_args.json'), 'w') as f: + json.dump(model_args, f) + # Enable distributed data parallel if applicable if dist.world_size > 1: model = DistributedDataParallel( @@ -196,11 +221,38 @@ def main(cfg: DictConfig) -> None: regression_checkpoint_path = to_absolute_path( cfg.training.io.regression_checkpoint_path ) - if not os.path.exists(regression_checkpoint_path): + if not os.path.isdir(regression_checkpoint_path): raise FileNotFoundError( f"Expected this regression checkpoint but not found: {regression_checkpoint_path}" ) - regression_net = torch.nn.Module() #Module.from_checkpoint(regression_checkpoint_path) figure out how to save and load models, also, some basic functions like num_params, device + #regression_net = torch.nn.Module() #TODO Module.from_checkpoint(regression_checkpoint_path) figure out how to save and load models, also, some basic functions like num_params, device + #TODO make regression model loading more robust (model type is both in rergession_checkpoint_path and regression_name) + #TODO add the option to choose epoch to load from / regression_checkpoint_path is now a folder + regression_model_args_path = os.path.join(regression_checkpoint_path, 'model_args.json') + if not os.path.isfile(regression_model_args_path): + raise FileNotFoundError(f"Missing config file at '{regression_model_args_path}'.") + + with open(regression_model_args_path, 'r') as f: + regression_model_args = json.load(f) + + if cfg.model.name == "lt_aware_patched_diffusion": + regression_net = UNet( + img_in_channels=img_in_channels + + model_args["N_grid_channels"] + + model_args["lead_time_channels"], + **regression_model_args, + ) + else: + regression_net = UNet( + img_in_channels=img_in_channels + model_args["N_grid_channels"], + **regression_model_args, + ) + + _ = load_checkpoint( + path=regression_checkpoint_path, + model=regression_net, + device=dist.device + ) regression_net.eval().requires_grad_(False).to(dist.device) logger0.success("Loaded the pre-trained regression model") @@ -248,14 +300,12 @@ def main(cfg: DictConfig) -> None: if dist.world_size > 1: torch.distributed.barrier() try: - cur_nimg = 0 - # Fix loading and saving checkpoint - #load_checkpoint( - # path=checkpoint_dir, - # models=model, - # optimizer=optimizer, - # device=dist.device, - # ) + cur_nimg = load_checkpoint( + path=checkpoint_dir, + model=model, + optimizer=optimizer, + device=dist.device, + ) except: cur_nimg = 0 @@ -445,13 +495,12 @@ def main(cfg: DictConfig) -> None: dist.rank, rank_0_only=True, ): - # figure out how to do save and load checkpoint - #save_checkpoint( - # path=checkpoint_dir, - # models=model, - # optimizer=optimizer, - # epoch=cur_nimg, - # ) + save_checkpoint( + path=checkpoint_dir, + model=model, + optimizer=optimizer, + epoch=cur_nimg, + ) pass # Done. diff --git a/src/hirad/utils/checkpoint.py b/src/hirad/utils/checkpoint.py index 5ce194a1..03b423de 100644 --- a/src/hirad/utils/checkpoint.py +++ b/src/hirad/utils/checkpoint.py @@ -17,7 +17,7 @@ import glob import re from pathlib import Path -from typing import Any, Dict, List, NewType, Optional, Union +from typing import Any, Dict, List, NewType, Optional, Union, Tuple import torch from torch.cuda.amp import GradScaler @@ -25,7 +25,6 @@ from hirad.distributed import DistributedManager from hirad.utils.console import PythonLogger -from hirad.utils.capture import _StaticCapture optimizer = NewType("optimizer", torch.optim.Optimizer) scheduler = NewType("scheduler", _LRScheduler) @@ -133,51 +132,9 @@ def _get_checkpoint_filename( return checkpoint_filename -def _unique_model_names( - models: List[torch.nn.Module], -) -> Dict[str, torch.nn.Module]: - """Util to clean model names and index if repeat names, will also strip DDP wrappers - if they exist. - - Parameters - ---------- - model : List[torch.nn.Module] - List of models to generate names for - - Returns - ------- - Dict[str, torch.nn.Module] - Dictionary of model names and respective modules - """ - # Loop through provided models and set up base names - model_dict = {} - for model0 in models: - if hasattr(model0, "module"): - # Strip out DDP layer - model0 = model0.module - # Base name of model is meta.name unless pytorch model - base_name = model0.__class__.__name__ - # If we have multiple models of the same name, introduce another index - if base_name in model_dict: - model_dict[base_name].append(model0) - else: - model_dict[base_name] = [model0] - - # Set up unique model names if needed - output_dict = {} - for key, model in model_dict.items(): - if len(model) > 1: - for i, model0 in enumerate(model): - output_dict[key + str(i)] = model0 - else: - output_dict[key] = model[0] - - return output_dict - - def save_checkpoint( path: str, - models: Union[torch.nn.Module, List[torch.nn.Module], None] = None, + model: Union[torch.nn.Module, None] = None, optimizer: Union[optimizer, None] = None, scheduler: Union[scheduler, None] = None, scaler: Union[scaler, None] = None, @@ -217,19 +174,20 @@ def save_checkpoint( Path(path).mkdir(parents=True, exist_ok=True) # == Saving model checkpoint == - if models: - if not isinstance(models, list): - models = [models] - models = _unique_model_names(models) - for name, model in models.items(): - # Get full file path / name - file_name = _get_checkpoint_filename( - path, name, index=epoch, saving=True, model_type="pt" - ) + if model: + if hasattr(model, "module"): + # Strip out DDP layer + model = model.module + # Base name of model is meta.name unless pytorch model + name = model.__class__.__name__ + # Get full file path / name + file_name = _get_checkpoint_filename( + path, name, index=epoch, saving=True, model_type="pt" + ) - # Save state dictionary - torch.save(model.state_dict(), file_name) - checkpoint_logging.success(f"Saved model state dictionary: {file_name}") + # Save state dictionary + torch.save(model.state_dict(), file_name) + checkpoint_logging.success(f"Saved model state dictionary: {file_name}") # == Saving training checkpoint == checkpoint_dict = {} @@ -265,7 +223,7 @@ def save_checkpoint( def load_checkpoint( path: str, - models: Union[torch.nn.Module, List[torch.nn.Module], None] = None, + model: torch.nn.Module, optimizer: Union[optimizer, None] = None, scheduler: Union[scheduler, None] = None, scaler: Union[scaler, None] = None, @@ -311,29 +269,26 @@ def load_checkpoint( return 0 # == Loading model checkpoint == - if models: - if not isinstance(models, list): - models = [models] - models = _unique_model_names(models) - for name, model in models.items(): - # Get model type - model_type = "pt" - - # Get full file path / name - file_name = _get_checkpoint_filename( - path, name, index=epoch, model_type=model_type - ) - if not Path(file_name).exists(): - checkpoint_logging.error( - f"Could not find valid model file {file_name}, skipping load" - ) - continue - # Load state dictionary - model.load_state_dict(torch.load(file_name, map_location=device)) + if hasattr(model, "module"): + # Strip out DDP layer + model = model.module + # Base name of model is meta.name unless pytorch model + name = model.__class__.__name__ + # Get full file path / name + file_name = _get_checkpoint_filename( + path, name, index=epoch, + ) + if not Path(file_name).exists(): + checkpoint_logging.error( + f"Could not find valid model file {file_name}, skipping load" + ) + else: + # Load state dictionary + model.load_state_dict(torch.load(file_name, map_location=device)) - checkpoint_logging.success( - f"Loaded model state dictionary {file_name} to device {device}" - ) + checkpoint_logging.success( + f"Loaded model state dictionary {file_name} to device {device}" + ) # == Loading training checkpoint == checkpoint_filename = _get_checkpoint_filename(path, index=epoch, model_type="pt") From 1029d4c7bb16f105e34181810a959797a1f32c8e Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 25 Apr 2025 16:52:27 +0200 Subject: [PATCH 013/302] fix model imports and dependency on module metadata --- src/hirad/models/meta.py | 50 +++++++++++++++++++++++++++++ src/hirad/models/preconditioning.py | 14 ++++---- src/hirad/models/song_unet.py | 6 ++-- src/hirad/models/unet.py | 6 ++-- 4 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 src/hirad/models/meta.py diff --git a/src/hirad/models/meta.py b/src/hirad/models/meta.py new file mode 100644 index 00000000..aab8e453 --- /dev/null +++ b/src/hirad/models/meta.py @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from dataclasses import dataclass + + +@dataclass +class ModelMetaData: + """Data class for storing essential meta data needed for all Hirad Models""" + + # Model info + name: str = "HiradModule" + # Optimization + jit: bool = False + cuda_graphs: bool = False + amp: bool = False + amp_cpu: bool = None + amp_gpu: bool = None + torch_fx: bool = False + # Data type + bf16: bool = False + # Inference + onnx: bool = False + onnx_gpu: bool = None + onnx_cpu: bool = None + onnx_runtime: bool = False + trt: bool = False + # Physics informed + var_dim: int = -1 + func_torch: bool = False + auto_grad: bool = False + + def __post_init__(self): + self.amp_cpu = self.amp if self.amp_cpu is None else self.amp_cpu + self.amp_gpu = self.amp if self.amp_gpu is None else self.amp_gpu + self.onnx_cpu = self.onnx if self.onnx_cpu is None else self.onnx_cpu + self.onnx_gpu = self.onnx if self.onnx_gpu is None else self.onnx_gpu diff --git a/src/hirad/models/preconditioning.py b/src/hirad/models/preconditioning.py index 7b621e29..b0e924e7 100644 --- a/src/hirad/models/preconditioning.py +++ b/src/hirad/models/preconditioning.py @@ -29,11 +29,11 @@ import torch import torch.nn as nn -from src.models import ( +from hirad.models import ( DhariwalUNet, # noqa: F401 for globals SongUNet, # noqa: F401 for globals ) -from physicsnemo.models.meta import ModelMetaData +from hirad.models import ModelMetaData network_module = importlib.import_module("physicsnemo.models.diffusion") @@ -105,7 +105,7 @@ def __init__( model_type: str = "SongUNet", **model_kwargs: dict, ): - super().__init__(meta=VPPrecondMetaData) + super().__init__() #meta=VPPrecondMetaData self.img_resolution = img_resolution self.img_channels = img_channels self.label_dim = label_dim @@ -282,7 +282,7 @@ def __init__( model_type: str = "SongUNet", **model_kwargs: dict, ): - super().__init__(meta=VEPrecondMetaData) + super().__init__() #meta=VEPrecondMetaData self.img_resolution = img_resolution self.img_channels = img_channels self.label_dim = label_dim @@ -414,7 +414,7 @@ def __init__( model_type="DhariwalUNet", **model_kwargs, ): - super().__init__(meta=iDDPMPrecondMetaData) + super().__init__() #meta=iDDPMPrecondMetaData self.img_resolution = img_resolution self.img_channels = img_channels self.label_dim = label_dim @@ -601,7 +601,7 @@ def __init__( img_out_channels=None, **model_kwargs, ): - super().__init__(meta=EDMPrecondMetaData) + super().__init__() #meta=EDMPrecondMetaData self.img_resolution = img_resolution if img_in_channels is not None: img_in_channels = img_in_channels @@ -767,7 +767,7 @@ def __init__( scale_cond_input=True, **model_kwargs, ): - super().__init__(meta=EDMPrecondSRMetaData) + super().__init__() #meta=EDMPrecondSRMetaData self.img_resolution = img_resolution self.img_channels = img_channels # TODO: this is not used, remove it self.img_in_channels = img_in_channels diff --git a/src/hirad/models/song_unet.py b/src/hirad/models/song_unet.py index 68adbdaf..5bfca8a2 100644 --- a/src/hirad/models/song_unet.py +++ b/src/hirad/models/song_unet.py @@ -29,7 +29,7 @@ from torch.utils.checkpoint import checkpoint import torch.nn as nn -from src.models import ( +from hirad.models import ( Conv2d, FourierEmbedding, GroupNorm, @@ -37,7 +37,7 @@ PositionalEmbedding, UNetBlock, ) -from physicsnemo.models.meta import ModelMetaData +from hirad.models import ModelMetaData @dataclass @@ -175,7 +175,7 @@ def __init__( f"Invalid decoder_type: {decoder_type}. Must be one of {valid_decoder_types}." ) - super().__init__(meta=MetaData()) + super().__init__() #meta=MetaData() self.label_dropout = label_dropout self.embedding_type = embedding_type emb_channels = model_channels * channel_mult_emb diff --git a/src/hirad/models/unet.py b/src/hirad/models/unet.py index db8e4f86..1333bdae 100644 --- a/src/hirad/models/unet.py +++ b/src/hirad/models/unet.py @@ -20,7 +20,7 @@ import torch import torch.nn as nn -from physicsnemo.models.meta import ModelMetaData +from hirad.models import ModelMetaData network_module = importlib.import_module("src.models") @@ -92,7 +92,7 @@ def __init__( model_type="SongUNetPosEmbd", **model_kwargs, ): - super().__init__(meta=MetaData) + super().__init__() #meta=MetaData self.img_channels = img_channels @@ -207,7 +207,7 @@ def __init__( model_type="SongUNet", **model_kwargs, ): - super().__init__(meta=MetaData("StormCastUNet")) + super().__init__() #meta=MetaData("StormCastUNet") if isinstance(img_resolution, int): self.img_shape_x = self.img_shape_y = img_resolution From a1daebec9efd89f08604af2323d92eb39b04047d Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 25 Apr 2025 16:53:10 +0200 Subject: [PATCH 014/302] add generate utils --- src/hirad/utils/deterministic_sampler.py | 2 +- src/hirad/utils/generate_utils.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 src/hirad/utils/generate_utils.py diff --git a/src/hirad/utils/deterministic_sampler.py b/src/hirad/utils/deterministic_sampler.py index 4b2f32b4..9fcea1db 100644 --- a/src/hirad/utils/deterministic_sampler.py +++ b/src/hirad/utils/deterministic_sampler.py @@ -19,7 +19,7 @@ import nvtx import torch -from src.models import EDMPrecond +from hirad.models import EDMPrecond # ruff: noqa: E731 diff --git a/src/hirad/utils/generate_utils.py b/src/hirad/utils/generate_utils.py new file mode 100644 index 00000000..29f7eb4b --- /dev/null +++ b/src/hirad/utils/generate_utils.py @@ -0,0 +1,24 @@ +import datetime +from hirad.datasets import init_dataset_from_config +from hirad.utils.function_utils import convert_datetime_to_cftime + + +def get_dataset_and_sampler(dataset_cfg, times, has_lead_time=False): + """ + Get a dataset and sampler for generation. + """ + (dataset, _) = init_dataset_from_config(dataset_cfg, batch_size=1) + if has_lead_time: + plot_times = times + else: + plot_times = [ + convert_datetime_to_cftime( + datetime.datetime.strptime(time, "%Y-%m-%dT%H:%M:%S") + ) + for time in times + ] + all_times = dataset.time() + time_indices = [all_times.index(t) for t in plot_times] + sampler = time_indices + + return dataset, sampler \ No newline at end of file From 81647669d5cc090d59eae5611871b0b645b11ec0 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 25 Apr 2025 16:53:40 +0200 Subject: [PATCH 015/302] add sceleton for era5-cosmo dataset --- src/hirad/datasets/__init__.py | 3 +++ src/hirad/datasets/dataset.py | 6 ++---- src/hirad/datasets/era5_cosmo.py | 5 +++++ 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 src/hirad/datasets/__init__.py create mode 100644 src/hirad/datasets/era5_cosmo.py diff --git a/src/hirad/datasets/__init__.py b/src/hirad/datasets/__init__.py new file mode 100644 index 00000000..706284e6 --- /dev/null +++ b/src/hirad/datasets/__init__.py @@ -0,0 +1,3 @@ +from .dataset import init_train_valid_datasets_from_config, init_dataset_from_config +from .era5_cosmo import ERA5_COSMO +from .base import DownscalingDataset \ No newline at end of file diff --git a/src/hirad/datasets/dataset.py b/src/hirad/datasets/dataset.py index 2a26630b..1928e4d4 100644 --- a/src/hirad/datasets/dataset.py +++ b/src/hirad/datasets/dataset.py @@ -21,14 +21,12 @@ from hirad.utils.function_utils import InfiniteSampler from hirad.distributed import DistributedManager -from . import base, cwb, hrrrmini, gefs_hrrr +from hirad.datasets import ERA5_COSMO # this maps all known dataset types to the corresponding init function known_datasets = { - "cwb": cwb.get_zarr_dataset, - "hrrr_mini": hrrrmini.HRRRMiniDataset, - "gefs_hrrr": gefs_hrrr.HrrrForecastGEFSDataset, + "era5_cosmo": ERA5_COSMO, } diff --git a/src/hirad/datasets/era5_cosmo.py b/src/hirad/datasets/era5_cosmo.py new file mode 100644 index 00000000..2ec94be7 --- /dev/null +++ b/src/hirad/datasets/era5_cosmo.py @@ -0,0 +1,5 @@ +from hirad.datasets.base import DownscalingDataset, ChannelMetadata + +class ERA5_COSMO(DownscalingDataset): + def __init__(self): + super().__init__() \ No newline at end of file From a9f70349f5b07c4c436da53fa239e1dbb5295e59 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 25 Apr 2025 17:25:02 +0200 Subject: [PATCH 016/302] add in_channels to arg saving list --- src/hirad/training/train.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index d6fe5630..0539aadb 100644 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -180,6 +180,7 @@ def main(cfg: DictConfig) -> None: img_in_channels=img_in_channels + model_args["N_grid_channels"], **model_args, ) + model_args["image_in_channels"] = img_in_channels + model_args["N_grid_channels"] elif cfg.model.name == "lt_aware_ce_regression": model = UNet( img_in_channels=img_in_channels @@ -187,6 +188,7 @@ def main(cfg: DictConfig) -> None: + model_args["lead_time_channels"], **model_args, ) + model_args["image_in_channels"] = img_in_channels + model_args["N_grid_channels"] + model_args["lead_time_channels"] elif cfg.model.name == "lt_aware_patched_diffusion": model = EDMPrecondSR( img_in_channels=img_in_channels @@ -194,15 +196,17 @@ def main(cfg: DictConfig) -> None: + model_args["lead_time_channels"], **model_args, ) + model_args["image_in_channels"] = img_in_channels + model_args["N_grid_channels"] + model_args["lead_time_channels"] else: # diffusion or patched diffusion model = EDMPrecondSR( img_in_channels=img_in_channels + model_args["N_grid_channels"], **model_args, ) - + model_args["image_in_channels"] = img_in_channels + model_args["N_grid_channels"] + model.train().requires_grad_(True).to(dist.device) - if not os.path.exists(os.path.join(checkpoint_dir, 'model_args.json')): + if dist.rank==0 and not os.path.exists(os.path.join(checkpoint_dir, 'model_args.json')): with open(os.path.join(checkpoint_dir, 'model_args.json'), 'w') as f: json.dump(model_args, f) @@ -235,18 +239,7 @@ def main(cfg: DictConfig) -> None: with open(regression_model_args_path, 'r') as f: regression_model_args = json.load(f) - if cfg.model.name == "lt_aware_patched_diffusion": - regression_net = UNet( - img_in_channels=img_in_channels - + model_args["N_grid_channels"] - + model_args["lead_time_channels"], - **regression_model_args, - ) - else: - regression_net = UNet( - img_in_channels=img_in_channels + model_args["N_grid_channels"], - **regression_model_args, - ) + regression_net = UNet(**regression_model_args) _ = load_checkpoint( path=regression_checkpoint_path, From e0059c83ab8ad7f30d8c10ecc46b744811fe10c8 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 28 Apr 2025 13:47:39 +0200 Subject: [PATCH 017/302] add dataset era5_cosmo --- src/hirad/datasets/dataset.py | 9 +-- src/hirad/datasets/era5_cosmo.py | 95 +++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 7 deletions(-) diff --git a/src/hirad/datasets/dataset.py b/src/hirad/datasets/dataset.py index 1928e4d4..6e402d99 100644 --- a/src/hirad/datasets/dataset.py +++ b/src/hirad/datasets/dataset.py @@ -21,7 +21,8 @@ from hirad.utils.function_utils import InfiniteSampler from hirad.distributed import DistributedManager -from hirad.datasets import ERA5_COSMO +from .era5_cosmo import ERA5_COSMO +from .base import DownscalingDataset # this maps all known dataset types to the corresponding init function @@ -38,9 +39,9 @@ def init_train_valid_datasets_from_config( validation_dataset_cfg: Union[dict, None] = None, train_test_split: bool = True, ) -> Tuple[ - base.DownscalingDataset, + DownscalingDataset, Iterable, - Union[base.DownscalingDataset, None], + Union[DownscalingDataset, None], Union[Iterable, None], ]: """ @@ -79,7 +80,7 @@ def init_dataset_from_config( dataloader_cfg: Union[dict, None] = None, batch_size: int = 1, seed: int = 0, -) -> Tuple[base.DownscalingDataset, Iterable]: +) -> Tuple[DownscalingDataset, Iterable]: dataset_cfg = copy.deepcopy(dataset_cfg) dataset_type = dataset_cfg.pop("type", "cwb") if "train_test_split" in dataset_cfg: diff --git a/src/hirad/datasets/era5_cosmo.py b/src/hirad/datasets/era5_cosmo.py index 2ec94be7..597bdfca 100644 --- a/src/hirad/datasets/era5_cosmo.py +++ b/src/hirad/datasets/era5_cosmo.py @@ -1,5 +1,94 @@ -from hirad.datasets.base import DownscalingDataset, ChannelMetadata +from .base import DownscalingDataset, ChannelMetadata +import os +import numpy as np +import torch +from typing import List, Tuple +import yaml class ERA5_COSMO(DownscalingDataset): - def __init__(self): - super().__init__() \ No newline at end of file + def __init__(self, dataset_path: str): + super().__init__() + + #TODO switch hanbdling paths to Path rather than pure strings + self._dataset_path = dataset_path + self._era5_path = os.path.join(dataset_path, 'era-interpolated') + self._cosmo_path = os.path.join(dataset_path, 'cosmo') + self._info_path = os.path.join(dataset_path, 'info') + + # load file list (each file is one date-time state) + self._file_list = os.listdir(self._cosmo_path) + + # Load cosmo info and channel names + with open(os.path.join(self._info_path,'cosmo.yaml'), 'r') as file: + self._cosmo_info = yaml.safe_load(file) + self._cosmo_channels = [ChannelMetadata(name) for name in self._cosmo_info['select']] + + # Load era5 info and channel names + with open(os.path.join(self._info_path,'era.yaml'), 'r') as file: + self._era_info = yaml.safe_load(file) + self._era_channels = [ChannelMetadata(name) if len(name.split('_'))==1 + else ChannelMetadata(name.split('_')[0],name.split('_')[1]) + for name in self._era_info['select']] + + # Load stats for normalizing channels of input and output + + cosmo_stats = torch.load(os.path.join(self._info_path,'cosmo-stats'), weights_only=False) + print(cosmo_stats) + + + def __len__(self): + return len(self._file_list) + + + def longitude(self) -> np.ndarray: + """Get longitude values from the dataset.""" + lon_lat = torch.load(os.path.join(self._info_path,'cosmo-lat-lon'), weights_only=False) + return lon_lat[:,0] + + + def latitude(self) -> np.ndarray: + """Get latitude values from the dataset.""" + lon_lat = torch.load(os.path.join(self._info_path,'cosmo-lat-lon'), weights_only=False) + return lon_lat[:,1] + + + def input_channels(self) -> List[ChannelMetadata]: + """Metadata for the input channels. A list of ChannelMetadata, one for each channel""" + return self._era_channels + + + def output_channels(self) -> List[ChannelMetadata]: + """Metadata for the output channels. A list of ChannelMetadata, one for each channel""" + return self._cosmo_channels + + + def time(self) -> List: + """Get time values from the dataset.""" + #TODO Choose the time format and convert to that, currently it's a string from a filename + return [file.split('.')[0] for file in self._file_list] + + + def image_shape(self) -> Tuple[int, int]: + """Get the (height, width) of the data (same for input and output).""" + #TODO load from info, I hardcode it for now + return 390,582 + + + def normalize_input(self, x: np.ndarray) -> np.ndarray: + """Convert input from physical units to normalized data.""" + return (x - self.input_mean) / self.input_std + + + def denormalize_input(self, x: np.ndarray) -> np.ndarray: + """Convert input from normalized data to physical units.""" + return x * self.input_std + self.input_mean + + + def normalize_output(self, x: np.ndarray) -> np.ndarray: + """Convert output from physical units to normalized data.""" + return (x - self.output_mean) / self.output_std + + + def denormalize_output(self, x: np.ndarray) -> np.ndarray: + """Convert output from normalized data to physical units.""" + return x * self.output_std + self.output_mean \ No newline at end of file From da4cb6ccc9abc901128ea1db2d401387ff0374e1 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 28 Apr 2025 13:48:06 +0200 Subject: [PATCH 018/302] fix imports --- src/hirad/distributed/manager.py | 2 +- src/hirad/inference/generate.py | 159 ++++++++++++++++++++++++++++ src/hirad/models/layers.py | 2 +- src/hirad/models/preconditioning.py | 4 +- src/hirad/models/song_unet.py | 4 +- src/hirad/models/unet.py | 2 +- src/hirad/utils/checkpoint.py | 2 +- src/hirad/utils/generate_utils.py | 2 +- src/hirad/utils/inference_utils.py | 2 +- 9 files changed, 169 insertions(+), 10 deletions(-) create mode 100644 src/hirad/inference/generate.py diff --git a/src/hirad/distributed/manager.py b/src/hirad/distributed/manager.py index e80ce138..647d0544 100644 --- a/src/hirad/distributed/manager.py +++ b/src/hirad/distributed/manager.py @@ -25,7 +25,7 @@ import torch import torch.distributed as dist -from hirad.distributed.config import ProcessGroupConfig, ProcessGroupNode +from .config import ProcessGroupConfig, ProcessGroupNode warnings.simplefilter("default", DeprecationWarning) diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py new file mode 100644 index 00000000..6e5273af --- /dev/null +++ b/src/hirad/inference/generate.py @@ -0,0 +1,159 @@ +import hydra +import os +import json +from omegaconf import OmegaConf, DictConfig +import torch +import torch._dynamo +import nvtx +import numpy as np +from hirad.distributed import DistributedManager +from hirad.utils.console import PythonLogger, RankZeroLoggingWrapper +from concurrent.futures import ThreadPoolExecutor +from functools import partial +from einops import rearrange +from torch.distributed import gather + + +from hydra.utils import to_absolute_path +from hirad.models import EDMPrecond, UNet +from hirad.utils.stochastic_sampler import stochastic_sampler +from hirad.utils.deterministic_sampler import deterministic_sampler +from hirad.utils.inference_utils import ( + get_time_from_range, + regression_step, + diffusion_step, +) +from hirad.utils.checkpoint import load_checkpoint + + +from hirad.utils.generate_utils import ( + get_dataset_and_sampler +) + +from hirad.utils.train_helpers import set_patch_shape + + +@hydra.main(version_base="1.2", config_path="conf", config_name="config_generate") +def main(cfg: DictConfig) -> None: + """Generate random dowscaled atmospheric states using the techniques described in the paper + "Elucidating the Design Space of Diffusion-Based Generative Models". + """ + + # Initialize distributed manager + DistributedManager.initialize() + dist = DistributedManager() + device = dist.device + + # Initialize logger + logger = PythonLogger("generate") # General python logger + logger0 = RankZeroLoggingWrapper(logger, dist) + logger.file_logging("generate.log") + + # Handle the batch size + seeds = list(np.arange(cfg.generation.num_ensembles)) + num_batches = ( + (len(seeds) - 1) // (cfg.generation.seed_batch_size * dist.world_size) + 1 + ) * dist.world_size + all_batches = torch.as_tensor(seeds).tensor_split(num_batches) + rank_batches = all_batches[dist.rank :: dist.world_size] + + # Synchronize + if dist.world_size > 1: + torch.distributed.barrier() + + # Parse the inference input times + if cfg.generation.times_range and cfg.generation.times: + raise ValueError("Either times_range or times must be provided, but not both") + if cfg.generation.times_range: + times = get_time_from_range(cfg.generation.times_range) #TODO check what time formats we are using and adapt + else: + times = cfg.generation.times + + # Create dataset object + dataset_cfg = OmegaConf.to_container(cfg.dataset) + if "has_lead_time" in cfg.generation: + has_lead_time = cfg.generation["has_lead_time"] + else: + has_lead_time = False + dataset, sampler = get_dataset_and_sampler( + dataset_cfg=dataset_cfg, times=times, has_lead_time=has_lead_time + ) + img_shape = dataset.image_shape() + img_out_channels = len(dataset.output_channels()) + + # Parse the patch shape + if hasattr(cfg.generation, "patch_shape_x"): # TODO better config handling + patch_shape_x = cfg.generation.patch_shape_x + else: + patch_shape_x = None + if hasattr(cfg.generation, "patch_shape_y"): + patch_shape_y = cfg.generation.patch_shape_y + else: + patch_shape_y = None + patch_shape = (patch_shape_y, patch_shape_x) + img_shape, patch_shape = set_patch_shape(img_shape, patch_shape) + if patch_shape != img_shape: + logger0.info("Patch-based training enabled") + else: + logger0.info("Patch-based training disabled") + + # Parse the inference mode + if cfg.generation.inference_mode == "regression": + load_net_reg, load_net_res = True, False + elif cfg.generation.inference_mode == "diffusion": + load_net_reg, load_net_res = False, True + elif cfg.generation.inference_mode == "all": + load_net_reg, load_net_res = True, True + else: + raise ValueError(f"Invalid inference mode {cfg.generation.inference_mode}") + + # Load diffusion network, move to device, change precision + if load_net_res: + res_ckpt_path = cfg.generation.io.res_ckpt_path + logger0.info(f'Loading residual network from "{res_ckpt_path}"...') + + diffusion_model_args_path = os.path.join(res_ckpt_path, 'model_args.json') + if not os.path.isfile(diffusion_model_args_path): + raise FileNotFoundError(f"Missing config file at '{diffusion_model_args_path}'.") + with open(diffusion_model_args_path, 'r') as f: + diffusion_model_args = json.load(f) + + net_res = EDMPrecond(**diffusion_model_args) + + _ = load_checkpoint( + path=res_ckpt_path, + model=net_res, + device=dist.device + ) + + net_res = net_res.eval().to(device).to(memory_format=torch.channels_last) + if cfg.generation.perf.force_fp16: + net_res.use_fp16 = True + else: + net_res = None + + # load regression network, move to device, change precision + if load_net_reg: + reg_ckpt_path = cfg.generation.io.reg_ckpt_path + logger0.info(f'Loading network from "{reg_ckpt_path}"...') + + + regression_model_args_path = os.path.join(reg_ckpt_path, 'model_args.json') + if not os.path.isfile(regression_model_args_path): + raise FileNotFoundError(f"Missing config file at '{regression_model_args_path}'.") + with open(regression_model_args_path, 'r') as f: + regression_model_args = json.load(f) + + net_reg = EDMPrecond(**regression_model_args) + + _ = load_checkpoint( + path=reg_ckpt_path, + model=net_reg, + device=dist.device + ) + + net_reg = net_reg.eval().to(device).to(memory_format=torch.channels_last) + if cfg.generation.perf.force_fp16: + net_reg.use_fp16 = True + else: + net_reg = None \ No newline at end of file diff --git a/src/hirad/models/layers.py b/src/hirad/models/layers.py index d5a1ab29..ddb23b68 100644 --- a/src/hirad/models/layers.py +++ b/src/hirad/models/layers.py @@ -26,7 +26,7 @@ from einops import rearrange from torch.nn.functional import silu -from src.utils.model_utils import weight_init +from hirad.utils.model_utils import weight_init class Linear(torch.nn.Module): diff --git a/src/hirad/models/preconditioning.py b/src/hirad/models/preconditioning.py index b0e924e7..9c100048 100644 --- a/src/hirad/models/preconditioning.py +++ b/src/hirad/models/preconditioning.py @@ -29,11 +29,11 @@ import torch import torch.nn as nn -from hirad.models import ( +from .song_unet import ( DhariwalUNet, # noqa: F401 for globals SongUNet, # noqa: F401 for globals ) -from hirad.models import ModelMetaData +from .meta import ModelMetaData network_module = importlib.import_module("physicsnemo.models.diffusion") diff --git a/src/hirad/models/song_unet.py b/src/hirad/models/song_unet.py index 5bfca8a2..6267dfcc 100644 --- a/src/hirad/models/song_unet.py +++ b/src/hirad/models/song_unet.py @@ -29,7 +29,7 @@ from torch.utils.checkpoint import checkpoint import torch.nn as nn -from hirad.models import ( +from .layers import ( Conv2d, FourierEmbedding, GroupNorm, @@ -37,7 +37,7 @@ PositionalEmbedding, UNetBlock, ) -from hirad.models import ModelMetaData +from .meta import ModelMetaData @dataclass diff --git a/src/hirad/models/unet.py b/src/hirad/models/unet.py index 1333bdae..d81a734e 100644 --- a/src/hirad/models/unet.py +++ b/src/hirad/models/unet.py @@ -20,7 +20,7 @@ import torch import torch.nn as nn -from hirad.models import ModelMetaData +from .meta import ModelMetaData network_module = importlib.import_module("src.models") diff --git a/src/hirad/utils/checkpoint.py b/src/hirad/utils/checkpoint.py index 03b423de..e0f8d58d 100644 --- a/src/hirad/utils/checkpoint.py +++ b/src/hirad/utils/checkpoint.py @@ -24,7 +24,7 @@ from torch.optim.lr_scheduler import _LRScheduler from hirad.distributed import DistributedManager -from hirad.utils.console import PythonLogger +from .console import PythonLogger optimizer = NewType("optimizer", torch.optim.Optimizer) scheduler = NewType("scheduler", _LRScheduler) diff --git a/src/hirad/utils/generate_utils.py b/src/hirad/utils/generate_utils.py index 29f7eb4b..b99852fd 100644 --- a/src/hirad/utils/generate_utils.py +++ b/src/hirad/utils/generate_utils.py @@ -1,6 +1,6 @@ import datetime from hirad.datasets import init_dataset_from_config -from hirad.utils.function_utils import convert_datetime_to_cftime +from .function_utils import convert_datetime_to_cftime def get_dataset_and_sampler(dataset_cfg, times, has_lead_time=False): diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index 842bdd37..b158ec0c 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -21,7 +21,7 @@ import torch import tqdm -from src.utils.function_utils import StackedRandomGenerator, time_range +from .function_utils import StackedRandomGenerator, time_range ############################################################################ # CorrDiff Generation Utilities # From c284d0aa948bca579290fc9abe1ebb75212e3ad4 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 29 Apr 2025 14:51:43 +0200 Subject: [PATCH 019/302] add getitem to dataset --- src/hirad/datasets/era5_cosmo.py | 40 +++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/src/hirad/datasets/era5_cosmo.py b/src/hirad/datasets/era5_cosmo.py index 597bdfca..4d9187d9 100644 --- a/src/hirad/datasets/era5_cosmo.py +++ b/src/hirad/datasets/era5_cosmo.py @@ -33,8 +33,28 @@ def __init__(self, dataset_path: str): # Load stats for normalizing channels of input and output cosmo_stats = torch.load(os.path.join(self._info_path,'cosmo-stats'), weights_only=False) - print(cosmo_stats) - + self.output_mean = cosmo_stats['mean'] + self.output_std = cosmo_stats['stdev'] + + era_stats = torch.load(os.path.join(self._info_path,'era-stats'), weights_only=False) + #TODO Switch from cosmo to era stats once era-interpolated has all channels + self.input_mean = cosmo_stats['mean'] + self.input_std = cosmo_stats['stdev'] + + + def __getitem__(self, idx): + # get era5 data point + era5_data = torch.load(os.path.join(self._era5_path,self._file_list[idx]), weights_only=False)\ + .squeeze()\ + .reshape(-1,*self.image_shape()) + era5_data = self.normalize_input(era5_data) + # get cosmo data point + cosmo_data = torch.load(os.path.join(self._cosmo_path,self._file_list[idx]), weights_only=False)\ + .squeeze()\ + .reshape(-1,*self.image_shape()) + cosmo_data = self.normalize_output(cosmo_data) + # return samples + return cosmo_data, era5_data, 0 def __len__(self): return len(self._file_list) @@ -70,25 +90,29 @@ def time(self) -> List: def image_shape(self) -> Tuple[int, int]: """Get the (height, width) of the data (same for input and output).""" - #TODO load from info, I hardcode it for now - return 390,582 + #TODO load from info, I hardcode it for now (cosmo from anemoi-datasets minus trim-edge=20) + return 350,542 def normalize_input(self, x: np.ndarray) -> np.ndarray: """Convert input from physical units to normalized data.""" - return (x - self.input_mean) / self.input_std + return (x - self.input_mean.reshape((self.input_mean.shape[0],1,1))) \ + / self.input_std.reshape((self.input_std.shape[0],1,1)) def denormalize_input(self, x: np.ndarray) -> np.ndarray: """Convert input from normalized data to physical units.""" - return x * self.input_std + self.input_mean + return x * self.input_std.reshape((self.input_std.shape[0],1,1)) \ + + self.input_mean.reshape((self.input_mean.shape[0],1,1)) def normalize_output(self, x: np.ndarray) -> np.ndarray: """Convert output from physical units to normalized data.""" - return (x - self.output_mean) / self.output_std + return (x - self.output_mean.reshape((self.output_mean.shape[0],1,1))) \ + / self.output_std.reshape((self.output_std.shape[0],1,1)) def denormalize_output(self, x: np.ndarray) -> np.ndarray: """Convert output from normalized data to physical units.""" - return x * self.output_std + self.output_mean \ No newline at end of file + return x * self.output_std.reshape((self.output_std.shape[0],1,1)) \ + + self.output_mean.reshape((self.output_mean.shape[0],1,1)) \ No newline at end of file From e7d5b1b6324a8affba89bc216074395ca7b8af25 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 29 Apr 2025 15:11:52 +0200 Subject: [PATCH 020/302] add grid flip to start at top left corner --- src/hirad/datasets/era5_cosmo.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/hirad/datasets/era5_cosmo.py b/src/hirad/datasets/era5_cosmo.py index 4d9187d9..89a0581a 100644 --- a/src/hirad/datasets/era5_cosmo.py +++ b/src/hirad/datasets/era5_cosmo.py @@ -44,14 +44,19 @@ def __init__(self, dataset_path: str): def __getitem__(self, idx): # get era5 data point - era5_data = torch.load(os.path.join(self._era5_path,self._file_list[idx]), weights_only=False)\ - .squeeze()\ - .reshape(-1,*self.image_shape()) + # squeeze the ensemble dimesnsion + # reshape to image_shape + # flip so that it starts in top-left corner (by default it is bottom left) + era5_data = np.flip(torch.load(os.path.join(self._era5_path,self._file_list[idx]), weights_only=False)\ + .squeeze() \ + .reshape(-1,*self.image_shape()), + 1) era5_data = self.normalize_input(era5_data) # get cosmo data point - cosmo_data = torch.load(os.path.join(self._cosmo_path,self._file_list[idx]), weights_only=False)\ - .squeeze()\ - .reshape(-1,*self.image_shape()) + cosmo_data = np.flip(torch.load(os.path.join(self._cosmo_path,self._file_list[idx]), weights_only=False)\ + .squeeze() \ + .reshape(-1,*self.image_shape()), + 1) cosmo_data = self.normalize_output(cosmo_data) # return samples return cosmo_data, era5_data, 0 From d093e662e23f079f891accbd656b258fa7e5b8ac Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 29 Apr 2025 15:26:51 +0200 Subject: [PATCH 021/302] small fix --- src/hirad/datasets/dataset.py | 2 +- src/hirad/datasets/era5_cosmo.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/hirad/datasets/dataset.py b/src/hirad/datasets/dataset.py index 6e402d99..6cc6165b 100644 --- a/src/hirad/datasets/dataset.py +++ b/src/hirad/datasets/dataset.py @@ -82,7 +82,7 @@ def init_dataset_from_config( seed: int = 0, ) -> Tuple[DownscalingDataset, Iterable]: dataset_cfg = copy.deepcopy(dataset_cfg) - dataset_type = dataset_cfg.pop("type", "cwb") + dataset_type = dataset_cfg.pop("type", "era5_cosmo") if "train_test_split" in dataset_cfg: # handled by init_train_valid_datasets_from_config del dataset_cfg["train_test_split"] diff --git a/src/hirad/datasets/era5_cosmo.py b/src/hirad/datasets/era5_cosmo.py index 89a0581a..e7de4568 100644 --- a/src/hirad/datasets/era5_cosmo.py +++ b/src/hirad/datasets/era5_cosmo.py @@ -13,7 +13,7 @@ def __init__(self, dataset_path: str): self._dataset_path = dataset_path self._era5_path = os.path.join(dataset_path, 'era-interpolated') self._cosmo_path = os.path.join(dataset_path, 'cosmo') - self._info_path = os.path.join(dataset_path, 'info') + self._info_path = os.path.join(dataset_path, 'old/info') # load file list (each file is one date-time state) self._file_list = os.listdir(self._cosmo_path) @@ -37,9 +37,8 @@ def __init__(self, dataset_path: str): self.output_std = cosmo_stats['stdev'] era_stats = torch.load(os.path.join(self._info_path,'era-stats'), weights_only=False) - #TODO Switch from cosmo to era stats once era-interpolated has all channels - self.input_mean = cosmo_stats['mean'] - self.input_std = cosmo_stats['stdev'] + self.input_mean = era_stats['mean'] + self.input_std = era_stats['stdev'] def __getitem__(self, idx): @@ -59,7 +58,7 @@ def __getitem__(self, idx): 1) cosmo_data = self.normalize_output(cosmo_data) # return samples - return cosmo_data, era5_data, 0 + return torch.tensor(cosmo_data), torch.tensor(era5_data), 0 def __len__(self): return len(self._file_list) From 7e695f0305f6190aa4484c020926cdafb62d64e1 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 7 May 2025 15:19:32 +0200 Subject: [PATCH 022/302] update everything for training --- src/hirad/conf/dataset/era_cosmo.yaml | 2 + src/hirad/conf/model/era_cosmo_diffusion.yaml | 5 + .../conf/model/era_cosmo_regression.yaml | 2 + src/hirad/conf/training.yaml | 0 .../conf/training/era_cosmo_diffusion.yaml | 41 +++ .../conf/training/era_cosmo_regression.yaml | 38 +++ .../conf/training_era_cosmo_diffusion.yaml | 19 ++ .../conf/training_era_cosmo_regression.yaml | 19 ++ src/hirad/datasets/era5_cosmo.py | 13 +- src/hirad/distributed/config.py | 2 +- src/hirad/distributed/manager.py | 34 ++- src/hirad/models/__init__.py | 7 +- src/hirad/models/dhariwal_unet.py | 259 ++++++++++++++++++ src/hirad/models/preconditioning.py | 6 +- src/hirad/models/unet.py | 2 +- src/hirad/testrun.sh | 41 +++ src/hirad/training/train.py | 37 +-- 17 files changed, 485 insertions(+), 42 deletions(-) create mode 100644 src/hirad/conf/dataset/era_cosmo.yaml create mode 100644 src/hirad/conf/model/era_cosmo_diffusion.yaml create mode 100644 src/hirad/conf/model/era_cosmo_regression.yaml delete mode 100644 src/hirad/conf/training.yaml create mode 100644 src/hirad/conf/training/era_cosmo_diffusion.yaml create mode 100644 src/hirad/conf/training/era_cosmo_regression.yaml create mode 100644 src/hirad/conf/training_era_cosmo_diffusion.yaml create mode 100644 src/hirad/conf/training_era_cosmo_regression.yaml create mode 100644 src/hirad/models/dhariwal_unet.py create mode 100644 src/hirad/testrun.sh diff --git a/src/hirad/conf/dataset/era_cosmo.yaml b/src/hirad/conf/dataset/era_cosmo.yaml new file mode 100644 index 00000000..854b7753 --- /dev/null +++ b/src/hirad/conf/dataset/era_cosmo.yaml @@ -0,0 +1,2 @@ +type: era5_cosmo +dataset_path: /store_new/mch/msopr/hirad-gen/basic-torch \ No newline at end of file diff --git a/src/hirad/conf/model/era_cosmo_diffusion.yaml b/src/hirad/conf/model/era_cosmo_diffusion.yaml new file mode 100644 index 00000000..06aa2a47 --- /dev/null +++ b/src/hirad/conf/model/era_cosmo_diffusion.yaml @@ -0,0 +1,5 @@ +name: diffusion + # Name of the preconditioner +hr_mean_conditioning: True + # High-res mean (regression's output) as additional condition +scale_cond_input: False \ No newline at end of file diff --git a/src/hirad/conf/model/era_cosmo_regression.yaml b/src/hirad/conf/model/era_cosmo_regression.yaml new file mode 100644 index 00000000..487eb4b4 --- /dev/null +++ b/src/hirad/conf/model/era_cosmo_regression.yaml @@ -0,0 +1,2 @@ +name: regression +hr_mean_conditioning: False \ No newline at end of file diff --git a/src/hirad/conf/training.yaml b/src/hirad/conf/training.yaml deleted file mode 100644 index e69de29b..00000000 diff --git a/src/hirad/conf/training/era_cosmo_diffusion.yaml b/src/hirad/conf/training/era_cosmo_diffusion.yaml new file mode 100644 index 00000000..b61603a6 --- /dev/null +++ b/src/hirad/conf/training/era_cosmo_diffusion.yaml @@ -0,0 +1,41 @@ +# Hyperparameters +hp: + training_duration: 128 + # Training duration based on the number of processed samples + total_batch_size: 16 + # Total batch size + batch_size_per_gpu: "auto" + # Batch size per GPU + lr: 0.0002 + # Learning rate + grad_clip_threshold: null + # no gradient clipping for defualt non-patch-based training + lr_decay: 1 + # LR decay rate + lr_rampup: 0 + # Rampup for learning rate, in number of samples + +# Performance +perf: + fp_optimizations: amp-bf16 + # Floating point mode, one of ["fp32", "fp16", "amp-fp16", "amp-bf16"] + # "amp-{fp16,bf16}" activates Automatic Mixed Precision (AMP) with {float16,bfloat16} + dataloader_workers: 4 + # DataLoader worker processes + songunet_checkpoint_level: 0 # 0 means no checkpointing + # Gradient checkpointing level, value is number of layers to checkpoint + +# I/O +io: + regression_checkpoint_path: /scratch/mch/pstamenk/output/regression/checkpoints_regression + # Where to load the regression checkpoint + print_progress_freq: 32 + # How often to print progress + save_checkpoint_freq: 5000 + # How often to save the checkpoints, measured in number of processed samples + validation_freq: 5000 + # how often to record the validation loss, measured in number of processed samples + validation_steps: 10 + # how many loss evaluations are used to compute the validation loss per checkpoint + # how many loss evaluations are used to compute the validation loss per checkpoint + checkpoint_dir: /scratch/mch/pstamenk/output/diffusion \ No newline at end of file diff --git a/src/hirad/conf/training/era_cosmo_regression.yaml b/src/hirad/conf/training/era_cosmo_regression.yaml new file mode 100644 index 00000000..7c443f00 --- /dev/null +++ b/src/hirad/conf/training/era_cosmo_regression.yaml @@ -0,0 +1,38 @@ +# Hyperparameters +hp: + training_duration: 16 + # Training duration based on the number of processed samples + total_batch_size: 16 + # Total batch size + batch_size_per_gpu: "auto" + # Batch size per GPU + lr: 0.0002 + # Learning rate + grad_clip_threshold: null + # no gradient clipping for defualt non-patch-based training + lr_decay: 1 + # LR decay rate + lr_rampup: 0 + # Rampup for learning rate, in number of samples + +# Performance +perf: + fp_optimizations: amp-bf16 + # Floating point mode, one of ["fp32", "fp16", "amp-fp16", "amp-bf16"] + # "amp-{fp16,bf16}" activates Automatic Mixed Precision (AMP) with {float16,bfloat16} + dataloader_workers: 4 + # DataLoader worker processes + songunet_checkpoint_level: 0 # 0 means no checkpointing + # Gradient checkpointing level, value is number of layers to checkpoint + +# I/O +io: + print_progress_freq: 32 + # How often to print progress + save_checkpoint_freq: 5000 + # How often to save the checkpoints, measured in number of processed samples + validation_freq: 5000 + # how often to record the validation loss, measured in number of processed samples + validation_steps: 10 + # how many loss evaluations are used to compute the validation loss per checkpoint + checkpoint_dir: /scratch/mch/pstamenk/output/regression \ No newline at end of file diff --git a/src/hirad/conf/training_era_cosmo_diffusion.yaml b/src/hirad/conf/training_era_cosmo_diffusion.yaml new file mode 100644 index 00000000..7ee7dba5 --- /dev/null +++ b/src/hirad/conf/training_era_cosmo_diffusion.yaml @@ -0,0 +1,19 @@ +hydra: + job: + chdir: true + name: diffusion + run: + dir: /scratch/mch/pstamenk/output/${hydra:job.name} + +# Get defaults +defaults: + - _self_ + + # Dataset + - dataset/era_cosmo + + # Model + - model/era_cosmo_diffusion + + # Training + - training/era_cosmo_diffusion \ No newline at end of file diff --git a/src/hirad/conf/training_era_cosmo_regression.yaml b/src/hirad/conf/training_era_cosmo_regression.yaml new file mode 100644 index 00000000..d857d12a --- /dev/null +++ b/src/hirad/conf/training_era_cosmo_regression.yaml @@ -0,0 +1,19 @@ +hydra: + job: + chdir: true + name: regression + run: + dir: /scratch/mch/pstamenk/output/${hydra:job.name} + +# Get defaults +defaults: + - _self_ + + # Dataset + - dataset/era_cosmo + + # Model + - model/era_cosmo_regression + + # Training + - training/era_cosmo_regression \ No newline at end of file diff --git a/src/hirad/datasets/era5_cosmo.py b/src/hirad/datasets/era5_cosmo.py index e7de4568..8b0d60f5 100644 --- a/src/hirad/datasets/era5_cosmo.py +++ b/src/hirad/datasets/era5_cosmo.py @@ -4,6 +4,7 @@ import torch from typing import List, Tuple import yaml +import torch.nn.functional as F class ERA5_COSMO(DownscalingDataset): def __init__(self, dataset_path: str): @@ -42,23 +43,27 @@ def __init__(self, dataset_path: str): def __getitem__(self, idx): + """Get cosmo and era5 interpolated to cosmo grid""" # get era5 data point # squeeze the ensemble dimesnsion # reshape to image_shape # flip so that it starts in top-left corner (by default it is bottom left) + orig_shape = [350,542] #TODO currently padding to be divisible by 16 era5_data = np.flip(torch.load(os.path.join(self._era5_path,self._file_list[idx]), weights_only=False)\ .squeeze() \ - .reshape(-1,*self.image_shape()), + .reshape(-1,*orig_shape), 1) era5_data = self.normalize_input(era5_data) # get cosmo data point cosmo_data = np.flip(torch.load(os.path.join(self._cosmo_path,self._file_list[idx]), weights_only=False)\ .squeeze() \ - .reshape(-1,*self.image_shape()), + .reshape(-1,*orig_shape), 1) cosmo_data = self.normalize_output(cosmo_data) # return samples - return torch.tensor(cosmo_data), torch.tensor(era5_data), 0 + return F.pad(torch.tensor(cosmo_data), pad=(1,1,1,1), mode='constant', value=0), \ + F.pad(torch.tensor(era5_data), pad=(1,1,1,1), mode='constant', value=0), \ + 0 def __len__(self): return len(self._file_list) @@ -95,7 +100,7 @@ def time(self) -> List: def image_shape(self) -> Tuple[int, int]: """Get the (height, width) of the data (same for input and output).""" #TODO load from info, I hardcode it for now (cosmo from anemoi-datasets minus trim-edge=20) - return 350,542 + return 352,544 #TODO 350,542 is orig size, UNet requires dimenions divisible by 16, for now, I just add zeros to orig images def normalize_input(self, x: np.ndarray) -> np.ndarray: diff --git a/src/hirad/distributed/config.py b/src/hirad/distributed/config.py index c5414b4a..2808d926 100644 --- a/src/hirad/distributed/config.py +++ b/src/hirad/distributed/config.py @@ -84,7 +84,7 @@ class ProcessGroupConfig: Examples -------- - >>> from physicsnemo.distributed import ProcessGroupNode, ProcessGroupConfig + >>> from hirad.distributed import ProcessGroupNode, ProcessGroupConfig >>> >>> # Create world group that contains all processes that are part of this job >>> world = ProcessGroupNode("world") diff --git a/src/hirad/distributed/manager.py b/src/hirad/distributed/manager.py index 647d0544..eca46c63 100644 --- a/src/hirad/distributed/manager.py +++ b/src/hirad/distributed/manager.py @@ -348,7 +348,7 @@ def initialize_slurm(port): rank = int(os.environ.get("SLURM_PROCID")) world_size = int(os.environ.get("SLURM_NPROCS")) local_rank = int(os.environ.get("SLURM_LOCALID")) - addr = os.environ.get("SLURM_LAUNCH_NODE_IPADDR") + addr = os.environ.get("MASTER_ADDR") DistributedManager.setup( rank=rank, @@ -388,6 +388,7 @@ def initialize(): port = os.getenv("MASTER_PORT", "12355") # https://pytorch.org/docs/master/notes/cuda.html#id5 # was changed in version 2.2 + #TODO why is setting this important? if torch.__version__ < (2, 2): os.environ["NCCL_ASYNC_ERROR_HANDLING"] = "0" else: @@ -542,22 +543,25 @@ def setup( f"cuda:{manager.local_rank}" if torch.cuda.is_available() else "cpu" ) + #TODO device_id makes the init hang, couldn't figure out why if manager._distributed: # Setup distributed process group - try: - dist.init_process_group( - backend, - rank=manager.rank, - world_size=manager.world_size, - device_id=manager.device, - ) - except TypeError: - # device_id only introduced in PyTorch 2.3 - dist.init_process_group( - backend, - rank=manager.rank, - world_size=manager.world_size, - ) + # try: + dist.init_process_group( + backend, + rank=manager.rank, + world_size=manager.world_size, + ) + # rank=manager.rank, + # world_size=manager.world_size, + # device_id=manager.device, + # except TypeError: + # # device_id only introduced in PyTorch 2.3 + # dist.init_process_group( + # backend, + # rank=manager.rank, + # world_size=manager.world_size, + # ) if torch.cuda.is_available(): # Set device for this process and empty cache to optimize memory usage diff --git a/src/hirad/models/__init__.py b/src/hirad/models/__init__.py index f17e5cef..3ab4a6f3 100644 --- a/src/hirad/models/__init__.py +++ b/src/hirad/models/__init__.py @@ -1,5 +1,6 @@ -from .unet import UNet +from .layers import Linear, Conv2d, GroupNorm, AttentionOp, UNetBlock, PositionalEmbedding, FourierEmbedding +from .meta import ModelMetaData from .song_unet import SongUNet, SongUNetPosEmbd, SongUNetPosLtEmbd +from .dhariwal_unet import DhariwalUNet +from .unet import UNet from .preconditioning import EDMPrecondSR, EDMPrecond -from .layers import Linear, Conv2d, GroupNorm, AttentionOp, UNetBlock, PositionalEmbedding, FourierEmbedding -from .meta import ModelMetaData \ No newline at end of file diff --git a/src/hirad/models/dhariwal_unet.py b/src/hirad/models/dhariwal_unet.py new file mode 100644 index 00000000..3880cd0a --- /dev/null +++ b/src/hirad/models/dhariwal_unet.py @@ -0,0 +1,259 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Model architectures used in the paper "Elucidating the Design Space of +Diffusion-Based Generative Models". +""" + +from dataclasses import dataclass +from typing import List + +import numpy as np +import torch +from torch.nn.functional import silu +import torch.nn as nn + +from .layers import ( + Conv2d, + GroupNorm, + Linear, + PositionalEmbedding, + UNetBlock, +) +from .meta import ModelMetaData + + +@dataclass +class MetaData(ModelMetaData): + name: str = "DhariwalUNet" + # Optimization + jit: bool = False + cuda_graphs: bool = False + amp_cpu: bool = False + amp_gpu: bool = True + torch_fx: bool = False + # Data type + bf16: bool = True + # Inference + onnx: bool = False + # Physics informed + func_torch: bool = False + auto_grad: bool = False + + +class DhariwalUNet(nn.Module): + """ + Reimplementation of the ADM architecture, a U-Net variant, with optional + self-attention. + + This model supports conditional and unconditional setups, as well as several + options for various internal architectural choices such as encoder and decoder + type, embedding type, etc., making it flexible and adaptable to different tasks + and configurations. + + Parameters + ----------- + img_resolution : int + The resolution of the input/output image. + in_channels : int + Number of channels in the input image. + out_channels : int + Number of channels in the output image. + label_dim : int, optional + Number of class labels; 0 indicates an unconditional model. By default 0. + augment_dim : int, optional + Dimensionality of augmentation labels; 0 means no augmentation. By default 0. + model_channels : int, optional + Base multiplier for the number of channels across the network, by default 192. + channel_mult : List[int], optional + Per-resolution multipliers for the number of channels. By default [1,2,3,4]. + channel_mult_emb : int, optional + Multiplier for the dimensionality of the embedding vector. By default 4. + num_blocks : int, optional + Number of residual blocks per resolution. By default 3. + attn_resolutions : List[int], optional + Resolutions at which self-attention layers are applied. By default [32, 16, 8]. + dropout : float, optional + Dropout probability applied to intermediate activations. By default 0.10. + label_dropout : float, optional + Dropout probability of class labels for classifier-free guidance. By default 0.0. + + Reference + ---------- + Reference: Dhariwal, P. and Nichol, A., 2021. Diffusion models beat gans on image + synthesis. Advances in neural information processing systems, 34, pp.8780-8794. + + Note + ----- + Equivalent to the original implementation by Dhariwal and Nichol, available at + https://github.com/openai/guided-diffusion + + Example + -------- + >>> model = DhariwalUNet(img_resolution=16, in_channels=2, out_channels=2) + >>> noise_labels = torch.randn([1]) + >>> class_labels = torch.randint(0, 1, (1, 1)) + >>> input_image = torch.ones([1, 2, 16, 16]) + >>> output_image = model(input_image, noise_labels, class_labels) + """ + + def __init__( + self, + img_resolution: int, + in_channels: int, + out_channels: int, + label_dim: int = 0, + augment_dim: int = 0, + model_channels: int = 192, + channel_mult: List[int] = [1, 2, 3, 4], + channel_mult_emb: int = 4, + num_blocks: int = 3, + attn_resolutions: List[int] = [32, 16, 8], + dropout: float = 0.10, + label_dropout: float = 0.0, + ): + super().__init__(meta=MetaData()) + self.label_dropout = label_dropout + emb_channels = model_channels * channel_mult_emb + init = dict( + init_mode="kaiming_uniform", + init_weight=np.sqrt(1 / 3), + init_bias=np.sqrt(1 / 3), + ) + init_zero = dict(init_mode="kaiming_uniform", init_weight=0, init_bias=0) + block_kwargs = dict( + emb_channels=emb_channels, + channels_per_head=64, + dropout=dropout, + init=init, + init_zero=init_zero, + ) + + # Mapping. + self.map_noise = PositionalEmbedding(num_channels=model_channels) + self.map_augment = ( + Linear( + in_features=augment_dim, + out_features=model_channels, + bias=False, + **init_zero, + ) + if augment_dim + else None + ) + self.map_layer0 = Linear( + in_features=model_channels, out_features=emb_channels, **init + ) + self.map_layer1 = Linear( + in_features=emb_channels, out_features=emb_channels, **init + ) + self.map_label = ( + Linear( + in_features=label_dim, + out_features=emb_channels, + bias=False, + init_mode="kaiming_normal", + init_weight=np.sqrt(label_dim), + ) + if label_dim + else None + ) + + # Encoder. + self.enc = torch.nn.ModuleDict() + cout = in_channels + for level, mult in enumerate(channel_mult): + res = img_resolution >> level + if level == 0: + cin = cout + cout = model_channels * mult + self.enc[f"{res}x{res}_conv"] = Conv2d( + in_channels=cin, out_channels=cout, kernel=3, **init + ) + else: + self.enc[f"{res}x{res}_down"] = UNetBlock( + in_channels=cout, out_channels=cout, down=True, **block_kwargs + ) + for idx in range(num_blocks): + cin = cout + cout = model_channels * mult + self.enc[f"{res}x{res}_block{idx}"] = UNetBlock( + in_channels=cin, + out_channels=cout, + attention=(res in attn_resolutions), + **block_kwargs, + ) + skips = [block.out_channels for block in self.enc.values()] + + # Decoder. + self.dec = torch.nn.ModuleDict() + for level, mult in reversed(list(enumerate(channel_mult))): + res = img_resolution >> level + if level == len(channel_mult) - 1: + self.dec[f"{res}x{res}_in0"] = UNetBlock( + in_channels=cout, out_channels=cout, attention=True, **block_kwargs + ) + self.dec[f"{res}x{res}_in1"] = UNetBlock( + in_channels=cout, out_channels=cout, **block_kwargs + ) + else: + self.dec[f"{res}x{res}_up"] = UNetBlock( + in_channels=cout, out_channels=cout, up=True, **block_kwargs + ) + for idx in range(num_blocks + 1): + cin = cout + skips.pop() + cout = model_channels * mult + self.dec[f"{res}x{res}_block{idx}"] = UNetBlock( + in_channels=cin, + out_channels=cout, + attention=(res in attn_resolutions), + **block_kwargs, + ) + self.out_norm = GroupNorm(num_channels=cout) + self.out_conv = Conv2d( + in_channels=cout, out_channels=out_channels, kernel=3, **init_zero + ) + + def forward(self, x, noise_labels, class_labels, augment_labels=None): + # Mapping. + emb = self.map_noise(noise_labels) + if self.map_augment is not None and augment_labels is not None: + emb = emb + self.map_augment(augment_labels) + emb = silu(self.map_layer0(emb)) + emb = self.map_layer1(emb) + if self.map_label is not None: + tmp = class_labels + if self.training and self.label_dropout: + tmp = tmp * ( + torch.rand([x.shape[0], 1], device=x.device) >= self.label_dropout + ).to(tmp.dtype) + emb = emb + self.map_label(tmp) + emb = silu(emb) + + # Encoder. + skips = [] + for block in self.enc.values(): + x = block(x, emb) if isinstance(block, UNetBlock) else block(x) + skips.append(x) + + # Decoder. + for block in self.dec.values(): + if x.shape[1] != block.in_channels: + x = torch.cat([x, skips.pop()], dim=1) + x = block(x, emb) + x = self.out_conv(silu(self.out_norm(x))) + return x diff --git a/src/hirad/models/preconditioning.py b/src/hirad/models/preconditioning.py index 9c100048..c66b6b60 100644 --- a/src/hirad/models/preconditioning.py +++ b/src/hirad/models/preconditioning.py @@ -30,12 +30,14 @@ import torch.nn as nn from .song_unet import ( - DhariwalUNet, # noqa: F401 for globals SongUNet, # noqa: F401 for globals ) +from .dhariwal_unet import ( + DhariwalUNet, # noqa: F401 for globals +) from .meta import ModelMetaData -network_module = importlib.import_module("physicsnemo.models.diffusion") +network_module = importlib.import_module("hirad.models") @dataclass diff --git a/src/hirad/models/unet.py b/src/hirad/models/unet.py index d81a734e..10079ec2 100644 --- a/src/hirad/models/unet.py +++ b/src/hirad/models/unet.py @@ -22,7 +22,7 @@ from .meta import ModelMetaData -network_module = importlib.import_module("src.models") +network_module = importlib.import_module("hirad.models") @dataclass diff --git a/src/hirad/testrun.sh b/src/hirad/testrun.sh new file mode 100644 index 00000000..ee4a9776 --- /dev/null +++ b/src/hirad/testrun.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +#SBATCH --job-name="testrun" + +### HARDWARE ### +#SBATCH --partition=debug +#SBATCH --nodes=1 +#SBATCH --gres=gpu:4 +#SBATCH --ntasks-per-node=4 +#SBATCH --cpus-per-task=16 +#SBATCH --mem=16G +#SBATCH --time=00:30:00 +#SBATCH --no-requeue +#SBATCH --exclusive + +### OUTPUT ### +#SBATCH --output=/scratch/mch/pstamenk/logs/regression_test.log +#SBATCH --error=/scratch/mch/pstamenk/logs/regression_test.err + +# Choose method to initialize dist in pythorch +export DISTRIBUTED_INITIALIZATION_METHOD=ENV + +# Get number of physical cores using Python +PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") +# Use SLURM_NTASKS (number of processes to be launched by torchrun) +LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} +# Compute threads per process +OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) +export OMP_NUM_THREADS=$OMP_THREADS +echo "Node: $(hostname)" +echo "Physical cores: $PHYSICAL_CORES" +echo "Local processes: $LOCAL_PROCS" +echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" + +# activate conda env +CONDA_ENV=train +source /users/pstamenk/.bashrc +mamba activate $CONDA_ENV + +# python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml +torchrun --nproc-per-node=$LOCAL_PROCS src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml \ No newline at end of file diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 0539aadb..88b21182 100644 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -9,6 +9,7 @@ from hydra.utils import to_absolute_path from torch.utils.tensorboard import SummaryWriter from torch.nn.parallel import DistributedDataParallel +from torchinfo import summary from hirad.distributed import DistributedManager from hirad.utils.console import PythonLogger, RankZeroLoggingWrapper @@ -20,9 +21,10 @@ from hirad.losses import ResLoss, RegressionLoss, RegressionLossCE from hirad.datasets import init_train_valid_datasets_from_config -@hydra.main(version_base=None, config_path="conf", config_name="training") +from matplotlib import pyplot as plt + +@hydra.main(version_base=None, config_path="../conf", config_name="training") def main(cfg: DictConfig) -> None: - # Initialize distributed environment for training DistributedManager.initialize() dist = DistributedManager() @@ -45,10 +47,12 @@ def main(cfg: DictConfig) -> None: fp16 = fp_optimizations == "fp16" enable_amp = fp_optimizations.startswith("amp") amp_dtype = torch.float16 if (fp_optimizations == "amp-fp16") else torch.bfloat16 - logger.info(f"Saving the outputs in {os.getcwd()}") + logger0.info(f"Saving the outputs in {os.getcwd()}") checkpoint_dir = os.path.join( cfg.training.io.get("checkpoint_dir", "."), f"checkpoints_{cfg.model.name}" ) + if dist.rank==0 and not os.path.exists(checkpoint_dir): + os.makedirs(checkpoint_dir) # added creating checkpoint dir if cfg.training.hp.batch_size_per_gpu == "auto": cfg.training.hp.batch_size_per_gpu = ( cfg.training.hp.total_batch_size // dist.world_size @@ -85,12 +89,14 @@ def main(cfg: DictConfig) -> None: if cfg.model.hr_mean_conditioning: img_in_channels += img_out_channels + if cfg.model.name == "lt_aware_ce_regression": - prob_channels = dataset.get_prob_channel_index() + prob_channels = dataset.get_prob_channel_index() #TODO figure out what prob_channel are and update dataloader else: prob_channels = None # Parse the patch shape + #TODO figure out patched diffusion and how to use it if ( cfg.model.name == "patched_diffusion" or cfg.model.name == "lt_aware_patched_diffusion" @@ -109,9 +115,8 @@ def main(cfg: DictConfig) -> None: # interpolate global channel if patch-based model is used if img_shape[1] != patch_shape[1]: img_in_channels += dataset_channels - - # Instantiate the model and move to device. + # Instantiate the model and move to device. if cfg.model.name not in ( "regression", "lt_aware_ce_regression", @@ -180,7 +185,7 @@ def main(cfg: DictConfig) -> None: img_in_channels=img_in_channels + model_args["N_grid_channels"], **model_args, ) - model_args["image_in_channels"] = img_in_channels + model_args["N_grid_channels"] + model_args["img_in_channels"] = img_in_channels + model_args["N_grid_channels"] elif cfg.model.name == "lt_aware_ce_regression": model = UNet( img_in_channels=img_in_channels @@ -188,7 +193,7 @@ def main(cfg: DictConfig) -> None: + model_args["lead_time_channels"], **model_args, ) - model_args["image_in_channels"] = img_in_channels + model_args["N_grid_channels"] + model_args["lead_time_channels"] + model_args["img_in_channels"] = img_in_channels + model_args["N_grid_channels"] + model_args["lead_time_channels"] elif cfg.model.name == "lt_aware_patched_diffusion": model = EDMPrecondSR( img_in_channels=img_in_channels @@ -196,18 +201,21 @@ def main(cfg: DictConfig) -> None: + model_args["lead_time_channels"], **model_args, ) - model_args["image_in_channels"] = img_in_channels + model_args["N_grid_channels"] + model_args["lead_time_channels"] + model_args["img_in_channels"] = img_in_channels + model_args["N_grid_channels"] + model_args["lead_time_channels"] else: # diffusion or patched diffusion model = EDMPrecondSR( img_in_channels=img_in_channels + model_args["N_grid_channels"], **model_args, ) - model_args["image_in_channels"] = img_in_channels + model_args["N_grid_channels"] + model_args["img_in_channels"] = img_in_channels + model_args["N_grid_channels"] model.train().requires_grad_(True).to(dist.device) + # TODO write summry from rank=0 possibly + # summary(model, input_size=[(4,img_out_channels,*img_shape),(4,img_in_channels,*img_shape),(4,1),(4,1)]) + if dist.rank==0 and not os.path.exists(os.path.join(checkpoint_dir, 'model_args.json')): - with open(os.path.join(checkpoint_dir, 'model_args.json'), 'w') as f: + with open(os.path.join(checkpoint_dir, f'model_args.json'), 'w') as f: json.dump(model_args, f) # Enable distributed data parallel if applicable @@ -220,7 +228,7 @@ def main(cfg: DictConfig) -> None: find_unused_parameters=dist.find_unused_parameters, ) - # Load the regression checkpoint if applicable + # Load the regression checkpoint if applicable #TODO test when training correction if hasattr(cfg.training.io, "regression_checkpoint_path"): regression_checkpoint_path = to_absolute_path( cfg.training.io.regression_checkpoint_path @@ -286,8 +294,7 @@ def main(cfg: DictConfig) -> None: dist.world_size, ) batch_size_per_gpu = cfg.training.hp.batch_size_per_gpu - logger0.info(f"Using {num_accumulation_rounds} gradient accumulation rounds") - + logger0.info(f"Using {num_accumulation_rounds} gradient accumulation {"rounds" if num_accumulation_rounds>1 else "round"}.") ## Resume training from previous checkpoints if exists if dist.world_size > 1: @@ -313,7 +320,6 @@ def main(cfg: DictConfig) -> None: average_loss_running_mean = 0 n_average_loss_running_mean = 1 - while not done: tick_start_nimg = cur_nimg tick_start_time = time.time() @@ -494,7 +500,6 @@ def main(cfg: DictConfig) -> None: optimizer=optimizer, epoch=cur_nimg, ) - pass # Done. logger0.info("Training Completed.") From 7510cf1ad7f40c0794d881d06277de793bbdde94 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 7 May 2025 15:20:07 +0200 Subject: [PATCH 023/302] small fix --- src/hirad/utils/checkpoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hirad/utils/checkpoint.py b/src/hirad/utils/checkpoint.py index e0f8d58d..a346b16f 100644 --- a/src/hirad/utils/checkpoint.py +++ b/src/hirad/utils/checkpoint.py @@ -294,7 +294,7 @@ def load_checkpoint( checkpoint_filename = _get_checkpoint_filename(path, index=epoch, model_type="pt") if not Path(checkpoint_filename).is_file(): checkpoint_logging.warning( - "Could not find valid checkpoint file, skipping load" + f"Could not find valid checkpoint file {checkpoint_filename} skipping load" ) return 0 From 75db04fb5b550787fac0cea394d34e090127ae77 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 7 May 2025 15:27:18 +0200 Subject: [PATCH 024/302] remove tracked .pyc files --- .../models/__pycache__/__init__.cpython-312.pyc | Bin 185 -> 0 bytes .../models/__pycache__/dummy.cpython-312.pyc | Bin 451 -> 0 bytes .../models/__pycache__/unet.cpython-312.pyc | Bin 8966 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/hirad/models/__pycache__/__init__.cpython-312.pyc delete mode 100644 src/hirad/models/__pycache__/dummy.cpython-312.pyc delete mode 100644 src/hirad/models/__pycache__/unet.cpython-312.pyc diff --git a/src/hirad/models/__pycache__/__init__.cpython-312.pyc b/src/hirad/models/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 70d5748263686e7ee01a00c9c9ffd61e4261990b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 185 zcmX@j%ge<81eP(8=gQRm475F^(AnLQ) zQDjD9>o~pA)a3YQB1#<>S}gTyk;VCqxJWL~XOnrH3tdDayH#g=oRqaZdZNZE447`7==k|mj}EK82PsyZ3&%#yR( zo!Q)(l}KR)vxSQ;+q#mP6r@`haex4oDiu*4g2+$KQ(yWbg(|!mH~@j92#mZ?pxh>Y zNzS?RvrBqy2Tp*#_zF6E&;2>~@7#0GcZUBJk4G3t8zaA+{guElzd*)K0VlI@7c$F? z%*bq!$>OiC$d-IrA8VKSvwm8~mG~@A%lySaDVPnijE_;6Ledr3_?5Yvtj%S^iqFR! zWn}(MMh+;!n|{0erqehZp|wG%4Jmw~t0BY9xjnKm+A<6+BVW)mzQCrVAEAKhG;4)r z$&_+M$uNM4PwBE!oKj3_5=t*c43f)~u*k@{1LChUOJ?Jrfmw!CW?7lZ`p&=}W&H}L z@Urh&sFD3ySQBuZ!-XivJ6xD@0m=ol5z2*}x+vwsPF;+05vMLrxv0Y>C>L|MHp<13 zTS%natdP4Fi#s!|m=>o=CFO|aFQ}#!%~fPcoF!6u&ai?~sVwHomD6@^wp_77rcQEm zVt(G@X7VEkEKb+7c`K5yXgQHqSz)PS>L@{;PA9F9D57*xv?8JiyH+V;J|>Fasz^n< zhJ;{ZmftXm)h3FPrs<|+s=8)~A_-#^H>2xCf?7$UNMcChND@fekf279b|5#H4WP^? zgToa=A;xgoFkyV{{P2tFsqx97qlz{R2RLjW;BZA#%uIQKpkhQ*i@+!Autvv;O zAA=ri?I;CuU2EHW7mr`%R=M7K*UrUbHS<2Vlk~U)Z_*_mN-=G`2;^^?v?$h;iL^ED>V%E33oSzeCFp*PuVKnBgv25Zbbn+?6fq@yGV;-vrJ7$zb>Ix_^P zFMI>ex~vc(uM=THCuCC1=Q9!7;G{%g?@ZW6$__OW<<-)xNEAaaR`9rlF+tT#T7kk( z&nZGvrJff|C{eX?#T?cvCj1C$Nt#tMo~Ya$oITvCErNvUN@)h>2NH665hm%jYxGn# z(JkE4xzU%7{FxG9Mqez#nRcsLfUb%8^2mWP0d;jy(95_bQqkLv!}Ch6Vk&eKDw?d2 z;)1Hp3RDdR$rO&9962DAiIP(d==l7gFtZ@YN?xiIp-w6q&ORCHY)KMJsx~I%i@IdC z7->q?)KaA+7?pBaC#E6`nywm(P*g4`FtaAP`|KTbWJ>d&BU76H17*@#4fLy8zCYb8 z9u4Ye%Rf9{R&qF1xU8Bi5gFEgL8Vpz&mA?#BoZtPwhSe0S;o`$NOUtrbD^w^fgsXi zZzj;B3uty+v0Kk#ljQ!%IfZ+{f8d`rsS-7af^r5Z- zR>w5xwZi#t9SE)%RgP0iULlH>Q?}kQS~~@MA!+KMFqIh;4naHpqH10m6sCcl(B~9w zP3~7Iq)g|iCtz)r-EUYVpZlX zu&*XqK|9@IZ#td_%T#?;Ms#0t#>D4`rYdWC@^9j7Ad$8zJI_LU*DGQmk0aBhZL6}f z56%0g7Od=d%Dk3UKeWI%kftgJTw`1wIB(=?061Ki-7*LqzJ)dy0uCQVn#?{g_lTRT zhTWN8j{2BtROYKJjLltw232@X-tku>-)H{;SLFF;3rspNeS@_)cm^bT3?JcM-SAt9 z&DP(FHW^_n)@)BnH}puiTRd1Kg`h_O7ukfz9rw+Op&ZwGTM;mS8XAF|g69qX2v!?& zQdtq_o0$cx4QrYQO=3awz*fjL%U0MkoL*J}x5X|+i(&<5VTC66APH~@;0~X6ycLV5 z_dOlAI72Ds$zw2Sl17q2GK^#&k`W}MNcIDfc$2WdANfKCzgWuLf!Ao-3@i$5B0D04%E2&p`M2U zCf0RT`+n|zbZ>q1g}bpAYLoY4y({K_?;2j)HFRCNw`(6QsW&rgyE53jw=V3%-@dhO zp~n4vyxy}1eiH7_j>mv&`>?C;9qoEywd-Il`m>a<&hTB4`p$H{yZ`Rqv3uQP^`7*D zAe60#n6{k{!jP+RZ$+rjlXG1hSOtU1Pp|75T&-oegn!v%I#-fm3eI)IhHe7I_2qps zd&}pkG+Sj0%`%;)jf-FKuBj`9Vcd&<87{(??_Kb&YeTZ^ms$LG(=jub;gT1;i`-~O zo~&|48s_d@S!H+R#?KrNbrEeT3nto;`6f;c$mfXt|Dl}F=rIam$0loB}q4D8Fk zrupIR*^=Cu7`+VxDF>^8e*q}jS}xRt|CM)F|5q%u`tn*9s-hbnnI?N+UVtq9vReA+ zcaSU&Vb=|x#mnfvSOIi@zy!u@+fbBdz%Tx!$8(d=p`a}VQx|3w;gbmXE#Ohl>Zb5X z7`$x5R3teASdX_snLj6)me16!Aa&68k6J-+&e5#e#_JGF$l~bOc8ie%xk$kDqu&ZJ z5x^rl%KJyjL1<#j^B)1z?w=Ir4ZKIL^%JP|gZb(NJT3WBj9Ng4TY!c}KbVL`W-Z!& zH`@1MGPNAO7G4=y$*(+ledK;}s5ZV9i(i>sn*2^`x$j!vYHU}HtwXEOwa`1M@Akdj zx0>w#AUS+5Iego9d;Ipd?(AMoK3n7015A5rIsT9F?-g#p{NvYt_}Y)p{qWpR@;`n0 zrH6hd-f?B>Z>N6Q-h~p^gJ5{qV@z95O}iypLdLR0I@x%i@*cMrjFgqSuk75p_?0gW1T+P4U!NikSjx8NqN&PhT z$SwYZf$@6-((E`kfO$S-6{gdBgAPOsq2?79_TH zT*)uxzjJ2!+_iJ7iAQVxdRs@$F}cl#6nz~0zcB~|{G%^0x9$Z1^<|~w&^Y3r(}b>F zPzbng!YsIjnm|ulxPaO)V5Ct$CBalQ16)G%@)QHSV(QpC7!&1e!I@aJ1!`iEEqP2h zqk*$|mg=!kR1I)wsg7q((^1ZOGs8Zo;i7zcMmc-VO*1&9m=&VgE;#mchWBJOkKh%$ z1GHS83oyz=zo5zpZSV?s8)JL6C?V}5N8t=0ZeobGBq+fPL{0-S@X!S3GDL|EFhZn~ zE+y%OXewF3=AA&mDd=UAsVi?Ry|GeRZF~G~=yCey-e8Zw-9)2Luo*!EZUseA)^iY( z;pvW2cZ0l!)n~Cf?)WD;rC1cj8!Y)clp3flFWBx0y0J8_fbNiiDXTH==@p7_7(oygZ0j?dgo&wryl1c8`Eqb-}%4KCHN=o2nb5a!GABgMs@=&)0c>V zgzX3jjeP{s=vwht;1Ej_V6<@KBnz~ko}?WGx!4p1@fvH@KnHsL)L< zzY42-6;}BwtkM)qbMBiRG9VbuB?yJBkQ*+@H9PBQb|~<83M|@|I6>5j6S$TL;`le6 zR}x@{lVceZWzYmM0uRNzu*8cIcv1jedk#5`5xDVyMhjNvJ{32>xI|;5KS{*E-09BT z#C@Wv8ggfT8NRYqBXR(tR@E=_5FH3R(E$v&(*)6h;53qvq4=Xd-X_MwjE80P5Hg;)ch zT_-hz>@LsnZ$jJu2|bK|2ZCt3yMeafePi{JXDHCvksJPjM@SS0+emk zx0uysjzZZ)V=On!(1L zuktOWa1M}d%&yfJPG^w-WYJX61b3AKxEgFN7a{H@Aj|&|8W5-kz^4jJZ-Ek>s%V0Q zAzy@PLKy?>j#uJcC-7Ki!D*pEV(M-+dEVm?MIWP1kb5QPg@I;wJB{8Del7wifw-3n z*wg{Wc2K1-u=(=~^re;HqZuYfra?x&Ebbo_)Vy$-RFsS`X@6ufu*&(+;t4+`!H1JA zvuohoG2vL=nHokA1w_vR_Yy2b`XI5QOL04{;JfVGk?XsE(0`-<&hGaI-W#|dn|Q$U zfW`xe%Y&G6;+7PbC;kXr4st#lmy2(0hp&fR?E|BkSG_)Z&qVc&GZ3acgjU|3XVtSBlpjXfy_K|^Fi zRQuz9M$;@G{NG-lTQJm|p(!QZ`GbfI{Y-#X+KSuKoG7~ee52!I1*Yg<0Z`0n`wr+Q z*fYV9ofUc(0_5;_0?!c*SbV08IUwtPmSs2gFl_tJnccr&Vn1h6|HF)|wGUkF`JjE^ zUi-lH-qrR4iwQVBU-Y3b!S>cJZ!qw);fI2CEO@wQo@Jk?jb1spbnqd36=$D#c)>rz TvYl5CEg!pf>|+M=bSA$CZ1ZH& From 4b5ecb4f7cfa0048c3d253ed50b9ae6537848245 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 9 May 2025 14:21:47 +0200 Subject: [PATCH 025/302] add small loggign changes --- src/hirad/training/train.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) mode change 100644 => 100755 src/hirad/training/train.py diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py old mode 100644 new mode 100755 index 88b21182..9ce619de --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -80,6 +80,7 @@ def main(cfg: DictConfig) -> None: validation_dataset_cfg=validation_dataset_cfg, train_test_split=train_test_split, ) + logger0.info(f"Training on dataset with size {len(dataset)}") # Parse image configuration & update model args dataset_channels = len(dataset.input_channels()) @@ -295,7 +296,7 @@ def main(cfg: DictConfig) -> None: ) batch_size_per_gpu = cfg.training.hp.batch_size_per_gpu logger0.info(f"Using {num_accumulation_rounds} gradient accumulation {"rounds" if num_accumulation_rounds>1 else "round"}.") - + logger0.info(f"Batch size per gpu: {batch_size_per_gpu}") ## Resume training from previous checkpoints if exists if dist.world_size > 1: torch.distributed.barrier() From 3eb4d0a7b15af18b42ae647a2f08a1c5b45b5b8e Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 9 May 2025 14:22:55 +0200 Subject: [PATCH 026/302] adapt sbatch script to slurm config --- src/hirad/testrun.sh | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/hirad/testrun.sh b/src/hirad/testrun.sh index ee4a9776..ac631c81 100644 --- a/src/hirad/testrun.sh +++ b/src/hirad/testrun.sh @@ -5,20 +5,33 @@ ### HARDWARE ### #SBATCH --partition=debug #SBATCH --nodes=1 -#SBATCH --gres=gpu:4 -#SBATCH --ntasks-per-node=4 -#SBATCH --cpus-per-task=16 -#SBATCH --mem=16G +#SBATCH --ntasks-per-node=1 +#SBATCH --gpus-per-node=1 +#SBATCH --cpus-per-task=72 #SBATCH --time=00:30:00 #SBATCH --no-requeue #SBATCH --exclusive ### OUTPUT ### -#SBATCH --output=/scratch/mch/pstamenk/logs/regression_test.log -#SBATCH --error=/scratch/mch/pstamenk/logs/regression_test.err +#SBATCH --output=/iopsstor/scratch/cscs/pstamenk/logs/regression_test.log +#SBATCH --error=/iopsstor/scratch/cscs/pstamenk/logs/regression_test.err + +### ENVIRONMENT #### +#SBATCH --uenv=pytorch/v2.6.0:/user-environment +#SBATCH --view=default +#SBATCH -A a-a01 # Choose method to initialize dist in pythorch -export DISTRIBUTED_INITIALIZATION_METHOD=ENV +export DISTRIBUTED_INITIALIZATION_METHOD=SLURM + +MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" +echo "Master node : $MASTER_ADDR" +# Get IP for hostname. +MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" +echo "Master address : $MASTER_ADDR" +export MASTER_ADDR +export MASTER_PORT=29500 +echo "Master port: $MASTER_PORT" # Get number of physical cores using Python PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") @@ -27,15 +40,12 @@ LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} # Compute threads per process OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) export OMP_NUM_THREADS=$OMP_THREADS -echo "Node: $(hostname)" echo "Physical cores: $PHYSICAL_CORES" echo "Local processes: $LOCAL_PROCS" echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" -# activate conda env -CONDA_ENV=train -source /users/pstamenk/.bashrc -mamba activate $CONDA_ENV - # python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml -torchrun --nproc-per-node=$LOCAL_PROCS src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml \ No newline at end of file +srun bash -c " + . ./train_env/bin/activate + python src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml +" \ No newline at end of file From 9aaea9d2b2f7ddb9825613beb6303c32e10ed784 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 9 May 2025 14:23:40 +0200 Subject: [PATCH 027/302] adapt era5cosmo loader to trim_edge 19 --- src/hirad/datasets/era5_cosmo.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/hirad/datasets/era5_cosmo.py b/src/hirad/datasets/era5_cosmo.py index 8b0d60f5..f8835d1d 100644 --- a/src/hirad/datasets/era5_cosmo.py +++ b/src/hirad/datasets/era5_cosmo.py @@ -14,7 +14,7 @@ def __init__(self, dataset_path: str): self._dataset_path = dataset_path self._era5_path = os.path.join(dataset_path, 'era-interpolated') self._cosmo_path = os.path.join(dataset_path, 'cosmo') - self._info_path = os.path.join(dataset_path, 'old/info') + self._info_path = os.path.join(dataset_path, 'info') # load file list (each file is one date-time state) self._file_list = os.listdir(self._cosmo_path) @@ -48,7 +48,8 @@ def __getitem__(self, idx): # squeeze the ensemble dimesnsion # reshape to image_shape # flip so that it starts in top-left corner (by default it is bottom left) - orig_shape = [350,542] #TODO currently padding to be divisible by 16 + # orig_shape = [350,542] #TODO currently padding to be divisible by 16 + orig_shape = self.image_shape() era5_data = np.flip(torch.load(os.path.join(self._era5_path,self._file_list[idx]), weights_only=False)\ .squeeze() \ .reshape(-1,*orig_shape), @@ -61,9 +62,12 @@ def __getitem__(self, idx): 1) cosmo_data = self.normalize_output(cosmo_data) # return samples - return F.pad(torch.tensor(cosmo_data), pad=(1,1,1,1), mode='constant', value=0), \ - F.pad(torch.tensor(era5_data), pad=(1,1,1,1), mode='constant', value=0), \ + return torch.tensor(cosmo_data),\ + torch.tensor(era5_data),\ 0 + # return F.pad(torch.tensor(cosmo_data), pad=(1,1,1,1), mode='constant', value=0), \ + # F.pad(torch.tensor(era5_data), pad=(1,1,1,1), mode='constant', value=0), \ + # 0 def __len__(self): return len(self._file_list) From dca7ff446056ce2596248559e3db2b853f576cd4 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 12 May 2025 17:32:56 +0200 Subject: [PATCH 028/302] add inference --- src/hirad/conf/generate_era_cosmo.yaml | 20 ++ src/hirad/conf/generation/era_cosmo.yaml | 37 ++++ src/hirad/conf/sampler/deterministic.yaml | 4 + src/hirad/conf/sampler/stochastic.yaml | 3 + src/hirad/inference/generate.py | 253 +++++++++++++++++++++- src/hirad/utils/generate_utils.py | 18 +- 6 files changed, 319 insertions(+), 16 deletions(-) create mode 100644 src/hirad/conf/generate_era_cosmo.yaml create mode 100644 src/hirad/conf/generation/era_cosmo.yaml create mode 100644 src/hirad/conf/sampler/deterministic.yaml create mode 100644 src/hirad/conf/sampler/stochastic.yaml diff --git a/src/hirad/conf/generate_era_cosmo.yaml b/src/hirad/conf/generate_era_cosmo.yaml new file mode 100644 index 00000000..03650e28 --- /dev/null +++ b/src/hirad/conf/generate_era_cosmo.yaml @@ -0,0 +1,20 @@ +hydra: + job: + chdir: true + name: generation + run: + dir: ./outputs/${hydra:job.name} + +# Get defaults +defaults: + + # Dataset + - dataset/era_cosmo + + # Sampler + - sampler/stochastic + #- sampler/deterministic + + # Generation + - generation/era_cosmo + #- generation/patched_based \ No newline at end of file diff --git a/src/hirad/conf/generation/era_cosmo.yaml b/src/hirad/conf/generation/era_cosmo.yaml new file mode 100644 index 00000000..2e37a632 --- /dev/null +++ b/src/hirad/conf/generation/era_cosmo.yaml @@ -0,0 +1,37 @@ +num_ensembles: 64 + # Number of ensembles to generate per input +seed_batch_size: 1 + # Size of the batched inference +inference_mode: regression + # Choose between "all" (regression + diffusion), "regression" or "diffusion" + # Patch size. Patch-based sampling will be utilized if these dimensions differ from + # img_shape_x and img_shape_y +overlap_pixels: 0 + # Number of overlapping pixels between adjacent patches +boundary_pixels: 0 + # Number of boundary pixels to be cropped out. 2 is recommanded to address the boundary + # artifact. +hr_mean_conditioning: False +sample_res: full + # Sampling resolution +times_range: null +times: + - 20160101-0000 + +perf: + force_fp16: false + # Whether to force fp16 precision for the model. If false, it'll use the precision + # specified upon training. + use_torch_compile: false + # whether to use torch.compile on the diffusion model + # this will make the first time stamp generation very slow due to compilation overheads + # but will significantly speed up subsequent inference runs + num_writer_workers: 1 + # number of workers to use for writing file + # To support multiple workers a threadsafe version of the netCDF library must be used + +io: + res_ckpt_path: diffusion_checkpoint + # Checkpoint filename for the diffusion model + reg_ckpt_path: regression_checkpoint + # Checkpoint filename for the mean predictor model \ No newline at end of file diff --git a/src/hirad/conf/sampler/deterministic.yaml b/src/hirad/conf/sampler/deterministic.yaml new file mode 100644 index 00000000..35bc0f63 --- /dev/null +++ b/src/hirad/conf/sampler/deterministic.yaml @@ -0,0 +1,4 @@ +type: deterministic +num_steps: 9 + # Number of denoising steps +solver: euler \ No newline at end of file diff --git a/src/hirad/conf/sampler/stochastic.yaml b/src/hirad/conf/sampler/stochastic.yaml new file mode 100644 index 00000000..5e8fa888 --- /dev/null +++ b/src/hirad/conf/sampler/stochastic.yaml @@ -0,0 +1,3 @@ +type: stochastic +boundary_pix: 2 +overlap_pix: 4 \ No newline at end of file diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index 6e5273af..adb882e9 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -10,6 +10,9 @@ from hirad.utils.console import PythonLogger, RankZeroLoggingWrapper from concurrent.futures import ThreadPoolExecutor from functools import partial + +from matplotlib import pyplot as plt +import cartopy.crs as ccrs from einops import rearrange from torch.distributed import gather @@ -57,7 +60,7 @@ def main(cfg: DictConfig) -> None: all_batches = torch.as_tensor(seeds).tensor_split(num_batches) rank_batches = all_batches[dist.rank :: dist.world_size] - # Synchronize + # Synchronize if dist.world_size > 1: torch.distributed.barrier() @@ -65,7 +68,7 @@ def main(cfg: DictConfig) -> None: if cfg.generation.times_range and cfg.generation.times: raise ValueError("Either times_range or times must be provided, but not both") if cfg.generation.times_range: - times = get_time_from_range(cfg.generation.times_range) #TODO check what time formats we are using and adapt + times = get_time_from_range(cfg.generation.times_range, time_format="%Y%m%d-%H%M") #TODO check what time formats we are using and adapt else: times = cfg.generation.times @@ -110,7 +113,7 @@ def main(cfg: DictConfig) -> None: # Load diffusion network, move to device, change precision if load_net_res: res_ckpt_path = cfg.generation.io.res_ckpt_path - logger0.info(f'Loading residual network from "{res_ckpt_path}"...') + logger0.info(f'Loading correction network from "{res_ckpt_path}"...') diffusion_model_args_path = os.path.join(res_ckpt_path, 'model_args.json') if not os.path.isfile(diffusion_model_args_path): @@ -135,7 +138,7 @@ def main(cfg: DictConfig) -> None: # load regression network, move to device, change precision if load_net_reg: reg_ckpt_path = cfg.generation.io.reg_ckpt_path - logger0.info(f'Loading network from "{reg_ckpt_path}"...') + logger0.info(f'Loading regression network from "{reg_ckpt_path}"...') regression_model_args_path = os.path.join(reg_ckpt_path, 'model_args.json') @@ -144,7 +147,7 @@ def main(cfg: DictConfig) -> None: with open(regression_model_args_path, 'r') as f: regression_model_args = json.load(f) - net_reg = EDMPrecond(**regression_model_args) + net_reg = UNet(**regression_model_args) _ = load_checkpoint( path=reg_ckpt_path, @@ -156,4 +159,242 @@ def main(cfg: DictConfig) -> None: if cfg.generation.perf.force_fp16: net_reg.use_fp16 = True else: - net_reg = None \ No newline at end of file + net_reg = None + + # Reset since we are using a different mode. + if cfg.generation.perf.use_torch_compile: + torch._dynamo.reset() + # Only compile residual network + # Overhead of compiling regression network outweights any benefits + if net_res: + net_res = torch.compile(net_res, mode="reduce-overhead") + + # Partially instantiate the sampler based on the configs + if cfg.sampler.type == "deterministic": + if cfg.generation.hr_mean_conditioning: + raise NotImplementedError( + "High-res mean conditioning is not yet implemented for the deterministic sampler" + ) + sampler_fn = partial( + deterministic_sampler, + num_steps=cfg.sampler.num_steps, + # num_ensembles=cfg.generation.num_ensembles, + solver=cfg.sampler.solver, + ) + elif cfg.sampler.type == "stochastic": + sampler_fn = partial( + stochastic_sampler, + img_shape=img_shape[1], + patch_shape=patch_shape[1], + boundary_pix=cfg.sampler.boundary_pix, + overlap_pix=cfg.sampler.overlap_pix, + ) + else: + raise ValueError(f"Unknown sampling method {cfg.sampling.type}") + + + # Main generation definition + def generate_fn(image_lr, lead_time_label): + img_shape_y, img_shape_x = img_shape + with nvtx.annotate("generate_fn", color="green"): + if cfg.generation.sample_res == "full": + image_lr_patch = image_lr + else: + torch.cuda.nvtx.range_push("rearrange") + image_lr_patch = rearrange( + image_lr, + "b c (h1 h) (w1 w) -> (b h1 w1) c h w", + h1=img_shape_y // patch_shape[0], + w1=img_shape_x // patch_shape[1], + ) + torch.cuda.nvtx.range_pop() + image_lr_patch = image_lr_patch.to(memory_format=torch.channels_last) + + if net_reg: + with nvtx.annotate("regression_model", color="yellow"): + image_reg = regression_step( + net=net_reg, + img_lr=image_lr_patch, + latents_shape=( + cfg.generation.seed_batch_size, + img_out_channels, + img_shape[0], + img_shape[1], + ), + lead_time_label=lead_time_label, + ) + if net_res: + if cfg.generation.hr_mean_conditioning: + mean_hr = image_reg[0:1] + else: + mean_hr = None + with nvtx.annotate("diffusion model", color="purple"): + image_res = diffusion_step( + net=net_res, + sampler_fn=sampler_fn, + seed_batch_size=cfg.generation.seed_batch_size, + img_shape=img_shape, + img_out_channels=img_out_channels, + rank_batches=rank_batches, + img_lr=image_lr_patch.expand( + cfg.generation.seed_batch_size, -1, -1, -1 + ).to(memory_format=torch.channels_last), + rank=dist.rank, + device=device, + hr_mean=mean_hr, + lead_time_label=lead_time_label, + ) + if cfg.generation.inference_mode == "regression": + image_out = image_reg + elif cfg.generation.inference_mode == "diffusion": + image_out = image_res + else: + image_out = image_reg + image_res + + if cfg.generation.sample_res != "full": + image_out = rearrange( + image_out, + "(b h1 w1) c h w -> b c (h1 h) (w1 w)", + h1=img_shape_y // patch_shape[0], + w1=img_shape_x // patch_shape[1], + ) + # Gather tensors on rank 0 + if dist.world_size > 1: + if dist.rank == 0: + gathered_tensors = [ + torch.zeros_like( + image_out, dtype=image_out.dtype, device=image_out.device + ) + for _ in range(dist.world_size) + ] + else: + gathered_tensors = None + + torch.distributed.barrier() + gather( + image_out, + gather_list=gathered_tensors if dist.rank == 0 else None, + dst=0, + ) + + if dist.rank == 0: + return torch.cat(gathered_tensors) + else: + return None + else: + return image_out + + # generate images + output_path = getattr(cfg.generation.io, "output_path", "./outputs") + logger0.info(f"Generating images, saving results to {output_path}...") + batch_size = 1 + warmup_steps = min(len(times) - 1, 2) + # Generates model predictions from the input data using the specified + # `generate_fn`, and save the predictions to the provided NetCDF file. It iterates + # through the dataset using a data loader, computes predictions, and saves them along + # with associated metadata. + + with torch.cuda.profiler.profile(): + with torch.autograd.profiler.emit_nvtx(): + + data_loader = torch.utils.data.DataLoader( + dataset=dataset, sampler=sampler, batch_size=1, pin_memory=True + ) + time_index = -1 + if dist.rank == 0: + writer_executor = ThreadPoolExecutor( + max_workers=cfg.generation.perf.num_writer_workers + ) + writer_threads = [] + + start = torch.cuda.Event(enable_timing=True) + end = torch.cuda.Event(enable_timing=True) + + times = dataset.time() + for image_tar, image_lr, index, *lead_time_label in iter(data_loader): + time_index += 1 + if dist.rank == 0: + logger0.info(f"starting index: {time_index}") + + if time_index == warmup_steps: + start.record() + + # continue + if lead_time_label: + lead_time_label = lead_time_label[0].to(dist.device).contiguous() + else: + lead_time_label = None + image_lr = ( + image_lr.to(device=device) + .to(torch.float32) + .to(memory_format=torch.channels_last) + ) + image_tar = image_tar.to(device=device).to(torch.float32) + image_out = generate_fn(image_lr,lead_time_label) + if dist.rank == 0: + batch_size = image_out.shape[0] + # write out data in a seperate thread so we don't hold up inferencing + writer_threads.append( + writer_executor.submit( + save_images, + output_path, + dataset, + image_out.cpu(), + image_tar.cpu(), + image_lr.cpu(), + ) + ) + end.record() + end.synchronize() + elapsed_time = start.elapsed_time(end) / 1000.0 # Convert ms to s + timed_steps = time_index + 1 - warmup_steps + if dist.rank == 0: + average_time_per_batch_element = elapsed_time / timed_steps / batch_size + logger.info( + f"Total time to run {timed_steps} steps and {batch_size} members = {elapsed_time} s" + ) + logger.info( + f"Average time per batch element = {average_time_per_batch_element} s" + ) + + # make sure all the workers are done writing + if dist.rank == 0: + for thread in list(writer_threads): + thread.result() + writer_threads.remove(thread) + writer_executor.shutdown() + + if dist.rank == 0: + f.close() + logger0.info("Generation Completed.") + +def save_images(output_path, dataset, image_pred, image_hr, image_lr): + longitudes = dataset.longitude() + latitudes = dataset.latitude() + input_channels = dataset.input_channels() + output_channels = dataset.output_channels() + image_pred = np.flip(dataset.denormalize_output(image_pred.numpy()),1).reshape(len(output_channels),-1) + image_hr = np.flip(dataset.denormalize_output(image_hr.numpy()),1).reshape(len(output_channels),-1) + image_lr = np.flip(dataset.denormalize_input(image_lr.numpy()),1).reshape(len(input_channels),-1) + for idx, channel in enumerate(output_channels): + input_channel_idx = input_channels.index(channel) + _plot_projection(longitudes,latitudes,image_lr[input_channel_idx,:],os.path.join(output_path,f'{channel.name}-lr.jpg')) + _plot_projection(longitudes,latitudes,image_hr[idx,:],os.path.join(output_path,f'{channel.name}-hr.jpg')) + _plot_projection(longitudes,latitudes,image_pred[idx,:],os.path.join(output_path,f'{channel.name}-hr-pred.jpg')) + +def _plot_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, cmap=None, vmin = None, vmax = None): + + """Plot observed or interpolated data in a scatter plot.""" + # TODO: Refactor this somehow, it's not really generalizing well across variables. + fig = plt.figure() + fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) + p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) + ax.coastlines() + ax.gridlines(draw_labels=True) + plt.colorbar(p, label="K", orientation="horizontal") + plt.savefig(filename) + plt.close('all') + +if __name__ == "__main__": + main() + \ No newline at end of file diff --git a/src/hirad/utils/generate_utils.py b/src/hirad/utils/generate_utils.py index b99852fd..43f83b63 100644 --- a/src/hirad/utils/generate_utils.py +++ b/src/hirad/utils/generate_utils.py @@ -8,17 +8,15 @@ def get_dataset_and_sampler(dataset_cfg, times, has_lead_time=False): Get a dataset and sampler for generation. """ (dataset, _) = init_dataset_from_config(dataset_cfg, batch_size=1) - if has_lead_time: - plot_times = times - else: - plot_times = [ - convert_datetime_to_cftime( - datetime.datetime.strptime(time, "%Y-%m-%dT%H:%M:%S") - ) - for time in times - ] + # if has_lead_time: + # plot_times = times + # else: + # plot_times = [ + # datetime.datetime.strptime(time, "%Y-%m-%dT%H:%M:%S") + # for time in times + # ] all_times = dataset.time() - time_indices = [all_times.index(t) for t in plot_times] + time_indices = [all_times.index(t) for t in times] sampler = time_indices return dataset, sampler \ No newline at end of file From 8ba0c5a21e21a09ff1b248221f01378e4bc30754 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 12 May 2025 18:38:19 +0200 Subject: [PATCH 029/302] Plot absolute error onto a projection for a given date --- src/hirad/eval/metrics.py | 21 ++++++++++++++++++++ src/hirad/eval/plotting.py | 16 ++++++++++++++++ src/hirad/eval/run_scoring.py | 36 +++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 src/hirad/eval/metrics.py create mode 100644 src/hirad/eval/plotting.py create mode 100644 src/hirad/eval/run_scoring.py diff --git a/src/hirad/eval/metrics.py b/src/hirad/eval/metrics.py new file mode 100644 index 00000000..c8fda438 --- /dev/null +++ b/src/hirad/eval/metrics.py @@ -0,0 +1,21 @@ +import numpy as np +import torch + + +# set up MAE calculation to be run for each channel for a given date/time (for target COSMO, prediction, and ERA interpolated) + +# input will be a 2D tensor of values with the COSMO lat/lon. + +# Extracted from physicsnemo/examples/weather/regen/paper_figures/score_inference.py + +def absolute_error(pred, target): + return torch.abs(pred-target) + +def compute_mae(pred, target): + # Exclude any target NaNs (not expected, but precautionary) + mask = ~np.isnan(target) + pred = pred[:, mask] + target = target[mask] + + return torch.mean(absolute_error(pred, target)) + diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py new file mode 100644 index 00000000..141b5c9b --- /dev/null +++ b/src/hirad/eval/plotting.py @@ -0,0 +1,16 @@ +import logging + +import cartopy.crs as ccrs +import matplotlib.pyplot as plt +import numpy as np + +def plot_error_projection(values: np.array, latitudes: np.array, longitudes: np.array, filename: str): + fig = plt.figure() + fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) + logging.info(f'plotting values to {filename}') + p = ax.scatter(x=longitudes, y=latitudes, c=values) + ax.coastlines() + ax.gridlines(draw_labels=True) + plt.colorbar(p, label="absolute error", orientation="horizontal") + plt.savefig(filename) + plt.close('all') diff --git a/src/hirad/eval/run_scoring.py b/src/hirad/eval/run_scoring.py new file mode 100644 index 00000000..57ca69fe --- /dev/null +++ b/src/hirad/eval/run_scoring.py @@ -0,0 +1,36 @@ +import os +import sys + +import metrics +import plotting +import torch +import yaml + + +def main(): + if len(sys.argv) < 4: + raise ValueError('Expected call run_scoring.py [input data directory] [predictions directory] [date]') + + input_directory = sys.argv[1] + predictions_directory = sys.argv[2] + date = sys.argv[3] + + target = torch.load(os.path.join(input_directory, 'cosmo', date), weights_only=False) + baseline = torch.load(os.path.join(input_directory, 'era-interpolated', date), weights_only=False) + #prediction_file = torch.load(os.path.join(predictions_directory, date), weights_only=False) + prediction = torch.load(os.path.join(input_directory, 'cosmo', '20160101-0000'), weights_only=False) + lat_lon = torch.load(os.path.join(input_directory, 'info', 'cosmo-lat-lon'), weights_only=False) + + # Reshape grides to be the same as prediction + #target = target.squeeze().reshape(-1,*prediction.shape), + target = torch.from_numpy(target) + prediction = torch.from_numpy(prediction) + #prediction = prediction.squeeze().reshape(-1,*prediction.shape) + latitudes = lat_lon[:,0] #.squeeze().reshape(-1,*prediction.shape) + longitudes = lat_lon[:,1] #squeeze().reshape(-1,*prediction.shape) + + errors = metrics.absolute_error(prediction[0,:,:], target[0,:,:]) + plotting.plot_error_projection(errors, latitudes, longitudes, os.path.join('plots/errors/', date)) + +if __name__ == "__main__": + main() \ No newline at end of file From c6e632a1bdc52d9f932baf15beab93abc187d94f Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 12 May 2025 18:39:35 +0200 Subject: [PATCH 030/302] Adjust how reshaping is done before error calcs --- src/hirad/eval/run_scoring.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/hirad/eval/run_scoring.py b/src/hirad/eval/run_scoring.py index 57ca69fe..ce2c3434 100644 --- a/src/hirad/eval/run_scoring.py +++ b/src/hirad/eval/run_scoring.py @@ -17,18 +17,25 @@ def main(): target = torch.load(os.path.join(input_directory, 'cosmo', date), weights_only=False) baseline = torch.load(os.path.join(input_directory, 'era-interpolated', date), weights_only=False) - #prediction_file = torch.load(os.path.join(predictions_directory, date), weights_only=False) - prediction = torch.load(os.path.join(input_directory, 'cosmo', '20160101-0000'), weights_only=False) + prediction = torch.load(os.path.join(predictions_directory, date), weights_only=False) lat_lon = torch.load(os.path.join(input_directory, 'info', 'cosmo-lat-lon'), weights_only=False) - # Reshape grides to be the same as prediction - #target = target.squeeze().reshape(-1,*prediction.shape), + with open(os.path.join(input_directory), 'info', 'cosmo.yaml') as cosmo_file: + cosmo_config = yaml.safe_load(cosmo_file) + channels = cosmo_config['select'] + + # Reshape predictions, if necessary + # target is shape [channels, ensembles, points] + # prediction is shape [channels, ensembles, x, y] + prediction = prediction.reshape(*target.shape) + + latitudes = lat_lon[:,0] + longitudes = lat_lon[:,1] + + # convert to torch target = torch.from_numpy(target) prediction = torch.from_numpy(prediction) - #prediction = prediction.squeeze().reshape(-1,*prediction.shape) - latitudes = lat_lon[:,0] #.squeeze().reshape(-1,*prediction.shape) - longitudes = lat_lon[:,1] #squeeze().reshape(-1,*prediction.shape) - + errors = metrics.absolute_error(prediction[0,:,:], target[0,:,:]) plotting.plot_error_projection(errors, latitudes, longitudes, os.path.join('plots/errors/', date)) From d21ec35ab055d313d7128163a941b909a3b44745 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 12 May 2025 18:58:47 +0200 Subject: [PATCH 031/302] plot for all channels, and against baseline --- src/hirad/eval/run_scoring.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/hirad/eval/run_scoring.py b/src/hirad/eval/run_scoring.py index ce2c3434..0cc3c1de 100644 --- a/src/hirad/eval/run_scoring.py +++ b/src/hirad/eval/run_scoring.py @@ -20,9 +20,13 @@ def main(): prediction = torch.load(os.path.join(predictions_directory, date), weights_only=False) lat_lon = torch.load(os.path.join(input_directory, 'info', 'cosmo-lat-lon'), weights_only=False) - with open(os.path.join(input_directory), 'info', 'cosmo.yaml') as cosmo_file: + with open(os.path.join(input_directory, 'info', 'cosmo.yaml')) as cosmo_file: cosmo_config = yaml.safe_load(cosmo_file) - channels = cosmo_config['select'] + target_channels = cosmo_config['select'] + + with open(os.path.join(input_directory, 'info', 'era.yaml')) as era_file: + era_config = yaml.safe_load(era_file) + input_channels = era_config['select'] # Reshape predictions, if necessary # target is shape [channels, ensembles, points] @@ -34,10 +38,18 @@ def main(): # convert to torch target = torch.from_numpy(target) + baseline = torch.from_numpy(baseline) prediction = torch.from_numpy(prediction) - errors = metrics.absolute_error(prediction[0,:,:], target[0,:,:]) - plotting.plot_error_projection(errors, latitudes, longitudes, os.path.join('plots/errors/', date)) + # plot baseline error + for t_c in range(len(target_channels)): + b_c = input_channels.index(target_channels[t_c]) + if b_c > -1: + baseline_errors = metrics.absolute_error(baseline[b_c,:,:], target[t_c,:,:]) + plotting.plot_error_projection(baseline_errors, latitudes, longitudes, os.path.join('plots/errors/', 'baseline', target_channels[t_c] + '-' + date)) + prediction_errors = metrics.absolute_error(prediction[t_c,:,:], target[t_c,:,:]) + plotting.plot_error_projection(prediction_errors, latitudes, longitudes, os.path.join('plots/errors/', 'prediction', target_channels[t_c] + '-' + date)) + if __name__ == "__main__": main() \ No newline at end of file From 5b32566bf13bc19ae4de8c0f957ac0f44c6ce892 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 13 May 2025 09:14:41 +0200 Subject: [PATCH 032/302] Add MAE output --- src/hirad/eval/metrics.py | 6 ++++-- src/hirad/eval/run_scoring.py | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/hirad/eval/metrics.py b/src/hirad/eval/metrics.py index c8fda438..133cc210 100644 --- a/src/hirad/eval/metrics.py +++ b/src/hirad/eval/metrics.py @@ -8,7 +8,7 @@ # Extracted from physicsnemo/examples/weather/regen/paper_figures/score_inference.py -def absolute_error(pred, target): +def absolute_error(pred, target) -> tuple[float, np.ndarray]: return torch.abs(pred-target) def compute_mae(pred, target): @@ -17,5 +17,7 @@ def compute_mae(pred, target): pred = pred[:, mask] target = target[mask] - return torch.mean(absolute_error(pred, target)) + ae = absolute_error(pred, target) + + return torch.mean(absolute_error(pred, target)), ae diff --git a/src/hirad/eval/run_scoring.py b/src/hirad/eval/run_scoring.py index 0cc3c1de..fee984e0 100644 --- a/src/hirad/eval/run_scoring.py +++ b/src/hirad/eval/run_scoring.py @@ -41,15 +41,15 @@ def main(): baseline = torch.from_numpy(baseline) prediction = torch.from_numpy(prediction) - # plot baseline error + # plot errors for t_c in range(len(target_channels)): b_c = input_channels.index(target_channels[t_c]) if b_c > -1: - baseline_errors = metrics.absolute_error(baseline[b_c,:,:], target[t_c,:,:]) + baseline_mae, baseline_errors = metrics.compute_mae(baseline[b_c,:,:], target[t_c,:,:]) plotting.plot_error_projection(baseline_errors, latitudes, longitudes, os.path.join('plots/errors/', 'baseline', target_channels[t_c] + '-' + date)) - prediction_errors = metrics.absolute_error(prediction[t_c,:,:], target[t_c,:,:]) + prediction_mae, prediction_errors = metrics.compute_mae(prediction[t_c,:,:], target[t_c,:,:]) plotting.plot_error_projection(prediction_errors, latitudes, longitudes, os.path.join('plots/errors/', 'prediction', target_channels[t_c] + '-' + date)) - + print(f'baseline MAE={baseline_mae}, prediction MAE={prediction_mae}') if __name__ == "__main__": main() \ No newline at end of file From 60c8affbc46d240940d84255c9ab3b32e82cfc8b Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 13 May 2025 09:23:15 +0200 Subject: [PATCH 033/302] Fix indexing error --- src/hirad/eval/metrics.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hirad/eval/metrics.py b/src/hirad/eval/metrics.py index 133cc210..e6e1afb5 100644 --- a/src/hirad/eval/metrics.py +++ b/src/hirad/eval/metrics.py @@ -13,8 +13,9 @@ def absolute_error(pred, target) -> tuple[float, np.ndarray]: def compute_mae(pred, target): # Exclude any target NaNs (not expected, but precautionary) + # TODO: Fix the deprecated warning (index with dtype torch.bool instead of torch.uint8) mask = ~np.isnan(target) - pred = pred[:, mask] + pred = pred[mask] target = target[mask] ae = absolute_error(pred, target) From 5cc42e9c0de8b04f2f865c90a12bf928443e9ad5 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 14 May 2025 10:08:43 +0200 Subject: [PATCH 034/302] Try adding spectral graph --- .gitignore | 17 ++++++++++++++++- src/hirad/eval/plotting.py | 7 +++++++ src/hirad/eval/run_scoring.py | 19 +++++++++++++++---- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index c514c5d0..dee6b078 100644 --- a/.gitignore +++ b/.gitignore @@ -168,4 +168,19 @@ poetry.toml .ruff_cache/ # LSP config files -pyrightconfig.json \ No newline at end of file +pyrightconfig.json + +# output files +*.out +*.torch +plots/* +*.npz + +# conda +.conda/* + +# temp +temp.* + +# local script +interpolate.sh \ No newline at end of file diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index 141b5c9b..262109f8 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -14,3 +14,10 @@ def plot_error_projection(values: np.array, latitudes: np.array, longitudes: np. plt.colorbar(p, label="absolute error", orientation="horizontal") plt.savefig(filename) plt.close('all') + +def plot_power_spectrum(x, filename): + fig = plt.figure() + plt.psd(x) + logging.info(f'plotting values to {filename}') + plt.savefig(filename) + plt.close('all') \ No newline at end of file diff --git a/src/hirad/eval/run_scoring.py b/src/hirad/eval/run_scoring.py index fee984e0..df37a9ac 100644 --- a/src/hirad/eval/run_scoring.py +++ b/src/hirad/eval/run_scoring.py @@ -31,7 +31,8 @@ def main(): # Reshape predictions, if necessary # target is shape [channels, ensembles, points] # prediction is shape [channels, ensembles, x, y] - prediction = prediction.reshape(*target.shape) + prediction_1d = prediction.reshape(*target.shape) + prediction_2d = prediction.reshape(prediction.shape[0],352,544) latitudes = lat_lon[:,0] longitudes = lat_lon[:,1] @@ -39,7 +40,7 @@ def main(): # convert to torch target = torch.from_numpy(target) baseline = torch.from_numpy(baseline) - prediction = torch.from_numpy(prediction) + prediction_1d = torch.from_numpy(prediction_1d) # plot errors for t_c in range(len(target_channels)): @@ -47,9 +48,19 @@ def main(): if b_c > -1: baseline_mae, baseline_errors = metrics.compute_mae(baseline[b_c,:,:], target[t_c,:,:]) plotting.plot_error_projection(baseline_errors, latitudes, longitudes, os.path.join('plots/errors/', 'baseline', target_channels[t_c] + '-' + date)) - prediction_mae, prediction_errors = metrics.compute_mae(prediction[t_c,:,:], target[t_c,:,:]) - plotting.plot_error_projection(prediction_errors, latitudes, longitudes, os.path.join('plots/errors/', 'prediction', target_channels[t_c] + '-' + date)) + plotting.plot_power_spectrum(baseline[b_c,:,:], os.path.join('plots/spectra/', 'baseline', target_channels[t_c] + date)) + prediction_mae, prediction_errors = metrics.compute_mae(prediction_1d[t_c,:,:], target[t_c,:,:]) + plotting.plot_error_projection(prediction_errors, latitudes, longitudes, os.path.join('plots/errors/', 'prediction', target_channels[t_c] + '-' + date)) + plotting.plot_power_spectrum(prediction[t_c,0,:], os.path.join('plots/spectra/', 'prediction', target_channels[t_c] + date)) + plotting.plot_power_spectrum(prediction_2d[t_c,:,:], os.path.join('plots/spectra/', 'prediction2d', target_channels[t_c] + date)) print(f'baseline MAE={baseline_mae}, prediction MAE={prediction_mae}') + # Plot power spectra + freq, power = metrics.compute_power_spectrum(prediction, 1) + plotting.plot_power_spectrum(prediction, 'plots/errors/powerspec-prediction') + plotting.plot_power_spectrum(prediction, 'plots/errors/powerspec-prediction') + + + if __name__ == "__main__": main() \ No newline at end of file From 5b77dbebd984a35e3797ed40e97c6cdb1e03669c Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Thu, 15 May 2025 15:38:46 +0200 Subject: [PATCH 035/302] fix inference for diffusion --- pyproject.toml | 10 ++- src/hirad/conf/dataset/era_cosmo.yaml | 2 +- src/hirad/conf/generate_era_cosmo.yaml | 6 +- src/hirad/conf/generation/era_cosmo.yaml | 7 ++- .../conf/training/era_cosmo_diffusion.yaml | 10 +-- .../conf/training/era_cosmo_regression.yaml | 11 ++-- src/hirad/datasets/era5_cosmo.py | 8 +-- src/hirad/generate.sh | 51 +++++++++++++++ src/hirad/inference/generate.py | 62 ++++++++++++------- src/hirad/models/layers.py | 3 +- src/hirad/train.sh | 51 +++++++++++++++ src/hirad/training/train.py | 2 +- src/hirad/utils/inference_utils.py | 13 ++-- src/hirad/utils/stochastic_sampler.py | 60 ++++++++++-------- 14 files changed, 218 insertions(+), 78 deletions(-) create mode 100644 src/hirad/generate.sh create mode 100644 src/hirad/train.sh diff --git a/pyproject.toml b/pyproject.toml index b2fa56c9..1477899a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,15 @@ requires-python = ">=3.12" license = {file = "LICENSE"} dependencies = [ - "torch>=2.6.0" + "cartopy>=0.24.1", + "cftime>=1.6.4", + "hydra-core>=1.3.2", + "matplotlib>=3.10.1", + "omegaconf>=2.3.0", + "tensorboard>=2.19.0", + "termcolor>=3.1.0", + "torchinfo>=1.8.0", + "treelib>=1.7.1" ] [tool.setuptools] diff --git a/src/hirad/conf/dataset/era_cosmo.yaml b/src/hirad/conf/dataset/era_cosmo.yaml index 854b7753..63d7361a 100644 --- a/src/hirad/conf/dataset/era_cosmo.yaml +++ b/src/hirad/conf/dataset/era_cosmo.yaml @@ -1,2 +1,2 @@ type: era5_cosmo -dataset_path: /store_new/mch/msopr/hirad-gen/basic-torch \ No newline at end of file +dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/trim_19_overfit \ No newline at end of file diff --git a/src/hirad/conf/generate_era_cosmo.yaml b/src/hirad/conf/generate_era_cosmo.yaml index 03650e28..5d7649de 100644 --- a/src/hirad/conf/generate_era_cosmo.yaml +++ b/src/hirad/conf/generate_era_cosmo.yaml @@ -1,13 +1,13 @@ hydra: job: chdir: true - name: generation + name: generation_full run: - dir: ./outputs/${hydra:job.name} + dir: /iopsstor/scratch/cscs/pstamenk/outputs/${hydra:job.name} # Get defaults defaults: - + - _self_ # Dataset - dataset/era_cosmo diff --git a/src/hirad/conf/generation/era_cosmo.yaml b/src/hirad/conf/generation/era_cosmo.yaml index 2e37a632..51795203 100644 --- a/src/hirad/conf/generation/era_cosmo.yaml +++ b/src/hirad/conf/generation/era_cosmo.yaml @@ -31,7 +31,8 @@ perf: # To support multiple workers a threadsafe version of the netCDF library must be used io: - res_ckpt_path: diffusion_checkpoint + res_ckpt_path: null # Checkpoint filename for the diffusion model - reg_ckpt_path: regression_checkpoint - # Checkpoint filename for the mean predictor model \ No newline at end of file + reg_ckpt_path: /iopsstor/scratch/cscs/pstamenk/outputs/regression_overfit/checkpoints_regression + # Checkpoint filename for the mean predictor model + output_path: ./images \ No newline at end of file diff --git a/src/hirad/conf/training/era_cosmo_diffusion.yaml b/src/hirad/conf/training/era_cosmo_diffusion.yaml index b61603a6..b06ec611 100644 --- a/src/hirad/conf/training/era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training/era_cosmo_diffusion.yaml @@ -1,8 +1,8 @@ # Hyperparameters hp: - training_duration: 128 + training_duration: 16 # Training duration based on the number of processed samples - total_batch_size: 16 + total_batch_size: 4 # Total batch size batch_size_per_gpu: "auto" # Batch size per GPU @@ -20,14 +20,14 @@ perf: fp_optimizations: amp-bf16 # Floating point mode, one of ["fp32", "fp16", "amp-fp16", "amp-bf16"] # "amp-{fp16,bf16}" activates Automatic Mixed Precision (AMP) with {float16,bfloat16} - dataloader_workers: 4 + dataloader_workers: 8 # DataLoader worker processes songunet_checkpoint_level: 0 # 0 means no checkpointing # Gradient checkpointing level, value is number of layers to checkpoint # I/O io: - regression_checkpoint_path: /scratch/mch/pstamenk/output/regression/checkpoints_regression + regression_checkpoint_path: /iopsstor/scratch/cscs/pstamenk/outputs/regression_overfit/checkpoints_regression # Where to load the regression checkpoint print_progress_freq: 32 # How often to print progress @@ -38,4 +38,4 @@ io: validation_steps: 10 # how many loss evaluations are used to compute the validation loss per checkpoint # how many loss evaluations are used to compute the validation loss per checkpoint - checkpoint_dir: /scratch/mch/pstamenk/output/diffusion \ No newline at end of file + checkpoint_dir: . \ No newline at end of file diff --git a/src/hirad/conf/training/era_cosmo_regression.yaml b/src/hirad/conf/training/era_cosmo_regression.yaml index 7c443f00..76bdc4eb 100644 --- a/src/hirad/conf/training/era_cosmo_regression.yaml +++ b/src/hirad/conf/training/era_cosmo_regression.yaml @@ -1,12 +1,13 @@ # Hyperparameters hp: - training_duration: 16 + training_duration: 8 # Training duration based on the number of processed samples - total_batch_size: 16 + total_batch_size: 4 # Total batch size batch_size_per_gpu: "auto" # Batch size per GPU - lr: 0.0002 + lr: 0.001 + #0.0002 # Learning rate grad_clip_threshold: null # no gradient clipping for defualt non-patch-based training @@ -27,7 +28,7 @@ perf: # I/O io: - print_progress_freq: 32 + print_progress_freq: 128 # How often to print progress save_checkpoint_freq: 5000 # How often to save the checkpoints, measured in number of processed samples @@ -35,4 +36,4 @@ io: # how often to record the validation loss, measured in number of processed samples validation_steps: 10 # how many loss evaluations are used to compute the validation loss per checkpoint - checkpoint_dir: /scratch/mch/pstamenk/output/regression \ No newline at end of file + checkpoint_dir: . \ No newline at end of file diff --git a/src/hirad/datasets/era5_cosmo.py b/src/hirad/datasets/era5_cosmo.py index f8835d1d..674dbf07 100644 --- a/src/hirad/datasets/era5_cosmo.py +++ b/src/hirad/datasets/era5_cosmo.py @@ -75,14 +75,14 @@ def __len__(self): def longitude(self) -> np.ndarray: """Get longitude values from the dataset.""" - lon_lat = torch.load(os.path.join(self._info_path,'cosmo-lat-lon'), weights_only=False) - return lon_lat[:,0] + lat_lon = torch.load(os.path.join(self._info_path,'cosmo-lat-lon'), weights_only=False) + return lat_lon[:,1] def latitude(self) -> np.ndarray: """Get latitude values from the dataset.""" - lon_lat = torch.load(os.path.join(self._info_path,'cosmo-lat-lon'), weights_only=False) - return lon_lat[:,1] + lat_lon = torch.load(os.path.join(self._info_path,'cosmo-lat-lon'), weights_only=False) + return lat_lon[:,0] def input_channels(self) -> List[ChannelMetadata]: diff --git a/src/hirad/generate.sh b/src/hirad/generate.sh new file mode 100644 index 00000000..87c8979c --- /dev/null +++ b/src/hirad/generate.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +#SBATCH --job-name="testrun" + +### HARDWARE ### +#SBATCH --partition=debug +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --gpus-per-node=1 +#SBATCH --cpus-per-task=72 +#SBATCH --time=00:30:00 +#SBATCH --no-requeue +#SBATCH --exclusive + +### OUTPUT ### +#SBATCH --output=/iopsstor/scratch/cscs/pstamenk/logs/full_generation.log +#SBATCH --error=/iopsstor/scratch/cscs/pstamenk/logs/full_generation.err + +### ENVIRONMENT #### +#SBATCH --uenv=pytorch/v2.6.0:/user-environment +#SBATCH --view=default +#SBATCH -A a-a122 + +# Choose method to initialize dist in pythorch +export DISTRIBUTED_INITIALIZATION_METHOD=SLURM + +MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" +echo "Master node : $MASTER_ADDR" +# Get IP for hostname. +MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" +echo "Master address : $MASTER_ADDR" +export MASTER_ADDR +export MASTER_PORT=29500 +echo "Master port: $MASTER_PORT" + +# Get number of physical cores using Python +PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") +# Use SLURM_NTASKS (number of processes to be launched by torchrun) +LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} +# Compute threads per process +OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) +export OMP_NUM_THREADS=$OMP_THREADS +echo "Physical cores: $PHYSICAL_CORES" +echo "Local processes: $LOCAL_PROCS" +echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" + +# python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml +srun bash -c " + . ./train_env/bin/activate + python src/hirad/inference/generate.py --config-name=generate_era_cosmo.yaml +" \ No newline at end of file diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index adb882e9..5558a200 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -11,14 +11,14 @@ from concurrent.futures import ThreadPoolExecutor from functools import partial -from matplotlib import pyplot as plt import cartopy.crs as ccrs +from matplotlib import pyplot as plt from einops import rearrange from torch.distributed import gather from hydra.utils import to_absolute_path -from hirad.models import EDMPrecond, UNet +from hirad.models import EDMPrecondSR, UNet from hirad.utils.stochastic_sampler import stochastic_sampler from hirad.utils.deterministic_sampler import deterministic_sampler from hirad.utils.inference_utils import ( @@ -36,12 +36,12 @@ from hirad.utils.train_helpers import set_patch_shape -@hydra.main(version_base="1.2", config_path="conf", config_name="config_generate") +@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") def main(cfg: DictConfig) -> None: """Generate random dowscaled atmospheric states using the techniques described in the paper "Elucidating the Design Space of Diffusion-Based Generative Models". """ - + torch.backends.cudnn.enabled = False # Initialize distributed manager DistributedManager.initialize() dist = DistributedManager() @@ -50,7 +50,7 @@ def main(cfg: DictConfig) -> None: # Initialize logger logger = PythonLogger("generate") # General python logger logger0 = RankZeroLoggingWrapper(logger, dist) - logger.file_logging("generate.log") + # logger.file_logging("generate.log") # Handle the batch size seeds = list(np.arange(cfg.generation.num_ensembles)) @@ -121,7 +121,7 @@ def main(cfg: DictConfig) -> None: with open(diffusion_model_args_path, 'r') as f: diffusion_model_args = json.load(f) - net_res = EDMPrecond(**diffusion_model_args) + net_res = EDMPrecondSR(**diffusion_model_args) _ = load_checkpoint( path=res_ckpt_path, @@ -129,7 +129,8 @@ def main(cfg: DictConfig) -> None: device=dist.device ) - net_res = net_res.eval().to(device).to(memory_format=torch.channels_last) + #TODO fix to use channels_last which is optimal for H100 + net_res = net_res.eval().to(device)#.to(memory_format=torch.channels_last) if cfg.generation.perf.force_fp16: net_res.use_fp16 = True else: @@ -155,7 +156,7 @@ def main(cfg: DictConfig) -> None: device=dist.device ) - net_reg = net_reg.eval().to(device).to(memory_format=torch.channels_last) + net_reg = net_reg.eval().to(device)#.to(memory_format=torch.channels_last) if cfg.generation.perf.force_fp16: net_reg.use_fp16 = True else: @@ -184,8 +185,9 @@ def main(cfg: DictConfig) -> None: elif cfg.sampler.type == "stochastic": sampler_fn = partial( stochastic_sampler, - img_shape=img_shape[1], - patch_shape=patch_shape[1], + img_shape=img_shape, + patch_shape_x=patch_shape[0], + patch_shape_y=patch_shape[1], boundary_pix=cfg.sampler.boundary_pix, overlap_pix=cfg.sampler.overlap_pix, ) @@ -194,7 +196,7 @@ def main(cfg: DictConfig) -> None: # Main generation definition - def generate_fn(image_lr, lead_time_label): + def generate_fn(image_lr, labels, lead_time_label): img_shape_y, img_shape_x = img_shape with nvtx.annotate("generate_fn", color="green"): if cfg.generation.sample_res == "full": @@ -208,13 +210,14 @@ def generate_fn(image_lr, lead_time_label): w1=img_shape_x // patch_shape[1], ) torch.cuda.nvtx.range_pop() - image_lr_patch = image_lr_patch.to(memory_format=torch.channels_last) + image_lr_patch = image_lr_patch #.to(memory_format=torch.channels_last) if net_reg: with nvtx.annotate("regression_model", color="yellow"): image_reg = regression_step( net=net_reg, img_lr=image_lr_patch, + labels=labels, latents_shape=( cfg.generation.seed_batch_size, img_out_channels, @@ -238,7 +241,7 @@ def generate_fn(image_lr, lead_time_label): rank_batches=rank_batches, img_lr=image_lr_patch.expand( cfg.generation.seed_batch_size, -1, -1, -1 - ).to(memory_format=torch.channels_last), + ), #.to(memory_format=torch.channels_last), rank=dist.rank, device=device, hr_mean=mean_hr, @@ -282,7 +285,10 @@ def generate_fn(image_lr, lead_time_label): else: return None else: - return image_out + #TODO do this for multi-gpu setting above too + if cfg.generation.inference_mode != "regression": + return image_out, image_reg + return image_out, None # generate images output_path = getattr(cfg.generation.io, "output_path", "./outputs") @@ -311,7 +317,7 @@ def generate_fn(image_lr, lead_time_label): end = torch.cuda.Event(enable_timing=True) times = dataset.time() - for image_tar, image_lr, index, *lead_time_label in iter(data_loader): + for image_tar, image_lr, labels, *lead_time_label in iter(data_loader): time_index += 1 if dist.rank == 0: logger0.info(f"starting index: {time_index}") @@ -327,10 +333,11 @@ def generate_fn(image_lr, lead_time_label): image_lr = ( image_lr.to(device=device) .to(torch.float32) - .to(memory_format=torch.channels_last) + #.to(memory_format=torch.channels_last) ) image_tar = image_tar.to(device=device).to(torch.float32) - image_out = generate_fn(image_lr,lead_time_label) + labels = labels.to(device).to(torch.float32).contiguous() + image_out, image_reg = generate_fn(image_lr,labels,lead_time_label) if dist.rank == 0: batch_size = image_out.shape[0] # write out data in a seperate thread so we don't hold up inferencing @@ -342,6 +349,7 @@ def generate_fn(image_lr, lead_time_label): image_out.cpu(), image_tar.cpu(), image_lr.cpu(), + image_reg.cpu(), ) ) end.record() @@ -368,19 +376,29 @@ def generate_fn(image_lr, lead_time_label): f.close() logger0.info("Generation Completed.") -def save_images(output_path, dataset, image_pred, image_hr, image_lr): +def save_images(output_path, dataset, image_pred, image_hr, image_lr, mean_pred): longitudes = dataset.longitude() latitudes = dataset.latitude() input_channels = dataset.input_channels() output_channels = dataset.output_channels() - image_pred = np.flip(dataset.denormalize_output(image_pred.numpy()),1).reshape(len(output_channels),-1) - image_hr = np.flip(dataset.denormalize_output(image_hr.numpy()),1).reshape(len(output_channels),-1) - image_lr = np.flip(dataset.denormalize_input(image_lr.numpy()),1).reshape(len(input_channels),-1) + image_pred = image_pred.numpy() + image_pred_final = np.flip(dataset.denormalize_output(image_pred[-1,::].squeeze()),1).reshape(len(output_channels),-1) + image_pred_first_step = np.flip(dataset.denormalize_output(image_pred[0,::].squeeze()),1).reshape(len(output_channels),-1) + image_pred_mid_step = np.flip(dataset.denormalize_output(image_pred[32,::].squeeze()),1).reshape(len(output_channels),-1) + image_hr = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze().numpy()),1).reshape(len(output_channels),-1) + image_lr = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze().numpy()),1).reshape(len(input_channels),-1) + if mean_pred is not None: + mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze().numpy()),1).reshape(len(output_channels),-1) + os.makedirs(output_path, exist_ok=True) for idx, channel in enumerate(output_channels): input_channel_idx = input_channels.index(channel) _plot_projection(longitudes,latitudes,image_lr[input_channel_idx,:],os.path.join(output_path,f'{channel.name}-lr.jpg')) _plot_projection(longitudes,latitudes,image_hr[idx,:],os.path.join(output_path,f'{channel.name}-hr.jpg')) - _plot_projection(longitudes,latitudes,image_pred[idx,:],os.path.join(output_path,f'{channel.name}-hr-pred.jpg')) + _plot_projection(longitudes,latitudes,image_pred_final[idx,:],os.path.join(output_path,f'{channel.name}-hr-pred.jpg')) + _plot_projection(longitudes,latitudes,image_pred_first_step[idx,:],os.path.join(output_path,f'{channel.name}-hr-pred-0.jpg')) + _plot_projection(longitudes,latitudes,image_pred_mid_step[idx,:],os.path.join(output_path,f'{channel.name}-hr-pred-mid.jpg')) + if mean_pred is not None: + _plot_projection(longitudes,latitudes,mean_pred[idx,:],os.path.join(output_path,f'{channel.name}-mean-pred.jpg')) def _plot_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, cmap=None, vmin = None, vmax = None): diff --git a/src/hirad/models/layers.py b/src/hirad/models/layers.py index ddb23b68..8612da7e 100644 --- a/src/hirad/models/layers.py +++ b/src/hirad/models/layers.py @@ -221,6 +221,8 @@ def forward(self, x): padding=f_pad, ) if w is not None: + #TODO during inference, model breaks here for some reason + # current fix is to disable torch.backends.cudnn.enabled = False x = torch.nn.functional.conv2d(x, w, padding=w_pad) if b is not None: x = x.add_(b.reshape(1, -1, 1, 1)) @@ -473,7 +475,6 @@ def forward(self, x, emb): torch.cuda.nvtx.range_push("UNetBlock") orig = x x = self.conv0(silu(self.norm0(x))) - params = self.affine(emb).unsqueeze(2).unsqueeze(3).to(x.dtype) if self.adaptive_scale: scale, shift = params.chunk(chunks=2, dim=1) diff --git a/src/hirad/train.sh b/src/hirad/train.sh new file mode 100644 index 00000000..a31cec06 --- /dev/null +++ b/src/hirad/train.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +#SBATCH --job-name="testrun" + +### HARDWARE ### +#SBATCH --partition=debug +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --gpus-per-node=1 +#SBATCH --cpus-per-task=72 +#SBATCH --time=00:30:00 +#SBATCH --no-requeue +#SBATCH --exclusive + +### OUTPUT ### +#SBATCH --output=/iopsstor/scratch/cscs/pstamenk/logs/regression_test.log +#SBATCH --error=/iopsstor/scratch/cscs/pstamenk/logs/regression_test.err + +### ENVIRONMENT #### +#SBATCH --uenv=pytorch/v2.6.0:/user-environment +#SBATCH --view=default +#SBATCH -A a-a122 + +# Choose method to initialize dist in pythorch +export DISTRIBUTED_INITIALIZATION_METHOD=SLURM + +MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" +echo "Master node : $MASTER_ADDR" +# Get IP for hostname. +MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" +echo "Master address : $MASTER_ADDR" +export MASTER_ADDR +export MASTER_PORT=29500 +echo "Master port: $MASTER_PORT" + +# Get number of physical cores using Python +PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") +# Use SLURM_NTASKS (number of processes to be launched by torchrun) +LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} +# Compute threads per process +OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) +export OMP_NUM_THREADS=$OMP_THREADS +echo "Physical cores: $PHYSICAL_CORES" +echo "Local processes: $LOCAL_PROCS" +echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" + +# python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml +srun bash -c " + . ./train_env/bin/activate + python src/hirad/training/train.py --config-name=training_era_cosmo_diffusion.yaml +" \ No newline at end of file diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 9ce619de..37e61107 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -213,7 +213,7 @@ def main(cfg: DictConfig) -> None: model.train().requires_grad_(True).to(dist.device) # TODO write summry from rank=0 possibly - # summary(model, input_size=[(4,img_out_channels,*img_shape),(4,img_in_channels,*img_shape),(4,1),(4,1)]) + # summary(model, input_size=[(1,img_out_channels,*img_shape),(1,img_in_channels,*img_shape),(1,1)]) if dist.rank==0 and not os.path.exists(os.path.join(checkpoint_dir, 'model_args.json')): with open(os.path.join(checkpoint_dir, f'model_args.json'), 'w') as f: diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index b158ec0c..4831bdd7 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -31,6 +31,7 @@ def regression_step( net: torch.nn.Module, img_lr: torch.Tensor, + labels: torch.Tensor, latents_shape: torch.Size, lead_time_label: torch.Tensor = None, ) -> torch.Tensor: @@ -50,15 +51,15 @@ def regression_step( torch.Tensor: Predicted output at the next time step. """ # Create a tensor of zeros with the given shape and move it to the appropriate device - x_hat = torch.zeros(latents_shape, dtype=torch.float64, device=net.device) - t_hat = torch.tensor(1.0, dtype=torch.float64, device=net.device) + x_hat = torch.zeros(latents_shape, dtype=img_lr.dtype, device=img_lr.device) + t_hat = torch.tensor(1.0, dtype=img_lr.dtype, device=img_lr.device).reshape((1,1,1,1)) # Perform regression on a single batch element with torch.inference_mode(): if lead_time_label is not None: - x = net(x_hat[0:1], img_lr, t_hat, lead_time_label=lead_time_label) + x = net(x_hat, img_lr, t_hat, labels, lead_time_label=lead_time_label) else: - x = net(x_hat[0:1], img_lr, t_hat) + x = net(x_hat, img_lr, t_hat, labels) # If the batch size is greater than 1, repeat the prediction if x_hat.shape[0] > 1: @@ -100,7 +101,7 @@ def diffusion_step( # TODO generalize the module and add defaults torch.Tensor: Generated images concatenated across batches. """ - img_lr = img_lr.to(memory_format=torch.channels_last) + img_lr = img_lr #.to(memory_format=torch.channels_last) # Handling of the high-res mean additional_args = {} @@ -128,7 +129,7 @@ def diffusion_step( # TODO generalize the module and add defaults img_shape[1], ], device=device, - ).to(memory_format=torch.channels_last) + )#.to(memory_format=torch.channels_last) with torch.inference_mode(): images = sampler_fn( diff --git a/src/hirad/utils/stochastic_sampler.py b/src/hirad/utils/stochastic_sampler.py index ddcf9cc7..ac5c13ba 100644 --- a/src/hirad/utils/stochastic_sampler.py +++ b/src/hirad/utils/stochastic_sampler.py @@ -292,8 +292,9 @@ def stochastic_sampler( img_lr: Tensor, class_labels: Optional[Tensor] = None, randn_like: Callable[[Tensor], Tensor] = torch.randn_like, - img_shape: int = 448, - patch_shape: int = 448, + img_shape: tuple[int,int] = (448,448), + patch_shape_x: int = 448, + patch_shape_y: int = 448, overlap_pix: int = 4, boundary_pix: int = 2, mean_hr: Optional[Tensor] = None, @@ -360,12 +361,13 @@ def stochastic_sampler( "Proposed EDM sampler (Algorithm 2) with minor changes to enable super-resolution." sigma_min = max(sigma_min, net.sigma_min) sigma_max = min(sigma_max, net.sigma_max) - if isinstance(img_shape, tuple): - img_shape_y, img_shape_x = img_shape - else: - img_shape_x = img_shape_y = img_shape - if patch_shape > img_shape_x or patch_shape > img_shape_y: - patch_shape = min(img_shape_x, img_shape_y) + # if isinstance(img_shape, tuple): + # img_shape_y, img_shape_x = img_shape + # else: + # img_shape_x = img_shape_y = img_shape + img_shape_x, img_shape_y = img_shape + patch_shape_x = min(img_shape_x, patch_shape_x) + patch_shape_y = min(img_shape_y, patch_shape_y) # Time step discretization. step_indices = torch.arange(num_steps, dtype=torch.float64, device=latents.device) @@ -394,16 +396,16 @@ def stochastic_sampler( global_index = None # input and position padding + patching - if patch_shape != img_shape_x or patch_shape != img_shape_y: + if patch_shape_x != img_shape_x or patch_shape_y != img_shape_y: input_interp = torch.nn.functional.interpolate( - img_lr, (patch_shape, patch_shape), mode="bilinear" + img_lr, (patch_shape_x, patch_shape_y), mode="bilinear" ) x_lr = image_batching( x_lr, img_shape_y, img_shape_x, - patch_shape, - patch_shape, + patch_shape_x, + patch_shape_y, batch_size, overlap_pix, boundary_pix, @@ -413,8 +415,8 @@ def stochastic_sampler( grid.float(), img_shape_y, img_shape_x, - patch_shape, - patch_shape, + patch_shape_x, + patch_shape_y, batch_size, overlap_pix, boundary_pix, @@ -433,13 +435,13 @@ def stochastic_sampler( # Euler step. Perform patching operation on score tensor if patch-based generation is used # denoised = net(x_hat, t_hat, class_labels,lead_time_label=lead_time_label).to(torch.float64) #x_lr - if patch_shape != img_shape_x or patch_shape != img_shape_y: + if patch_shape_x != img_shape_x or patch_shape_y != img_shape_y: x_hat_batch = image_batching( x_hat, img_shape_y, img_shape_x, - patch_shape, - patch_shape, + patch_shape_x, + patch_shape_y, batch_size, overlap_pix, boundary_pix, @@ -461,6 +463,12 @@ def stochastic_sampler( global_index=global_index, ).to(torch.float64) else: + # print("Sizes") + # print(x_hat_batch.shape) + # print(x_lr.shape) + # print(t_hat) + # print(class_labels) + # print(global_index) denoised = net( x_hat_batch, x_lr, @@ -468,14 +476,14 @@ def stochastic_sampler( class_labels, global_index=global_index, ).to(torch.float64) - if patch_shape != img_shape_x or patch_shape != img_shape_y: + if patch_shape_x != img_shape_x or patch_shape_y != img_shape_y: denoised = image_fuse( denoised, img_shape_y, img_shape_x, - patch_shape, - patch_shape, + patch_shape_x, + patch_shape_y, batch_size, overlap_pix, boundary_pix, @@ -485,13 +493,13 @@ def stochastic_sampler( # Apply 2nd order correction. if i < num_steps - 1: - if patch_shape != img_shape_x or patch_shape != img_shape_y: + if patch_shape_x != img_shape_x or patch_shape_y != img_shape_y: x_next_batch = image_batching( x_next, img_shape_y, img_shape_x, - patch_shape, - patch_shape, + patch_shape_x, + patch_shape_y, batch_size, overlap_pix, boundary_pix, @@ -517,13 +525,13 @@ def stochastic_sampler( class_labels, global_index=global_index, ).to(torch.float64) - if patch_shape != img_shape_x or patch_shape != img_shape_y: + if patch_shape_x != img_shape_x or patch_shape_y != img_shape_y: denoised = image_fuse( denoised, img_shape_y, img_shape_x, - patch_shape, - patch_shape, + patch_shape_x, + patch_shape_y, batch_size, overlap_pix, boundary_pix, From 83716f45821e6f15f04422b140bc90f493a3be79 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Thu, 15 May 2025 15:55:32 +0200 Subject: [PATCH 036/302] clean up --- src/hirad/conf/generation/era_cosmo.yaml | 11 ++--- src/hirad/testrun.sh | 51 ------------------------ 2 files changed, 6 insertions(+), 56 deletions(-) delete mode 100644 src/hirad/testrun.sh diff --git a/src/hirad/conf/generation/era_cosmo.yaml b/src/hirad/conf/generation/era_cosmo.yaml index 51795203..a0c5a407 100644 --- a/src/hirad/conf/generation/era_cosmo.yaml +++ b/src/hirad/conf/generation/era_cosmo.yaml @@ -2,7 +2,7 @@ num_ensembles: 64 # Number of ensembles to generate per input seed_batch_size: 1 # Size of the batched inference -inference_mode: regression +inference_mode: all # Choose between "all" (regression + diffusion), "regression" or "diffusion" # Patch size. Patch-based sampling will be utilized if these dimensions differ from # img_shape_x and img_shape_y @@ -11,7 +11,7 @@ overlap_pixels: 0 boundary_pixels: 0 # Number of boundary pixels to be cropped out. 2 is recommanded to address the boundary # artifact. -hr_mean_conditioning: False +hr_mean_conditioning: True sample_res: full # Sampling resolution times_range: null @@ -19,10 +19,10 @@ times: - 20160101-0000 perf: - force_fp16: false + force_fp16: False # Whether to force fp16 precision for the model. If false, it'll use the precision # specified upon training. - use_torch_compile: false + use_torch_compile: False # whether to use torch.compile on the diffusion model # this will make the first time stamp generation very slow due to compilation overheads # but will significantly speed up subsequent inference runs @@ -31,8 +31,9 @@ perf: # To support multiple workers a threadsafe version of the netCDF library must be used io: - res_ckpt_path: null + res_ckpt_path: /iopsstor/scratch/cscs/pstamenk/outputs/diffusion_test/checkpoints_diffusion # Checkpoint filename for the diffusion model reg_ckpt_path: /iopsstor/scratch/cscs/pstamenk/outputs/regression_overfit/checkpoints_regression + # reg_ckpt_path: /iopsstor/scratch/cscs/pstamenk/outputs/regression_test/checkpoints_regression # Checkpoint filename for the mean predictor model output_path: ./images \ No newline at end of file diff --git a/src/hirad/testrun.sh b/src/hirad/testrun.sh deleted file mode 100644 index ac631c81..00000000 --- a/src/hirad/testrun.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -#SBATCH --job-name="testrun" - -### HARDWARE ### -#SBATCH --partition=debug -#SBATCH --nodes=1 -#SBATCH --ntasks-per-node=1 -#SBATCH --gpus-per-node=1 -#SBATCH --cpus-per-task=72 -#SBATCH --time=00:30:00 -#SBATCH --no-requeue -#SBATCH --exclusive - -### OUTPUT ### -#SBATCH --output=/iopsstor/scratch/cscs/pstamenk/logs/regression_test.log -#SBATCH --error=/iopsstor/scratch/cscs/pstamenk/logs/regression_test.err - -### ENVIRONMENT #### -#SBATCH --uenv=pytorch/v2.6.0:/user-environment -#SBATCH --view=default -#SBATCH -A a-a01 - -# Choose method to initialize dist in pythorch -export DISTRIBUTED_INITIALIZATION_METHOD=SLURM - -MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" -echo "Master node : $MASTER_ADDR" -# Get IP for hostname. -MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" -echo "Master address : $MASTER_ADDR" -export MASTER_ADDR -export MASTER_PORT=29500 -echo "Master port: $MASTER_PORT" - -# Get number of physical cores using Python -PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") -# Use SLURM_NTASKS (number of processes to be launched by torchrun) -LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} -# Compute threads per process -OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) -export OMP_NUM_THREADS=$OMP_THREADS -echo "Physical cores: $PHYSICAL_CORES" -echo "Local processes: $LOCAL_PROCS" -echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" - -# python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml -srun bash -c " - . ./train_env/bin/activate - python src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml -" \ No newline at end of file From 69f10ddc0f3964eebb116233489d0d55f6b34077 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 15 May 2025 16:45:29 +0200 Subject: [PATCH 037/302] Add power spectrum plots --- src/hirad/eval/metrics.py | 39 +++++++- src/hirad/eval/plotting.py | 11 ++- src/hirad/eval/run_scoring.py | 165 +++++++++++++++++++++++++++------- 3 files changed, 178 insertions(+), 37 deletions(-) diff --git a/src/hirad/eval/metrics.py b/src/hirad/eval/metrics.py index e6e1afb5..1170ea16 100644 --- a/src/hirad/eval/metrics.py +++ b/src/hirad/eval/metrics.py @@ -1,6 +1,10 @@ +import logging + import numpy as np import torch +from scipy.signal import periodogram + # set up MAE calculation to be run for each channel for a given date/time (for target COSMO, prediction, and ERA interpolated) @@ -9,7 +13,7 @@ # Extracted from physicsnemo/examples/weather/regen/paper_figures/score_inference.py def absolute_error(pred, target) -> tuple[float, np.ndarray]: - return torch.abs(pred-target) + return np.abs(pred-target) def compute_mae(pred, target): # Exclude any target NaNs (not expected, but precautionary) @@ -20,5 +24,34 @@ def compute_mae(pred, target): ae = absolute_error(pred, target) - return torch.mean(absolute_error(pred, target)), ae - + # TODO, consider adding axis=-1 to choose what axis to average + return np.mean(absolute_error(pred, target)), ae + +def average_power_spectrum(data: np.ndarray, d=2.0): # d=2km by default + """ + Compute the average power spectrum of a data array. + + This function calculates the power spectrum for each row of the input data and + then averages them to obtain the overall power spectrum, repeating until + dimensionality is reduced to 1D. + The power spectrum represents the distribution of signal power as a function of frequency. + + Parameters: + data (numpy.ndarray): Input data array. + d (float): Sampling interval (time between data points). + + Returns: + tuple: A tuple containing the frequency values and the average power spectrum. + - freqs (numpy.ndarray): Frequency values corresponding to the power spectrum. + - power_spectra (numpy.ndarray): Average power spectrum of the input data. + """ + # Compute the power spectrum along the highest dimension for each row + freqs, power_spectra = periodogram(data, fs=1 / d, axis=-1) + logging.info(f'freqs.shape={freqs.shape}, power_spectra.shape={power_spectra.shape}') + + # Average along the first dimension + while power_spectra.ndim > 1: + power_spectra = power_spectra.mean(axis=0) + logging.info(f'power spectra shape={power_spectra.shape}') + + return freqs, power_spectra diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index 262109f8..1ca11c2e 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -15,9 +15,16 @@ def plot_error_projection(values: np.array, latitudes: np.array, longitudes: np. plt.savefig(filename) plt.close('all') -def plot_power_spectrum(x, filename): +def plot_power_spectra(freqs: dict, spec: dict, channel_name, filename): fig = plt.figure() - plt.psd(x) + for k in freqs.keys(): + plt.loglog(freqs[k], spec[k], label=k) + plt.title(channel_name) + plt.legend() + plt.xlabel("Frequency (1/km)") + plt.ylabel("Power Spectrum") + plt.ylim(bottom=1e-1) + #plt.psd(x) logging.info(f'plotting values to {filename}') plt.savefig(filename) plt.close('all') \ No newline at end of file diff --git a/src/hirad/eval/run_scoring.py b/src/hirad/eval/run_scoring.py index df37a9ac..da98a044 100644 --- a/src/hirad/eval/run_scoring.py +++ b/src/hirad/eval/run_scoring.py @@ -2,23 +2,22 @@ import sys import metrics +import numpy as np import plotting import torch import yaml +X = 352 # length of grid from N-S +Y = 544 # length of grid from E-W def main(): - if len(sys.argv) < 4: - raise ValueError('Expected call run_scoring.py [input data directory] [predictions directory] [date]') + # TODO: Better arg parsing. + if len(sys.argv) < 3: + raise ValueError('Expected call run_scoring.py [input data directory] [predictions directory] [output plot directory]') input_directory = sys.argv[1] predictions_directory = sys.argv[2] - date = sys.argv[3] - - target = torch.load(os.path.join(input_directory, 'cosmo', date), weights_only=False) - baseline = torch.load(os.path.join(input_directory, 'era-interpolated', date), weights_only=False) - prediction = torch.load(os.path.join(predictions_directory, date), weights_only=False) - lat_lon = torch.load(os.path.join(input_directory, 'info', 'cosmo-lat-lon'), weights_only=False) + output_directory = sys.argv[3] with open(os.path.join(input_directory, 'info', 'cosmo.yaml')) as cosmo_file: cosmo_config = yaml.safe_load(cosmo_file) @@ -28,37 +27,139 @@ def main(): era_config = yaml.safe_load(era_file) input_channels = era_config['select'] - # Reshape predictions, if necessary - # target is shape [channels, ensembles, points] - # prediction is shape [channels, ensembles, x, y] - prediction_1d = prediction.reshape(*target.shape) - prediction_2d = prediction.reshape(prediction.shape[0],352,544) - + lat_lon = torch.load(os.path.join(input_directory, 'info', 'cosmo-lat-lon'), weights_only=False) latitudes = lat_lon[:,0] longitudes = lat_lon[:,1] - - # convert to torch - target = torch.from_numpy(target) - baseline = torch.from_numpy(baseline) - prediction_1d = torch.from_numpy(prediction_1d) - # plot errors + # Iterate over all files in the ground truth directory + files = os.listdir(os.path.join(input_directory, 'cosmo')) + files = sorted(files) + + + # Plot power spectra + # TODO: Handle ensembles + prediction_tensor = np.ndarray([len(files), len(target_channels), X, Y]) + baseline_tensor = np.ndarray([len(files), len(input_channels), X, Y]) + target_tensor = np.ndarray([len(files), len(target_channels), X, Y]) + + for i in range(len(files)): + datetime = files[i] + target = torch.load(os.path.join(input_directory, 'cosmo', datetime), weights_only=False) + baseline = torch.load(os.path.join(input_directory, 'era-interpolated', datetime), weights_only=False) + prediction = torch.load(os.path.join(predictions_directory, datetime), weights_only=False) + + # TODO: Handle ensembles + prediction_1d = prediction.reshape(prediction.shape[0], X*Y) + prediction_2d = prediction.reshape(prediction.shape[0], X, Y) + + baseline_1d = baseline.reshape(baseline.shape[0], X*Y) + baseline_2d = baseline.reshape(baseline.shape[0], X, Y) + + target_1d = target.reshape(target.shape[0], X*Y) + target_2d = target.reshape(target.shape[0], X, Y) + + baseline_tensor[i, :] = baseline_2d + prediction_tensor[i, :] = prediction_2d + target_tensor[i,:] = target_2d + + + # Calc spectra for t_c in range(len(target_channels)): b_c = input_channels.index(target_channels[t_c]) + freqs = {} + power = {} if b_c > -1: - baseline_mae, baseline_errors = metrics.compute_mae(baseline[b_c,:,:], target[t_c,:,:]) - plotting.plot_error_projection(baseline_errors, latitudes, longitudes, os.path.join('plots/errors/', 'baseline', target_channels[t_c] + '-' + date)) - plotting.plot_power_spectrum(baseline[b_c,:,:], os.path.join('plots/spectra/', 'baseline', target_channels[t_c] + date)) - prediction_mae, prediction_errors = metrics.compute_mae(prediction_1d[t_c,:,:], target[t_c,:,:]) - plotting.plot_error_projection(prediction_errors, latitudes, longitudes, os.path.join('plots/errors/', 'prediction', target_channels[t_c] + '-' + date)) - plotting.plot_power_spectrum(prediction[t_c,0,:], os.path.join('plots/spectra/', 'prediction', target_channels[t_c] + date)) - plotting.plot_power_spectrum(prediction_2d[t_c,:,:], os.path.join('plots/spectra/', 'prediction2d', target_channels[t_c] + date)) - print(f'baseline MAE={baseline_mae}, prediction MAE={prediction_mae}') + b_freq, b_power = metrics.average_power_spectrum(baseline_tensor[:,b_c,:,:].squeeze(), 2.0) + freqs['baseline'] = b_freq + power['baseline'] = b_power + #plotting.plot_power_spectrum(b_freq, b_power, target_channels[t_c], os.path.join('plots/spectra/baseline2dt', target_channels[t_c] + '-all_dates')) + t_freq, t_power = metrics.average_power_spectrum(target_tensor[:,t_c,:,:].squeeze(), 2.0) + freqs['target'] = t_freq + power['target'] = t_power + #p_freq, p_power = metrics.average_power_spectrum(prediction_tensor[:,t_c,:,:].squeeze(), 2.0) + #freqs['prediction'] = p_freq + #power['prediction'] = p_power + plotting.plot_power_spectra(freqs, power, target_channels[t_c], os.path.join(output_directory, 'spectra', target_channels[t_c] + '-alldates')) + + # store MAE as tensor of date:channel:ensembles:points + # TODO: Handle ensembles + baseline_absolute_error = np.ndarray([len(files),len(target_channels),1,X*Y]) + prediction_absolute_error = np.ndarray([len(files),len(target_channels),1,X*Y]) + + for i in range(len(files)): + datetime = files[i] + target = torch.load(os.path.join(input_directory, 'cosmo', datetime), weights_only=False) + baseline = torch.load(os.path.join(input_directory, 'era-interpolated', datetime), weights_only=False) + prediction = torch.load(os.path.join(predictions_directory, datetime), weights_only=False) + + + prediction_1d = prediction.reshape(prediction.shape[0], 1, X*Y) + prediction_2d = prediction.reshape(prediction.shape[0], 1, X, Y) + + # Get MAE + for t_c in range(len(target_channels)): + b_c = input_channels.index(target_channels[t_c]) + if b_c > -1: + _, baseline_errors = metrics.compute_mae(baseline[b_c,:,:], target[t_c,:,:]) + baseline_absolute_error[i, t_c, :, :] = baseline_errors + #plotting.plot_error_projection(baseline_errors, latitudes, longitudes, os.path.join('plots/errors/', 'baseline', target_channels[t_c] + '-' + date)) + #plotting.plot_power_spectrum(baseline[b_c,:,:], os.path.join('plots/spectra/', 'baseline', target_channels[t_c] + date)) + _, prediction_errors = metrics.compute_mae(prediction_1d[t_c,:,:], target[t_c,:,:]) + prediction_absolute_error[i, t_c, :, :] = prediction_errors + #plotting.plot_error_projection(prediction_errors, latitudes, longitudes, os.path.join('plots/errors/', 'prediction', target_channels[t_c] + '-' + date)) + #plotting.plot_power_spectrum(prediction[t_c,0,:], os.path.join('plots/spectra/', 'prediction', target_channels[t_c] + date)) + #plotting.plot_power_spectrum(prediction_2d[t_c,:,:], os.path.join('plots/spectra/', 'prediction2d', target_channels[t_c] + date)) + + + print(f'baseline_absolute_error.shape={baseline_absolute_error.shape}, prediction_absolute_error.shape={prediction_absolute_error.shape}') + # Average errors over ensembles + baseline_mae = np.mean(baseline_absolute_error, axis=2) + prediction_mae = np.mean(prediction_absolute_error, axis=2) + + # Average errors over time + baseline_mae = np.mean(baseline_mae, axis=0) + prediction_mae = np.mean(prediction_mae, axis = 0) + + print(f'baseline mean error = {np.mean(baseline_mae, axis=-1)}') + print(f'prediction mean error = {np.mean(prediction_mae, axis=-1)}') + + # Plot the mean error onto the grid. + for t_c in range(len(target_channels)): + plotting.plot_error_projection(baseline_mae[t_c,:], latitudes, longitudes, os.path.join(output_directory, 'baseline-error' + target_channels[t_c] + '-' + 'average_over_time')) + plotting.plot_error_projection(prediction_mae[t_c,:], latitudes, longitudes, os.path.join(output_directory, 'prediction-error' + target_channels[t_c] + '-' + 'average_over_time')) + + + + + #for i in range(4): + # dates = ['20160101-0000', '20160115-0000', '20160201-0000', '20160215-0000'] + # pred = torch.load(os.path.join(predictions_directory, dates[i]), weights_only=False) + # base = torch.load(os.path.join(input_directory, 'era-interpolated', dates[i]), weights_only=False) + # pred_2d = pred.reshape(pred.shape[0],352,544) + # base_2d = base.reshape(baseline.shape[0],352,544) + # base_2d = np.transpose(base_2d, (0,-1,-2)) + # preds_tensor[i,:] = pred_2d + # baseline_tensor[i,:] = base_2d + #for t_c in range(len(target_channels)): + # freq, power = metrics.average_power_spectrum(baseline_tensor[:,t_c,:,:].squeeze(), 2) + # b_c = input_channels.index(target_channels[t_c]) + ## if b_c > -1: + # plotting.plot_power_spectrum(freq, power, target_channels[t_c], os.path.join('plots/spectra/baseline2dt', target_channels[t_c] + date)) + + + # plot errors + #for t_c in range(len(target_channels)): + # b_c = input_channels.index(target_channels[t_c]) + # if b_c > -1: + # baseline_mae, baseline_errors = metrics.compute_mae(baseline[b_c,:,:], target[t_c,:,:]) + # plotting.plot_error_projection(baseline_errors, latitudes, longitudes, os.path.join('plots/errors/', 'baseline', target_channels[t_c] + '-' + date)) + # #plotting.plot_power_spectrum(baseline[b_c,:,:], os.path.join('plots/spectra/', 'baseline', target_channels[t_c] + date)) + # prediction_mae, prediction_errors = metrics.compute_mae(prediction_1d[t_c,:,:], target[t_c,:,:]) + # plotting.plot_error_projection(prediction_errors, latitudes, longitudes, os.path.join('plots/errors/', 'prediction', target_channels[t_c] + '-' + date)) + # #plotting.plot_power_spectrum(prediction[t_c,0,:], os.path.join('plots/spectra/', 'prediction', target_channels[t_c] + date)) + #plotting.plot_power_spectrum(prediction_2d[t_c,:,:], os.path.join('plots/spectra/', 'prediction2d', target_channels[t_c] + date)) + - # Plot power spectra - freq, power = metrics.compute_power_spectrum(prediction, 1) - plotting.plot_power_spectrum(prediction, 'plots/errors/powerspec-prediction') - plotting.plot_power_spectrum(prediction, 'plots/errors/powerspec-prediction') From b4c97c51baf4d543f6365d193afeab68b01dd317 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 15 May 2025 16:46:17 +0200 Subject: [PATCH 038/302] clean up a bit --- src/hirad/eval/run_scoring.py | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/src/hirad/eval/run_scoring.py b/src/hirad/eval/run_scoring.py index da98a044..fd7c2ab1 100644 --- a/src/hirad/eval/run_scoring.py +++ b/src/hirad/eval/run_scoring.py @@ -129,39 +129,5 @@ def main(): plotting.plot_error_projection(prediction_mae[t_c,:], latitudes, longitudes, os.path.join(output_directory, 'prediction-error' + target_channels[t_c] + '-' + 'average_over_time')) - - - #for i in range(4): - # dates = ['20160101-0000', '20160115-0000', '20160201-0000', '20160215-0000'] - # pred = torch.load(os.path.join(predictions_directory, dates[i]), weights_only=False) - # base = torch.load(os.path.join(input_directory, 'era-interpolated', dates[i]), weights_only=False) - # pred_2d = pred.reshape(pred.shape[0],352,544) - # base_2d = base.reshape(baseline.shape[0],352,544) - # base_2d = np.transpose(base_2d, (0,-1,-2)) - # preds_tensor[i,:] = pred_2d - # baseline_tensor[i,:] = base_2d - #for t_c in range(len(target_channels)): - # freq, power = metrics.average_power_spectrum(baseline_tensor[:,t_c,:,:].squeeze(), 2) - # b_c = input_channels.index(target_channels[t_c]) - ## if b_c > -1: - # plotting.plot_power_spectrum(freq, power, target_channels[t_c], os.path.join('plots/spectra/baseline2dt', target_channels[t_c] + date)) - - - # plot errors - #for t_c in range(len(target_channels)): - # b_c = input_channels.index(target_channels[t_c]) - # if b_c > -1: - # baseline_mae, baseline_errors = metrics.compute_mae(baseline[b_c,:,:], target[t_c,:,:]) - # plotting.plot_error_projection(baseline_errors, latitudes, longitudes, os.path.join('plots/errors/', 'baseline', target_channels[t_c] + '-' + date)) - # #plotting.plot_power_spectrum(baseline[b_c,:,:], os.path.join('plots/spectra/', 'baseline', target_channels[t_c] + date)) - # prediction_mae, prediction_errors = metrics.compute_mae(prediction_1d[t_c,:,:], target[t_c,:,:]) - # plotting.plot_error_projection(prediction_errors, latitudes, longitudes, os.path.join('plots/errors/', 'prediction', target_channels[t_c] + '-' + date)) - # #plotting.plot_power_spectrum(prediction[t_c,0,:], os.path.join('plots/spectra/', 'prediction', target_channels[t_c] + date)) - #plotting.plot_power_spectrum(prediction_2d[t_c,:,:], os.path.join('plots/spectra/', 'prediction2d', target_channels[t_c] + date)) - - - - - if __name__ == "__main__": main() \ No newline at end of file From 90c7e28719a83d8534c102240faa756b0883f01e Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 15 May 2025 16:47:28 +0200 Subject: [PATCH 039/302] clean up a bit --- src/hirad/eval/run_scoring.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/hirad/eval/run_scoring.py b/src/hirad/eval/run_scoring.py index fd7c2ab1..4f2fcd83 100644 --- a/src/hirad/eval/run_scoring.py +++ b/src/hirad/eval/run_scoring.py @@ -76,7 +76,8 @@ def main(): t_freq, t_power = metrics.average_power_spectrum(target_tensor[:,t_c,:,:].squeeze(), 2.0) freqs['target'] = t_freq power['target'] = t_power - #p_freq, p_power = metrics.average_power_spectrum(prediction_tensor[:,t_c,:,:].squeeze(), 2.0) + p_freq, p_power = metrics.average_power_spectrum(prediction_tensor[:,t_c,:,:].squeeze(), 2.0) + # TODO: Uncomment when we have predictions #freqs['prediction'] = p_freq #power['prediction'] = p_power plotting.plot_power_spectra(freqs, power, target_channels[t_c], os.path.join(output_directory, 'spectra', target_channels[t_c] + '-alldates')) @@ -102,14 +103,9 @@ def main(): if b_c > -1: _, baseline_errors = metrics.compute_mae(baseline[b_c,:,:], target[t_c,:,:]) baseline_absolute_error[i, t_c, :, :] = baseline_errors - #plotting.plot_error_projection(baseline_errors, latitudes, longitudes, os.path.join('plots/errors/', 'baseline', target_channels[t_c] + '-' + date)) - #plotting.plot_power_spectrum(baseline[b_c,:,:], os.path.join('plots/spectra/', 'baseline', target_channels[t_c] + date)) _, prediction_errors = metrics.compute_mae(prediction_1d[t_c,:,:], target[t_c,:,:]) prediction_absolute_error[i, t_c, :, :] = prediction_errors - #plotting.plot_error_projection(prediction_errors, latitudes, longitudes, os.path.join('plots/errors/', 'prediction', target_channels[t_c] + '-' + date)) - #plotting.plot_power_spectrum(prediction[t_c,0,:], os.path.join('plots/spectra/', 'prediction', target_channels[t_c] + date)) - #plotting.plot_power_spectrum(prediction_2d[t_c,:,:], os.path.join('plots/spectra/', 'prediction2d', target_channels[t_c] + date)) - + print(f'baseline_absolute_error.shape={baseline_absolute_error.shape}, prediction_absolute_error.shape={prediction_absolute_error.shape}') # Average errors over ensembles From a9056027b6ea337c6417a2997e6a9419740e33b8 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Thu, 15 May 2025 17:49:32 +0200 Subject: [PATCH 040/302] add readme for training --- README.md | 110 ++++++++++++++++++ .../conf/training/era_cosmo_diffusion.yaml | 2 +- src/hirad/train_diffusion.sh | 45 +++++++ src/hirad/{train.sh => train_regression.sh} | 16 +-- 4 files changed, 161 insertions(+), 12 deletions(-) create mode 100644 src/hirad/train_diffusion.sh rename src/hirad/{train.sh => train_regression.sh} (73%) diff --git a/README.md b/README.md index e69de29b..3e660623 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,110 @@ +# HiRAD-Gen + +HiRAD-Gen is short for high-resolution atmospheric downscaling using generative models. This repository contains the code and configuration required to train and use the model. + +## Installation (Alps) + +To set up the environment for **HiRAD-Gen** on Alps supercomputer, follow these steps: + +1. **Start the PyTorch user environment**: + ```bash + uenv start pytorch/v2.6.0:v1 --view=default + ``` + +2. **Create a Python virtual environment** (replace `{env_name}` with your desired environment name): + ```bash + python -m venv ./{env_name} + ``` + +3. **Activate the virtual environment**: + ```bash + source ./{env_name}/bin/activate + ``` + +4. **Install project dependencies**: + ```bash + pip install -e . + ``` + +This will set up the necessary environment to run HiRAD-Gen within the Alps infrastructure. + +## Run regression model training (Alps) + +1. Script for running the training of regression model is in `src/hirad/train_regression.sh`. +Inside this script set the following: +```bash +### OUTPUT ### +#SBATCH --output=your_path_to_output_log +#SBATCH --error=your_path_to_output_error +``` +```bash +#SBATCH -A your_compute_group +``` +```bash +srun bash -c " + . ./{your_env_name}/bin/activate + python src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml +" +``` + +2. Setup the following config files in `src/hirad/conf`: + +- In `training_era_cosmo_regression.yaml` set: +``` +hydra: + run: + dir: your_path_to_save_training_output +``` +- In `training/era_cosmo_regression.yaml` set: +``` +hp: + training_duration: number of samples to train for (set to 4 for debugging, 512 fits into 30 minutes on 1 gpu with total_batch_size: 4) +``` +- In `dataset/era_cosmo.yaml` set the `dataset_path` if different from default. + +3. Submit the job with: +```bash +sbatch src/hirad/train_regression.sh +``` + +## Run diffusion model training (Alps) +Before training diffusion model, checkpoint for regression model has to exist. + +1. Script for running the training of diffusion model is in `src/hirad/train_diffusion.sh`. +Inside this script set the following: +```bash +### OUTPUT ### +#SBATCH --output=your_path_to_output_log +#SBATCH --error=your_path_to_output_error +``` +```bash +#SBATCH -A your_compute_group +``` +```bash +srun bash -c " + . ./{your_env_name}/bin/activate + python src/hirad/training/train.py --config-name=training_era_cosmo_diffusion.yaml +" +``` + +2. Setup the following config files in `src/hirad/conf`: + +- In `training_era_cosmo_diffusion.yaml` set: +``` +hydra: + run: + dir: your_path_to_save_training_output +``` +- In `training/era_cosmo_regression.yaml` set: +``` +hp: + training_duration: number of samples to train for (set to 4 for debugging, 512 fits into 30 minutes on 1 gpu with total_batch_size: 4) +io: + regression_checkpoint_path: path_to_directory_containing_regression_training_model_checkpoints +``` +- In `dataset/era_cosmo.yaml` set the `dataset_path` if different from default. + +3. Submit the job with: +```bash +sbatch src/hirad/train_diffusion.sh +``` \ No newline at end of file diff --git a/src/hirad/conf/training/era_cosmo_diffusion.yaml b/src/hirad/conf/training/era_cosmo_diffusion.yaml index b06ec611..f8d19e66 100644 --- a/src/hirad/conf/training/era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training/era_cosmo_diffusion.yaml @@ -29,7 +29,7 @@ perf: io: regression_checkpoint_path: /iopsstor/scratch/cscs/pstamenk/outputs/regression_overfit/checkpoints_regression # Where to load the regression checkpoint - print_progress_freq: 32 + print_progress_freq: 128 # How often to print progress save_checkpoint_freq: 5000 # How often to save the checkpoints, measured in number of processed samples diff --git a/src/hirad/train_diffusion.sh b/src/hirad/train_diffusion.sh new file mode 100644 index 00000000..cf2f88f3 --- /dev/null +++ b/src/hirad/train_diffusion.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +#SBATCH --job-name="testrun" + +### HARDWARE ### +#SBATCH --partition=debug +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --gpus-per-node=1 +#SBATCH --cpus-per-task=72 +#SBATCH --time=00:30:00 +#SBATCH --no-requeue +#SBATCH --exclusive + +### OUTPUT ### +#SBATCH --output=/iopsstor/scratch/cscs/pstamenk/logs/diffusion.log +#SBATCH --error=/iopsstor/scratch/cscs/pstamenk/logs/diffusion.err + +### ENVIRONMENT #### +#SBATCH --uenv=pytorch/v2.6.0:/user-environment +#SBATCH --view=default +#SBATCH -A a-a122 + +# Choose method to initialize dist in pythorch +export DISTRIBUTED_INITIALIZATION_METHOD=SLURM + +# Get master node. +MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" +# Get IP for hostname. +MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" +export MASTER_ADDR +export MASTER_PORT=29500 + +# Get number of physical cores using Python +PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") +LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} +# Compute cores per process +OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) +export OMP_NUM_THREADS=$OMP_THREADS + +# python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml +srun bash -c " + . ./train_env/bin/activate + python src/hirad/training/train.py --config-name=training_era_cosmo_diffusion.yaml +" \ No newline at end of file diff --git a/src/hirad/train.sh b/src/hirad/train_regression.sh similarity index 73% rename from src/hirad/train.sh rename to src/hirad/train_regression.sh index a31cec06..c0654773 100644 --- a/src/hirad/train.sh +++ b/src/hirad/train_regression.sh @@ -13,8 +13,8 @@ #SBATCH --exclusive ### OUTPUT ### -#SBATCH --output=/iopsstor/scratch/cscs/pstamenk/logs/regression_test.log -#SBATCH --error=/iopsstor/scratch/cscs/pstamenk/logs/regression_test.err +#SBATCH --output=/iopsstor/scratch/cscs/pstamenk/logs/regression.log +#SBATCH --error=/iopsstor/scratch/cscs/pstamenk/logs/regression.err ### ENVIRONMENT #### #SBATCH --uenv=pytorch/v2.6.0:/user-environment @@ -24,28 +24,22 @@ # Choose method to initialize dist in pythorch export DISTRIBUTED_INITIALIZATION_METHOD=SLURM +# Get master node. MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" -echo "Master node : $MASTER_ADDR" # Get IP for hostname. MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" -echo "Master address : $MASTER_ADDR" export MASTER_ADDR export MASTER_PORT=29500 -echo "Master port: $MASTER_PORT" # Get number of physical cores using Python PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") -# Use SLURM_NTASKS (number of processes to be launched by torchrun) LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} -# Compute threads per process +# Compute cores per process OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) export OMP_NUM_THREADS=$OMP_THREADS -echo "Physical cores: $PHYSICAL_CORES" -echo "Local processes: $LOCAL_PROCS" -echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" # python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml srun bash -c " . ./train_env/bin/activate - python src/hirad/training/train.py --config-name=training_era_cosmo_diffusion.yaml + python src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml " \ No newline at end of file From 573dc2387af0851ee16de51c6bbda4e16519d57d Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 16 May 2025 13:16:20 +0200 Subject: [PATCH 041/302] update readme for inference --- README.md | 65 +++++++++++++++++-- .../conf/training_era_cosmo_diffusion.yaml | 4 +- .../conf/training_era_cosmo_regression.yaml | 2 +- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3e660623..b0dbd2eb 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,9 @@ To set up the environment for **HiRAD-Gen** on Alps supercomputer, follow these This will set up the necessary environment to run HiRAD-Gen within the Alps infrastructure. -## Run regression model training (Alps) +## Training + +### Run regression model training (Alps) 1. Script for running the training of regression model is in `src/hirad/train_regression.sh`. Inside this script set the following: @@ -47,7 +49,7 @@ srun bash -c " " ``` -2. Setup the following config files in `src/hirad/conf`: +2. Set up the following config files in `src/hirad/conf`: - In `training_era_cosmo_regression.yaml` set: ``` @@ -67,7 +69,7 @@ hp: sbatch src/hirad/train_regression.sh ``` -## Run diffusion model training (Alps) +### Run diffusion model training (Alps) Before training diffusion model, checkpoint for regression model has to exist. 1. Script for running the training of diffusion model is in `src/hirad/train_diffusion.sh`. @@ -87,7 +89,7 @@ srun bash -c " " ``` -2. Setup the following config files in `src/hirad/conf`: +2. Set up the following config files in `src/hirad/conf`: - In `training_era_cosmo_diffusion.yaml` set: ``` @@ -107,4 +109,59 @@ io: 3. Submit the job with: ```bash sbatch src/hirad/train_diffusion.sh +``` + +## Inference + +### Running inference on Alps + +1. Script for running the inference is in `src/hirad/generate.sh`. +Inside this script set the following: +```bash +### OUTPUT ### +#SBATCH --output=your_path_to_output_log +#SBATCH --error=your_path_to_output_error +``` +```bash +#SBATCH -A your_compute_group +``` +```bash +srun bash -c " + . ./{your_env_name}/bin/activate + python src/hirad/inference/generate.py --config-name=generate_era_cosmo.yaml +" +``` + +2. Set up the following config files in `src/hirad/conf`: + +- In `generate_era_cosmo.yaml` set: +``` +hydra: + run: + dir: your_path_to_save_inference_output +``` +- In `generation/era_cosmo.yaml`: +Choose the inference mode: +``` +inference_mode: all/regression/diffusion +``` +by default `all` does both regression and diffusion. Depending on mode, regression and/or diffusion model pretrained weights should be provided: +``` +io: + res_ckpt_path: path_to_directory_containing_diffusion_training_model_checkpoints + reg_ckpt_path: path_to_directory_containing_regression_training_model_checkpoints +``` +Finally, from the dataset, subset of time steps can be chosen to do inference for. + +One way is to list steps under `times:` in format `%Y%m%d-%H%M` for era5_cosmo dataset. + +The other way is to specify `times_range:` with three items: first time step (`%Y%m%d-%H%M`), last time step (`%Y%m%d-%H%M`), hour shift (int). Hour shift specifies distance in hours between closest time steps for specific dataset (6 for era_cosmo). + +By default, inference is done for one time step `20160101-0000` + +- In `dataset/era_cosmo.yaml` set the `dataset_path` if different from default. + +3. Submit the job with: +```bash +sbatch src/hirad/generate.sh ``` \ No newline at end of file diff --git a/src/hirad/conf/training_era_cosmo_diffusion.yaml b/src/hirad/conf/training_era_cosmo_diffusion.yaml index 7ee7dba5..2c8d37f6 100644 --- a/src/hirad/conf/training_era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training_era_cosmo_diffusion.yaml @@ -1,9 +1,9 @@ hydra: job: chdir: true - name: diffusion + name: diffusion_test run: - dir: /scratch/mch/pstamenk/output/${hydra:job.name} + dir: /iopsstor/scratch/cscs/pstamenk/outputs/${hydra:job.name} # Get defaults defaults: diff --git a/src/hirad/conf/training_era_cosmo_regression.yaml b/src/hirad/conf/training_era_cosmo_regression.yaml index d857d12a..dc498ce9 100644 --- a/src/hirad/conf/training_era_cosmo_regression.yaml +++ b/src/hirad/conf/training_era_cosmo_regression.yaml @@ -3,7 +3,7 @@ hydra: chdir: true name: regression run: - dir: /scratch/mch/pstamenk/output/${hydra:job.name} + dir: /iopsstor/scratch/cscs/pstamenk/outputs/${hydra:job.name} # Get defaults defaults: From f4d856b0e562e78fa002b5f9780d9c7d878a14d9 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 16 May 2025 18:04:37 +0200 Subject: [PATCH 042/302] small fix for inference on multiple time steps --- .../conf/training_era_cosmo_diffusion.yaml | 2 +- src/hirad/inference/generate.py | 23 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/hirad/conf/training_era_cosmo_diffusion.yaml b/src/hirad/conf/training_era_cosmo_diffusion.yaml index 2c8d37f6..4271e446 100644 --- a/src/hirad/conf/training_era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training_era_cosmo_diffusion.yaml @@ -1,7 +1,7 @@ hydra: job: chdir: true - name: diffusion_test + name: diffusion run: dir: /iopsstor/scratch/cscs/pstamenk/outputs/${hydra:job.name} diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index 5558a200..7cb96859 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -345,11 +345,12 @@ def generate_fn(image_lr, labels, lead_time_label): writer_executor.submit( save_images, output_path, + times[sampler[time_index]], dataset, image_out.cpu(), image_tar.cpu(), image_lr.cpu(), - image_reg.cpu(), + image_reg.cpu() if image_reg is not None else None, ) ) end.record() @@ -376,15 +377,16 @@ def generate_fn(image_lr, labels, lead_time_label): f.close() logger0.info("Generation Completed.") -def save_images(output_path, dataset, image_pred, image_hr, image_lr, mean_pred): +def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): longitudes = dataset.longitude() latitudes = dataset.latitude() input_channels = dataset.input_channels() output_channels = dataset.output_channels() image_pred = image_pred.numpy() image_pred_final = np.flip(dataset.denormalize_output(image_pred[-1,::].squeeze()),1).reshape(len(output_channels),-1) - image_pred_first_step = np.flip(dataset.denormalize_output(image_pred[0,::].squeeze()),1).reshape(len(output_channels),-1) - image_pred_mid_step = np.flip(dataset.denormalize_output(image_pred[32,::].squeeze()),1).reshape(len(output_channels),-1) + if image_pred.shape[0]>1: + image_pred_first_step = np.flip(dataset.denormalize_output(image_pred[0,::].squeeze()),1).reshape(len(output_channels),-1) + image_pred_mid_step = np.flip(dataset.denormalize_output(image_pred[32,::].squeeze()),1).reshape(len(output_channels),-1) image_hr = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze().numpy()),1).reshape(len(output_channels),-1) image_lr = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze().numpy()),1).reshape(len(input_channels),-1) if mean_pred is not None: @@ -392,13 +394,14 @@ def save_images(output_path, dataset, image_pred, image_hr, image_lr, mean_pred) os.makedirs(output_path, exist_ok=True) for idx, channel in enumerate(output_channels): input_channel_idx = input_channels.index(channel) - _plot_projection(longitudes,latitudes,image_lr[input_channel_idx,:],os.path.join(output_path,f'{channel.name}-lr.jpg')) - _plot_projection(longitudes,latitudes,image_hr[idx,:],os.path.join(output_path,f'{channel.name}-hr.jpg')) - _plot_projection(longitudes,latitudes,image_pred_final[idx,:],os.path.join(output_path,f'{channel.name}-hr-pred.jpg')) - _plot_projection(longitudes,latitudes,image_pred_first_step[idx,:],os.path.join(output_path,f'{channel.name}-hr-pred-0.jpg')) - _plot_projection(longitudes,latitudes,image_pred_mid_step[idx,:],os.path.join(output_path,f'{channel.name}-hr-pred-mid.jpg')) + _plot_projection(longitudes,latitudes,image_lr[input_channel_idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-lr.jpg')) + _plot_projection(longitudes,latitudes,image_hr[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr.jpg')) + _plot_projection(longitudes,latitudes,image_pred_final[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred.jpg')) + if image_pred.shape[0]>1: + _plot_projection(longitudes,latitudes,image_pred_first_step[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred-0.jpg')) + _plot_projection(longitudes,latitudes,image_pred_mid_step[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred-mid.jpg')) if mean_pred is not None: - _plot_projection(longitudes,latitudes,mean_pred[idx,:],os.path.join(output_path,f'{channel.name}-mean-pred.jpg')) + _plot_projection(longitudes,latitudes,mean_pred[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-mean-pred.jpg')) def _plot_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, cmap=None, vmin = None, vmax = None): From dcc2a067c1e7f0a7dc1a2da7b58ee5568d4dd6a6 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 21 May 2025 13:00:23 +0200 Subject: [PATCH 043/302] enable validation during training --- src/hirad/datasets/dataset.py | 8 ++++---- src/hirad/inference/generate.py | 7 ++++--- src/hirad/training/train.py | 5 +---- src/hirad/utils/inference_utils.py | 6 +++--- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/hirad/datasets/dataset.py b/src/hirad/datasets/dataset.py index 6cc6165b..380f7978 100644 --- a/src/hirad/datasets/dataset.py +++ b/src/hirad/datasets/dataset.py @@ -36,7 +36,6 @@ def init_train_valid_datasets_from_config( dataloader_cfg: Union[dict, None] = None, batch_size: int = 1, seed: int = 0, - validation_dataset_cfg: Union[dict, None] = None, train_test_split: bool = True, ) -> Tuple[ DownscalingDataset, @@ -59,13 +58,14 @@ def init_train_valid_datasets_from_config( """ config = copy.deepcopy(dataset_cfg) + del config['validation_path'] (dataset, dataset_iter) = init_dataset_from_config( config, dataloader_cfg, batch_size=batch_size, seed=seed ) if train_test_split: - valid_dataset_cfg = copy.deepcopy(config) - if validation_dataset_cfg: - valid_dataset_cfg.update(validation_dataset_cfg) + valid_dataset_cfg = copy.deepcopy(dataset_cfg) + valid_dataset_cfg["dataset_path"] = valid_dataset_cfg["validation_path"] + del valid_dataset_cfg['validation_path'] (valid_dataset, valid_dataset_iter) = init_dataset_from_config( valid_dataset_cfg, dataloader_cfg, batch_size=batch_size, seed=seed ) diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index 7cb96859..ce8ed7b9 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -50,7 +50,6 @@ def main(cfg: DictConfig) -> None: # Initialize logger logger = PythonLogger("generate") # General python logger logger0 = RankZeroLoggingWrapper(logger, dist) - # logger.file_logging("generate.log") # Handle the batch size seeds = list(np.arange(cfg.generation.num_ensembles)) @@ -252,7 +251,7 @@ def generate_fn(image_lr, labels, lead_time_label): elif cfg.generation.inference_mode == "diffusion": image_out = image_res else: - image_out = image_reg + image_res + image_out = image_reg[0:1,::] + image_res if cfg.generation.sample_res != "full": image_out = rearrange( @@ -385,8 +384,9 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, image_pred = image_pred.numpy() image_pred_final = np.flip(dataset.denormalize_output(image_pred[-1,::].squeeze()),1).reshape(len(output_channels),-1) if image_pred.shape[0]>1: + image_pred_mean = np.flip(dataset.denormalize_output(image_pred.mean(axis=0)),1).reshape(len(output_channels),-1) image_pred_first_step = np.flip(dataset.denormalize_output(image_pred[0,::].squeeze()),1).reshape(len(output_channels),-1) - image_pred_mid_step = np.flip(dataset.denormalize_output(image_pred[32,::].squeeze()),1).reshape(len(output_channels),-1) + image_pred_mid_step = np.flip(dataset.denormalize_output(image_pred[image_pred.shape[0]//2,::].squeeze()),1).reshape(len(output_channels),-1) image_hr = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze().numpy()),1).reshape(len(output_channels),-1) image_lr = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze().numpy()),1).reshape(len(input_channels),-1) if mean_pred is not None: @@ -398,6 +398,7 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, _plot_projection(longitudes,latitudes,image_hr[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr.jpg')) _plot_projection(longitudes,latitudes,image_pred_final[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred.jpg')) if image_pred.shape[0]>1: + _plot_projection(longitudes,latitudes,image_pred_mean[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred-mean.jpg')) _plot_projection(longitudes,latitudes,image_pred_first_step[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred-0.jpg')) _plot_projection(longitudes,latitudes,image_pred_mid_step[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred-mid.jpg')) if mean_pred is not None: diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 37e61107..664a6a55 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -36,12 +36,10 @@ def main(cfg: DictConfig) -> None: OmegaConf.resolve(cfg) dataset_cfg = OmegaConf.to_container(cfg.dataset) - if hasattr(cfg, "validation"): + if hasattr(cfg.dataset, "validation_path"): train_test_split = True - validation_dataset_cfg = OmegaConf.to_container(cfg.validation) else: train_test_split = False - validation_dataset_cfg = None fp_optimizations = cfg.training.perf.fp_optimizations songunet_checkpoint_level = cfg.training.perf.songunet_checkpoint_level fp16 = fp_optimizations == "fp16" @@ -77,7 +75,6 @@ def main(cfg: DictConfig) -> None: data_loader_kwargs, batch_size=cfg.training.hp.batch_size_per_gpu, seed=0, - validation_dataset_cfg=validation_dataset_cfg, train_test_split=train_test_split, ) logger0.info(f"Training on dataset with size {len(dataset)}") diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index 4831bdd7..ace05ba3 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -52,14 +52,14 @@ def regression_step( """ # Create a tensor of zeros with the given shape and move it to the appropriate device x_hat = torch.zeros(latents_shape, dtype=img_lr.dtype, device=img_lr.device) - t_hat = torch.tensor(1.0, dtype=img_lr.dtype, device=img_lr.device).reshape((1,1,1,1)) + t_hat = torch.tensor(1.0, dtype=img_lr.dtype, device=img_lr.device)#.reshape((1,1,1,1)) # Perform regression on a single batch element with torch.inference_mode(): if lead_time_label is not None: - x = net(x_hat, img_lr, t_hat, labels, lead_time_label=lead_time_label) + x = net(x_hat[0:1], img_lr, t_hat, labels, lead_time_label=lead_time_label) else: - x = net(x_hat, img_lr, t_hat, labels) + x = net(x_hat[0:1], img_lr, t_hat, labels) # If the batch size is greater than 1, repeat the prediction if x_hat.shape[0] > 1: From cadccd502fb913e18db7d7ea4eba2787f8fca5c5 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Thu, 22 May 2025 15:57:25 +0200 Subject: [PATCH 044/302] change generate eval to new functions --- src/hirad/inference/generate.py | 107 +++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 37 deletions(-) diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index ce8ed7b9..fbfd8cf6 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -35,6 +35,7 @@ from hirad.utils.train_helpers import set_patch_shape +from hirad.eval import compute_mae, average_power_spectrum, plot_error_projection, plot_power_spectra @hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") def main(cfg: DictConfig) -> None: @@ -346,10 +347,10 @@ def generate_fn(image_lr, labels, lead_time_label): output_path, times[sampler[time_index]], dataset, - image_out.cpu(), - image_tar.cpu(), - image_lr.cpu(), - image_reg.cpu() if image_reg is not None else None, + image_out.cpu().numpy(), + image_tar.cpu().numpy(), + image_lr.cpu().numpy(), + image_reg.cpu().numpy() if image_reg is not None else None, ) ) end.record() @@ -381,41 +382,73 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, latitudes = dataset.latitude() input_channels = dataset.input_channels() output_channels = dataset.output_channels() - image_pred = image_pred.numpy() - image_pred_final = np.flip(dataset.denormalize_output(image_pred[-1,::].squeeze()),1).reshape(len(output_channels),-1) - if image_pred.shape[0]>1: - image_pred_mean = np.flip(dataset.denormalize_output(image_pred.mean(axis=0)),1).reshape(len(output_channels),-1) - image_pred_first_step = np.flip(dataset.denormalize_output(image_pred[0,::].squeeze()),1).reshape(len(output_channels),-1) - image_pred_mid_step = np.flip(dataset.denormalize_output(image_pred[image_pred.shape[0]//2,::].squeeze()),1).reshape(len(output_channels),-1) - image_hr = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze().numpy()),1).reshape(len(output_channels),-1) - image_lr = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze().numpy()),1).reshape(len(input_channels),-1) - if mean_pred is not None: - mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze().numpy()),1).reshape(len(output_channels),-1) - os.makedirs(output_path, exist_ok=True) + + target = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze()),1) #.reshape(len(output_channels),-1) + prediction = np.flip(dataset.denormalize_output(image_pred[-1,::].squeeze()),1) #.reshape(len(output_channels),-1) + baseline = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze()),1)# .reshape(len(input_channels),-1) + + freqs = {} + power = {} for idx, channel in enumerate(output_channels): input_channel_idx = input_channels.index(channel) - _plot_projection(longitudes,latitudes,image_lr[input_channel_idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-lr.jpg')) - _plot_projection(longitudes,latitudes,image_hr[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr.jpg')) - _plot_projection(longitudes,latitudes,image_pred_final[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred.jpg')) - if image_pred.shape[0]>1: - _plot_projection(longitudes,latitudes,image_pred_mean[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred-mean.jpg')) - _plot_projection(longitudes,latitudes,image_pred_first_step[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred-0.jpg')) - _plot_projection(longitudes,latitudes,image_pred_mid_step[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred-mid.jpg')) - if mean_pred is not None: - _plot_projection(longitudes,latitudes,mean_pred[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-mean-pred.jpg')) - -def _plot_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, cmap=None, vmin = None, vmax = None): - - """Plot observed or interpolated data in a scatter plot.""" - # TODO: Refactor this somehow, it's not really generalizing well across variables. - fig = plt.figure() - fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) - p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) - ax.coastlines() - ax.gridlines(draw_labels=True) - plt.colorbar(p, label="K", orientation="horizontal") - plt.savefig(filename) - plt.close('all') + _, baseline_errors = compute_mae(baseline[input_channel_idx,:,:], target[idx,:,:]) + _, prediction_errors = compute_mae(prediction[idx,:,:], target[idx,:,:]) + + plot_error_projection(baseline_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path, f'{time_step}-{channel.name}-baseline-error.jpg')) + plot_error_projection(prediction_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path, f'{time_step}-{channel.name}-prediction-error.jpg')) + + b_freq, b_power = average_power_spectrum(baseline[input_channel_idx,:,:].squeeze(), 2.0) + freqs['baseline'] = b_freq + power['baseline'] = b_power + #plotting.plot_power_spectrum(b_freq, b_power, target_channels[t_c], os.path.join('plots/spectra/baseline2dt', target_channels[t_c] + '-all_dates')) + t_freq, t_power = average_power_spectrum(target[idx,:,:].squeeze(), 2.0) + freqs['target'] = t_freq + power['target'] = t_power + p_freq, p_power = average_power_spectrum(prediction[idx,:,:].squeeze(), 2.0) + freqs['prediction'] = p_freq + power['prediction'] = p_power + plot_power_spectra(freqs, power, channel.name, os.path.join(output_path, f'{time_step}-{channel.name}-spectra.jpg')) + +# def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): +# longitudes = dataset.longitude() +# latitudes = dataset.latitude() +# input_channels = dataset.input_channels() +# output_channels = dataset.output_channels() +# image_pred = image_pred.numpy() +# image_pred_final = np.flip(dataset.denormalize_output(image_pred[-1,::].squeeze()),1).reshape(len(output_channels),-1) +# if image_pred.shape[0]>1: +# image_pred_mean = np.flip(dataset.denormalize_output(image_pred.mean(axis=0)),1).reshape(len(output_channels),-1) +# image_pred_first_step = np.flip(dataset.denormalize_output(image_pred[0,::].squeeze()),1).reshape(len(output_channels),-1) +# image_pred_mid_step = np.flip(dataset.denormalize_output(image_pred[image_pred.shape[0]//2,::].squeeze()),1).reshape(len(output_channels),-1) +# image_hr = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze().numpy()),1).reshape(len(output_channels),-1) +# image_lr = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze().numpy()),1).reshape(len(input_channels),-1) +# if mean_pred is not None: +# mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze().numpy()),1).reshape(len(output_channels),-1) +# os.makedirs(output_path, exist_ok=True) +# for idx, channel in enumerate(output_channels): +# input_channel_idx = input_channels.index(channel) +# _plot_projection(longitudes,latitudes,image_lr[input_channel_idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-lr.jpg')) +# _plot_projection(longitudes,latitudes,image_hr[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr.jpg')) +# _plot_projection(longitudes,latitudes,image_pred_final[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred.jpg')) +# if image_pred.shape[0]>1: +# _plot_projection(longitudes,latitudes,image_pred_mean[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred-mean.jpg')) +# _plot_projection(longitudes,latitudes,image_pred_first_step[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred-0.jpg')) +# _plot_projection(longitudes,latitudes,image_pred_mid_step[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred-mid.jpg')) +# if mean_pred is not None: +# _plot_projection(longitudes,latitudes,mean_pred[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-mean-pred.jpg')) + +# def _plot_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, cmap=None, vmin = None, vmax = None): + +# """Plot observed or interpolated data in a scatter plot.""" +# # TODO: Refactor this somehow, it's not really generalizing well across variables. +# fig = plt.figure() +# fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) +# p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) +# ax.coastlines() +# ax.gridlines(draw_labels=True) +# plt.colorbar(p, label="K", orientation="horizontal") +# plt.savefig(filename) +# plt.close('all') if __name__ == "__main__": main() From 92b08b7fad3c2fdf09b07ea2a82a69ba9175f62e Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 23 May 2025 14:28:30 +0200 Subject: [PATCH 045/302] fix average training loss tracking --- src/hirad/training/train.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 664a6a55..559d8003 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -366,18 +366,6 @@ def main(cfg: DictConfig) -> None: "training_loss_running_mean", average_loss_running_mean, cur_nimg ) - ptt = is_time_for_periodic_task( - cur_nimg, - cfg.training.io.print_progress_freq, - done, - cfg.training.hp.total_batch_size, - dist.rank, - rank_0_only=True, - ) - if ptt: - # reset running mean of average loss - average_loss_running_mean = 0 - n_average_loss_running_mean = 1 # Update weights. lr_rampup = cfg.training.hp.lr_rampup # ramp up the learning rate @@ -481,6 +469,19 @@ def main(cfg: DictConfig) -> None: logger0.info(" ".join(fields)) torch.cuda.reset_peak_memory_stats() + ptt = is_time_for_periodic_task( + cur_nimg, + cfg.training.io.print_progress_freq, + done, + cfg.training.hp.total_batch_size, + dist.rank, + rank_0_only=True, + ) + if ptt: + # reset running mean of average loss + average_loss_running_mean = 0 + n_average_loss_running_mean = 1 + # Save checkpoints if dist.world_size > 1: torch.distributed.barrier() From 97970fcc634f7ca1934cda32ee58b8c27d79dc02 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 23 May 2025 14:33:58 +0200 Subject: [PATCH 046/302] fix validation bug --- src/hirad/datasets/dataset.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hirad/datasets/dataset.py b/src/hirad/datasets/dataset.py index 380f7978..7ba88330 100644 --- a/src/hirad/datasets/dataset.py +++ b/src/hirad/datasets/dataset.py @@ -58,7 +58,8 @@ def init_train_valid_datasets_from_config( """ config = copy.deepcopy(dataset_cfg) - del config['validation_path'] + if 'validation_path': + del config['validation_path'] (dataset, dataset_iter) = init_dataset_from_config( config, dataloader_cfg, batch_size=batch_size, seed=seed ) @@ -83,6 +84,8 @@ def init_dataset_from_config( ) -> Tuple[DownscalingDataset, Iterable]: dataset_cfg = copy.deepcopy(dataset_cfg) dataset_type = dataset_cfg.pop("type", "era5_cosmo") + if "validation_path" in dataset_cfg: + del dataset_cfg['validation_path'] if "train_test_split" in dataset_cfg: # handled by init_train_valid_datasets_from_config del dataset_cfg["train_test_split"] From 937e7c97e129ee6c0d696ef318300372b17f29a3 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 26 May 2025 18:20:09 +0200 Subject: [PATCH 047/302] update to latest corrdiff version --- src/hirad/conf/generation/era_cosmo.yaml | 19 +- src/hirad/conf/model/era_cosmo_diffusion.yaml | 12 +- .../conf/model/era_cosmo_regression.yaml | 10 +- src/hirad/conf/model_size/mini.yaml | 26 + src/hirad/conf/model_size/normal.yaml | 26 + src/hirad/conf/sampler/stochastic.yaml | 4 +- .../conf/training_era_cosmo_regression.yaml | 2 + src/hirad/datasets/era5_cosmo.py | 3 +- src/hirad/inference/generate.py | 132 ++- src/hirad/losses/__init__.py | 2 +- src/hirad/losses/loss.py | 630 ++++++----- src/hirad/models/__init__.py | 12 +- src/hirad/models/layers.py | 354 +++++-- src/hirad/models/preconditioning.py | 352 +++++-- src/hirad/models/song_unet.py | 996 ++++++++++++------ src/hirad/models/unet.py | 177 +++- src/hirad/training/train.py | 741 ++++++++----- src/hirad/utils/deterministic_sampler.py | 162 ++- src/hirad/utils/function_utils.py | 37 +- src/hirad/utils/inference_utils.py | 145 ++- src/hirad/utils/patching.py | 767 ++++++++++++++ src/hirad/utils/stochastic_sampler.py | 524 +++------ src/hirad/utils/train_helpers.py | 14 +- 23 files changed, 3584 insertions(+), 1563 deletions(-) create mode 100644 src/hirad/conf/model_size/mini.yaml create mode 100644 src/hirad/conf/model_size/normal.yaml create mode 100644 src/hirad/utils/patching.py diff --git a/src/hirad/conf/generation/era_cosmo.yaml b/src/hirad/conf/generation/era_cosmo.yaml index a0c5a407..be4219d2 100644 --- a/src/hirad/conf/generation/era_cosmo.yaml +++ b/src/hirad/conf/generation/era_cosmo.yaml @@ -1,22 +1,26 @@ -num_ensembles: 64 +num_ensembles: 8 # Number of ensembles to generate per input -seed_batch_size: 1 +seed_batch_size: 4 # Size of the batched inference inference_mode: all # Choose between "all" (regression + diffusion), "regression" or "diffusion" # Patch size. Patch-based sampling will be utilized if these dimensions differ from # img_shape_x and img_shape_y -overlap_pixels: 0 +# overlap_pixels: 0 # Number of overlapping pixels between adjacent patches -boundary_pixels: 0 +# boundary_pixels: 0 # Number of boundary pixels to be cropped out. 2 is recommanded to address the boundary # artifact. +patching: False hr_mean_conditioning: True -sample_res: full +# sample_res: full # Sampling resolution times_range: null times: - 20160101-0000 + # - 20160101-0600 + # - 20160101-1200 +has_laed_time: False perf: force_fp16: False @@ -31,9 +35,10 @@ perf: # To support multiple workers a threadsafe version of the netCDF library must be used io: - res_ckpt_path: /iopsstor/scratch/cscs/pstamenk/outputs/diffusion_test/checkpoints_diffusion + res_ckpt_path: /iopsstor/scratch/cscs/pstamenk/outputs/diffusion_refactoring/checkpoints_diffusion + # res_ckpt_path: null # Checkpoint filename for the diffusion model - reg_ckpt_path: /iopsstor/scratch/cscs/pstamenk/outputs/regression_overfit/checkpoints_regression + reg_ckpt_path: /iopsstor/scratch/cscs/pstamenk/outputs/regression_refactoring/checkpoints_regression # reg_ckpt_path: /iopsstor/scratch/cscs/pstamenk/outputs/regression_test/checkpoints_regression # Checkpoint filename for the mean predictor model output_path: ./images \ No newline at end of file diff --git a/src/hirad/conf/model/era_cosmo_diffusion.yaml b/src/hirad/conf/model/era_cosmo_diffusion.yaml index 06aa2a47..441239e1 100644 --- a/src/hirad/conf/model/era_cosmo_diffusion.yaml +++ b/src/hirad/conf/model/era_cosmo_diffusion.yaml @@ -2,4 +2,14 @@ name: diffusion # Name of the preconditioner hr_mean_conditioning: True # High-res mean (regression's output) as additional condition -scale_cond_input: False \ No newline at end of file + +# Standard model parameters. +model_args: + gridtype: "sinusoidal" + # Type of positional grid to use: 'sinusoidal', 'learnable', 'linear'. + # Controls how positional information is encoded. + N_grid_channels: 4 + # Number of channels for positional grid embeddings + embedding_type: "zero" + # Type of timestep embedding: 'positional' for DDPM++, 'fourier' for NCSN++, + # 'zero' for none \ No newline at end of file diff --git a/src/hirad/conf/model/era_cosmo_regression.yaml b/src/hirad/conf/model/era_cosmo_regression.yaml index 487eb4b4..29b43e8f 100644 --- a/src/hirad/conf/model/era_cosmo_regression.yaml +++ b/src/hirad/conf/model/era_cosmo_regression.yaml @@ -1,2 +1,10 @@ name: regression -hr_mean_conditioning: False \ No newline at end of file +hr_mean_conditioning: False + +# Default regression model parameters. Do not modify. +model_args: + "N_grid_channels": 4 + # Number of channels for positional grid embeddings + "embedding_type": "zero" + # Type of timestep embedding: 'positional' for DDPM++, 'fourier' for NCSN++, + # 'zero' for none \ No newline at end of file diff --git a/src/hirad/conf/model_size/mini.yaml b/src/hirad/conf/model_size/mini.yaml new file mode 100644 index 00000000..2eb8f8ab --- /dev/null +++ b/src/hirad/conf/model_size/mini.yaml @@ -0,0 +1,26 @@ +# @package _global_.model + +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +model_args: + # Base multiplier for the number of channels across the network. + model_channels: 64 + # Per-resolution multipliers for the number of channels. + channel_mult: [1, 2, 2] + # Resolutions at which self-attention layers are applied. + attn_resolutions: [16] \ No newline at end of file diff --git a/src/hirad/conf/model_size/normal.yaml b/src/hirad/conf/model_size/normal.yaml new file mode 100644 index 00000000..b81fe153 --- /dev/null +++ b/src/hirad/conf/model_size/normal.yaml @@ -0,0 +1,26 @@ +# @package _global_.model + +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +model_args: + # Base multiplier for the number of channels across the network. + model_channels: 128 + # Per-resolution multipliers for the number of channels. + channel_mult: [1, 2, 2, 2, 2] + # Resolutions at which self-attention layers are applied. + attn_resolutions: [28] \ No newline at end of file diff --git a/src/hirad/conf/sampler/stochastic.yaml b/src/hirad/conf/sampler/stochastic.yaml index 5e8fa888..2481cd37 100644 --- a/src/hirad/conf/sampler/stochastic.yaml +++ b/src/hirad/conf/sampler/stochastic.yaml @@ -1,3 +1,3 @@ type: stochastic -boundary_pix: 2 -overlap_pix: 4 \ No newline at end of file +# boundary_pix: 2 +# overlap_pix: 4 \ No newline at end of file diff --git a/src/hirad/conf/training_era_cosmo_regression.yaml b/src/hirad/conf/training_era_cosmo_regression.yaml index dc498ce9..1de83d91 100644 --- a/src/hirad/conf/training_era_cosmo_regression.yaml +++ b/src/hirad/conf/training_era_cosmo_regression.yaml @@ -15,5 +15,7 @@ defaults: # Model - model/era_cosmo_regression + - model_size/normal + # Training - training/era_cosmo_regression \ No newline at end of file diff --git a/src/hirad/datasets/era5_cosmo.py b/src/hirad/datasets/era5_cosmo.py index 674dbf07..f97dbc64 100644 --- a/src/hirad/datasets/era5_cosmo.py +++ b/src/hirad/datasets/era5_cosmo.py @@ -63,8 +63,7 @@ def __getitem__(self, idx): cosmo_data = self.normalize_output(cosmo_data) # return samples return torch.tensor(cosmo_data),\ - torch.tensor(era5_data),\ - 0 + torch.tensor(era5_data), # return F.pad(torch.tensor(cosmo_data), pad=(1,1,1,1), mode='constant', value=0), \ # F.pad(torch.tensor(era5_data), pad=(1,1,1,1), mode='constant', value=0), \ # 0 diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index fbfd8cf6..8fed809f 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -6,6 +6,8 @@ import torch._dynamo import nvtx import numpy as np +import contextlib + from hirad.distributed import DistributedManager from hirad.utils.console import PythonLogger, RankZeroLoggingWrapper from concurrent.futures import ThreadPoolExecutor @@ -18,7 +20,8 @@ from hydra.utils import to_absolute_path -from hirad.models import EDMPrecondSR, UNet +from hirad.models import EDMPrecondSuperResolution, UNet +from hirad.utils.patching import GridPatching2D from hirad.utils.stochastic_sampler import stochastic_sampler from hirad.utils.deterministic_sampler import deterministic_sampler from hirad.utils.inference_utils import ( @@ -85,19 +88,23 @@ def main(cfg: DictConfig) -> None: img_out_channels = len(dataset.output_channels()) # Parse the patch shape - if hasattr(cfg.generation, "patch_shape_x"): # TODO better config handling + if cfg.generation.patching: patch_shape_x = cfg.generation.patch_shape_x - else: - patch_shape_x = None - if hasattr(cfg.generation, "patch_shape_y"): patch_shape_y = cfg.generation.patch_shape_y else: - patch_shape_y = None + patch_shape_x, patch_shape_y = None, None patch_shape = (patch_shape_y, patch_shape_x) - img_shape, patch_shape = set_patch_shape(img_shape, patch_shape) - if patch_shape != img_shape: + use_patching, img_shape, patch_shape = set_patch_shape(img_shape, patch_shape) + if use_patching: + patching = GridPatching2D( + img_shape=img_shape, + patch_shape=patch_shape, + boundary_pix=cfg.generation.boundary_pix, + overlap_pix=cfg.generation.overlap_pix, + ) logger0.info("Patch-based training enabled") else: + patching = None logger0.info("Patch-based training disabled") # Parse the inference mode @@ -121,7 +128,7 @@ def main(cfg: DictConfig) -> None: with open(diffusion_model_args_path, 'r') as f: diffusion_model_args = json.load(f) - net_res = EDMPrecondSR(**diffusion_model_args) + net_res = EDMPrecondSuperResolution(**diffusion_model_args) _ = load_checkpoint( path=res_ckpt_path, @@ -130,9 +137,13 @@ def main(cfg: DictConfig) -> None: ) #TODO fix to use channels_last which is optimal for H100 - net_res = net_res.eval().to(device)#.to(memory_format=torch.channels_last) + net_res = net_res.eval().to(device).to(memory_format=torch.channels_last) if cfg.generation.perf.force_fp16: net_res.use_fp16 = True + + # Disable AMP for inference (even if model is trained with AMP) + if hasattr(net_res, "amp_mode"): + net_res.amp_mode = False else: net_res = None @@ -156,9 +167,13 @@ def main(cfg: DictConfig) -> None: device=dist.device ) - net_reg = net_reg.eval().to(device)#.to(memory_format=torch.channels_last) + net_reg = net_reg.eval().to(device).to(memory_format=torch.channels_last) if cfg.generation.perf.force_fp16: net_reg.use_fp16 = True + + # Disable AMP for inference (even if model is trained with AMP) + if hasattr(net_reg, "amp_mode"): + net_reg.amp_mode = False else: net_reg = None @@ -183,47 +198,28 @@ def main(cfg: DictConfig) -> None: solver=cfg.sampler.solver, ) elif cfg.sampler.type == "stochastic": - sampler_fn = partial( - stochastic_sampler, - img_shape=img_shape, - patch_shape_x=patch_shape[0], - patch_shape_y=patch_shape[1], - boundary_pix=cfg.sampler.boundary_pix, - overlap_pix=cfg.sampler.overlap_pix, - ) + sampler_fn = partial(stochastic_sampler, patching=patching) else: raise ValueError(f"Unknown sampling method {cfg.sampling.type}") # Main generation definition - def generate_fn(image_lr, labels, lead_time_label): - img_shape_y, img_shape_x = img_shape + def generate_fn(image_lr, lead_time_label): with nvtx.annotate("generate_fn", color="green"): - if cfg.generation.sample_res == "full": - image_lr_patch = image_lr - else: - torch.cuda.nvtx.range_push("rearrange") - image_lr_patch = rearrange( - image_lr, - "b c (h1 h) (w1 w) -> (b h1 w1) c h w", - h1=img_shape_y // patch_shape[0], - w1=img_shape_x // patch_shape[1], - ) - torch.cuda.nvtx.range_pop() - image_lr_patch = image_lr_patch #.to(memory_format=torch.channels_last) + # (1, C, H, W) + image_lr = image_lr.to(memory_format=torch.channels_last) if net_reg: with nvtx.annotate("regression_model", color="yellow"): image_reg = regression_step( net=net_reg, - img_lr=image_lr_patch, - labels=labels, + img_lr=image_lr, latents_shape=( cfg.generation.seed_batch_size, img_out_channels, img_shape[0], img_shape[1], - ), + ), # (batch_size, C, H, W) lead_time_label=lead_time_label, ) if net_res: @@ -235,16 +231,15 @@ def generate_fn(image_lr, labels, lead_time_label): image_res = diffusion_step( net=net_res, sampler_fn=sampler_fn, - seed_batch_size=cfg.generation.seed_batch_size, img_shape=img_shape, img_out_channels=img_out_channels, rank_batches=rank_batches, - img_lr=image_lr_patch.expand( + img_lr=image_lr.expand( cfg.generation.seed_batch_size, -1, -1, -1 ), #.to(memory_format=torch.channels_last), rank=dist.rank, device=device, - hr_mean=mean_hr, + mean_hr=mean_hr, lead_time_label=lead_time_label, ) if cfg.generation.inference_mode == "regression": @@ -254,13 +249,6 @@ def generate_fn(image_lr, labels, lead_time_label): else: image_out = image_reg[0:1,::] + image_res - if cfg.generation.sample_res != "full": - image_out = rearrange( - image_out, - "(b h1 w1) c h w -> b c (h1 h) (w1 w)", - h1=img_shape_y // patch_shape[0], - w1=img_shape_x // patch_shape[1], - ) # Gather tensors on rank 0 if dist.world_size > 1: if dist.rank == 0: @@ -300,8 +288,18 @@ def generate_fn(image_lr, labels, lead_time_label): # through the dataset using a data loader, computes predictions, and saves them along # with associated metadata. - with torch.cuda.profiler.profile(): - with torch.autograd.profiler.emit_nvtx(): + torch_cuda_profiler = ( + torch.cuda.profiler.profile() + if torch.cuda.is_available() + else contextlib.nullcontext() + ) + torch_nvtx_profiler = ( + torch.autograd.profiler.emit_nvtx() + if torch.cuda.is_available() + else contextlib.nullcontext() + ) + with torch_cuda_profiler: + with torch_nvtx_profiler: data_loader = torch.utils.data.DataLoader( dataset=dataset, sampler=sampler, batch_size=1, pin_memory=True @@ -313,11 +311,29 @@ def generate_fn(image_lr, labels, lead_time_label): ) writer_threads = [] - start = torch.cuda.Event(enable_timing=True) - end = torch.cuda.Event(enable_timing=True) + # Create timer objects only if CUDA is available + use_cuda_timing = torch.cuda.is_available() + if use_cuda_timing: + start = torch.cuda.Event(enable_timing=True) + end = torch.cuda.Event(enable_timing=True) + else: + # Dummy no-op functions for CPU case + class DummyEvent: + def record(self): + pass + + def synchronize(self): + pass + + def elapsed_time(self, _): + return 0 + + start = end = DummyEvent() times = dataset.time() - for image_tar, image_lr, labels, *lead_time_label in iter(data_loader): + for index, (image_tar, image_lr, *lead_time_label) in enumerate( + iter(data_loader) + ): time_index += 1 if dist.rank == 0: logger0.info(f"starting index: {time_index}") @@ -333,11 +349,10 @@ def generate_fn(image_lr, labels, lead_time_label): image_lr = ( image_lr.to(device=device) .to(torch.float32) - #.to(memory_format=torch.channels_last) + .to(memory_format=torch.channels_last) ) image_tar = image_tar.to(device=device).to(torch.float32) - labels = labels.to(device).to(torch.float32).contiguous() - image_out, image_reg = generate_fn(image_lr,labels,lead_time_label) + image_out, image_reg = generate_fn(image_lr,lead_time_label) if dist.rank == 0: batch_size = image_out.shape[0] # write out data in a seperate thread so we don't hold up inferencing @@ -355,9 +370,11 @@ def generate_fn(image_lr, labels, lead_time_label): ) end.record() end.synchronize() - elapsed_time = start.elapsed_time(end) / 1000.0 # Convert ms to s + elapsed_time = ( + start.elapsed_time(end) / 1000.0 if use_cuda_timing else 0 + ) # Convert ms to s timed_steps = time_index + 1 - warmup_steps - if dist.rank == 0: + if dist.rank == 0 and use_cuda_timing: average_time_per_batch_element = elapsed_time / timed_steps / batch_size logger.info( f"Total time to run {timed_steps} steps and {batch_size} members = {elapsed_time} s" @@ -378,6 +395,9 @@ def generate_fn(image_lr, labels, lead_time_label): logger0.info("Generation Completed.") def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): + + os.makedirs(output_path, exist_ok=True) + longitudes = dataset.longitude() latitudes = dataset.latitude() input_channels = dataset.input_channels() diff --git a/src/hirad/losses/__init__.py b/src/hirad/losses/__init__.py index 185527b6..868ffdf2 100644 --- a/src/hirad/losses/__init__.py +++ b/src/hirad/losses/__init__.py @@ -1 +1 @@ -from .loss import ResLoss, RegressionLoss, RegressionLossCE \ No newline at end of file +from .loss import ResidualLoss, RegressionLoss, RegressionLossCE \ No newline at end of file diff --git a/src/hirad/losses/loss.py b/src/hirad/losses/loss.py index 18dde13b..fb659607 100644 --- a/src/hirad/losses/loss.py +++ b/src/hirad/losses/loss.py @@ -18,12 +18,12 @@ """Loss functions used in the paper "Elucidating the Design Space of Diffusion-Based Generative Models".""" -import random -from typing import Callable, Optional, Union +from typing import Callable, Optional, Tuple, Union import numpy as np import torch +from hirad.utils.patching import RandomPatching2D class VPLoss: """ @@ -333,7 +333,7 @@ def __call__(self, net, img_clean, img_lr, labels=None, augment_pipe=None): sigma = (rnd_normal * self.P_std + self.P_mean).exp() weight = (sigma**2 + self.sigma_data**2) / (sigma * self.sigma_data) ** 2 - # augment for conditional generaiton + # augment for conditional generation img_tot = torch.cat((img_clean, img_lr), dim=1) y_tot, augment_labels = ( augment_pipe(img_tot) if augment_pipe is not None else (img_tot, None) @@ -349,16 +349,13 @@ def __call__(self, net, img_clean, img_lr, labels=None, augment_pipe=None): class RegressionLoss: """ - Regression loss function for the U-Net for deterministic predictions. + Regression loss function for the deterministic predictions. + Note: this loss does not apply any reduction. - Parameters + Attributes ---------- - P_mean: float, optional - Mean value for `sigma` computation, by default -1.2. - P_std: float, optional: - Standard deviation for `sigma` computation, by default 1.2. - sigma_data: float, optional - Standard deviation for data, by default 0.5. + sigma_data: float + Standard deviation for data. Deprecated and ignored. Note ---- @@ -368,43 +365,68 @@ class RegressionLoss: arXiv preprint arXiv:2309.15214. """ - def __init__( - self, P_mean: float = -1.2, P_std: float = 1.2, sigma_data: float = 0.5 - ): - self.P_mean = P_mean - self.P_std = P_std - self.sigma_data = sigma_data + def __init__(self): + """ + Arguments + ---------- + """ + return - def __call__(self, net, img_clean, img_lr, labels=None, augment_pipe=None): + def __call__( + self, + net: torch.nn.Module, + img_clean: torch.Tensor, + img_lr: torch.Tensor, + augment_pipe: Optional[ + Callable[[torch.Tensor], Tuple[torch.Tensor, Optional[torch.Tensor]]] + ] = None, + ) -> torch.Tensor: """ - Calculate and return the loss for the U-Net for deterministic predictions. + Calculate and return the regression loss for + deterministic predictions. - Parameters: + Parameters ---------- - net: torch.nn.Module + net : torch.nn.Module The neural network model that will make predictions. + Expected signature: `net(x, img_lr, + augment_labels=augment_labels, force_fp32=False)`, where: + x (torch.Tensor): Tensor of shape (B, C_hr, H, W). Is zero-filled. + img_lr (torch.Tensor): Low-resolution input of shape (B, C_lr, H, W) + augment_labels (torch.Tensor, optional): Optional augmentation + labels, returned by `augment_pipe`. + force_fp32 (bool, optional): Whether to force the model to use + fp32, by default False. + Returns: + torch.Tensor: Predictions of shape (B, C_hr, H, W) + + img_clean : torch.Tensor + High-resolution input images of shape (B, C_hr, H, W). + Used as ground truth and for data augmentation if 'augment_pipe' is provided. + + img_lr : torch.Tensor + Low-resolution input images of shape (B, C_lr, H, W). + Used as input to the neural network. + + augment_pipe : callable, optional + An optional data augmentation function. + Expected signature: + img_tot (torch.Tensor): Concatenated high and low resolution + images of shape (B, C_hr+C_lr, H, W) + Returns: + Tuple[torch.Tensor, Optional[torch.Tensor]]: + - Augmented images of shape (B, C_hr+C_lr, H, W) + - Optional augmentation labels - img_clean: torch.Tensor - Input images (high resolution) to the neural network. - - img_lr: torch.Tensor - Input images (low resolution) to the neural network. - - labels: torch.Tensor - Ground truth labels for the input images. - - augment_pipe: callable, optional - An optional data augmentation function that takes images as input and - returns augmented images. If not provided, no data augmentation is applied. - - Returns: + Returns ------- torch.Tensor - A tensor representing the loss calculated based on the network's - predictions. + A tensor representing the per-sample element-wise squared + difference between the network's predictions and the high + resolution images `img_clean` (possibly data-augmented by + `augment_pipe`). + Shape: (B, C_hr, H, W), same as `img_clean`. """ - rnd_normal = torch.randn([img_clean.shape[0], 1, 1, 1], device=img_clean.device) - sigma = (rnd_normal * self.P_std + self.P_mean).exp() weight = ( 1.0 # (sigma ** 2 + self.sigma_data ** 2) / (sigma * self.sigma_data) ** 2 ) @@ -416,100 +438,214 @@ def __call__(self, net, img_clean, img_lr, labels=None, augment_pipe=None): y = y_tot[:, : img_clean.shape[1], :, :] y_lr = y_tot[:, img_clean.shape[1] :, :, :] - input = torch.zeros_like(y, device=img_clean.device) - D_yn = net(input, y_lr, sigma, labels, augment_labels=augment_labels) + zero_input = torch.zeros_like(y, device=img_clean.device) + D_yn = net(zero_input, y_lr, force_fp32=False, augment_labels=augment_labels) loss = weight * ((D_yn - y) ** 2) return loss -class ResLoss: +class ResidualLoss: """ Mixture loss function for denoising score matching. - Parameters + This class implements a loss function that combines deterministic + regression with denoising score matching. It uses a pre-trained regression + network to compute residuals before applying the diffusion process. + + Attributes ---------- - P_mean: float, optional - Mean value for `sigma` computation, by default -1.2. - P_std: float, optional: - Standard deviation for `sigma` computation, by default 1.2. - sigma_data: float, optional - Standard deviation for data, by default 0.5. + regression_net : torch.nn.Module + The regression network used for computing residuals. + P_mean : float + Mean value for noise level computation. + P_std : float + Standard deviation for noise level computation. + sigma_data : float + Standard deviation for data weighting. + hr_mean_conditioning : bool + Flag indicating whether to use high-resolution mean for conditioning. Note ---- Reference: Mardani, M., Brenowitz, N., Cohen, Y., Pathak, J., Chen, C.Y., - Liu, C.C.,Vahdat, A., Kashinath, K., Kautz, J. and Pritchard, M., 2023. - Generative Residual Diffusion Modeling for Km-scale Atmospheric Downscaling. - arXiv preprint arXiv:2309.15214. + Liu, C.C., Vahdat, A., Kashinath, K., Kautz, J. and Pritchard, M., 2023. + Generative Residual Diffusion Modeling for Km-scale Atmospheric + Downscaling. arXiv preprint arXiv:2309.15214. """ def __init__( self, - regression_net, - img_shape_x, - img_shape_y, - patch_shape_x, - patch_shape_y, - patch_num, + regression_net: torch.nn.Module, P_mean: float = 0.0, P_std: float = 1.2, sigma_data: float = 0.5, hr_mean_conditioning: bool = False, ): - self.unet = regression_net + """ + Arguments + ---------- + regression_net : torch.nn.Module + Pre-trained regression network used to compute residuals. + Expected signature: `net(zero_input, y_lr, + lead_time_label=lead_time_label, augment_labels=augment_labels)` or + `net(zero_input, y_lr, augment_labels=augment_labels)`, where: + zero_input (torch.Tensor): Zero tensor of shape (B, C_hr, H, W) + y_lr (torch.Tensor): Low-resolution input of shape (B, C_lr, H, W) + lead_time_label (torch.Tensor, optional): Optional lead time labels + augment_labels (torch.Tensor, optional): Optional augmentation labels + Returns: + torch.Tensor: Predictions of shape (B, C_hr, H, W) + + P_mean : float, optional + Mean value for noise level computation, by default 0.0. + + P_std : float, optional + Standard deviation for noise level computation, by default 1.2. + + sigma_data : float, optional + Standard deviation for data weighting, by default 0.5. + + hr_mean_conditioning : bool, optional + Whether to use high-resolution mean for conditioning predicted, by default False. + When True, the mean prediction from `regression_net` is channel-wise + concatenated with `img_lr` for conditioning. + """ + self.regression_net = regression_net self.P_mean = P_mean self.P_std = P_std self.sigma_data = sigma_data - self.img_shape_x = img_shape_x - self.img_shape_y = img_shape_y - self.patch_shape_x = patch_shape_x - self.patch_shape_y = patch_shape_y - self.patch_num = patch_num self.hr_mean_conditioning = hr_mean_conditioning + self.y_mean = None def __call__( self, - net, - img_clean, - img_lr, - labels=None, - lead_time_label=None, - augment_pipe=None, - ): + net: torch.nn.Module, + img_clean: torch.Tensor, + img_lr: torch.Tensor, + patching: Optional[RandomPatching2D] = None, + lead_time_label: Optional[torch.Tensor] = None, + augment_pipe: Optional[ + Callable[[torch.Tensor], Tuple[torch.Tensor, Optional[torch.Tensor]]] + ] = None, + use_patch_grad_acc: bool = False, + ) -> torch.Tensor: """ Calculate and return the loss for denoising score matching. - Parameters: - ---------- - net: torch.nn.Module - The neural network model that will make predictions. - - img_clean: torch.Tensor - Input images (high resolution) to the neural network. - - img_lr: torch.Tensor - Input images (low resolution) to the neural network. - - labels: torch.Tensor - Ground truth labels for the input images. + This method computes a mixture loss that combines deterministic + regression with denoising score matching. It first computes residuals + using the regression network, then applies the diffusion process to + these residuals. + + In addition to the standard denoising score matching loss, this method + also supports optional patching for multi-diffusion. In this case, the spatial + dimensions of the input are decomposed into `P` smaller patches of shape + (H_patch, W_patch), that are grouped along the batch dimension, and the + model is applied to each patch individually. In the following, if `patching` + is not provided, then the input is not patched and `P=1` and `(H_patch, + W_patch) = (H, W)`. When patching is used, the original non-patched conditioning is + interpolated onto a spatial grid of shape `(H_patch, W_patch)` and channel-wise + concatenated to the patched conditioning. This ensures that each patch + maintains global information from the entire domain. + + The diffusion model `net` is expected to be conditioned on an input with + `C_cond` channels, which should be: + - `C_cond = C_lr` if `hr_mean_conditioning` is `False` and + `patching` is None. + - `C_cond = C_hr + C_lr` if `hr_mean_conditioning` is `True` and + `patching` is None. + - `C_cond = C_hr + 2*C_lr` if `hr_mean_conditioning` is `True` and + `patching` is not None. + - `C_cond = 2*C_lr` if `hr_mean_conditioning` is `False` and + `patching` is not None. + Additionally, `C_cond` should also include any embedding channels, + such as positional embeddings or time embeddings. + + Note: this loss function does not apply any reduction. - augment_pipe: callable, optional - An optional data augmentation function that takes images as input and - returns augmented images. If not provided, no data augmentation is applied. + Parameters + ---------- + net : torch.nn.Module + The neural network model for the diffusion process. + Expected signature: `net(latent, y_lr, sigma, + embedding_selector=embedding_selector, lead_time_label=lead_time_label, + augment_labels=augment_labels)`, where: + latent (torch.Tensor): Noisy input of shape (B[*P], C_hr, H_patch, W_patch) + y_lr (torch.Tensor): Conditioning of shape (B[*P], C_cond, H_patch, W_patch) + sigma (torch.Tensor): Noise level of shape (B[*P], 1, 1, 1) + embedding_selector (callable, optional): Function to select + positional embeddings. Only used if `patching` is provided. + lead_time_label (torch.Tensor, optional): Lead time labels. + augment_labels (torch.Tensor, optional): Augmentation labels + Returns: + torch.Tensor: Predictions of shape (B[*P], C_hr, H_patch, W_patch) + + img_clean : torch.Tensor + High-resolution input images of shape (B, C_hr, H, W). + Used as ground truth and for data augmentation if 'augment_pipe' is provided. + + img_lr : torch.Tensor + Low-resolution input images of shape (B, C_lr, H, W). + Used as input to the regression network and conditioning for the + diffusion process. + + patching : Optional[RandomPatching2D], optional + Patching strategy for processing large images, by default None. See + :class:`physicsnemo.utils.patching.RandomPatching2D` for details. + When provided, the patching strategy is used for both image patches + and positional embeddings selection in the diffusion model `net`. + Transforms tensors from shape (B, C, H, W) to (B*P, C, H_patch, + W_patch). + + lead_time_label : Optional[torch.Tensor], optional + Labels for lead-time aware predictions, by default None. + Shape can vary based on model requirements, typically (B,) or scalar. + + augment_pipe : Optional[Callable[[torch.Tensor], Tuple[torch.Tensor, Optional[torch.Tensor]]]] + Data augmentation function. + Expected signature: + img_tot (torch.Tensor): Concatenated high and low resolution images + of shape (B, C_hr+C_lr, H, W) + Returns: + Tuple[torch.Tensor, Optional[torch.Tensor]]: + - Augmented images of shape (B, C_hr+C_lr, H, W) + - Optional augmentation labels + use_patch_grad_acc: bool, optional + A boolean flag indicating whether to enable multi-iterations of patching accumulations + for amortizing regression cost. Default False. - Returns: + Returns ------- torch.Tensor - A tensor representing the loss calculated based on the network's - predictions. + If patching is not used: + A tensor of shape (B, C_hr, H, W) representing the per-sample loss. + If patching is used: + A tensor of shape (B*P, C_hr, H_patch, W_patch) representing + the per-patch loss. + + Raises + ------ + ValueError + If patching is provided but is not an instance of RandomPatching2D. + If shapes of img_clean and img_lr are incompatible. """ - rnd_normal = torch.randn([img_clean.shape[0], 1, 1, 1], device=img_clean.device) - sigma = (rnd_normal * self.P_std + self.P_mean).exp() - weight = (sigma**2 + self.sigma_data**2) / (sigma * self.sigma_data) ** 2 + # Safety check: enforce patching object + if patching and not isinstance(patching, RandomPatching2D): + raise ValueError("patching must be a 'RandomPatching2D' object.") + # Safety check: enforce shapes + if ( + img_clean.shape[0] != img_lr.shape[0] + or img_clean.shape[2:] != img_lr.shape[2:] + ): + raise ValueError( + f"Shape mismatch between img_clean {img_clean.shape} and " + f"img_lr {img_lr.shape}. " + f"Batch size, height and width must match." + ) - # augment for conditional generaiton + # augment for conditional generation img_tot = torch.cat((img_clean, img_lr), dim=1) y_tot, augment_labels = ( augment_pipe(img_tot) if augment_pipe is not None else (img_tot, None) @@ -517,114 +653,71 @@ def __call__( y = y_tot[:, : img_clean.shape[1], :, :] y_lr = y_tot[:, img_clean.shape[1] :, :, :] y_lr_res = y_lr - - # global index - b = y.shape[0] - Nx = torch.arange(self.img_shape_x).int() - Ny = torch.arange(self.img_shape_y).int() - grid = torch.stack(torch.meshgrid(Ny, Nx, indexing="ij"), dim=0)[ - None, - ].expand(b, -1, -1, -1) - - # form residual - if lead_time_label is not None: - y_mean = self.unet( - torch.zeros_like(y, device=img_clean.device), - y_lr_res, - sigma, - labels, - lead_time_label=lead_time_label, - augment_labels=augment_labels, - ) + batch_size = y.shape[0] + + # if using multi-iterations of patching, switch to optimized version + if use_patch_grad_acc: + # form residual + if self.y_mean is None: + if lead_time_label is not None: + y_mean = self.regression_net( + torch.zeros_like(y, device=img_clean.device), + y_lr_res, + lead_time_label=lead_time_label, + augment_labels=augment_labels, + ) + else: + y_mean = self.regression_net( + torch.zeros_like(y, device=img_clean.device), + y_lr_res, + augment_labels=augment_labels, + ) + self.y_mean = y_mean + + # if on full domain, or if using patching without multi-iterations else: - y_mean = self.unet( - torch.zeros_like(y, device=img_clean.device), - y_lr_res, - sigma, - labels, - augment_labels=augment_labels, - ) + # form residual + if lead_time_label is not None: + y_mean = self.regression_net( + torch.zeros_like(y, device=img_clean.device), + y_lr_res, + lead_time_label=lead_time_label, + augment_labels=augment_labels, + ) + else: + y_mean = self.regression_net( + torch.zeros_like(y, device=img_clean.device), + y_lr_res, + augment_labels=augment_labels, + ) - y = y - y_mean + self.y_mean = y_mean + + y = y - self.y_mean if self.hr_mean_conditioning: - y_lr = torch.cat((y_mean, y_lr), dim=1).contiguous() - global_index = None + y_lr = torch.cat((self.y_mean, y_lr), dim=1) + # patchified training # conditioning: cat(y_mean, y_lr, input_interp, pos_embd), 4+12+100+4 - if ( - self.img_shape_x != self.patch_shape_x - or self.img_shape_y != self.patch_shape_y - ): - c_in = y_lr.shape[1] - c_out = y.shape[1] - rnd_normal = torch.randn( - [img_clean.shape[0] * self.patch_num, 1, 1, 1], device=img_clean.device - ) - sigma = (rnd_normal * self.P_std + self.P_mean).exp() - weight = (sigma**2 + self.sigma_data**2) / ( - sigma * self.sigma_data - ) ** 2 - - # global interpolation - input_interp = torch.nn.functional.interpolate( - img_lr, - (self.patch_shape_y, self.patch_shape_x), - mode="bilinear", - ) + # removed patch_embedding_selector due to compilation issue with dynamo. + if patching: + # Patched residual + # (batch_size * patch_num, c_out, patch_shape_y, patch_shape_x) + y_patched = patching.apply(input=y) + # Patched conditioning on y_lr and interp(img_lr) + # (batch_size * patch_num, 2*c_in, patch_shape_y, patch_shape_x) + y_lr_patched = patching.apply(input=y_lr, additional_input=img_lr) + + y = y_patched + y_lr = y_lr_patched + + # Noise + rnd_normal = torch.randn([y.shape[0], 1, 1, 1], device=img_clean.device) + sigma = (rnd_normal * self.P_std + self.P_mean).exp() + weight = (sigma**2 + self.sigma_data**2) / (sigma * self.sigma_data) ** 2 - # patch generation from a single sample (not from random samples due to memory consumption of regression) - y_new = torch.zeros( - b * self.patch_num, - c_out, - self.patch_shape_y, - self.patch_shape_x, - device=img_clean.device, - ) - y_lr_new = torch.zeros( - b * self.patch_num, - c_in + input_interp.shape[1], - self.patch_shape_y, - self.patch_shape_x, - device=img_clean.device, - ) - global_index = torch.zeros( - b * self.patch_num, - 2, - self.patch_shape_y, - self.patch_shape_x, - dtype=torch.int, - device=img_clean.device, - ) - for i in range(self.patch_num): - rnd_x = random.randint(0, self.img_shape_x - self.patch_shape_x) - rnd_y = random.randint(0, self.img_shape_y - self.patch_shape_y) - y_new[b * i : b * (i + 1),] = y[ - :, - :, - rnd_y : rnd_y + self.patch_shape_y, - rnd_x : rnd_x + self.patch_shape_x, - ] - global_index[b * i : b * (i + 1),] = grid[ - :, - :, - rnd_y : rnd_y + self.patch_shape_y, - rnd_x : rnd_x + self.patch_shape_x, - ] - y_lr_new[b * i : b * (i + 1),] = torch.cat( - ( - y_lr[ - :, - :, - rnd_y : rnd_y + self.patch_shape_y, - rnd_x : rnd_x + self.patch_shape_x, - ], - input_interp, - ), - 1, - ) - y = y_new - y_lr = y_lr_new + # Input + noise latent = y + torch.randn_like(y) * sigma if lead_time_label is not None: @@ -632,8 +725,10 @@ def __call__( latent, y_lr, sigma, - labels, - global_index=global_index, + embedding_selector=None, + global_index=patching.global_index(batch_size, img_clean.device) + if patching is not None + else None, lead_time_label=lead_time_label, augment_labels=augment_labels, ) @@ -642,8 +737,10 @@ def __call__( latent, y_lr, sigma, - labels, - global_index=global_index, + embedding_selector=None, + global_index=patching.global_index(batch_size, img_clean.device) + if patching is not None + else None, augment_labels=augment_labels, ) loss = weight * ((D_yn - y) ** 2) @@ -651,6 +748,7 @@ def __call__( return loss + class VELoss_dfsr: """ Loss function for dfsr model, modified from class VELoss. @@ -792,20 +890,19 @@ def __call__(self, net, images, labels, augment_pipe=None): class RegressionLossCE: """ - A regression loss function for the GEFS-HRRR model with probability channels, adapted - from RegressionLoss. In this version, probability channels are evaluated using - CrossEntropyLoss instead of MSELoss. - - Parameters + A regression loss function for deterministic predictions with probability + channels and lead time labels. Adapted from + :class:`physicsnemo.metrics.diffusion.loss.RegressionLoss`. In this version, + probability channels are evaluated using CrossEntropyLoss instead of + squared error. + Note: this loss does not apply any reduction. + + Attributes ---------- - P_mean: float, optional - Mean value for `sigma` computation, by default -1.2. - P_std: float, optional: - Standard deviation for `sigma` computation, by default 1.2. - sigma_data: float, optional - Standard deviation for data, by default 0.5. - prob_channels: list, optional - A index list of output probability channels. + entropy : torch.nn.CrossEntropyLoss + Cross entropy loss function used for probability channels. + prob_channels : list[int] + List of channel indices to be treated as probability channels. Note ---- @@ -817,62 +914,86 @@ class RegressionLossCE: def __init__( self, - P_mean: float = -1.2, - P_std: float = 1.2, - sigma_data: float = 0.5, - prob_channels: list = [4, 5, 6, 7, 8], + prob_channels: list[int] = [4, 5, 6, 7, 8], ): - self.P_mean = P_mean - self.P_std = P_std - self.sigma_data = sigma_data + """ + Arguments + ---------- + prob_channels: list[int], optional + List of channel indices from the target tensor to be treated as + probability channels. Cross entropy loss is computed over these + channels, while the remaining channels are treated as scalar + channels and the squared error loss is computed over them. By + default, [4, 5, 6, 7, 8]. + """ self.entropy = torch.nn.CrossEntropyLoss(reduction="none") self.prob_channels = prob_channels def __call__( self, - net, - img_clean, - img_lr, - lead_time_label=None, - labels=None, - augment_pipe=None, - ): + net: torch.nn.Module, + img_clean: torch.Tensor, + img_lr: torch.Tensor, + lead_time_label: Optional[torch.Tensor] = None, + augment_pipe: Optional[ + Callable[[torch.Tensor], Tuple[torch.Tensor, Optional[torch.Tensor]]] + ] = None, + ) -> torch.Tensor: """ - Calculate and return the loss for the U-Net for deterministic predictions. + Calculate and return the loss for deterministic + predictions, treating specific channels as probability distributions. - Parameters: + Parameters ---------- - net: torch.nn.Module + net : torch.nn.Module The neural network model that will make predictions. + Expected signature: `net(input, img_lr, lead_time_label=lead_time_label, augment_labels=augment_labels)`, + where: + input (torch.Tensor): Tensor of shape (B, C_hr, H, W). Zero-filled. + y_lr (torch.Tensor): Low-resolution input of shape (B, C_lr, H, W) + lead_time_label (torch.Tensor, optional): Optional lead time + labels. If provided, should be of shape (B,). + augment_labels (torch.Tensor, optional): Optional augmentation + labels, returned by `augment_pipe`. + Returns: + torch.Tensor: Predictions of shape (B, C_hr, H, W) + + img_clean : torch.Tensor + High-resolution input images of shape (B, C_hr, H, W). + Used as ground truth and for data augmentation if `augment_pipe` is provided. + + img_lr : torch.Tensor + Low-resolution input images of shape (B, C_lr, H, W). + Used as input to the neural network. + + lead_time_label : Optional[torch.Tensor], optional + Lead time labels for temporal predictions, by default None. + Shape can vary based on model requirements, typically (B,) or scalar. + + augment_pipe : Optional[Callable[[torch.Tensor], Tuple[torch.Tensor, Optional[torch.Tensor]]]] + Data augmentation function. + Expected signature: + img_tot (torch.Tensor): Concatenated high and low resolution + images of shape (B, C_hr+C_lr, H, W). + Returns: + Tuple[torch.Tensor, Optional[torch.Tensor]]: + - Augmented images of shape (B, C_hr+C_lr, H, W) + - Optional augmentation labels - img_clean: torch.Tensor - Input images (high resolution) to the neural network. - - img_lr: torch.Tensor - Input images (low resolution) to the neural network. - - lead_time_label: torch.Tensor - Lead time labels for input batches. - - labels: torch.Tensor - Ground truth labels for the input images. - - augment_pipe: callable, optional - An optional data augmentation function that takes images as input and - returns augmented images. If not provided, no data augmentation is applied. - - Returns: + Returns ------- torch.Tensor - A tensor representing the loss calculated based on the network's - predictions. + A tensor of shape (B, C_loss, H, W) representing the pixel-wise + loss., where `C_loss = C_hr - len(prob_channels) + 1`. More + specifically, the last channel of the output tensor corresponds to + the cross-entropy loss computed over the channels specified in + `prob_channels`, while the first `C_hr - len(prob_channels)` + channels of the output tensor correspond to the squared error loss. """ all_channels = list(range(img_clean.shape[1])) # [0, 1, 2, ..., 10] scalar_channels = [ item for item in all_channels if item not in self.prob_channels ] - rnd_normal = torch.randn([img_clean.shape[0], 1, 1, 1], device=img_clean.device) - sigma = (rnd_normal * self.P_std + self.P_mean).exp() weight = ( 1.0 # (sigma ** 2 + self.sigma_data ** 2) / (sigma * self.sigma_data) ** 2 ) @@ -890,8 +1011,6 @@ def __call__( D_yn = net( input, y_lr, - sigma, - labels, lead_time_label=lead_time_label, augment_labels=augment_labels, ) @@ -899,11 +1018,10 @@ def __call__( D_yn = net( input, y_lr, - sigma, - labels, + lead_time_label=lead_time_label, augment_labels=augment_labels, ) - loss1 = weight * ((D_yn[:, scalar_channels] - y[:, scalar_channels]) ** 2) + loss1 = weight * (D_yn[:, scalar_channels] - y[:, scalar_channels]) ** 2 loss2 = ( weight * self.entropy(D_yn[:, self.prob_channels], y[:, self.prob_channels])[ @@ -911,4 +1029,4 @@ def __call__( ] ) loss = torch.cat((loss1, loss2), dim=1) - return loss + return loss \ No newline at end of file diff --git a/src/hirad/models/__init__.py b/src/hirad/models/__init__.py index 3ab4a6f3..b00a4777 100644 --- a/src/hirad/models/__init__.py +++ b/src/hirad/models/__init__.py @@ -1,6 +1,14 @@ -from .layers import Linear, Conv2d, GroupNorm, AttentionOp, UNetBlock, PositionalEmbedding, FourierEmbedding +from .layers import ( + Linear, + Conv2d, + GroupNorm, + AttentionOp, + UNetBlock, + PositionalEmbedding, + FourierEmbedding +) from .meta import ModelMetaData from .song_unet import SongUNet, SongUNetPosEmbd, SongUNetPosLtEmbd from .dhariwal_unet import DhariwalUNet from .unet import UNet -from .preconditioning import EDMPrecondSR, EDMPrecond +from .preconditioning import EDMPrecondSuperResolution, EDMPrecondSR, EDMPrecond diff --git a/src/hirad/models/layers.py b/src/hirad/models/layers.py index 8612da7e..d7e63d7b 100644 --- a/src/hirad/models/layers.py +++ b/src/hirad/models/layers.py @@ -19,15 +19,27 @@ Diffusion-Based Generative Models". """ +import contextlib +import importlib from typing import Any, Dict, List import numpy as np +import nvtx import torch +import torch.cuda.amp as amp from einops import rearrange -from torch.nn.functional import silu +from torch.nn.functional import elu, gelu, leaky_relu, relu, sigmoid, silu, tanh from hirad.utils.model_utils import weight_init +_is_apex_available = False +if torch.cuda.is_available(): + try: + apex_gn_module = importlib.import_module("apex.contrib.group_norm") + ApexGroupNorm = getattr(apex_gn_module, "GroupNorm") + _is_apex_available = True + except ImportError: + pass class Linear(torch.nn.Module): """ @@ -56,6 +68,8 @@ class Linear(torch.nn.Module): A scaling factor to multiply with the initialized weights. By default 1. init_bias : float, optional A scaling factor to multiply with the initialized biases. By default 0. + amp_mode : bool, optional + A boolean flag indicating whether mixed-precision (AMP) training is enabled. Defaults to False. """ def __init__( @@ -66,10 +80,12 @@ def __init__( init_mode: str = "kaiming_normal", init_weight: int = 1, init_bias: int = 0, + amp_mode: bool = False, ): super().__init__() self.in_features = in_features self.out_features = out_features + self.amp_mode = amp_mode init_kwargs = dict(mode=init_mode, fan_in=in_features, fan_out=out_features) self.weight = torch.nn.Parameter( weight_init([out_features, in_features], **init_kwargs) * init_weight @@ -81,9 +97,16 @@ def __init__( ) def forward(self, x): - x = x @ self.weight.to(x.dtype).t() + weight, bias = self.weight, self.bias + # pdb.set_trace() + if not self.amp_mode: + if self.weight is not None and self.weight.dtype != x.dtype: + weight = self.weight.to(x.dtype) + if self.bias is not None and self.bias.dtype != x.dtype: + bias = self.bias.to(x.dtype) + x = x @ weight.t() if self.bias is not None: - x = x.add_(self.bias.to(x.dtype)) + x = x.add_(bias) return x @@ -128,6 +151,10 @@ class Conv2d(torch.nn.Module): A scaling factor to multiply with the initialized weights. By default 1.0. init_bias : float, optional A scaling factor to multiply with the initialized biases. By default 0.0. + fused_conv_bias: bool, optional + A boolean flag indicating whether bias will be passed as a parameter of conv2d. By default False. + amp_mode : bool, optional + A boolean flag indicating whether mixed-precision (AMP) training is enabled. Defaults to False. """ def __init__( @@ -143,9 +170,16 @@ def __init__( init_mode: str = "kaiming_normal", init_weight: float = 1.0, init_bias: float = 0.0, + fused_conv_bias: bool = False, + amp_mode: bool = False, ): if up and down: raise ValueError("Both 'up' and 'down' cannot be true at the same time.") + if not kernel and fused_conv_bias: + print( + "Warning: Kernel is required when fused_conv_bias is enabled. Setting fused_conv_bias to False." + ) + fused_conv_bias = False super().__init__() self.in_channels = in_channels @@ -153,6 +187,8 @@ def __init__( self.up = up self.down = down self.fused_resample = fused_resample + self.fused_conv_bias = fused_conv_bias + self.amp_mode = amp_mode init_kwargs = dict( mode=init_mode, fan_in=in_channels * kernel * kernel, @@ -176,13 +212,21 @@ def __init__( self.register_buffer("resample_filter", f if up or down else None) def forward(self, x): - w = self.weight.to(x.dtype) if self.weight is not None else None - b = self.bias.to(x.dtype) if self.bias is not None else None - f = ( - self.resample_filter.to(x.dtype) - if self.resample_filter is not None - else None - ) + weight, bias, resample_filter = self.weight, self.bias, self.resample_filter + if not self.amp_mode: + if self.weight is not None and self.weight.dtype != x.dtype: + weight = self.weight.to(x.dtype) + if self.bias is not None and self.bias.dtype != x.dtype: + bias = self.bias.to(x.dtype) + if ( + self.resample_filter is not None + and self.resample_filter.dtype != x.dtype + ): + resample_filter = self.resample_filter.to(x.dtype) + + w = weight if weight is not None else None + b = bias if bias is not None else None + f = resample_filter if resample_filter is not None else None w_pad = w.shape[-1] // 2 if w is not None else 0 f_pad = (f.shape[-1] - 1) // 2 if f is not None else 0 @@ -194,15 +238,29 @@ def forward(self, x): stride=2, padding=max(f_pad - w_pad, 0), ) - x = torch.nn.functional.conv2d(x, w, padding=max(w_pad - f_pad, 0)) + if self.fused_conv_bias: + x = torch.nn.functional.conv2d( + x, w, padding=max(w_pad - f_pad, 0), bias=b + ) + else: + x = torch.nn.functional.conv2d(x, w, padding=max(w_pad - f_pad, 0)) elif self.fused_resample and self.down and w is not None: x = torch.nn.functional.conv2d(x, w, padding=w_pad + f_pad) - x = torch.nn.functional.conv2d( - x, - f.tile([self.out_channels, 1, 1, 1]), - groups=self.out_channels, - stride=2, - ) + if self.fused_conv_bias: + x = torch.nn.functional.conv2d( + x, + f.tile([self.out_channels, 1, 1, 1]), + groups=self.out_channels, + stride=2, + bias=b, + ) + else: + x = torch.nn.functional.conv2d( + x, + f.tile([self.out_channels, 1, 1, 1]), + groups=self.out_channels, + stride=2, + ) else: if self.up: x = torch.nn.functional.conv_transpose2d( @@ -220,11 +278,15 @@ def forward(self, x): stride=2, padding=f_pad, ) - if w is not None: - #TODO during inference, model breaks here for some reason - # current fix is to disable torch.backends.cudnn.enabled = False - x = torch.nn.functional.conv2d(x, w, padding=w_pad) - if b is not None: + + #TODO during inference, model breaks here for some reason + # current fix is to disable torch.backends.cudnn.enabled = False + if w is not None: # ask in corrdiff channel whether w will ever be none + if self.fused_conv_bias: + x = torch.nn.functional.conv2d(x, w, padding=w_pad, bias=b) + else: + x = torch.nn.functional.conv2d(x, w, padding=w_pad) + if b is not None and not self.fused_conv_bias: x = x.add_(b.reshape(1, -1, 1, 1)) return x @@ -251,7 +313,15 @@ class GroupNorm(torch.nn.Module): eps : float, optional A small number added to the variance to prevent division by zero, by default 1e-5. - + use_apex_gn : bool, optional + A boolean flag indicating whether we want to use Apex GroupNorm for NHWC layout. + Need to set this as False on cpu. Defaults to False. + fused_act : bool, optional + Whether to fuse the activation function with GroupNorm. Defaults to False. + act : str, optional + The activation function to use when fusing activation with GroupNorm. Defaults to None. + amp_mode : bool, optional + A boolean flag indicating whether mixed-precision (AMP) training is enabled. Defaults to False. Notes ----- If `num_channels` is not divisible by `num_groups`, the actual number of groups @@ -264,28 +334,71 @@ def __init__( num_groups: int = 32, min_channels_per_group: int = 4, eps: float = 1e-5, + use_apex_gn: bool = False, + fused_act: bool = False, + act: str = None, + amp_mode: bool = False, ): + if fused_act and act is None: + raise ValueError("'act' must be specified when 'fused_act' is set to True.") + super().__init__() self.num_groups = min(num_groups, num_channels // min_channels_per_group) self.eps = eps self.weight = torch.nn.Parameter(torch.ones(num_channels)) self.bias = torch.nn.Parameter(torch.zeros(num_channels)) + if use_apex_gn and not _is_apex_available: + raise ValueError("'apex' is not installed, set `use_apex_gn=False`") + self.use_apex_gn = use_apex_gn + self.fused_act = fused_act + self.act = act.lower() if act else act + self.act_fn = None + self.amp_mode = amp_mode + if self.use_apex_gn: + if self.act: + self.gn = ApexGroupNorm( + num_groups=self.num_groups, + num_channels=num_channels, + eps=self.eps, + affine=True, + act=self.act, + ) + + else: + self.gn = ApexGroupNorm( + num_groups=self.num_groups, + num_channels=num_channels, + eps=self.eps, + affine=True, + ) + if self.fused_act: + self.act_fn = self.get_activation_function() def forward(self, x): - if self.training: + weight, bias = self.weight, self.bias + if not self.amp_mode: + if not self.use_apex_gn: + if weight.dtype != x.dtype: + weight = self.weight.to(x.dtype) + if bias.dtype != x.dtype: + bias = self.bias.to(x.dtype) + if self.use_apex_gn: + x = self.gn(x) + elif self.training: # Use default torch implementation of GroupNorm for training # This does not support channels last memory format x = torch.nn.functional.group_norm( x, num_groups=self.num_groups, - weight=self.weight.to(x.dtype), - bias=self.bias.to(x.dtype), + weight=weight, + bias=bias, eps=self.eps, ) + if self.fused_act: + x = self.act_fn(x) else: # Use custom GroupNorm implementation that supports channels last # memory layout for inference - dtype = x.dtype x = x.float() x = rearrange(x, "b (g c) h w -> b g c h w", g=self.num_groups) @@ -295,12 +408,33 @@ def forward(self, x): x = (x - mean) * (var + self.eps).rsqrt() x = rearrange(x, "b g c h w -> b (g c) h w") - weight = rearrange(self.weight, "c -> 1 c 1 1") - bias = rearrange(self.bias, "c -> 1 c 1 1") + weight = rearrange(weight, "c -> 1 c 1 1") + bias = rearrange(bias, "c -> 1 c 1 1") x = x * weight + bias - x = x.type(dtype) + if self.fused_act: + x = self.act_fn(x) return x + + def get_activation_function(self): + """ + Get activation function given string input + """ + + activation_map = { + "silu": silu, + "relu": relu, + "leaky_relu": leaky_relu, + "sigmoid": sigmoid, + "tanh": tanh, + "gelu": gelu, + "elu": elu, + } + + act_fn = activation_map.get(self.act, None) + if act_fn is None: + raise ValueError(f"Unknown activation function: {self.act}") + return act_fn class AttentionOp(torch.autograd.Function): @@ -333,6 +467,7 @@ def backward(ctx, dw): dim=2, input_dtype=torch.float32, ) + dq = torch.einsum("nck,nqk->ncq", k.to(torch.float32), db).to( q.dtype ) / np.sqrt(k.shape[1]) @@ -385,6 +520,17 @@ class UNetBlock(torch.nn.Module): init_attn : dict, optional Initialization parameters specific to attention mechanism layers. Defaults to 'init' if not provided. + use_apex_gn : bool, optional + A boolean flag indicating whether we want to use Apex GroupNorm for NHWC layout. + Need to set this as False on cpu. Defaults to False. + act : str, optional + The activation function to use when fusing activation with GroupNorm. Defaults to None. + fused_conv_bias: bool, optional + A boolean flag indicating whether bias will be passed as a parameter of conv2d. By default False. + profile_mode: + A boolean flag indicating whether to enable all nvtx annotations during profiling. + amp_mode : bool, optional + A boolean flag indicating whether mixed-precision (AMP) training is enabled. Defaults to False. """ def __init__( @@ -406,6 +552,11 @@ def __init__( init: Dict[str, Any] = dict(), init_zero: Dict[str, Any] = dict(init_weight=0), init_attn: Any = None, + use_apex_gn: bool = False, + act: str = "silu", + fused_conv_bias: bool = False, + profile_mode: bool = False, + amp_mode: bool = False, ): super().__init__() @@ -423,7 +574,16 @@ def __init__( self.skip_scale = skip_scale self.adaptive_scale = adaptive_scale - self.norm0 = GroupNorm(num_channels=in_channels, eps=eps) + self.profile_mode = profile_mode + self.amp_mode = amp_mode + self.norm0 = GroupNorm( + num_channels=in_channels, + eps=eps, + use_apex_gn=use_apex_gn, + fused_act=True, + act=act, + amp_mode=amp_mode, + ) self.conv0 = Conv2d( in_channels=in_channels, out_channels=out_channels, @@ -431,21 +591,45 @@ def __init__( up=up, down=down, resample_filter=resample_filter, + fused_conv_bias=fused_conv_bias, + amp_mode=amp_mode, **init, ) self.affine = Linear( in_features=emb_channels, out_features=out_channels * (2 if adaptive_scale else 1), + amp_mode=amp_mode, **init, ) - self.norm1 = GroupNorm(num_channels=out_channels, eps=eps) + if self.adaptive_scale: + self.norm1 = GroupNorm( + num_channels=out_channels, + eps=eps, + use_apex_gn=use_apex_gn, + amp_mode=amp_mode, + ) + else: + self.norm1 = GroupNorm( + num_channels=out_channels, + eps=eps, + use_apex_gn=use_apex_gn, + act=act, + fused_act=True, + amp_mode=amp_mode, + ) self.conv1 = Conv2d( - in_channels=out_channels, out_channels=out_channels, kernel=3, **init_zero + in_channels=out_channels, + out_channels=out_channels, + kernel=3, + fused_conv_bias=fused_conv_bias, + amp_mode=amp_mode, + **init_zero, ) self.skip = None if out_channels != in_channels or up or down: kernel = 1 if resample_proj or out_channels != in_channels else 0 + fused_conv_bias = fused_conv_bias if kernel != 0 else False self.skip = Conv2d( in_channels=in_channels, out_channels=out_channels, @@ -453,55 +637,75 @@ def __init__( up=up, down=down, resample_filter=resample_filter, + fused_conv_bias=fused_conv_bias, + amp_mode=amp_mode, **init, ) if self.num_heads: - self.norm2 = GroupNorm(num_channels=out_channels, eps=eps) + self.norm2 = GroupNorm( + num_channels=out_channels, + eps=eps, + use_apex_gn=use_apex_gn, + amp_mode=amp_mode, + ) self.qkv = Conv2d( in_channels=out_channels, out_channels=out_channels * 3, kernel=1, + fused_conv_bias=fused_conv_bias, + amp_mode=amp_mode, **(init_attn if init_attn is not None else init), ) self.proj = Conv2d( in_channels=out_channels, out_channels=out_channels, kernel=1, + fused_conv_bias=fused_conv_bias, + amp_mode=amp_mode, **init_zero, ) def forward(self, x, emb): - torch.cuda.nvtx.range_push("UNetBlock") - orig = x - x = self.conv0(silu(self.norm0(x))) - params = self.affine(emb).unsqueeze(2).unsqueeze(3).to(x.dtype) - if self.adaptive_scale: - scale, shift = params.chunk(chunks=2, dim=1) - x = silu(torch.addcmul(shift, self.norm1(x), scale + 1)) - else: - x = silu(self.norm1(x.add_(params))) - - x = self.conv1( - torch.nn.functional.dropout(x, p=self.dropout, training=self.training) - ) - x = x.add_(self.skip(orig) if self.skip is not None else orig) - x = x * self.skip_scale - - if self.num_heads: - q, k, v = ( - self.qkv(self.norm2(x)) - .reshape( - x.shape[0] * self.num_heads, x.shape[1] // self.num_heads, 3, -1 - ) - .unbind(2) + with nvtx.annotate( + message="UNetBlock", color="purple" + ) if self.profile_mode else contextlib.nullcontext(): + orig = x + x = self.conv0(self.norm0(x)) + params = self.affine(emb).unsqueeze(2).unsqueeze(3) + if not self.amp_mode: + if params.dtype != x.dtype: + params = params.to(x.dtype) + + if self.adaptive_scale: + scale, shift = params.chunk(chunks=2, dim=1) + x = silu(torch.addcmul(shift, self.norm1(x), scale + 1)) + else: + x = self.norm1(x.add_(params)) + + x = self.conv1( + torch.nn.functional.dropout(x, p=self.dropout, training=self.training) ) - w = AttentionOp.apply(q, k) - a = torch.einsum("nqk,nck->ncq", w, v) - x = self.proj(a.reshape(*x.shape)).add_(x) + x = x.add_(self.skip(orig) if self.skip is not None else orig) x = x * self.skip_scale - torch.cuda.nvtx.range_pop() - return x + + if self.num_heads: + q, k, v = ( + self.qkv(self.norm2(x)) + .reshape( + x.shape[0], self.num_heads, x.shape[1] // self.num_heads, 3, -1 + ) + .unbind(3) + ) + # w = AttentionOp.apply(q, k) + # a = torch.einsum("nqk,nck->ncq", w, v) + # Compute attention in one step + with amp.autocast(enabled=self.amp_mode): + attn = torch.nn.functional.scaled_dot_product_attention(q, k, v) + x = self.proj(attn.reshape(*x.shape)).add_(x) + x = x * self.skip_scale + + return x class PositionalEmbedding(torch.nn.Module): @@ -517,16 +721,23 @@ class PositionalEmbedding(torch.nn.Module): Maximum number of positions for the embeddings, by default 10000. endpoint : bool, optional If True, the embedding considers the endpoint. By default False. + amp_mode : bool, optional + A boolean flag indicating whether mixed-precision (AMP) training is enabled. Defaults to False. """ def __init__( - self, num_channels: int, max_positions: int = 10000, endpoint: bool = False + self, + num_channels: int, + max_positions: int = 10000, + endpoint: bool = False, + amp_mode: bool = False, ): super().__init__() self.num_channels = num_channels self.max_positions = max_positions self.endpoint = endpoint + self.amp_mode = amp_mode def forward(self, x): freqs = torch.arange( @@ -534,7 +745,10 @@ def forward(self, x): ) freqs = freqs / (self.num_channels // 2 - (1 if self.endpoint else 0)) freqs = (1 / self.max_positions) ** freqs - x = x.ger(freqs.to(x.dtype)) + if not self.amp_mode: + if freqs.dtype != x.dtype: + freqs = freqs.to(x.dtype) + x = x.ger(freqs) x = torch.cat([x.cos(), x.sin()], dim=1) return x @@ -556,13 +770,21 @@ class FourierEmbedding(torch.nn.Module): scale : int, optional A scale factor applied to the random frequencies, controlling their range and thereby the frequency of oscillations in the embedding space. By default 16. + amp_mode : bool, optional + A boolean flag indicating whether mixed-precision (AMP) training is enabled. Defaults to False. """ - def __init__(self, num_channels: int, scale: int = 16): + def __init__(self, num_channels: int, scale: int = 16, amp_mode: bool = False): super().__init__() self.register_buffer("freqs", torch.randn(num_channels // 2) * scale) + self.amp_mode = amp_mode def forward(self, x): - x = x.ger((2 * np.pi * self.freqs).to(x.dtype)) + freqs = self.freqs + if not self.amp_mode: + if x.dtype != self.freqs.dtype: + freqs = self.freqs.to(x.dtype) + + x = x.ger((2 * np.pi * freqs)) x = torch.cat([x.cos(), x.sin()], dim=1) return x diff --git a/src/hirad/models/preconditioning.py b/src/hirad/models/preconditioning.py index c66b6b60..74496a59 100644 --- a/src/hirad/models/preconditioning.py +++ b/src/hirad/models/preconditioning.py @@ -22,19 +22,12 @@ import importlib import warnings from dataclasses import dataclass -from typing import List, Union +from typing import List, Literal, Tuple, Union import numpy as np -import nvtx import torch import torch.nn as nn -from .song_unet import ( - SongUNet, # noqa: F401 for globals -) -from .dhariwal_unet import ( - DhariwalUNet, # noqa: F401 for globals -) from .meta import ModelMetaData network_module = importlib.import_module("hirad.models") @@ -694,12 +687,11 @@ def round_sigma(sigma: Union[float, List, torch.Tensor]): """ return torch.as_tensor(sigma) - @dataclass -class EDMPrecondSRMetaData(ModelMetaData): +class EDMPrecondSuperResolutionMetaData(ModelMetaData): """EDMPrecondSR meta data""" - name: str = "EDMPrecondSR" + name: str = "EDMPrecondSuperResolution" # Optimization jit: bool = False cuda_graphs: bool = False @@ -715,33 +707,40 @@ class EDMPrecondSRMetaData(ModelMetaData): auto_grad: bool = False -class EDMPrecondSR(nn.Module): +class EDMPrecondSuperResolution(nn.Module): """ Improved preconditioning proposed in the paper "Elucidating the Design Space of - Diffusion-Based Generative Models" (EDM) for super-resolution tasks + Diffusion-Based Generative Models" (EDM). + + This is a variant of `EDMPrecond` that is specifically designed for super-resolution + tasks. It wraps a neural network that predicts the denoised high-resolution image + given a noisy high-resolution image, and additional conditioning that includes a + low-resolution image, and a noise level. Parameters ---------- - img_resolution : int - Image resolution. - img_channels : int - Number of color channels. + img_resolution : Union[int, Tuple[int, int]] + Spatial resolution `(H, W)` of the image. If a single int is provided, + the image is assumed to be square. img_in_channels : int - Number of input color channels. + Number of input channels in the low-resolution input image. img_out_channels : int - Number of output color channels. - use_fp16 : bool - Execute the underlying model at FP16 precision?, by default False. - sigma_min : float + Number of output channels in the high-resolution output image. + use_fp16 : bool, optional + Whether to use half-precision floating point (FP16) for model execution, + by default False. + model_type : str, optional + Class name of the underlying model. Must be one of the following: + 'SongUNet', 'SongUNetPosEmbd', 'SongUNetPosLtEmbd', 'DhariwalUNet'. + Defaults to 'SongUNetPosEmbd'. + sigma_data : float, optional + Expected standard deviation of the training data, by default 0.5. + sigma_min : float, optional Minimum supported noise level, by default 0.0. - sigma_max : float + sigma_max : float, optional Maximum supported noise level, by default inf. - sigma_data : float - Expected standard deviation of the training data, by default 0.5. - model_type :str - Class name of the underlying model, by default "SongUNetPosEmbd". **model_kwargs : dict - Keyword arguments for the underlying model. + Keyword arguments passed to the underlying model `__init__` method. Note ---- @@ -757,28 +756,26 @@ class EDMPrecondSR(nn.Module): def __init__( self, - img_resolution, - img_channels, - img_in_channels, - img_out_channels, - use_fp16=False, + img_resolution: Union[int, Tuple[int, int]], + img_in_channels: int, + img_out_channels: int, + use_fp16: bool = False, + model_type: Literal[ + "SongUNetPosEmbd", "SongUNetPosLtEmbd", "SongUNet", "DhariwalUNet" + ] = "SongUNetPosEmbd", + sigma_data: float = 0.5, sigma_min=0.0, sigma_max=float("inf"), - sigma_data=0.5, - model_type="SongUNetPosEmbd", - scale_cond_input=True, - **model_kwargs, + **model_kwargs: dict, ): super().__init__() #meta=EDMPrecondSRMetaData self.img_resolution = img_resolution - self.img_channels = img_channels # TODO: this is not used, remove it self.img_in_channels = img_in_channels self.img_out_channels = img_out_channels self.use_fp16 = use_fp16 + self.sigma_data = sigma_data self.sigma_min = sigma_min self.sigma_max = sigma_max - self.sigma_data = sigma_data - self.scale_cond_input = scale_cond_input model_class = getattr(network_module, model_type) self.model = model_class( @@ -787,39 +784,73 @@ def __init__( out_channels=img_out_channels, **model_kwargs, ) # TODO needs better handling - self.scaling_fn = self._get_scaling_fn() - - def _get_scaling_fn(self): - if self.scale_cond_input: - warnings.warn( - "scale_cond_input=True does not properly scale the conditional input. " - "(see https://github.com/NVIDIA/modulus/issues/229). " - "This setup will be deprecated. " - "Please set scale_cond_input=False.", - DeprecationWarning, - ) - return self._legacy_scaling_fn - else: - return self._scaling_fn + self.scaling_fn = self._scaling_fn @staticmethod - def _scaling_fn(x, img_lr, c_in): - return torch.cat([c_in * x, img_lr.to(x.dtype)], dim=1) + def _scaling_fn( + x: torch.Tensor, img_lr: torch.Tensor, c_in: torch.Tensor + ) -> torch.Tensor: + """ + Scale input tensors by first scaling the high-resolution tensor and then + concatenating with the low-resolution tensor. - @staticmethod - def _legacy_scaling_fn(x, img_lr, c_in): - return c_in * torch.cat([x, img_lr.to(x.dtype)], dim=1) + Parameters + ---------- + x : torch.Tensor + Noisy high-resolution image of shape (B, C_hr, H, W). + img_lr : torch.Tensor + Low-resolution image of shape (B, C_lr, H, W). + c_in : torch.Tensor + Scaling factor of shape (B, 1, 1, 1). + + Returns + ------- + torch.Tensor + Scaled and concatenated tensor of shape (B, C_in+C_out, H, W). + """ + return torch.cat([c_in * x, img_lr.to(x.dtype)], dim=1) - @nvtx.annotate(message="EDMPrecondSR", color="orange") def forward( self, - x, - img_lr, - sigma, - force_fp32=False, - **model_kwargs, - ): - # Concatenate input channels + x: torch.Tensor, + img_lr: torch.Tensor, + sigma: torch.Tensor, + force_fp32: bool = False, + **model_kwargs: dict, + ) -> torch.Tensor: + """ + Forward pass of the EDMPrecondSuperResolution model wrapper. + + This method applies the EDM preconditioning to compute the denoised image + from a noisy high-resolution image and low-resolution conditioning image. + + Parameters + ---------- + x : torch.Tensor + Noisy high-resolution image of shape (B, C_hr, H, W). The number of + channels `C_hr` should be equal to `img_out_channels`. + img_lr : torch.Tensor + Low-resolution conditioning image of shape (B, C_lr, H, W). The number + of channels `C_lr` should be equal to `img_in_channels`. + sigma : torch.Tensor + Noise level of shape (B) or (B, 1) or (B, 1, 1, 1). + force_fp32 : bool, optional + Whether to force FP32 precision regardless of the `use_fp16` attribute, + by default False. + **model_kwargs : dict + Additional keyword arguments to pass to the underlying model + `self.model` forward method. + + Returns + ------- + torch.Tensor + Denoised high-resolution image of shape (B, C_hr, H, W). + + Raises + ------ + ValueError + If the model output dtype doesn't match the expected dtype. + """ x = x.to(torch.float32) sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) dtype = ( @@ -855,13 +886,190 @@ def forward( return D_x @staticmethod - def round_sigma(sigma: Union[float, List, torch.Tensor]): + def round_sigma(sigma: Union[float, List, torch.Tensor]) -> torch.Tensor: """ Convert a given sigma value(s) to a tensor representation. - See EDMPrecond.round_sigma + + Parameters + ---------- + sigma : Union[float, List, torch.Tensor] + Sigma value(s) to convert. + + Returns + ------- + torch.Tensor + Tensor representation of sigma values. + + See Also + -------- + EDMPrecond.round_sigma """ return EDMPrecond.round_sigma(sigma) + @property + def amp_mode(self): + """ + Return the *amp_mode* flag of the wrapped model or *None*. + """ + return getattr(self.model, "amp_mode", None) + + @amp_mode.setter + def amp_mode(self, value: bool): + """ + Propagate *amp_mode* to the model and all its sub-modules. + """ + + if not isinstance(value, bool): + raise TypeError("amp_mode must be a boolean value.") + + if hasattr(self.model, "amp_mode"): + self.model.amp_mode = value + + for sub_module in self.model.modules(): + if hasattr(sub_module, "amp_mode"): + sub_module.amp_mode = value + +# NOTE: This is a deprecated version of the EDMPrecondSuperResolution model. +# This was used to maintain backwards compatibility and allow loading old models. +@dataclass +class EDMPrecondSRMetaData(ModelMetaData): + """EDMPrecondSR meta data""" + + name: str = "EDMPrecondSR" + # Optimization + jit: bool = False + cuda_graphs: bool = False + amp_cpu: bool = False + amp_gpu: bool = True + torch_fx: bool = False + # Data type + bf16: bool = False + # Inference + onnx: bool = False + # Physics informed + func_torch: bool = False + auto_grad: bool = False + + +class EDMPrecondSR(EDMPrecondSuperResolution): + """ + Improved preconditioning proposed in the paper "Elucidating the Design Space of + Diffusion-Based Generative Models" (EDM) for super-resolution tasks + + Parameters + ---------- + img_resolution : int + Image resolution. + img_channels : int + Number of color channels. + img_in_channels : int + Number of input color channels. + img_out_channels : int + Number of output color channels. + use_fp16 : bool + Execute the underlying model at FP16 precision?, by default False. + sigma_min : float + Minimum supported noise level, by default 0.0. + sigma_max : float + Maximum supported noise level, by default inf. + sigma_data : float + Expected standard deviation of the training data, by default 0.5. + model_type :str + Class name of the underlying model, by default "SongUNetPosEmbd". + **model_kwargs : dict + Keyword arguments for the underlying model. + + Note + ---- + References: + - Karras, T., Aittala, M., Aila, T. and Laine, S., 2022. Elucidating the + design space of diffusion-based generative models. Advances in Neural Information + Processing Systems, 35, pp.26565-26577. + - Mardani, M., Brenowitz, N., Cohen, Y., Pathak, J., Chen, C.Y., + Liu, C.C.,Vahdat, A., Kashinath, K., Kautz, J. and Pritchard, M., 2023. + Generative Residual Diffusion Modeling for Km-scale Atmospheric Downscaling. + arXiv preprint arXiv:2309.15214. + """ + + def __init__( + self, + img_resolution, + img_channels, #deprecated + img_in_channels, + img_out_channels, + use_fp16=False, + sigma_min=0.0, + sigma_max=float("inf"), + sigma_data=0.5, + model_type="SongUNetPosEmbd", + scale_cond_input=True, #deprecated + **model_kwargs, + ): + warnings.warn( + "EDMPrecondSR is deprecated and will be removed in a future version. " + "Please use EDMPrecondSuperResolution instead.", + DeprecationWarning, + stacklevel=2, + ) + + if scale_cond_input: + warnings.warn( + "scale_cond_input=True does not properly scale the conditional input. " + "(see https://github.com/NVIDIA/modulus/issues/229). " + "This setup will be deprecated. " + "Please set scale_cond_input=False.", + DeprecationWarning, + ) + + super().__init__( + img_resolution=img_resolution, + img_in_channels=img_in_channels, + img_out_channels=img_out_channels, + use_fp16=use_fp16, + sigma_min=sigma_min, + sigma_max=sigma_max, + sigma_data=sigma_data, + model_type=model_type, + **model_kwargs, + ) + + # Store deprecated parameters for backward compatibility + self.img_channels = img_channels + self.scale_cond_input = scale_cond_input + + def forward( + self, + x, + img_lr, + sigma, + force_fp32=False, + **model_kwargs, + ): + """ + Forward pass of the EDMPrecondSR model wrapper. + + Parameters + ---------- + x : torch.Tensor + Noisy high-resolution image of shape (B, C_hr, H, W). + img_lr : torch.Tensor + Low-resolution conditioning image of shape (B, C_lr, H, W). + sigma : torch.Tensor + Noise level of shape (B) or (B, 1) or (B, 1, 1, 1). + force_fp32 : bool, optional + Whether to force FP32 precision regardless of the `use_fp16` attribute, + by default False. + **model_kwargs : dict + Additional keyword arguments to pass to the underlying model. + + Returns + ------- + torch.Tensor + Denoised high-resolution image of shape (B, C_hr, H, W). + """ + return super().forward( + x=x, img_lr=img_lr, sigma=sigma, force_fp32=force_fp32, **model_kwargs + ) class VEPrecond_dfsr(nn.Module): """ @@ -912,7 +1120,8 @@ def __init__( self.img_channels = img_channels self.label_dim = label_dim self.use_fp16 = use_fp16 - self.model = globals()[model_type]( + model_class = getattr(network_module, model_type) + self.model = model_class( img_resolution=img_resolution, in_channels=self.img_channels, out_channels=img_channels, @@ -1011,7 +1220,8 @@ def __init__( self.img_channels = img_channels self.label_dim = label_dim self.use_fp16 = use_fp16 - self.model = globals()[model_type]( + model_class = getattr(network_module, model_type) + self.model = model_class( img_resolution=img_resolution, in_channels=model_kwargs["model_channels"] * 2, out_channels=img_channels, diff --git a/src/hirad/models/song_unet.py b/src/hirad/models/song_unet.py index 6267dfcc..a56f8613 100644 --- a/src/hirad/models/song_unet.py +++ b/src/hirad/models/song_unet.py @@ -19,8 +19,9 @@ Diffusion-Based Generative Models". """ +import contextlib from dataclasses import dataclass -from typing import List, Union +from typing import Callable, List, Optional, Union import numpy as np import nvtx @@ -71,7 +72,8 @@ class SongUNet(nn.Module): Parameters ----------- img_resolution : Union[List[int], int] - The resolution of the input/output image, 1 value represents a square image. + The resolution of the input/output image. Can be a single int for square images + or a list [height, width] for rectangular images. in_channels : int Number of channels in the input image. out_channels : int @@ -81,7 +83,7 @@ class SongUNet(nn.Module): augment_dim : int, optional Dimensionality of augmentation labels; 0 means no augmentation. By default 0. model_channels : int, optional - Base multiplier for the number of channels across the network, by default 128. + Base multiplier for the number of channels across the network. By default 128. channel_mult : List[int], optional Per-resolution multipliers for the number of channels. By default [1,2,2,2]. channel_mult_emb : int, optional @@ -93,29 +95,39 @@ class SongUNet(nn.Module): dropout : float, optional Dropout probability applied to intermediate activations. By default 0.10. label_dropout : float, optional - Dropout probability of class labels for classifier-free guidance. By default 0.0. + Dropout probability of class labels for classifier-free guidance. By default 0.0. embedding_type : str, optional - Timestep embedding type: 'positional' for DDPM++, 'fourier' for NCSN++, 'zero' for none + Timestep embedding type: 'positional' for DDPM++, 'fourier' for NCSN++, 'zero' for none. By default 'positional'. channel_mult_noise : int, optional Timestep embedding size: 1 for DDPM++, 2 for NCSN++. By default 1. encoder_type : str, optional - Encoder architecture: 'standard' for DDPM++, 'residual' for NCSN++. By default - 'standard'. + Encoder architecture: 'standard' for DDPM++, 'residual' for NCSN++. , 'skip' for skip connections. + By default 'standard'. decoder_type : str, optional - Decoder architecture: 'standard' for both DDPM++ and NCSN++. By default - 'standard'. - resample_filter : List[int], optional (default=[1,1]) - Resampling filter: [1,1] for DDPM++, [1,3,3,1] for NCSN++. - checkpoint_level : int, optional (default=0) - How many layers should use gradient checkpointing, 0 is None - additive_pos_embed: bool = False, - Set to True to add a learned position embedding after the first conv (used in StormCast) + Decoder architecture: 'standard' or 'skip' for skip connections. By default 'standard'. + resample_filter : List[int], optional + Resampling filter coefficients: [1,1] for DDPM++, [1,3,3,1] for NCSN++. By default [1,1]. + checkpoint_level : int, optional + Number of layers that should use gradient checkpointing (0 disables checkpointing). + Higher values trade memory for computation. By default 0. + additive_pos_embed : bool, optional + If True, adds a learned positional embedding after the first convolution layer. + Used in StormCast model. By default False. + use_apex_gn : bool, optional + A boolean flag indicating whether we want to use Apex GroupNorm for NHWC layout. + Need to set this as False on cpu. Defaults to False. + act : str, optional + The activation function to use when fusing activation with GroupNorm. Defaults to None. + profile_mode: + A boolean flag indicating whether to enable all nvtx annotations during profiling. + amp_mode : bool, optional + A boolean flag indicating whether mixed-precision (AMP) training is enabled. Defaults to False. Reference ---------- - Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and + Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and Poole, B., 2020. Score-based generative modeling through stochastic differential equations. arXiv preprint arXiv:2011.13456. @@ -156,6 +168,10 @@ def __init__( resample_filter: List[int] = [1, 1], checkpoint_level: int = 0, additive_pos_embed: bool = False, + use_apex_gn: bool = False, + act: str = "silu", + profile_mode: bool = False, + amp_mode: bool = False, ): valid_embedding_types = ["fourier", "positional", "zero"] if embedding_type not in valid_embedding_types: @@ -196,7 +212,14 @@ def __init__( init=init, init_zero=init_zero, init_attn=init_attn, + use_apex_gn=use_apex_gn, + act=act, + fused_conv_bias=True, + profile_mode=profile_mode, + amp_mode=amp_mode, ) + self.profile_mode = profile_mode + self.amp_mode = amp_mode # for compatibility with older versions that took only 1 dimension self.img_resolution = img_resolution @@ -220,12 +243,19 @@ def __init__( # Mapping. if self.embedding_type != "zero": self.map_noise = ( - PositionalEmbedding(num_channels=noise_channels, endpoint=True) + PositionalEmbedding( + num_channels=noise_channels, endpoint=True, amp_mode=amp_mode + ) if embedding_type == "positional" - else FourierEmbedding(num_channels=noise_channels) + else FourierEmbedding(num_channels=noise_channels, amp_mode=amp_mode) ) self.map_label = ( - Linear(in_features=label_dim, out_features=noise_channels, **init) + Linear( + in_features=label_dim, + out_features=noise_channels, + amp_mode=amp_mode, + **init, + ) if label_dim else None ) @@ -234,16 +264,23 @@ def __init__( in_features=augment_dim, out_features=noise_channels, bias=False, + amp_mode=amp_mode, **init, ) if augment_dim else None ) self.map_layer0 = Linear( - in_features=noise_channels, out_features=emb_channels, **init + in_features=noise_channels, + out_features=emb_channels, + amp_mode=amp_mode, + **init, ) self.map_layer1 = Linear( - in_features=emb_channels, out_features=emb_channels, **init + in_features=emb_channels, + out_features=emb_channels, + amp_mode=amp_mode, + **init, ) # Encoder. @@ -256,7 +293,12 @@ def __init__( cin = cout cout = model_channels self.enc[f"{res}x{res}_conv"] = Conv2d( - in_channels=cin, out_channels=cout, kernel=3, **init + in_channels=cin, + out_channels=cout, + kernel=3, + fused_conv_bias=True, + amp_mode=amp_mode, + **init, ) else: self.enc[f"{res}x{res}_down"] = UNetBlock( @@ -269,9 +311,15 @@ def __init__( kernel=0, down=True, resample_filter=resample_filter, + amp_mode=amp_mode, ) self.enc[f"{res}x{res}_aux_skip"] = Conv2d( - in_channels=caux, out_channels=cout, kernel=1, **init + in_channels=caux, + out_channels=cout, + kernel=1, + fused_conv_bias=True, + amp_mode=amp_mode, + **init, ) if encoder_type == "residual": self.enc[f"{res}x{res}_aux_residual"] = Conv2d( @@ -281,6 +329,8 @@ def __init__( down=True, resample_filter=resample_filter, fused_resample=True, + fused_conv_bias=True, + amp_mode=amp_mode, **init, ) caux = cout @@ -325,107 +375,138 @@ def __init__( kernel=0, up=True, resample_filter=resample_filter, + amp_mode=amp_mode, ) self.dec[f"{res}x{res}_aux_norm"] = GroupNorm( - num_channels=cout, eps=1e-6 + num_channels=cout, + eps=1e-6, + use_apex_gn=use_apex_gn, + amp_mode=amp_mode, ) self.dec[f"{res}x{res}_aux_conv"] = Conv2d( - in_channels=cout, out_channels=out_channels, kernel=3, **init_zero + in_channels=cout, + out_channels=out_channels, + kernel=3, + fused_conv_bias=True, + amp_mode=amp_mode, + **init_zero, ) - @nvtx.annotate(message="SongUNet", color="blue") def forward(self, x, noise_labels, class_labels, augment_labels=None): - if self.embedding_type != "zero": - # Mapping. - emb = self.map_noise(noise_labels) - emb = ( - emb.reshape(emb.shape[0], 2, -1).flip(1).reshape(*emb.shape) - ) # swap sin/cos - if self.map_label is not None: - tmp = class_labels - if self.training and self.label_dropout: - tmp = tmp * ( - torch.rand([x.shape[0], 1], device=x.device) - >= self.label_dropout - ).to(tmp.dtype) - emb = emb + self.map_label(tmp * np.sqrt(self.map_label.in_features)) - if self.map_augment is not None and augment_labels is not None: - emb = emb + self.map_augment(augment_labels) - emb = silu(self.map_layer0(emb)) - emb = silu(self.map_layer1(emb)) - else: - emb = torch.zeros( - (noise_labels.shape[0], self.emb_channels), device=x.device - ) + with nvtx.annotate( + message="SongUNet", color="blue" + ) if self.profile_mode else contextlib.nullcontext(): + if self.embedding_type != "zero": + # Mapping. + emb = self.map_noise(noise_labels) + emb = ( + emb.reshape(emb.shape[0], 2, -1).flip(1).reshape(*emb.shape) + ) # swap sin/cos + if self.map_label is not None: + tmp = class_labels + if self.training and self.label_dropout: + tmp = tmp * ( + torch.rand([x.shape[0], 1], device=x.device) + >= self.label_dropout + ).to(tmp.dtype) + emb = emb + self.map_label( + tmp * np.sqrt(self.map_label.in_features) + ) + if self.map_augment is not None and augment_labels is not None: + emb = emb + self.map_augment(augment_labels) + emb = silu(self.map_layer0(emb)) + emb = silu(self.map_layer1(emb)) + else: + emb = torch.zeros( + (noise_labels.shape[0], self.emb_channels), device=x.device + ) - # Encoder. - skips = [] - aux = x - for name, block in self.enc.items(): - with nvtx.annotate(f"SongUNet encoder: {name}", color="blue"): - if "aux_down" in name: - aux = block(aux) - elif "aux_skip" in name: - x = skips[-1] = x + block(aux) - elif "aux_residual" in name: - x = skips[-1] = aux = (x + block(aux)) / np.sqrt(2) - elif "_conv" in name: - x = block(x) - if self.additive_pos_embed: - x = x + self.spatial_emb.to(dtype=x.dtype) - skips.append(x) - else: - # For UNetBlocks check if we should use gradient checkpointing - if isinstance(block, UNetBlock): - if x.shape[-1] > self.checkpoint_threshold: - x = checkpoint(block, x, emb, use_reentrant=False) - else: - x = block(x, emb) - else: + # Encoder. + skips = [] + aux = x + for name, block in self.enc.items(): + with nvtx.annotate( + f"SongUNet encoder: {name}", color="blue" + ) if self.profile_mode else contextlib.nullcontext(): + if "aux_down" in name: + aux = block(aux) + elif "aux_skip" in name: + x = skips[-1] = x + block(aux) + elif "aux_residual" in name: + x = skips[-1] = aux = (x + block(aux)) / np.sqrt(2) + elif "_conv" in name: x = block(x) - skips.append(x) + if self.additive_pos_embed: + x = x + self.spatial_emb.to(dtype=x.dtype) + skips.append(x) + else: + # For UNetBlocks check if we should use gradient checkpointing + if isinstance(block, UNetBlock): + if x.shape[-1] > self.checkpoint_threshold: + # self.checkpoint = checkpoint? + # else: self.checkpoint = lambda(block,x,emb:block(x,emb)) + x = checkpoint(block, x, emb, use_reentrant=False) + else: + # AssertionError: Only support NHWC layout. + x = block(x, emb) + else: + x = block(x) + skips.append(x) - # Decoder. - aux = None - tmp = None - for name, block in self.dec.items(): - with nvtx.annotate(f"SongUNet decoder: {name}", color="blue"): - if "aux_up" in name: - aux = block(aux) - elif "aux_norm" in name: - tmp = block(x) - elif "aux_conv" in name: - tmp = block(silu(tmp)) - aux = tmp if aux is None else tmp + aux - else: - if x.shape[1] != block.in_channels: - x = torch.cat([x, skips.pop()], dim=1) - # check for checkpointing on decoder blocks and up sampling blocks - if ( - x.shape[-1] > self.checkpoint_threshold and "_block" in name - ) or ( - x.shape[-1] > (self.checkpoint_threshold / 2) and "_up" in name - ): - x = checkpoint(block, x, emb, use_reentrant=False) + # Decoder. + aux = None + tmp = None + for name, block in self.dec.items(): + with nvtx.annotate( + f"SongUNet decoder: {name}", color="blue" + ) if self.profile_mode else contextlib.nullcontext(): + if "aux_up" in name: + aux = block(aux) + elif "aux_norm" in name: + tmp = block(x) + elif "aux_conv" in name: + tmp = block(silu(tmp)) + aux = tmp if aux is None else tmp + aux else: - x = block(x, emb) - return aux + if x.shape[1] != block.in_channels: + x = torch.cat([x, skips.pop()], dim=1) + # check for checkpointing on decoder blocks and up sampling blocks + if ( + x.shape[-1] > self.checkpoint_threshold and "_block" in name + ) or ( + x.shape[-1] > (self.checkpoint_threshold / 2) + and "_up" in name + ): + x = checkpoint(block, x, emb, use_reentrant=False) + else: + x = block(x, emb) + return aux class SongUNetPosEmbd(SongUNet): - """ - Reimplementation of the DDPM++ and NCSN++ architectures, U-Net variants with - optional self-attention,embeddings, and encoder-decoder components. + """Extends SongUNet with positional embeddings. This model supports conditional and unconditional setups, as well as several options for various internal architectural choices such as encoder and decoder type, embedding type, etc., making it flexible and adaptable to different tasks and configurations. + This model adds positional embeddings to the base SongUNet architecture. The embeddings + can be selected using either a selector function or global indices, with the selector + approach being more computationally efficient. + + The model provides two methods for selecting positional embeddings: + + 1. Using a selector function (preferred method). See + :meth:`positional_embedding_selector` for details. + 2. Using global indices. See :meth:`positional_embedding_indexing` for + details. + Parameters ----------- img_resolution : Union[List[int], int] - The resolution of the input/output image, 1 value represents a square image. + The resolution of the input/output image. Can be a single int for square images + or a list [height, width] for rectangular images. in_channels : int Number of channels in the input image. out_channels : int @@ -435,39 +516,63 @@ class SongUNetPosEmbd(SongUNet): augment_dim : int, optional Dimensionality of augmentation labels; 0 means no augmentation. By default 0. model_channels : int, optional - Base multiplier for the number of channels across the network, by default 128. + Base multiplier for the number of channels across the network. By default 128. channel_mult : List[int], optional - Per-resolution multipliers for the number of channels. By default [1,2,2,2]. + Per-resolution multipliers for the number of channels. By default [1,2,2,2,2]. channel_mult_emb : int, optional Multiplier for the dimensionality of the embedding vector. By default 4. num_blocks : int, optional Number of residual blocks per resolution. By default 4. attn_resolutions : List[int], optional - Resolutions at which self-attention layers are applied. By default [16]. + Resolutions at which self-attention layers are applied. By default [28]. dropout : float, optional Dropout probability applied to intermediate activations. By default 0.13. label_dropout : float, optional - Dropout probability of class labels for classifier-free guidance. By default 0.0. + Dropout probability of class labels for classifier-free guidance. By default 0.0. embedding_type : str, optional Timestep embedding type: 'positional' for DDPM++, 'fourier' for NCSN++. By default 'positional'. channel_mult_noise : int, optional Timestep embedding size: 1 for DDPM++, 2 for NCSN++. By default 1. encoder_type : str, optional - Encoder architecture: 'standard' for DDPM++, 'residual' for NCSN++. By default - 'standard'. + Encoder architecture: 'standard' for DDPM++, 'residual' for NCSN++. , 'skip' for skip connections. + By default'standard'. decoder_type : str, optional - Decoder architecture: 'standard' for both DDPM++ and NCSN++. By default - 'standard'. - resample_filter : List[int], optional (default=[1,1]) - Resampling filter: [1,1] for DDPM++, [1,3,3,1] for NCSN++. - - - Reference - ---------- - Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and - Poole, B., 2020. Score-based generative modeling through stochastic differential - equations. arXiv preprint arXiv:2011.13456. + Decoder architecture: 'standard' or 'skip' for skip connections. By default 'standard'. + resample_filter : List[int], optional + Resampling filter coefficients: [1,1] for DDPM++, [1,3,3,1] for NCSN++. By default [1,1]. + gridtype : str, optional + Type of positional grid to use: 'sinusoidal', 'learnable', 'linear', or 'test'. + Controls how positional information is encoded. By default 'sinusoidal'. + N_grid_channels : int, optional + Number of channels in the positional embedding grid. For 'sinusoidal' must be 4 or + multiple of 4. For 'linear' must be 2. By default 4. + checkpoint_level : int, optional + Number of layers that should use gradient checkpointing (0 disables checkpointing). + Higher values trade memory for computation. By default 0. + additive_pos_embed : bool, optional + If True, adds a learned positional embedding after the first convolution layer. + Used in StormCast model. By default False. + use_apex_gn : bool, optional + A boolean flag indicating whether we want to use Apex GroupNorm for NHWC layout. + Need to set this as False on cpu. Defaults to False. + act : str, optional + The activation function to use when fusing activation with GroupNorm. Defaults to None. + profile_mode: + A boolean flag indicating whether to enable all nvtx annotations during profiling. + amp_mode : bool, optional + A boolean flag indicating whether mixed-precision (AMP) training is enabled. Defaults to False. + lead_time_mode : bool, optional + A boolean flag indicating whether we are running SongUNet with lead time embedding. Defaults to False. + lead_time_channels : int, optional + Number of channels in the lead time embedding. These are learned embeddings that + encode temporal forecast information. By default None. + lead_time_steps : int, optional + Number of discrete lead time steps to support. Each step gets its own learned + embedding vector. By default 9. + prob_channels : List[int], optional + Indices of probability output channels that should use softmax activation. + Used for classification outputs. By default empty list. Note ----- @@ -476,13 +581,41 @@ class SongUNetPosEmbd(SongUNet): Example -------- - >>> model = SongUNet(img_resolution=16, in_channels=2, out_channels=2) + >>> import torch + >>> from physicsnemo.models.diffusion.song_unet import SongUNetPosEmbd + >>> from physicsnemo.utils.patching import GridPatching2D + >>> + >>> # Model initialization - in_channels must include both original input channels (2) + >>> # and the positional embedding channels (N_grid_channels=4 by default) + >>> model = SongUNetPosEmbd(img_resolution=16, in_channels=2+4, out_channels=2) >>> noise_labels = torch.randn([1]) >>> class_labels = torch.randint(0, 1, (1, 1)) + >>> # The input has only the original 2 channels - positional embeddings are + >>> # added automatically inside the forward method >>> input_image = torch.ones([1, 2, 16, 16]) >>> output_image = model(input_image, noise_labels, class_labels) >>> output_image.shape torch.Size([1, 2, 16, 16]) + >>> + >>> # Using a global index to select all positional embeddings + >>> patching = GridPatching2D(img_shape=(16, 16), patch_shape=(16, 16)) + >>> global_index = patching.global_index(batch_size=1) + >>> output_image = model( + ... input_image, noise_labels, class_labels, + ... global_index=global_index + ... ) + >>> output_image.shape + torch.Size([1, 2, 16, 16]) + >>> + >>> # Using a custom embedding selector to select all positional embeddings + >>> def patch_embedding_selector(emb): + ... return patching.apply(emb[None].expand(1, -1, -1, -1)) + >>> output_image = model( + ... input_image, noise_labels, class_labels, + ... embedding_selector=patch_embedding_selector + ... ) + >>> output_image.shape + torch.Size([1, 2, 16, 16]) """ def __init__( @@ -507,6 +640,15 @@ def __init__( gridtype: str = "sinusoidal", N_grid_channels: int = 4, checkpoint_level: int = 0, + additive_pos_embed: bool = False, + use_apex_gn: bool = False, + act: str = "silu", + profile_mode: bool = False, + amp_mode: bool = False, + lead_time_mode: bool = False, + lead_time_channels: int = None, + lead_time_steps: int = 9, + prob_channels: List[int] = [], ): super().__init__( img_resolution, @@ -527,49 +669,286 @@ def __init__( decoder_type, resample_filter, checkpoint_level, + additive_pos_embed, + use_apex_gn, + act, + profile_mode, + amp_mode, ) self.gridtype = gridtype self.N_grid_channels = N_grid_channels - self.pos_embd = self._get_positional_embedding() + if self.gridtype == "learnable": + self.pos_embd = self._get_positional_embedding() + else: + self.register_buffer("pos_embd", self._get_positional_embedding().float()) + self.lead_time_mode = lead_time_mode + if self.lead_time_mode: + self.lead_time_channels = lead_time_channels + self.lead_time_steps = lead_time_steps + self.lt_embd = self._get_lead_time_embedding() + self.prob_channels = prob_channels + if self.prob_channels: + self.scalar = torch.nn.Parameter( + torch.ones((1, len(self.prob_channels), 1, 1)) + ) - @nvtx.annotate(message="SongUNet", color="blue") def forward( - self, x, noise_labels, class_labels, global_index=None, augment_labels=None + self, + x, + noise_labels, + class_labels, + global_index: Optional[torch.Tensor] = None, + embedding_selector: Optional[Callable] = None, + augment_labels=None, + lead_time_label=None, ): - # append positional embedding to input conditioning - if self.pos_embd is not None: - selected_pos_embd = self.positional_embedding_indexing(x, global_index) - x = torch.cat((x, selected_pos_embd), dim=1) + with nvtx.annotate( + message="SongUNetPosEmbd", color="blue" + ) if self.profile_mode else contextlib.nullcontext(): + if embedding_selector is not None and global_index is not None: + raise ValueError( + "Cannot provide both embedding_selector and global_index. " + "embedding_selector is the preferred approach for better efficiency." + ) + + if x.dtype != self.pos_embd.dtype: + self.pos_embd = self.pos_embd.to(x.dtype) + + # Append positional embedding to input conditioning + if self.pos_embd is not None: + # Select positional embeddings with a selector function + if embedding_selector is not None: + selected_pos_embd = self.positional_embedding_selector( + x, embedding_selector + ) + # Select positional embeddings using global indices (selects all + # embeddings if global_index is None) + else: + selected_pos_embd = self.positional_embedding_indexing( + x, global_index=global_index, lead_time_label=lead_time_label + ) + x = torch.cat((x, selected_pos_embd), dim=1) + + out = super().forward(x, noise_labels, class_labels, augment_labels) + + if self.lead_time_mode: + # if training mode, let crossEntropyLoss do softmax. The model outputs logits. + # if eval mode, the model outputs probability + all_channels = list(range(out.shape[1])) # [0, 1, 2, ..., 10] + scalar_channels = [ + item for item in all_channels if item not in self.prob_channels + ] + if self.prob_channels and (not self.training): + out_final = torch.cat( + ( + out[:, scalar_channels], + (out[:, self.prob_channels] * self.scalar).softmax(dim=1), + ), + dim=1, + ) + elif self.prob_channels and self.training: + out_final = torch.cat( + ( + out[:, scalar_channels], + (out[:, self.prob_channels] * self.scalar), + ), + dim=1, + ) + else: + out_final = out + return out_final + + return out + + def positional_embedding_indexing( + self, + x: torch.Tensor, + global_index: Optional[torch.Tensor] = None, + lead_time_label=None, + ) -> torch.Tensor: + """Select positional embeddings using global indices. - return super().forward(x, noise_labels, class_labels, augment_labels) + This method either uses global indices to select specific embeddings or expands + the embeddings for the full input when no indices are provided. + + Typically used in patch-based training, where the batch dimension + contains multiple patches extracted from a larger image. + + Arguments + --------- + x : torch.Tensor + Input tensor of shape (B, C, H, W), used to determine batch size + and device. + global_index : Optional[torch.Tensor] + Optional tensor of indices for selecting embeddings. These should + correspond to the spatial indices of the batch elements in the + input tensor x. When provided, should have shape (P, 2, H, W) where + the second dimension contains y,x coordinates (indices of the + positional embedding grid). + + Returns + ------- + torch.Tensor + Selected positional embeddings with shape: + - If global_index provided: (B, N_pe, H, W) + - If global_index is None: (B, N_pe, H_pe, W_pe) + where N_pe is the number of positional embedding channels, and H_pe + and W_pe are the height and width of the positional embedding grid. + + Example + ------- + >>> # Create global indices using patching utility: + >>> from physicsnemo.utils.patching import GridPatching2D + >>> patching = GridPatching2D(img_shape=(16, 16), patch_shape=(8, 8)) + >>> global_index = patching.global_index(batch_size=3) + >>> print(global_index.shape) + torch.Size([4, 2, 8, 8]) + + See Also + -------- + :meth:`physicsnemo.utils.patching.RandomPatching2D.global_index` + For generating random patch indices. + :meth:`physicsnemo.utils.patching.GridPatching2D.global_index` + For generating deterministic grid-based patch indices. + See these methods for possible ways to generate the global_index parameter. + """ + # If no global indices are provided, select all embeddings and expand + # to match the batch size of the input + if x.dtype != self.pos_embd.dtype: + self.pos_embd = self.pos_embd.to(x.dtype) - def positional_embedding_indexing(self, x, global_index): if global_index is None: - selected_pos_embd = ( - self.pos_embd.to(x.dtype) - .to(x.device)[None] - .expand((x.shape[0], -1, -1, -1)) - ) + if self.lead_time_mode: + selected_pos_embd = [] + if self.pos_embd is not None: + selected_pos_embd.append( + self.pos_embd[None].expand((x.shape[0], -1, -1, -1)) + ) + if self.lt_embd is not None: + selected_pos_embd.append( + torch.reshape( + self.lt_embd[lead_time_label.int()], + ( + x.shape[0], + self.lead_time_channels, + self.img_shape_y, + self.img_shape_x, + ), + ) + ) + if len(selected_pos_embd) > 0: + selected_pos_embd = torch.cat(selected_pos_embd, dim=1) + else: + selected_pos_embd = self.pos_embd[None].expand( + (x.shape[0], -1, -1, -1) + ) # (B, N_pe, H, W) + else: - B = global_index.shape[0] - X = global_index.shape[2] - Y = global_index.shape[3] + P = global_index.shape[0] + B = x.shape[0] // P + H = global_index.shape[2] + W = global_index.shape[3] + global_index = torch.reshape( torch.permute(global_index, (1, 0, 2, 3)), (2, -1) - ) # (B, 2, X, Y) to (2, B*X*Y) - selected_pos_embd = self.pos_embd.to(x.device)[ + ) # (P, 2, X, Y) to (2, P*X*Y) + selected_pos_embd = self.pos_embd[ :, global_index[0], global_index[1] - ] # (N_pe, B*X*Y) - selected_pos_embd = ( - torch.permute( - torch.reshape(selected_pos_embd, (self.pos_embd.shape[0], B, X, Y)), - (1, 0, 2, 3), - ) - .to(x.device) - .to(x.dtype) - ) # (B, N_pe, X, Y) + ] # (N_pe, P*X*Y) + selected_pos_embd = torch.permute( + torch.reshape(selected_pos_embd, (self.pos_embd.shape[0], P, H, W)), + (1, 0, 2, 3), + ) # (P, N_pe, X, Y) + + selected_pos_embd = selected_pos_embd.repeat( + B, 1, 1, 1 + ) # (B*P, N_pe, X, Y) + + # Append positional and lead time embeddings to input conditioning + if self.lead_time_mode: + embeds = [] + if self.pos_embd is not None: + embeds.append(selected_pos_embd) # reuse code below + if self.lt_embd is not None: + lt_embds = self.lt_embd[ + lead_time_label.int() + ] # (B, self.lead_time_channels, self.img_shape_y, self.img_shape_x), + + selected_lt_pos_embd = lt_embds[ + :, :, global_index[0], global_index[1] + ] # (B, N_lt, P*X*Y) + selected_lt_pos_embd = torch.reshape( + torch.permute( + torch.reshape( + selected_lt_pos_embd, + (B, self.lead_time_channels, P, H, W), + ), + (0, 2, 1, 3, 4), + ).contiguous(), + (B * P, self.lead_time_channels, H, W), + ) # (B*P, N_pe, X, Y) + embeds.append(selected_lt_pos_embd) + + if len(embeds) > 0: + selected_pos_embd = torch.cat(embeds, dim=1) + return selected_pos_embd + + def positional_embedding_selector( + self, + x: torch.Tensor, + embedding_selector: Callable[[torch.Tensor], torch.Tensor], + ) -> torch.Tensor: + """Select positional embeddings using a selector function. + + Similar to positional_embedding_indexing, but uses a selector function + to select the embeddings. This method provides a more efficient way to + select embeddings for batches of data. + Typically used with patch-based processing, where the batch dimension + contains multiple patches extracted from a larger image. + + Arguments + --------- + x : torch.Tensor + Input tensor of shape (B, C, H, W) only used to determine dtype and + device. + embedding_selector : Callable + Function that takes as input an embedding tensor of shape (N_pe, + H_pe, W_pe) and returns selected embeddings with shape (batch_size, N_pe, H, W). + Each selected embedding should correspond to the positional + information of each batch element in x. + For patch-based processing, typically this should be based on + :meth:`physicsnemo.utils.patching.BasePatching2D.apply` method to + maintain consistency with patch extraction. + embeds : Optional[torch.Tensor] + Optional tensor for combined positional and lead time embeddings tensor + + Returns + ------- + torch.Tensor + Selected positional embeddings with shape (B, N_pe, H, W) + where N_pe is the number of positional embedding channels. + + Example + ------- + >>> # Define a selector function with a patching utility: + >>> from physicsnemo.utils.patching import GridPatching2D + >>> patching = GridPatching2D(img_shape=(16, 16), patch_shape=(8, 8)) + >>> batch_size = 4 + >>> def embedding_selector(emb): + ... return patching.apply(emb[None].expand(batch_size, -1, -1, -1)) + >>> + + See Also + -------- + :meth:`physicsnemo.utils.patching.BasePatching2D.apply` + For the base patching method typically used in embedding_selector. + """ + if x.dtype != self.pos_embd.dtype: + self.pos_embd = self.pos_embd.to(x.dtype) + + return embedding_selector(self.pos_embd) # (B, N_pe, H, W) def _get_positional_embedding(self): if self.N_grid_channels == 0: @@ -577,14 +956,16 @@ def _get_positional_embedding(self): elif self.gridtype == "learnable": grid = torch.nn.Parameter( torch.randn(self.N_grid_channels, self.img_shape_y, self.img_shape_x) - ) + ) # (N_grid_channels, img_shape_y, img_shape_x) elif self.gridtype == "linear": if self.N_grid_channels != 2: raise ValueError("N_grid_channels must be set to 2 for gridtype linear") x = np.meshgrid(np.linspace(-1, 1, self.img_shape_y)) y = np.meshgrid(np.linspace(-1, 1, self.img_shape_x)) grid_x, grid_y = np.meshgrid(y, x) - grid = torch.from_numpy(np.stack((grid_x, grid_y), axis=0)) + grid = torch.from_numpy( + np.stack((grid_x, grid_y), axis=0) + ) # (2, img_shape_y, img_shape_x) grid.requires_grad = False elif self.gridtype == "sinusoidal" and self.N_grid_channels == 4: # print('sinusuidal grid added ......') @@ -600,7 +981,7 @@ def _get_positional_embedding(self): np.stack((grid_x1, grid_y1, grid_x2, grid_y2), axis=0), axis=0 ) ) - ) + ) # (4, img_shape_y, img_shape_x) grid.requires_grad = False elif self.gridtype == "sinusoidal" and self.N_grid_channels != 4: if self.N_grid_channels % 4 != 0: @@ -616,28 +997,50 @@ def _get_positional_embedding(self): for p_fn in [np.sin, np.cos]: grid_list.append(p_fn(grid_x * freq)) grid_list.append(p_fn(grid_y * freq)) - grid = torch.from_numpy(np.stack(grid_list, axis=0)) + grid = torch.from_numpy( + np.stack(grid_list, axis=0) + ) # (N_grid_channels, img_shape_y, img_shape_x) grid.requires_grad = False elif self.gridtype == "test" and self.N_grid_channels == 2: idx_x = torch.arange(self.img_shape_y) idx_y = torch.arange(self.img_shape_x) mesh_x, mesh_y = torch.meshgrid(idx_x, idx_y) - grid = torch.stack((mesh_x, mesh_y), dim=0) + grid = torch.stack((mesh_x, mesh_y), dim=0) # (2, img_shape_y, img_shape_x) else: raise ValueError("Gridtype not supported.") return grid + + def _get_lead_time_embedding(self): + if (self.lead_time_steps is None) or (self.lead_time_channels is None): + return None + grid = torch.nn.Parameter( + torch.randn( + self.lead_time_steps, + self.lead_time_channels, + self.img_shape_y, + self.img_shape_x, + ) + ) # (lead_time_steps, lead_time_channels, img_shape_y, img_shape_x) + return grid -class SongUNetPosLtEmbd(SongUNet): +class SongUNetPosLtEmbd(SongUNetPosEmbd): """ - This model is adapated from SongUNetPosEmbd, with the incoporatation of lead-time aware - embedding for the GEFS-HRRR model. The lead-time embedding is activated by setting the - lead_time_channels and lead_time_steps parameters. + This model is adapted from SongUNetPosEmbd, with the incorporation of lead-time aware + embeddings. The lead-time embedding is activated by setting the + `lead_time_channels` and `lead_time_steps` parameters. + + Like SongUNetPosEmbd, this model provides two methods for selecting positional embeddings: + 1. Using a selector function (preferred method). See + :meth:`positional_embedding_selector` for details. + 2. Using global indices. See :meth:`positional_embedding_indexing` for + details. Parameters ----------- img_resolution : Union[List[int], int] - The resolution of the input/output image, 1 value represents a square image. + The resolution of the input/output image. Can be a single int for square images + or a list [height, width] for rectangular images. in_channels : int Number of channels in the input image. out_channels : int @@ -647,44 +1050,63 @@ class SongUNetPosLtEmbd(SongUNet): augment_dim : int, optional Dimensionality of augmentation labels; 0 means no augmentation. By default 0. model_channels : int, optional - Base multiplier for the number of channels across the network, by default 128. + Base multiplier for the number of channels across the network. By default 128. channel_mult : List[int], optional - Per-resolution multipliers for the number of channels. By default [1,2,2,2]. + Per-resolution multipliers for the number of channels. By default [1,2,2,2,2]. channel_mult_emb : int, optional Multiplier for the dimensionality of the embedding vector. By default 4. num_blocks : int, optional Number of residual blocks per resolution. By default 4. attn_resolutions : List[int], optional - Resolutions at which self-attention layers are applied. By default [16]. + Resolutions at which self-attention layers are applied. By default [28]. dropout : float, optional Dropout probability applied to intermediate activations. By default 0.13. label_dropout : float, optional - Dropout probability of class labels for classifier-free guidance. By default 0.0. + Dropout probability of class labels for classifier-free guidance. By default 0.0. embedding_type : str, optional Timestep embedding type: 'positional' for DDPM++, 'fourier' for NCSN++. By default 'positional'. channel_mult_noise : int, optional Timestep embedding size: 1 for DDPM++, 2 for NCSN++. By default 1. encoder_type : str, optional - Encoder architecture: 'standard' for DDPM++, 'residual' for NCSN++. By default - 'standard'. + Encoder architecture: 'standard' for DDPM++, 'residual' for NCSN++, 'skip' for skip connections. + By default 'standard'. decoder_type : str, optional - Decoder architecture: 'standard' for both DDPM++ and NCSN++. By default - 'standard'. - resample_filter : List[int], optional (default=[1,1]) - Resampling filter: [1,1] for DDPM++, [1,3,3,1] for NCSN++. - lead_time_channels: int, optional - Length of lead time embedding vector - lead_time_steps: int, optional - Total number of lead times + Decoder architecture: 'standard' or 'skip' for skip connections. By default 'standard'. + resample_filter : List[int], optional + Resampling filter coefficients: [1,1] for DDPM++, [1,3,3,1] for NCSN++. By default [1,1]. + gridtype : str, optional + Type of positional grid to use: 'sinusoidal', 'learnable', 'linear', or 'test'. + Controls how positional information is encoded. By default 'sinusoidal'. + N_grid_channels : int, optional + Number of channels in the positional embedding grid. For 'sinusoidal' must be 4 or + multiple of 4. For 'linear' must be 2. By default 4. + lead_time_channels : int, optional + Number of channels in the lead time embedding. These are learned embeddings that + encode temporal forecast information. By default None. + lead_time_steps : int, optional + Number of discrete lead time steps to support. Each step gets its own learned + embedding vector. By default 9. + prob_channels : List[int], optional + Indices of probability output channels that should use softmax activation. + Used for classification outputs. By default empty list. + checkpoint_level : int, optional + Number of layers that should use gradient checkpointing (0 disables checkpointing). + Higher values trade memory for computation. By default 0. + additive_pos_embed : bool, optional + If True, adds a learned positional embedding after the first convolution layer. + Used in StormCast model. By default False. + use_apex_gn : bool, optional + A boolean flag indicating whether we want to use Apex GroupNorm for NHWC layout. + Need to set this as False on cpu. Defaults to False. + act : str, optional + The activation function to use when fusing activation with GroupNorm. Defaults to None. + profile_mode: + A boolean flag indicating whether to enable all nvtx annotations during profiling. + amp_mode : bool, optional + A boolean flag indicating whether mixed-precision (AMP) training is enabled. Defaults to False. - Reference - ---------- - Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and - Poole, B., 2020. Score-based generative modeling through stochastic differential - equations. arXiv preprint arXiv:2011.13456. - Note ----- Equivalent to the original implementation by Song et al., available at @@ -692,13 +1114,54 @@ class SongUNetPosLtEmbd(SongUNet): Example -------- - >>> model = SongUNet(img_resolution=16, in_channels=2, out_channels=2) + >>> import torch + >>> from physicsnemo.models.diffusion.song_unet import SongUNetPosLtEmbd + >>> from physicsnemo.utils.patching import GridPatching2D + >>> + >>> # Model initialization - in_channels must include original input channels (2), + >>> # positional embedding channels (N_grid_channels=4 by default) and + >>> # lead time embedding channels (4) + >>> model = SongUNetPosLtEmbd( + ... img_resolution=16, in_channels=2+4+4, out_channels=2, + ... lead_time_channels=4, lead_time_steps=9 + ... ) >>> noise_labels = torch.randn([1]) >>> class_labels = torch.randint(0, 1, (1, 1)) + >>> # The input has only the original 2 channels - positional embeddings and + >>> # lead time embeddings are added automatically inside the forward method >>> input_image = torch.ones([1, 2, 16, 16]) - >>> output_image = model(input_image, noise_labels, class_labels) + >>> lead_time_label = torch.tensor([3]) + >>> output_image = model( + ... input_image, noise_labels, class_labels, + ... lead_time_label=lead_time_label + ... ) + >>> output_image.shape + torch.Size([1, 2, 16, 16]) + >>> + >>> # Using global_index to select all the positional and lead time embeddings + >>> patching = GridPatching2D(img_shape=(16, 16), patch_shape=(16, 16)) + >>> global_index = patching.global_index(batch_size=1) + >>> output_image = model( + ... input_image, noise_labels, class_labels, + ... lead_time_label=lead_time_label, + ... global_index=global_index + ... ) >>> output_image.shape torch.Size([1, 2, 16, 16]) + + # NOTE: commented out doctest for embedding_selector due to compatibility issue + # >>> + # >>> # Using custom embedding selector to select all the positional and lead time embeddings + # >>> def patch_embedding_selector(emb): + # ... return patching.apply(emb[None].expand(1, -1, -1, -1)) + # >>> output_image = model( + # ... input_image, noise_labels, class_labels, + # ... lead_time_label=lead_time_label, + # ... embedding_selector=patch_embedding_selector + # ... ) + # >>> output_image.shape + # torch.Size([1, 2, 16, 16]) + """ def __init__( @@ -726,6 +1189,11 @@ def __init__( lead_time_steps: int = 9, prob_channels: List[int] = [], checkpoint_level: int = 0, + additive_pos_embed: bool = False, + use_apex_gn: bool = False, + act: str = "silu", + profile_mode: bool = False, + amp_mode: bool = False, ): super().__init__( img_resolution, @@ -745,162 +1213,38 @@ def __init__( encoder_type, decoder_type, resample_filter, + gridtype, + N_grid_channels, checkpoint_level, + additive_pos_embed, + use_apex_gn, + act, + profile_mode, + amp_mode, + True, # Note: lead_time_mode=True is enforced here + lead_time_channels, + lead_time_steps, + prob_channels, ) - self.gridtype = gridtype - self.N_grid_channels = N_grid_channels - self.pos_embd = self._get_positional_embedding() - self.lead_time_channels = lead_time_channels - self.lead_time_steps = lead_time_steps - self.lt_embd = self._get_lead_time_embedding() - self.prob_channels = prob_channels - if self.prob_channels: - self.scalar = torch.nn.Parameter( - torch.ones((1, len(self.prob_channels), 1, 1)) - ) - - @nvtx.annotate(message="SongUNet", color="blue") def forward( self, x, noise_labels, class_labels, lead_time_label=None, - global_index=None, + global_index: Optional[torch.Tensor] = None, + embedding_selector: Optional[Callable] = None, augment_labels=None, ): - # append positional embedding to input conditioning - embeds = [] - if self.pos_embd is not None: - embeds.append(self.pos_embd.to(x.device)) - if self.lt_embd is not None: - embeds.append( - torch.reshape( - self.lt_embd[lead_time_label.int()], - (self.lead_time_channels, self.img_shape_y, self.img_shape_x), - ).to(x.device) - ) - if len(embeds) > 0: - embeds = torch.cat(embeds, dim=0) - selected_pos_embd = self.positional_embedding_indexing( - x, embeds, global_index - ) - x = torch.cat((x, selected_pos_embd), dim=1) - out = super().forward(x, noise_labels, class_labels, augment_labels) - # if training mode, let crossEntropyLoss do softmax. The model outputs logits. - # if eval mode, the model outputs probability - all_channels = list(range(out.shape[1])) # [0, 1, 2, ..., 10] - scalar_channels = [ - item for item in all_channels if item not in self.prob_channels - ] - if self.prob_channels and (not self.training): - out_final = torch.cat( - ( - out[:, scalar_channels], - (out[:, self.prob_channels] * self.scalar).softmax(dim=1), - ), - dim=1, - ) - elif self.prob_channels and self.training: - out_final = torch.cat( - (out[:, scalar_channels], (out[:, self.prob_channels] * self.scalar)), - dim=1, - ) - else: - out_final = out - return out_final - - def positional_embedding_indexing(self, x, pos_embd, global_index): - if global_index is None: - selected_pos_embd = ( - pos_embd.to(x.dtype).to(x.device)[None].expand((x.shape[0], -1, -1, -1)) - ) - else: - B = global_index.shape[0] - X = global_index.shape[2] - Y = global_index.shape[3] - global_index = torch.reshape( - torch.permute(global_index, (1, 0, 2, 3)), (2, -1) - ) # (B, 2, X, Y) to (2, B*X*Y) - selected_pos_embd = pos_embd.to(x.device)[ - :, global_index[0], global_index[1] - ] # (N_pe, B*X*Y) - selected_pos_embd = ( - torch.permute( - torch.reshape(selected_pos_embd, (pos_embd.shape[0], B, X, Y)), - (1, 0, 2, 3), - ) - .to(x.device) - .to(x.dtype) - ) # (B, N_pe, X, Y) - return selected_pos_embd - - def _get_positional_embedding(self): - if self.N_grid_channels == 0: - return None - elif self.gridtype == "learnable": - grid = torch.nn.Parameter( - torch.randn(self.N_grid_channels, self.img_shape_y, self.img_shape_x) - ) - elif self.gridtype == "linear": - if self.N_grid_channels != 2: - raise ValueError("N_grid_channels must be set to 2 for gridtype linear") - x = np.meshgrid(np.linspace(-1, 1, self.img_shape_y)) - y = np.meshgrid(np.linspace(-1, 1, self.img_shape_x)) - grid_x, grid_y = np.meshgrid(y, x) - grid = torch.from_numpy(np.stack((grid_x, grid_y), axis=0)) - grid.requires_grad = False - elif self.gridtype == "sinusoidal" and self.N_grid_channels == 4: - # print('sinusuidal grid added ......') - x1 = np.meshgrid(np.sin(np.linspace(0, 2 * np.pi, self.img_shape_y))) - x2 = np.meshgrid(np.cos(np.linspace(0, 2 * np.pi, self.img_shape_y))) - y1 = np.meshgrid(np.sin(np.linspace(0, 2 * np.pi, self.img_shape_x))) - y2 = np.meshgrid(np.cos(np.linspace(0, 2 * np.pi, self.img_shape_x))) - grid_x1, grid_y1 = np.meshgrid(y1, x1) - grid_x2, grid_y2 = np.meshgrid(y2, x2) - grid = torch.squeeze( - torch.from_numpy( - np.expand_dims( - np.stack((grid_x1, grid_y1, grid_x2, grid_y2), axis=0), axis=0 - ) - ) - ) - grid.requires_grad = False - elif self.gridtype == "sinusoidal" and self.N_grid_channels != 4: - if self.N_grid_channels % 4 != 0: - raise ValueError("N_grid_channels must be a factor of 4") - num_freq = self.N_grid_channels // 4 - freq_bands = 2.0 ** np.linspace(0.0, num_freq, num=num_freq) - grid_list = [] - grid_x, grid_y = np.meshgrid( - np.linspace(0, 2 * np.pi, self.img_shape_x), - np.linspace(0, 2 * np.pi, self.img_shape_y), - ) - for freq in freq_bands: - for p_fn in [np.sin, np.cos]: - grid_list.append(p_fn(grid_x * freq)) - grid_list.append(p_fn(grid_y * freq)) - grid = torch.from_numpy(np.stack(grid_list, axis=0)) - grid.requires_grad = False - elif self.gridtype == "test" and self.N_grid_channels == 2: - idx_x = torch.arange(self.img_shape_y) - idx_y = torch.arange(self.img_shape_x) - mesh_x, mesh_y = torch.meshgrid(idx_x, idx_y) - grid = torch.stack((mesh_x, mesh_y), dim=0) - else: - raise ValueError("Gridtype not supported.") - return grid - - def _get_lead_time_embedding(self): - if (self.lead_time_steps is None) or (self.lead_time_channels is None): - return None - grid = torch.nn.Parameter( - torch.randn( - self.lead_time_steps, - self.lead_time_channels, - self.img_shape_y, - self.img_shape_x, - ) + return super().forward( + x=x, + noise_labels=noise_labels, + class_labels=class_labels, + global_index=global_index, + embedding_selector=embedding_selector, + augment_labels=augment_labels, + lead_time_label=lead_time_label, ) - return grid + + # Nothing else is re-implemented, because everything is already in the parent SongUNetPosEmb \ No newline at end of file diff --git a/src/hirad/models/unet.py b/src/hirad/models/unet.py index 10079ec2..e0a447aa 100644 --- a/src/hirad/models/unet.py +++ b/src/hirad/models/unet.py @@ -16,6 +16,7 @@ import importlib from dataclasses import dataclass +from typing import Any, Dict, List, Literal, Tuple, Union import torch import torch.nn as nn @@ -45,31 +46,35 @@ class MetaData(ModelMetaData): class UNet(nn.Module): # TODO a lot of redundancy, need to clean up """ - U-Net Wrapper for CorrDiff. + U-Net Wrapper for CorrDiff deterministic regression model. Parameters ----------- - img_resolution : int - The resolution of the input/output image. - img_channels : int - Number of color channels. + img_resolution : Union[int, Tuple[int, int]] + The resolution of the input/output image. If a single int is provided, + then the image is assumed to be square. img_in_channels : int - Number of input color channels. + Number of channels in the input image. img_out_channels : int - Number of output color channels. + Number of channels in the output image. use_fp16: bool, optional - Execute the underlying model at FP16 precision?, by default False. - sigma_min: float, optional - Minimum supported noise level, by default 0. - sigma_max: float, optional - Maximum supported noise level, by default float('inf'). - sigma_data: float, optional - Expected standard deviation of the training data, by default 0.5. + Execute the underlying model at FP16 precision, by default False. model_type: str, optional - Class name of the underlying model, by default 'DhariwalUNet'. + Class name of the underlying model. Must be one of the following: + 'SongUNet', 'SongUNetPosEmbd', 'SongUNetPosLtEmbd', 'DhariwalUNet'. + Defaults to 'SongUNetPosEmbd'. **model_kwargs : dict - Keyword arguments for the underlying model. + Keyword arguments passed to the underlying model `__init__` method. + + See Also + -------- + For information on model types and their usage: + :class:`~physicsnemo.models.diffusion.SongUNet`: Basic U-Net for diffusion models + :class:`~physicsnemo.models.diffusion.SongUNetPosEmbd`: U-Net with positional embeddings + :class:`~physicsnemo.models.diffusion.SongUNetPosLtEmbd`: U-Net with positional and lead-time embeddings + Please refer to the documentation of these classes for details on how to call + and use these models directly. References ---------- @@ -79,37 +84,66 @@ class UNet(nn.Module): # TODO a lot of redundancy, need to clean up arXiv preprint arXiv:2309.15214. """ + @classmethod + def _backward_compat_arg_mapper( + cls, version: str, args: Dict[str, Any] + ) -> Dict[str, Any]: + """Map arguments from older versions to current version format. + + Parameters + ---------- + version : str + Version of the checkpoint being loaded + args : Dict[str, Any] + Arguments dictionary from the checkpoint + + Returns + ------- + Dict[str, Any] + Updated arguments dictionary compatible with current version + """ + # Call parent class method first + args = super()._backward_compat_arg_mapper(version, args) + + if version == "0.1.0": + # In version 0.1.0, img_channels was unused + if "img_channels" in args: + _ = args.pop("img_channels") + + # Sigma parameters are also unused + if "sigma_min" in args: + _ = args.pop("sigma_min") + if "sigma_max" in args: + _ = args.pop("sigma_max") + if "sigma_data" in args: + _ = args.pop("sigma_data") + + return args + def __init__( self, - img_resolution, - img_channels, - img_in_channels, - img_out_channels, - use_fp16=False, - sigma_min=0, - sigma_max=float("inf"), - sigma_data=0.5, - model_type="SongUNetPosEmbd", - **model_kwargs, + img_resolution: Union[int, Tuple[int, int]], + img_in_channels: int, + img_out_channels: int, + use_fp16: bool = False, + model_type: Literal[ + "SongUNetPosEmbd", "SongUNetPosLtEmbd", "SongUNet", "DhariwalUNet" + ] = "SongUNetPosEmbd", + **model_kwargs: dict, ): super().__init__() #meta=MetaData - self.img_channels = img_channels - # for compatibility with older versions that took only 1 dimension if isinstance(img_resolution, int): self.img_shape_x = self.img_shape_y = img_resolution else: - self.img_shape_x = img_resolution[0] - self.img_shape_y = img_resolution[1] + self.img_shape_y = img_resolution[0] + self.img_shape_x = img_resolution[1] self.img_in_channels = img_in_channels self.img_out_channels = img_out_channels self.use_fp16 = use_fp16 - self.sigma_min = sigma_min - self.sigma_max = sigma_max - self.sigma_data = sigma_data model_class = getattr(network_module, model_type) self.model = model_class( img_resolution=img_resolution, @@ -118,13 +152,47 @@ def __init__( **model_kwargs, ) - def forward(self, x, img_lr, sigma, force_fp32=False, **model_kwargs): + def forward( + self, + x: torch.Tensor, + img_lr: torch.Tensor, + force_fp32: bool = False, + **model_kwargs: dict, + ) -> torch.Tensor: + """ + Forward pass of the UNet wrapper model. + + This method concatenates the input tensor with the low-resolution conditioning tensor + and passes the result through the underlying model. + + Parameters + ---------- + x : torch.Tensor + The input tensor, typically zero-filled, of shape (B, C_hr, H, W). + img_lr : torch.Tensor + Low-resolution conditioning image of shape (B, C_lr, H, W). + force_fp32 : bool, optional + Whether to force FP32 precision regardless of the `use_fp16` attribute, + by default False. + **model_kwargs : dict + Additional keyword arguments to pass to the underlying model + `self.model` forward method. + + Returns + ------- + torch.Tensor + Output tensor (prediction) of shape (B, C_hr, H, W). + + Raises + ------ + ValueError + If the model output dtype doesn't match the expected dtype. + """ + # SR: concatenate input channels if img_lr is not None: x = torch.cat((x, img_lr), dim=1) - x = x.to(torch.float32) - sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) dtype = ( torch.float16 if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") @@ -133,29 +201,27 @@ def forward(self, x, img_lr, sigma, force_fp32=False, **model_kwargs): F_x = self.model( x.to(dtype), # (c_in * x).to(dtype), - torch.zeros( - sigma.numel(), dtype=sigma.dtype, device=sigma.device - ), # c_noise.flatten() + torch.zeros(x.shape[0], dtype=dtype, device=x.device), # c_noise.flatten() class_labels=None, **model_kwargs, ) if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): raise ValueError( - f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." + f"Expected the dtype to be {dtype}, " f"but got {F_x.dtype} instead." ) - # skip connection - for SR there's size mismatch bwtween input and output + # skip connection D_x = F_x.to(torch.float32) return D_x - def round_sigma(self, sigma): + def round_sigma(self, sigma: Union[float, List, torch.Tensor]) -> torch.Tensor: """ Convert a given sigma value(s) to a tensor representation. Parameters ---------- - sigma : Union[float list, torch.Tensor] + sigma : Union[float, List, torch.Tensor] The sigma value(s) to convert. Returns @@ -164,8 +230,31 @@ def round_sigma(self, sigma): The tensor representation of the provided sigma value(s). """ return torch.as_tensor(sigma) + + @property + def amp_mode(self): + """ + Return the *amp_mode* flag of the underlying model if present. + """ + return getattr(self.model, "amp_mode", None) + + @amp_mode.setter + def amp_mode(self, value: bool): + """ + Update *amp_mode* on the wrapped model and its sub-modules. + """ + if not isinstance(value, bool): + raise TypeError("amp_mode must be a boolean value.") + + if hasattr(self.model, "amp_mode"): + self.model.amp_mode = value + # Recursively update sub-modules that define *amp_mode*. + for sub_module in self.model.modules(): + if hasattr(sub_module, "amp_mode"): + sub_module.amp_mode = value +# TODO: implement amp_mode property for StormCastUNet (same as UNet) class StormCastUNet(nn.Module): """ U-Net wrapper for StormCast; used so the same Song U-Net network can be re-used for this model. @@ -189,7 +278,7 @@ class StormCastUNet(nn.Module): sigma_data: float, optional Expected standard deviation of the training data, by default 0.5. model_type: str, optional - Class name of the underlying model, by default 'DhariwalUNet'. + Class name of the underlying model, by default 'SongUNet'. **model_kwargs : dict Keyword arguments for the underlying model. diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 559d8003..794dd553 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -5,6 +5,8 @@ import hydra from omegaconf import DictConfig, OmegaConf import json +from contextlib import nullcontext +import nvtx import torch from hydra.utils import to_absolute_path from torch.utils.tensorboard import SummaryWriter @@ -17,12 +19,44 @@ set_patch_shape, compute_num_accumulation_rounds, \ is_time_for_periodic_task, handle_and_clip_gradients from hirad.utils.checkpoint import load_checkpoint, save_checkpoint -from hirad.models import UNet, EDMPrecondSR -from hirad.losses import ResLoss, RegressionLoss, RegressionLossCE +from hirad.utils.patching import RandomPatching2D +from hirad.models import UNet, EDMPrecondSuperResolution, EDMPrecondSR +from hirad.losses import ResidualLoss, RegressionLoss, RegressionLossCE from hirad.datasets import init_train_valid_datasets_from_config from matplotlib import pyplot as plt +torch._dynamo.reset() +# Increase the cache size limit +torch._dynamo.config.cache_size_limit = 264 # Set to a higher value +torch._dynamo.config.verbose = True # Enable verbose logging +torch._dynamo.config.suppress_errors = False # Forces the error to show all details +torch._logging.set_logs(recompiles=True, graph_breaks=True) + +# Define safe CUDA profiler tools that fallback to no-ops when CUDA is not available +def cuda_profiler(): + if torch.cuda.is_available(): + return torch.cuda.profiler.profile() + else: + return nullcontext() + + +def cuda_profiler_start(): + if torch.cuda.is_available(): + torch.cuda.profiler.start() + + +def cuda_profiler_stop(): + if torch.cuda.is_available(): + torch.cuda.profiler.stop() + + +def profiler_emit_nvtx(): + if torch.cuda.is_available(): + return torch.autograd.profiler.emit_nvtx() + else: + return nullcontext() + @hydra.main(version_base=None, config_path="../conf", config_name="training") def main(cfg: DictConfig) -> None: # Initialize distributed environment for training @@ -63,7 +97,7 @@ def main(cfg: DictConfig) -> None: data_loader_kwargs = { "pin_memory": True, "num_workers": cfg.training.perf.dataloader_workers, - "prefetch_factor": 2, + "prefetch_factor": 2 if cfg.training.perf.dataloader_workers > 0 else None, } ( dataset, @@ -104,80 +138,64 @@ def main(cfg: DictConfig) -> None: else: patch_shape_x = None patch_shape_y = None + if ( + patch_shape_x + and patch_shape_y + and patch_shape_y >= img_shape[0] + and patch_shape_x >= img_shape[1] + ): + logger0.warning( + f"Patch shape {patch_shape_y}x{patch_shape_x} is larger than \ + the image shape {img_shape[0]}x{img_shape[1]}. Patching will not be used." + ) patch_shape = (patch_shape_y, patch_shape_x) - img_shape, patch_shape = set_patch_shape(img_shape, patch_shape) - if patch_shape != img_shape: + use_patching, img_shape, patch_shape = set_patch_shape(img_shape, patch_shape) + if use_patching: + # Utility to perform patches extraction and batching + patching = RandomPatching2D( + img_shape=img_shape, + patch_shape=patch_shape, + patch_num=getattr(cfg.training.hp, "patch_num", 1), + ) logger0.info("Patch-based training enabled") else: + patching = None logger0.info("Patch-based training disabled") # interpolate global channel if patch-based model is used - if img_shape[1] != patch_shape[1]: + if use_patching: img_in_channels += dataset_channels # Instantiate the model and move to device. - if cfg.model.name not in ( - "regression", - "lt_aware_ce_regression", - "diffusion", - "patched_diffusion", - "lt_aware_patched_diffusion", - ): - raise ValueError("Invalid model") model_args = { # default parameters for all networks "img_out_channels": img_out_channels, "img_resolution": list(img_shape), "use_fp16": fp16, + "checkpoint_level": songunet_checkpoint_level, } - standard_model_cfgs = { # default parameters for different network types - "regression": { - "img_channels": 4, - "N_grid_channels": 4, - "embedding_type": "zero", - "checkpoint_level": songunet_checkpoint_level, - }, - "lt_aware_ce_regression": { - "img_channels": 4, - "N_grid_channels": 4, - "embedding_type": "zero", - "lead_time_channels": 4, - "lead_time_steps": 9, - "prob_channels": prob_channels, - "checkpoint_level": songunet_checkpoint_level, - "model_type": "SongUNetPosLtEmbd", - }, - "diffusion": { - "img_channels": img_out_channels, - "gridtype": "sinusoidal", - "N_grid_channels": 4, - "checkpoint_level": songunet_checkpoint_level, - }, - "patched_diffusion": { - "img_channels": img_out_channels, - "gridtype": "learnable", - "N_grid_channels": 100, - "checkpoint_level": songunet_checkpoint_level, - }, - "lt_aware_patched_diffusion": { - "img_channels": img_out_channels, - "gridtype": "learnable", - "N_grid_channels": 100, - "lead_time_channels": 20, - "lead_time_steps": 9, - "checkpoint_level": songunet_checkpoint_level, - "model_type": "SongUNetPosLtEmbd", - }, - } - - - model_args.update(standard_model_cfgs[cfg.model.name]) - if cfg.model.name in ( - "diffusion", - "patched_diffusion", - "lt_aware_patched_diffusion", - ): - model_args["scale_cond_input"] = cfg.model.scale_cond_input + if cfg.model.name == "lt_aware_ce_regression": + model_args["prob_channels"] = prob_channels + if hasattr(cfg.model, "model_args"): # override defaults from config file model_args.update(OmegaConf.to_container(cfg.model.model_args)) + + use_torch_compile = False + use_apex_gn = False + profile_mode = False + + if hasattr(cfg.training.perf, "torch_compile"): + use_torch_compile = cfg.training.perf.torch_compile + if hasattr(cfg.training.perf, "use_apex_gn"): + use_apex_gn = cfg.training.perf.use_apex_gn + model_args["use_apex_gn"] = use_apex_gn + + if hasattr(cfg.training.perf, "profile_mode"): + profile_mode = cfg.training.perf.profile_mode + model_args["profile_mode"] = profile_mode + + if enable_amp: + model_args["amp_mode"] = enable_amp + + if cfg.model.name == "regression": model = UNet( img_in_channels=img_in_channels + model_args["N_grid_channels"], @@ -193,7 +211,7 @@ def main(cfg: DictConfig) -> None: ) model_args["img_in_channels"] = img_in_channels + model_args["N_grid_channels"] + model_args["lead_time_channels"] elif cfg.model.name == "lt_aware_patched_diffusion": - model = EDMPrecondSR( + model = EDMPrecondSuperResolution( img_in_channels=img_in_channels + model_args["N_grid_channels"] + model_args["lead_time_channels"], @@ -201,7 +219,7 @@ def main(cfg: DictConfig) -> None: ) model_args["img_in_channels"] = img_in_channels + model_args["N_grid_channels"] + model_args["lead_time_channels"] else: # diffusion or patched diffusion - model = EDMPrecondSR( + model = EDMPrecondSuperResolution( img_in_channels=img_in_channels + model_args["N_grid_channels"], **model_args, ) @@ -209,6 +227,18 @@ def main(cfg: DictConfig) -> None: model.train().requires_grad_(True).to(dist.device) + # param_to_name = {} + # ppp = False + # for name, param in model.named_parameters(): + # pid = id(param) + # if pid in param_to_name: + # print(f"[SHARED PARAM] {name} == {param_to_name[pid]}") + # ppp = True + # break + # else: + # param_to_name[pid] = name + # print(f'There are shared parameters: {ppp}') + # TODO write summry from rank=0 possibly # summary(model, input_size=[(1,img_out_channels,*img_shape),(1,img_in_channels,*img_shape),(1,1)]) @@ -216,6 +246,18 @@ def main(cfg: DictConfig) -> None: with open(os.path.join(checkpoint_dir, f'model_args.json'), 'w') as f: json.dump(model_args, f) + if use_apex_gn: + model.to(memory_format=torch.channels_last) + + # Check if regression model is used with patching + if ( + cfg.model.name in ["regression", "lt_aware_ce_regression"] + and patching is not None + ): + raise ValueError( + f"Regression model ({cfg.model.name}) cannot be used with patch-based training. " + ) + # Enable distributed data parallel if applicable if dist.world_size > 1: model = DistributedDataParallel( @@ -223,7 +265,9 @@ def main(cfg: DictConfig) -> None: device_ids=[dist.local_rank], broadcast_buffers=True, output_device=dist.device, - find_unused_parameters=dist.find_unused_parameters, + find_unused_parameters=True, # dist.find_unused_parameters, + bucket_cap_mb=35, + gradient_as_bucket_view=True, ) # Load the regression checkpoint if applicable #TODO test when training correction @@ -245,6 +289,12 @@ def main(cfg: DictConfig) -> None: with open(regression_model_args_path, 'r') as f: regression_model_args = json.load(f) + regression_model_args.update({ + "use_apex_gn": use_apex_gn, + "profile_mode": profile_mode, + "amp_mode": enable_amp, + }) + regression_net = UNet(**regression_model_args) _ = load_checkpoint( @@ -253,22 +303,81 @@ def main(cfg: DictConfig) -> None: device=dist.device ) regression_net.eval().requires_grad_(False).to(dist.device) + if use_apex_gn: + regression_net.to(memory_format=torch.channels_last) logger0.success("Loaded the pre-trained regression model") + else: + regression_net = None + + # Compile the model and regression net if applicable + if use_torch_compile: + model = torch.compile(model) + if regression_net: + regression_net = torch.compile(regression_net) + + + # Compute the number of required gradient accumulation rounds + # It is automatically used if batch_size_per_gpu * dist.world_size < total_batch_size + batch_gpu_total, num_accumulation_rounds = compute_num_accumulation_rounds( + cfg.training.hp.total_batch_size, + cfg.training.hp.batch_size_per_gpu, + dist.world_size, + ) + batch_size_per_gpu = cfg.training.hp.batch_size_per_gpu + logger0.info(f"Using {num_accumulation_rounds} gradient accumulation rounds") - # Instantiate the loss function patch_num = getattr(cfg.training.hp, "patch_num", 1) + max_patch_per_gpu = getattr(cfg.training.hp, "max_patch_per_gpu", 1) + + # calculate patch per iter + if hasattr(cfg.training.hp, "max_patch_per_gpu") and max_patch_per_gpu > 1: + max_patch_num_per_iter = min( + patch_num, (max_patch_per_gpu // batch_size_per_gpu) + ) # Ensure at least 1 patch per iter + patch_iterations = ( + patch_num + max_patch_num_per_iter - 1 + ) // max_patch_num_per_iter + patch_nums_iter = [ + min(max_patch_num_per_iter, patch_num - i * max_patch_num_per_iter) + for i in range(patch_iterations) + ] + print( + f"max_patch_num_per_iter is {max_patch_num_per_iter}, patch_iterations is {patch_iterations}, patch_nums_iter is {patch_nums_iter}" + ) + else: + patch_nums_iter = [patch_num] + + # Set patch gradient accumulation only for patched diffusion models + if cfg.model.name in { + "patched_diffusion", + "lt_aware_patched_diffusion", + }: + if len(patch_nums_iter) > 1: + if not patching: + logger0.info( + "Patching is not enabled: patch gradient accumulation automatically disabled." + ) + use_patch_grad_acc = False + else: + use_patch_grad_acc = True + else: + use_patch_grad_acc = False + # Automatically disable patch gradient accumulation for non-patched models + else: + logger0.info( + "Training a non-patched model: patch gradient accumulation automatically disabled." + ) + use_patch_grad_acc = None + + + # Instantiate the loss function if cfg.model.name in ( "diffusion", "patched_diffusion", "lt_aware_patched_diffusion", ): - loss_fn = ResLoss( + loss_fn = ResidualLoss( regression_net=regression_net, - img_shape_x=img_shape[1], - img_shape_y=img_shape[0], - patch_shape_x=patch_shape[1], - patch_shape_y=patch_shape[0], - patch_num=patch_num, hr_mean_conditioning=cfg.model.hr_mean_conditioning, ) elif cfg.model.name == "regression": @@ -278,23 +387,17 @@ def main(cfg: DictConfig) -> None: # Instantiate the optimizer optimizer = torch.optim.Adam( - params=model.parameters(), lr=cfg.training.hp.lr, betas=[0.9, 0.999], eps=1e-8 + params=model.parameters(), + lr=cfg.training.hp.lr, + betas=[0.9, 0.999], + eps=1e-8, + fused=True, ) # Record the current time to measure the duration of subsequent operations. start_time = time.time() - # Compute the number of required gradient accumulation rounds - # It is automatically used if batch_size_per_gpu * dist.world_size < total_batch_size - batch_gpu_total, num_accumulation_rounds = compute_num_accumulation_rounds( - cfg.training.hp.total_batch_size, - cfg.training.hp.batch_size_per_gpu, - dist.world_size, - ) - batch_size_per_gpu = cfg.training.hp.batch_size_per_gpu - logger0.info(f"Using {num_accumulation_rounds} gradient accumulation {"rounds" if num_accumulation_rounds>1 else "round"}.") - logger0.info(f"Batch size per gpu: {batch_size_per_gpu}") - ## Resume training from previous checkpoints if exists + # Load optimizer checkpoint if it exists if dist.world_size > 1: torch.distributed.barrier() try: @@ -317,188 +420,308 @@ def main(cfg: DictConfig) -> None: # init variables to monitor running mean of average loss since last periodic average_loss_running_mean = 0 n_average_loss_running_mean = 1 - - while not done: - tick_start_nimg = cur_nimg - tick_start_time = time.time() - # Compute & accumulate gradients - optimizer.zero_grad(set_to_none=True) - loss_accum = 0 - for _ in range(num_accumulation_rounds): - img_clean, img_lr, labels, *lead_time_label = next(dataset_iterator) # what are labels and lead_time_label - img_clean = img_clean.to(dist.device).to(torch.float32).contiguous() - img_lr = img_lr.to(dist.device).to(torch.float32).contiguous() - labels = labels.to(dist.device).contiguous() - loss_fn_kwargs = { - "net": model, - "img_clean": img_clean, - "img_lr": img_lr, - "labels": labels, - "augment_pipe": None, - } - if lead_time_label: - lead_time_label = lead_time_label[0].to(dist.device).contiguous() - loss_fn_kwargs.update({"lead_time_label": lead_time_label}) - else: - lead_time_label = None - with torch.autocast("cuda", dtype=amp_dtype, enabled=enable_amp): - loss = loss_fn(**loss_fn_kwargs) - loss = loss.sum() / batch_size_per_gpu - loss_accum += loss / num_accumulation_rounds - loss.backward() + start_nimg = cur_nimg + input_dtype = torch.float32 + if enable_amp: + input_dtype = torch.float32 + elif fp16: + input_dtype = torch.float16 + + # enable profiler: + with cuda_profiler(): + with profiler_emit_nvtx(): + while not done: + tick_start_nimg = cur_nimg + tick_start_time = time.time() + + if cur_nimg - start_nimg == 24 * cfg.training.hp.total_batch_size: + logger0.info(f"Starting Profiler at {cur_nimg}") + cuda_profiler_start() + + if cur_nimg - start_nimg == 25 * cfg.training.hp.total_batch_size: + logger0.info(f"Stopping Profiler at {cur_nimg}") + cuda_profiler_stop() + + with nvtx.annotate("Training iteration", color="green"): + # Compute & accumulate gradients + optimizer.zero_grad(set_to_none=True) + loss_accum = 0 + for n_i in range(num_accumulation_rounds): + with nvtx.annotate( + f"accumulation round {n_i}", color="Magenta" + ): + with nvtx.annotate("loading data", color="green"): + img_clean, img_lr, *lead_time_label = next( + dataset_iterator + ) + if use_apex_gn: + img_clean = img_clean.to( + dist.device, + dtype=input_dtype, + non_blocking=True, + ).to(memory_format=torch.channels_last) + img_lr = img_lr.to( + dist.device, + dtype=input_dtype, + non_blocking=True, + ).to(memory_format=torch.channels_last) + else: + img_clean = ( + img_clean.to(dist.device) + .to(input_dtype) + .contiguous() + ) + img_lr = ( + img_lr.to(dist.device) + .to(input_dtype) + .contiguous() + ) + loss_fn_kwargs = { + "net": model, + "img_clean": img_clean, + "img_lr": img_lr, + "augment_pipe": None, + } + if use_patch_grad_acc is not None: + loss_fn_kwargs[ + "use_patch_grad_acc" + ] = use_patch_grad_acc + + if lead_time_label: + lead_time_label = ( + lead_time_label[0].to(dist.device).contiguous() + ) + loss_fn_kwargs.update( + {"lead_time_label": lead_time_label} + ) + else: + lead_time_label = None + if use_patch_grad_acc: + loss_fn.y_mean = None + + for patch_num_per_iter in patch_nums_iter: + if patching is not None: + patching.set_patch_num(patch_num_per_iter) + loss_fn_kwargs.update({"patching": patching}) + with nvtx.annotate(f"loss forward", color="green"): + with torch.autocast( + "cuda", dtype=amp_dtype, enabled=enable_amp + ): + loss = loss_fn(**loss_fn_kwargs) + + loss = loss.sum() / batch_size_per_gpu + loss_accum += loss / num_accumulation_rounds + with nvtx.annotate(f"loss backward", color="yellow"): + loss.backward() - loss_sum = torch.tensor([loss_accum], device=dist.device) - if dist.world_size > 1: - torch.distributed.barrier() - torch.distributed.all_reduce(loss_sum, op=torch.distributed.ReduceOp.SUM) - average_loss = (loss_sum / dist.world_size).cpu().item() - - # update running mean of average loss since last periodic task - average_loss_running_mean += ( - average_loss - average_loss_running_mean - ) / n_average_loss_running_mean - n_average_loss_running_mean += 1 - - if dist.rank == 0: - writer.add_scalar("training_loss", average_loss, cur_nimg) - writer.add_scalar( - "training_loss_running_mean", average_loss_running_mean, cur_nimg - ) + with nvtx.annotate(f"loss aggregate", color="green"): + loss_sum = torch.tensor([loss_accum], device=dist.device) + if dist.world_size > 1: + torch.distributed.barrier() + torch.distributed.all_reduce( + loss_sum, op=torch.distributed.ReduceOp.SUM + ) + average_loss = (loss_sum / dist.world_size).cpu().item() + + # update running mean of average loss since last periodic task + average_loss_running_mean += ( + average_loss - average_loss_running_mean + ) / n_average_loss_running_mean + n_average_loss_running_mean += 1 - - # Update weights. - lr_rampup = cfg.training.hp.lr_rampup # ramp up the learning rate - for g in optimizer.param_groups: - if lr_rampup > 0: - g["lr"] = cfg.training.hp.lr * min(cur_nimg / lr_rampup, 1) - if cur_nimg >= lr_rampup: - g["lr"] *= cfg.training.hp.lr_decay ** ((cur_nimg - lr_rampup) // 5e6) - current_lr = g["lr"] - if dist.rank == 0: - writer.add_scalar("learning_rate", current_lr, cur_nimg) - handle_and_clip_gradients( - model, grad_clip_threshold=cfg.training.hp.grad_clip_threshold - ) - optimizer.step() - - cur_nimg += cfg.training.hp.total_batch_size - done = cur_nimg >= cfg.training.hp.training_duration - - # Validation - if validation_dataset_iterator is not None: - valid_loss_accum = 0 - if is_time_for_periodic_task( - cur_nimg, - cfg.training.io.validation_freq, - done, - cfg.training.hp.total_batch_size, - dist.rank, - ): - with torch.no_grad(): - for _ in range(cfg.training.io.validation_steps): - img_clean_valid, img_lr_valid, labels_valid = next( - validation_dataset_iterator - ) - - img_clean_valid = ( - img_clean_valid.to(dist.device) - .to(torch.float32) - .contiguous() - ) - img_lr_valid = ( - img_lr_valid.to(dist.device).to(torch.float32).contiguous() - ) - labels_valid = labels_valid.to(dist.device).contiguous() - loss_valid = loss_fn( - net=model, - img_clean=img_clean_valid, - img_lr=img_lr_valid, - labels=labels_valid, - augment_pipe=None, - ) - loss_valid = ( - (loss_valid.sum() / batch_size_per_gpu).cpu().item() - ) - valid_loss_accum += ( - loss_valid / cfg.training.io.validation_steps - ) - valid_loss_sum = torch.tensor( - [valid_loss_accum], device=dist.device - ) - if dist.world_size > 1: - torch.distributed.barrier() - torch.distributed.all_reduce( - valid_loss_sum, op=torch.distributed.ReduceOp.SUM - ) - average_valid_loss = valid_loss_sum / dist.world_size if dist.rank == 0: + writer.add_scalar("training_loss", average_loss, cur_nimg) writer.add_scalar( - "validation_loss", average_valid_loss, cur_nimg + "training_loss_running_mean", + average_loss_running_mean, + cur_nimg, ) - if is_time_for_periodic_task( - cur_nimg, - cfg.training.io.print_progress_freq, - done, - cfg.training.hp.total_batch_size, - dist.rank, - rank_0_only=True, - ): - # Print stats if we crossed the printing threshold with this batch - tick_end_time = time.time() - fields = [] - fields += [f"samples {cur_nimg:<9.1f}"] - fields += [f"training_loss {average_loss:<7.2f}"] - fields += [f"training_loss_running_mean {average_loss_running_mean:<7.2f}"] - fields += [f"learning_rate {current_lr:<7.8f}"] - fields += [f"total_sec {(tick_end_time - start_time):<7.1f}"] - fields += [f"sec_per_tick {(tick_end_time - tick_start_time):<7.1f}"] - fields += [ - f"sec_per_sample {((tick_end_time - tick_start_time) / (cur_nimg - tick_start_nimg)):<7.2f}" - ] - fields += [ - f"cpu_mem_gb {(psutil.Process(os.getpid()).memory_info().rss / 2**30):<6.2f}" - ] - fields += [ - f"peak_gpu_mem_gb {(torch.cuda.max_memory_allocated(dist.device) / 2**30):<6.2f}" - ] - fields += [ - f"peak_gpu_mem_reserved_gb {(torch.cuda.max_memory_reserved(dist.device) / 2**30):<6.2f}" - ] - logger0.info(" ".join(fields)) - torch.cuda.reset_peak_memory_stats() - - ptt = is_time_for_periodic_task( - cur_nimg, - cfg.training.io.print_progress_freq, - done, - cfg.training.hp.total_batch_size, - dist.rank, - rank_0_only=True, - ) - if ptt: - # reset running mean of average loss - average_loss_running_mean = 0 - n_average_loss_running_mean = 1 - - # Save checkpoints - if dist.world_size > 1: - torch.distributed.barrier() - if is_time_for_periodic_task( - cur_nimg, - cfg.training.io.save_checkpoint_freq, - done, - cfg.training.hp.total_batch_size, - dist.rank, - rank_0_only=True, - ): - save_checkpoint( - path=checkpoint_dir, - model=model, - optimizer=optimizer, - epoch=cur_nimg, - ) + ptt = is_time_for_periodic_task( + cur_nimg, + cfg.training.io.print_progress_freq, + done, + cfg.training.hp.total_batch_size, + dist.rank, + rank_0_only=True, + ) + if ptt: + # reset running mean of average loss + average_loss_running_mean = 0 + n_average_loss_running_mean = 1 + + # Update weights. + with nvtx.annotate("update weights", color="blue"): + + lr_rampup = cfg.training.hp.lr_rampup # ramp up the learning rate + for g in optimizer.param_groups: + if lr_rampup > 0: + g["lr"] = cfg.training.hp.lr * min(cur_nimg / lr_rampup, 1) + if cur_nimg >= lr_rampup: + g["lr"] *= cfg.training.hp.lr_decay ** ((cur_nimg - lr_rampup) // 5e6) + current_lr = g["lr"] + if dist.rank == 0: + writer.add_scalar("learning_rate", current_lr, cur_nimg) + handle_and_clip_gradients( + model, grad_clip_threshold=cfg.training.hp.grad_clip_threshold + ) + with nvtx.annotate("optimizer step", color="blue"): + optimizer.step() + + cur_nimg += cfg.training.hp.total_batch_size + done = cur_nimg >= cfg.training.hp.training_duration + + with nvtx.annotate("validation", color="red"): + # Validation + if validation_dataset_iterator is not None: + valid_loss_accum = 0 + if is_time_for_periodic_task( + cur_nimg, + cfg.training.io.validation_freq, + done, + cfg.training.hp.total_batch_size, + dist.rank, + ): + with torch.no_grad(): + for _ in range(cfg.training.io.validation_steps): + ( + img_clean_valid, + img_lr_valid, + *lead_time_label_valid, + ) = next(validation_dataset_iterator) + + if use_apex_gn: + img_clean_valid = img_clean_valid.to( + dist.device, + dtype=input_dtype, + non_blocking=True, + ).to(memory_format=torch.channels_last) + img_lr_valid = img_lr_valid.to( + dist.device, + dtype=input_dtype, + non_blocking=True, + ).to(memory_format=torch.channels_last) + + else: + img_clean_valid = ( + img_clean_valid.to(dist.device) + .to(input_dtype) + .contiguous() + ) + img_lr_valid = ( + img_lr_valid.to(dist.device) + .to(input_dtype) + .contiguous() + ) + + loss_valid_kwargs = { + "net": model, + "img_clean": img_clean_valid, + "img_lr": img_lr_valid, + "augment_pipe": None, + } + if use_patch_grad_acc is not None: + loss_valid_kwargs[ + "use_patch_grad_acc" + ] = use_patch_grad_acc + if lead_time_label_valid: + lead_time_label_valid = ( + lead_time_label_valid[0] + .to(dist.device) + .contiguous() + ) + loss_valid_kwargs.update( + {"lead_time_label": lead_time_label_valid} + ) + if use_patch_grad_acc: + loss_fn.y_mean = None + + for patch_num_per_iter in patch_nums_iter: + if patching is not None: + patching.set_patch_num(patch_num_per_iter) + loss_fn_kwargs.update( + {"patching": patching} + ) + with torch.autocast( + "cuda", dtype=amp_dtype, enabled=enable_amp + ): + loss_valid = loss_fn(**loss_valid_kwargs) + + loss_valid = ( + (loss_valid.sum() / batch_size_per_gpu) + .cpu() + .item() + ) + valid_loss_accum += ( + loss_valid + / cfg.training.io.validation_steps + ) + valid_loss_sum = torch.tensor( + [valid_loss_accum], device=dist.device + ) + if dist.world_size > 1: + torch.distributed.barrier() + torch.distributed.all_reduce( + valid_loss_sum, op=torch.distributed.ReduceOp.SUM + ) + average_valid_loss = valid_loss_sum / dist.world_size + if dist.rank == 0: + writer.add_scalar( + "validation_loss", average_valid_loss, cur_nimg + ) + + if is_time_for_periodic_task( + cur_nimg, + cfg.training.io.print_progress_freq, + done, + cfg.training.hp.total_batch_size, + dist.rank, + rank_0_only=True, + ): + # Print stats if we crossed the printing threshold with this batch + tick_end_time = time.time() + fields = [] + fields += [f"samples {cur_nimg:<9.1f}"] + fields += [f"training_loss {average_loss:<7.2f}"] + fields += [f"training_loss_running_mean {average_loss_running_mean:<7.2f}"] + fields += [f"learning_rate {current_lr:<7.8f}"] + fields += [f"total_sec {(tick_end_time - start_time):<7.1f}"] + fields += [f"sec_per_tick {(tick_end_time - tick_start_time):<7.1f}"] + fields += [ + f"sec_per_sample {((tick_end_time - tick_start_time) / (cur_nimg - tick_start_nimg)):<7.2f}" + ] + fields += [ + f"cpu_mem_gb {(psutil.Process(os.getpid()).memory_info().rss / 2**30):<6.2f}" + ] + if torch.cuda.is_available(): + fields += [ + f"peak_gpu_mem_gb {(torch.cuda.max_memory_allocated(dist.device) / 2**30):<6.2f}" + ] + fields += [ + f"peak_gpu_mem_reserved_gb {(torch.cuda.max_memory_reserved(dist.device) / 2**30):<6.2f}" + ] + torch.cuda.reset_peak_memory_stats() + logger0.info(" ".join(fields)) + + + # Save checkpoints + if dist.world_size > 1: + torch.distributed.barrier() + if is_time_for_periodic_task( + cur_nimg, + cfg.training.io.save_checkpoint_freq, + done, + cfg.training.hp.total_batch_size, + dist.rank, + rank_0_only=True, + ): + save_checkpoint( + path=checkpoint_dir, + model=model, + optimizer=optimizer, + epoch=cur_nimg, + ) # Done. logger0.info("Training Completed.") diff --git a/src/hirad/utils/deterministic_sampler.py b/src/hirad/utils/deterministic_sampler.py index 9fcea1db..e502875e 100644 --- a/src/hirad/utils/deterministic_sampler.py +++ b/src/hirad/utils/deterministic_sampler.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Callable, Literal, Optional import numpy as np import nvtx @@ -26,33 +27,142 @@ @nvtx.annotate(message="deterministic_sampler", color="red") def deterministic_sampler( - net, - latents, - img_lr, - img_shape=None, - class_labels=None, - randn_like=torch.randn_like, - num_steps=18, - sigma_min=None, - sigma_max=None, - rho=7, - solver="heun", - discretization="edm", - schedule="linear", - scaling="none", - epsilon_s=1e-3, - C_1=0.001, - C_2=0.008, - M=1000, - alpha=1, - S_churn=0, - S_min=0, - S_max=float("inf"), - S_noise=1, -): + net: torch.nn.Module, + latents: torch.Tensor, + img_lr: torch.Tensor, + class_labels: Optional[torch.Tensor] = None, + randn_like: Callable = torch.randn_like, + num_steps: int = 18, + sigma_min: Optional[float] = None, + sigma_max: Optional[float] = None, + rho: float = 7.0, + solver: Literal["heun", "euler"] = "heun", + discretization: Literal["vp", "ve", "iddpm", "edm"] = "edm", + schedule: Literal["vp", "ve", "linear"] = "linear", + scaling: Literal["vp", "none"] = "none", + epsilon_s: float = 1e-3, + C_1: float = 0.001, + C_2: float = 0.008, + M: int = 1000, + alpha: float = 1.0, + S_churn: int = 0, + S_min: float = 0.0, + S_max: float = float("inf"), + S_noise: float = 1.0, +) -> torch.Tensor: """ - Generalized sampler, representing the superset of all sampling methods discussed - in the paper "Elucidating the Design Space of Diffusion-Based Generative Models" + Generalized sampler, representing the superset of all sampling methods + discussed in the paper "Elucidating the Design Space of Diffusion-Based + Generative Models" (EDM). + - https://arxiv.org/abs/2206.00364 + + This function integrates an ODE (probability flow) or SDE over multiple + time-steps to generate samples from the diffusion model provided by the + argument 'net'. It can be used to combine multiple choices to + design a custom sampler, including multiple integration solver, + discretization method, noise schedule, and so on. + + Parameters: + ----------- + net : torch.nn.Module + The diffusion model to use in the sampling process. + latents : torch.Tensor + The latent random noise used as the initial condition for the + stochastic ODE. + img_lr : torch.Tensor + Low-resolution input image for conditioning the diffusion process. + Passed as a keywork argument to the model 'net'. + class_labels : Optional[torch.Tensor] + Labels of the classes used as input to a class-conditionned + diffusion model. Passed as a keyword argument to the model 'net'. + If provided, it must be a tensor containing integer values. + Defaults to None, in which case it is ignored. + randn_like: Callable + Random Number Generator to generate random noise that is added + during the stochastic sampling. Must have the same signature as + torch.randn_like and return torch.Tensor. Defaults to + torch.randn_like. + num_steps : Optional[int] + Number of time-steps for the stochastic ODE integration. Defaults + to 18. + sigma_min : Optional[float] + Minimum noise level for the diffusion process. 'sigma_min', + 'sigma_max', and 'rho' are used to compute the time-step + discretization, based on the choice of discretization. For the + default choice ("discretization='heun'"), the noise level schedule + is computed as: + :math:`\sigma_i = (\sigma_{max}^{1/\rho} + i / (num_steps - 1) * (\sigma_{min}^{1/\rho} - \sigma_{max}^{1/\rho}))^{rho}`. + For other choices of 'discretization', see details in the EDM + paper. Defaults to None, in which case defaults values depending + of the specified discretization are used. + sigma_max : Optional[float] + Maximum noise level for the diffusion process. See sigma_min for + details. Defaults to None, in which case defaults values depending + of the specified discretization are used. + rho : float, optional + Exponent used in the noise schedule. See sigma_min for details. + Only used when 'discretization' is 'heun'. Values in the range [5, + 10] produce better images. Lower values lead to truncation errors + equalized over all time steps. Defaults to 7. + solver : Literal["heun", "euler"] + The numerical method used to integrate the stochastic ODE. "euler" + is 1st order solver, which is faster but produces lower-quality + images. "heun" is 2nd order, more expensive, but produces + higher-quality images. Defaults to "heun". + discretization : Literal["vp", "ve", "iddpm", "edm"] + The method to discretize time-steps :math:`t_i` in the + diffusion process. See the EDM papper for details. Defaults to + "edm". + schedule : Literal["vp", "ve", "linear"] + The type of noise level schedule. Defaults to "linear". If + schedule='ve', then :math:`\sigma(t) = \sqrt{t}`. If + schedule='linear', then :math:`\sigma(t) = t`. If schedule='vp', + see EDM paper for details. Defaults to "linear". + scaling : Literal["vp", "none"] + The type of time-dependent signal scaling :math:`s(t)`, such that + :math:`x = s(t) \hat{x}`. See EDM paper for details on the 'vp' + scaling. Defaults to 'none', in which case :math:`s(t)=1`. + epsilon_s : float, optional + Parameter to compute both the noise level schedule and the + time-step discetization. Only used when discretization='vp' or + schedule='vp'. Ignored in other cases. Defaults to 1e-3. + C_1 : float, optional + Parameters to compute the time-step discetization. Only used when + discretization='iddpm'. Defaults to 0.001. + C_2 : float, optional + Same as for C_1. Only used when discretization='iddpm'. Defaults to + 0.008. + M : int, optional + Same as for C_1 and C_2. Only used when discretization='iddpm'. + Defaults to 1000. + alpha : float, optional + Controls (i.e. multiplies) the step size :math:`t_{i+1} - + \hat{t}_i` in the stochastic sampler, where :math:`\hat{t}_i` is + the temporarily increased noise level. Defaults to 1.0, which is + the recommended value. + S_churn : int, optional + Controls the amount of stochasticty injected in the SDE in the + stochatsic sampler. Larger values of S_churn lead to larger values + of :math:`\hat{t}_i`, which in turn lead to injecting more + stochasticity in the SDE by Defaults to 0, which means no + stochasticity is injected. + S_min : float, optional + S_min and S_max control the time-step range obver which + stochasticty is injected in the SDE. Stochasticity is injected + through `\hat{t}_i` for time-steps :math:`t_i` such that + :math:`S_{min} \leq t_i \leq S_{max}`. Defaults to 0.0. + S_max : float, optional + See S_min. Defaults to float("inf"). + S_noise : float, optional + Controls the amount of stochasticty injected in the SDE in the + stochatsic sampler. Added signal noise is proportinal to + :math:`\epsilon_i` where `\epsilon_i ~ N(0, S_{noise}^2)`. Defaults + to 1.0. + + Returns + ------- + torch.Tensor: + Generated batch of samples. Same shape as the input 'latents'. """ # conditioning diff --git a/src/hirad/utils/function_utils.py b/src/hirad/utils/function_utils.py index dcbb127e..347457c3 100644 --- a/src/hirad/utils/function_utils.py +++ b/src/hirad/utils/function_utils.py @@ -29,7 +29,7 @@ import sys import types import warnings -from typing import Any, List, Tuple, Union +from typing import Any, Iterator, List, Tuple, Union import cftime import numpy as np @@ -553,14 +553,37 @@ def decorator(*args, **kwargs): # indefinitely, shuffling items as it goes. -class InfiniteSampler(torch.utils.data.Sampler): # pragma: no cover - """ - Sampler for torch.utils.data.DataLoader that loops over the dataset - indefinitely, shuffling items as it goes. +class InfiniteSampler(torch.utils.data.Sampler[int]): # pragma: no cover + """Sampler for torch.utils.data.DataLoader that loops over the dataset indefinitely. + + This sampler yields indices indefinitely, optionally shuffling items as it goes. + It can also perform distributed sampling when rank and num_replicas are specified. + + Parameters + ---------- + dataset : torch.utils.data.Dataset + The dataset to sample from + rank : int, default=0 + The rank of the current process within num_replicas processes + num_replicas : int, default=1 + The number of processes participating in distributed sampling + shuffle : bool, default=True + Whether to shuffle the indices + seed : int, default=0 + Random seed for reproducibility when shuffling + window_size : float, default=0.5 + Fraction of dataset to use as window for shuffling. Must be between 0 and 1. + A larger window means more thorough shuffling but slower iteration. """ def __init__( - self, dataset, rank=0, num_replicas=1, shuffle=True, seed=0, window_size=0.5 + self, + dataset: torch.utils.data.Dataset, + rank: int = 0, + num_replicas: int = 1, + shuffle: bool = True, + seed: int = 0, + window_size: float = 0.5, ): if not len(dataset) > 0: raise ValueError("Dataset must contain at least one item") @@ -578,7 +601,7 @@ def __init__( self.seed = seed self.window_size = window_size - def __iter__(self): + def __iter__(self) -> Iterator[int]: order = np.arange(len(self.dataset)) rnd = None window = 0 diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index ace05ba3..8665536b 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -15,6 +15,7 @@ # limitations under the License. import datetime +from typing import Optional import cftime import nvtx @@ -23,6 +24,9 @@ from .function_utils import StackedRandomGenerator, time_range +from .stochastic_sampler import stochastic_sampler +from .deterministic_sampler import deterministic_sampler + ############################################################################ # CorrDiff Generation Utilities # ############################################################################ @@ -31,35 +35,56 @@ def regression_step( net: torch.nn.Module, img_lr: torch.Tensor, - labels: torch.Tensor, latents_shape: torch.Size, - lead_time_label: torch.Tensor = None, + lead_time_label: Optional[torch.Tensor] = None, ) -> torch.Tensor: """ - Given a low-res input, performs a regression step to produce ensemble mean. - This function performs the regression on a single instance and then replicates - the results across the batch dimension. - - Args: - net (torch.nn.Module): U-Net model for regression. - img_lr (torch.Tensor): Low-resolution input. - latents_shape (torch.Size): Shape of the latent representation. Typically - (batch_size, out_channels, image_shape_x, image_shape_y). - - - Returns: - torch.Tensor: Predicted output at the next time step. + Perform a regression step to produce ensemble mean prediction. + + This function takes a low-resolution input and performs a regression step to produce + an ensemble mean prediction. It processes a single instance and then replicates + the results across the batch dimension if needed. + + Parameters + ---------- + net : torch.nn.Module + U-Net model for regression. + img_lr : torch.Tensor + Low-resolution input to the network with shape (1, channels, height, width). + Must have a batch dimension of 1. + latents_shape : torch.Size + Shape of the latent representation with format + (batch_size, out_channels, image_shape_y, image_shape_x). + lead_time_label : Optional[torch.Tensor], optional + Lead time label tensor for lead time conditioning, + with shape (1, lead_time_dims). Default is None. + + Returns + ------- + torch.Tensor + Predicted ensemble mean at the next time step with shape matching latents_shape. + + Raises + ------ + ValueError + If img_lr has a batch size greater than 1. """ # Create a tensor of zeros with the given shape and move it to the appropriate device x_hat = torch.zeros(latents_shape, dtype=img_lr.dtype, device=img_lr.device) - t_hat = torch.tensor(1.0, dtype=img_lr.dtype, device=img_lr.device)#.reshape((1,1,1,1)) + + # Safety check: avoid silently ignoring batch elements in img_lr + if img_lr.shape[0] > 1: + raise ValueError( + f"Expected img_lr to have a batch size of 1, " + f"but found {img_lr.shape[0]}." + ) # Perform regression on a single batch element with torch.inference_mode(): if lead_time_label is not None: - x = net(x_hat[0:1], img_lr, t_hat, labels, lead_time_label=lead_time_label) + x = net(x=x_hat[0:1], img_lr=img_lr, lead_time_label=lead_time_label) else: - x = net(x_hat[0:1], img_lr, t_hat, labels) + x = net(x=x_hat[0:1], img_lr=img_lr) # If the batch size is greater than 1, repeat the prediction if x_hat.shape[0] > 1: @@ -68,48 +93,85 @@ def regression_step( return x -def diffusion_step( # TODO generalize the module and add defaults +def diffusion_step( net: torch.nn.Module, sampler_fn: callable, - seed_batch_size: int, img_shape: tuple, img_out_channels: int, rank_batches: list, img_lr: torch.Tensor, rank: int, device: torch.device, - hr_mean: torch.Tensor = None, + mean_hr: torch.Tensor = None, lead_time_label: torch.Tensor = None, ) -> torch.Tensor: """ Generate images using diffusion techniques as described in the relevant paper. - Args: - net (torch.nn.Module): The diffusion model network. - sampler_fn (callable): Function used to sample images from the diffusion model. - seed_batch_size (int): Number of seeds per batch. - img_shape (tuple): Shape of the images, (height, width). - img_out_channels (int): Number of output channels for the image. - rank_batches (list): List of batches of seeds to process. - img_lr (torch.Tensor): Low-resolution input image. - rank (int): Rank of the current process for distributed processing. - device (torch.device): Device to perform computations. - mean_hr (torch.Tensor, optional): High-resolution mean tensor, to be used as an additional input. By default None. - - Returns: - torch.Tensor: Generated images concatenated across batches. + This function applies a diffusion model to generate high-resolution images based on + low-resolution inputs. It supports optional conditioning on high-resolution mean + predictions and lead time labels. + + For each low-resolution sample in `img_lr`, the function generates multiple + high-resolution samples, with different random seeds, specified in `rank_batches`. + The function then concatenates these high-resolution samples across the batch dimension. + + Parameters + ---------- + net : torch.nn.Module + The diffusion model network. + sampler_fn : callable + Function used to sample images from the diffusion model. + img_shape : tuple + Shape of the images, (height, width). + img_out_channels : int + Number of output channels for the image. + rank_batches : list + List of batches of seeds to process. + img_lr : torch.Tensor + Low-resolution input image with shape (seed_batch_size, channels_lr, height, width). + rank : int, optional + Rank of the current process for distributed processing. + device : torch.device, optional + Device to perform computations. + mean_hr : torch.Tensor, optional + High-resolution mean tensor to be used as an additional input, + with shape (1, channels_hr, height, width). Default is None. + lead_time_label : torch.Tensor, optional + Lead time label tensor for temporal conditioning, + with shape (batch_size, lead_time_dims). Default is None. + + Returns + ------- + torch.Tensor + Generated images concatenated across batches with shape + (seed_batch_size * len(rank_batches), out_channels, height, width). """ - img_lr = img_lr #.to(memory_format=torch.channels_last) + # Check img_lr dimensions match expected shape + if img_lr.shape[2:] != img_shape: + raise ValueError( + f"img_lr shape {img_lr.shape[2:]} does not match expected shape img_shape {img_shape}" + ) + + # Check mean_hr dimensions if provided + if mean_hr is not None: + if mean_hr.shape[2:] != img_shape: + raise ValueError( + f"mean_hr shape {mean_hr.shape[2:]} does not match expected shape img_shape {img_shape}" + ) + if mean_hr.shape[0] != 1: + raise ValueError(f"mean_hr must have batch size 1, got {mean_hr.shape[0]}") + + img_lr = img_lr.to(memory_format=torch.channels_last) # Handling of the high-res mean additional_args = {} - if hr_mean is not None: - additional_args["mean_hr"] = hr_mean + if mean_hr is not None: + additional_args["mean_hr"] = mean_hr if lead_time_label is not None: additional_args["lead_time_label"] = lead_time_label - additional_args["img_shape"] = img_shape # Loop over batches all_images = [] @@ -123,7 +185,7 @@ def diffusion_step( # TODO generalize the module and add defaults rnd = StackedRandomGenerator(device, batch_seeds) latents = rnd.randn( [ - seed_batch_size, + img_lr.shape[0], img_out_channels, img_shape[0], img_shape[1], @@ -139,6 +201,9 @@ def diffusion_step( # TODO generalize the module and add defaults return torch.cat(all_images) +def generate(): + pass + ############################################################################ # CorrDiff writer utilities # ############################################################################ diff --git a/src/hirad/utils/patching.py b/src/hirad/utils/patching.py new file mode 100644 index 00000000..6f4bc4d8 --- /dev/null +++ b/src/hirad/utils/patching.py @@ -0,0 +1,767 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import math +import random +import warnings +from abc import ABC, abstractmethod +from typing import List, Optional, Tuple, Union + +import torch +from einops import rearrange +from torch import Tensor + +""" +This module defines utilities, including classes and functions, for domain +decomposition. +""" + + +class BasePatching2D(ABC): + """ + Abstract base class for 2D image patching operations. + + This class provides a foundation for implementing various image patching + strategies. + It handles basic validation and provides abstract methods that must be + implemented by subclasses. + + Parameters + ---------- + img_shape : Tuple[int, int] + The height and width of the input images (img_shape_y, img_shape_x). + patch_shape : Tuple[int, int] + The height and width of the patches (patch_shape_y, patch_shape_x) to + extract. + """ + + def __init__( + self, img_shape: Tuple[int, int], patch_shape: Tuple[int, int] + ) -> None: + # Check that img_shape and patch_shape are 2D + if len(img_shape) != 2: + raise ValueError(f"img_shape must be 2D, got {len(img_shape)}D") + if len(patch_shape) != 2: + raise ValueError(f"patch_shape must be 2D, got {len(patch_shape)}D") + + # Make sure patches fit within the image + if any(p > i for p, i in zip(patch_shape, img_shape)): + warnings.warn( + f"Patch shape {patch_shape} is larger than " + f"image shape {img_shape}. " + f"Patches will be cropped to fit within the image." + ) + self.img_shape = img_shape + self.patch_shape = tuple(min(p, i) for p, i in zip(patch_shape, img_shape)) + + @abstractmethod + def apply(self, input: Tensor, **kwargs) -> Tensor: + """ + Apply the patching operation to the input tensor. + + Parameters + ---------- + input : Tensor + Input tensor of shape (batch_size, channels, img_shape_y, + img_shape_x). + **kwargs : dict + Additional keyword arguments specific to the patching + implementation. + + Returns + ------- + Tensor + Patched tensor, shape depends on specific implementation. + """ + pass + + def fuse(self, input: Tensor, **kwargs) -> Tensor: + """ + Fuse patches back into a complete image. + + Parameters + ---------- + input : Tensor + Input tensor containing patches. + **kwargs : dict + Additional keyword arguments specific to the fusion implementation. + + Returns + ------- + Tensor + Fused tensor, shape depends on specific implementation. + + Raises + ------ + NotImplementedError + If the subclass does not implement this method. + """ + raise NotImplementedError("'fuse' method must be implemented in subclasses.") + + def global_index( + self, batch_size: int, device: Union[torch.device, str] = "cpu" + ) -> Tensor: + """ + Returns a tensor containing the global indices for each patch. + + Global indices correspond to (y, x) global grid coordinates of each + element within the original image (before patching). It is typically + used to keep track of the original position of each patch in the + original image. + + Parameters + ---------- + batch_size : int + The size of the batch of images to patch. + device : Union[torch.device, str] + Proper device to initialize global_index on. Default to `cpu` + + Returns + ------- + Tensor + A tensor of shape (self.patch_num, 2, patch_shape_y, + patch_shape_x). `global_index[:, 0, :, :]` contains the + y-coordinate (height), and `global_index[:, 1, :, :]` contains the + x-coordinate (width). + """ + Ny = torch.arange(self.img_shape[0], device=device).int() + Nx = torch.arange(self.img_shape[1], device=device).int() + grid = torch.stack(torch.meshgrid(Ny, Nx, indexing="ij"), dim=0).unsqueeze(0) + global_index = self.apply(grid).long() + return global_index + + +class RandomPatching2D(BasePatching2D): + """ + Class for randomly extracting patches from 2D images. + + This class provides utilities to randomly extract patches from images + represented as 4D tensors. It maintains a list of random patch indices + that can be reset as needed. + + Parameters + ---------- + img_shape : Tuple[int, int] + The height and width of the input images (img_shape_y, img_shape_x). + patch_shape : Tuple[int, int] + The height and width of the patches (patch_shape_y, patch_shape_x) to + extract. + patch_num : int + The number of patches to extract. + + Attributes + ---------- + patch_indices : List[Tuple[int, int]] + The indices of the patches to extract from the images. These indices + correspond to the (y, x) coordinates of the lower left corner of each + patch. + + See Also + -------- + :class:`physicsnemo.utils.patching.BasePatching2D` + The base class providing the patching interface. + :class:`physicsnemo.utils.patching.GridPatching2D` + Alternative patching strategy using deterministic patch locations. + """ + + def __init__( + self, img_shape: Tuple[int, int], patch_shape: Tuple[int, int], patch_num: int + ) -> None: + """ + Initialize the RandomPatching2D object with the provided image shape, + patch shape, and number of patches to extract. + + Parameters + ---------- + img_shape : Tuple[int, int] + The height and width of the input images (img_shape_y, + img_shape_x). + patch_shape : Tuple[int, int] + The height and width of the patches (patch_shape_y, patch_shape_x) + to extract. + patch_num : int + The number of patches to extract. + + Returns + ------- + None + """ + super().__init__(img_shape, patch_shape) + self._patch_num = patch_num + # Generate the indices of the patches to extract + self.reset_patch_indices() + + @property + def patch_num(self) -> int: + """ + Get the number of patches to extract. + + Returns + ------- + int + The number of patches to extract. + """ + return self._patch_num + + def set_patch_num(self, value: int) -> None: + """ + Set the number of patches to extract and reset patch indices. + This is the only way to modify the patch_num value. + + Parameters + ---------- + value : int + The new number of patches to extract. + """ + self._patch_num = value + self.reset_patch_indices() + + def reset_patch_indices(self) -> None: + """ + Generate new random indices for the patches to extract. These are the + starting indices of the patches to extract (upper left corner). + + Returns + ------- + None + """ + self.patch_indices = [ + ( + random.randint(0, self.img_shape[0] - self.patch_shape[0]), + random.randint(0, self.img_shape[1] - self.patch_shape[1]), + ) + for _ in range(self.patch_num) + ] + return + + def get_patch_indices(self) -> List[Tuple[int, int]]: + """ + Get the current list of patch starting indices. + + These are the upper-left coordinates of each extracted patch + from the full image. + + Returns + ------- + List[Tuple[int, int]] + A list of (row, column) tuples representing patch starting positions. + """ + return self.patch_indices + + def apply( + self, + input: Tensor, + additional_input: Optional[Tensor] = None, + ) -> Tensor: + """ + Applies the patching operation by extracting patches specified by + `self.patch_indices` from the `input` Tensor. Extracted patches are + batched along the first dimension of the output. The layout of the + output assumes that for any i, `out[B * i: B * (i + 1)]` + corresponds to the same patch exacted from each batch element of + `input`. + + Arguments + --------- + input : Tensor + The input tensor representing the full image with shape + (batch_size, channels_in, img_shape_y, img_shape_x). + additional_input : Optional[Tensor], optional + If provided, it is concatenated to each patch along `dim=1`. + Must have same batch size as `input`. Bilinear interpolation + is used to interpolate `additional_input` onto a 2D grid of shape + (patch_shape_y, patch_shape_x). + + Returns + ------- + Tensor + A tensor of shape (batch_size * self.patch_num, channels [+ + additional_channels], patch_shape_y, patch_shape_x). If + `additional_input` is provided, its channels are concatenated + along the channel dimension. + """ + B = input.shape[0] + out = torch.zeros( + B * self.patch_num, + ( + input.shape[1] + + (additional_input.shape[1] if additional_input is not None else 0) + ), + self.patch_shape[0], + self.patch_shape[1], + device=input.device, + ) + out = out.to( + memory_format=torch.channels_last + if input.is_contiguous(memory_format=torch.channels_last) + else torch.contiguous_format + ) + if additional_input is not None: + add_input_interp = torch.nn.functional.interpolate( + input=additional_input, size=self.patch_shape, mode="bilinear" + ) + + for i, (py, px) in enumerate(self.patch_indices): + if additional_input is not None: + out[B * i : B * (i + 1),] = torch.cat( + ( + input[ + :, + :, + py : py + self.patch_shape[0], + px : px + self.patch_shape[1], + ], + add_input_interp, + ), + dim=1, + ) + else: + out[B * i : B * (i + 1),] = input[ + :, + :, + py : py + self.patch_shape[0], + px : px + self.patch_shape[1], + ] + return out + + +class GridPatching2D(BasePatching2D): + """ + Class for deterministically extracting patches from 2D images in a grid pattern. + + This class provides utilities to extract patches from images in a + deterministic manner, with configurable overlap and boundary pixels. + The patches are extracted in a grid-like pattern covering the entire image. + + Parameters + ---------- + img_shape : Tuple[int, int] + The height and width of the input images (img_shape_y, img_shape_x). + patch_shape : Tuple[int, int] + The height and width of the patches (patch_shape_y, patch_shape_x) to + extract. + overlap_pix : int, optional + Number of pixels to overlap between adjacent patches, by default 0. + boundary_pix : int, optional + Number of pixels to crop as boundary from each patch, by default 0. + + Attributes + ---------- + patch_num : int + Total number of patches that will be extracted from the image, + calculated as patch_num_x * patch_num_y. + + See Also + -------- + :class:`physicsnemo.utils.patching.BasePatching2D` + The base class providing the patching interface. + :class:`physicsnemo.utils.patching.RandomPatching2D` + Alternative patching strategy using random patch locations. + """ + + def __init__( + self, + img_shape: Tuple[int, int], + patch_shape: Tuple[int, int], + overlap_pix: int = 0, + boundary_pix: int = 0, + ): + super().__init__(img_shape, patch_shape) + self.overlap_pix = overlap_pix + self.boundary_pix = boundary_pix + patch_num_x = math.ceil( + img_shape[1] / (patch_shape[1] - overlap_pix - boundary_pix) + ) + patch_num_y = math.ceil( + img_shape[0] / (patch_shape[0] - overlap_pix - boundary_pix) + ) + self.patch_num = patch_num_x * patch_num_y + + def apply( + self, + input: Tensor, + additional_input: Optional[Tensor] = None, + ) -> Tensor: + """ + Apply deterministic patching to the input tensor. + + Splits the input tensor into patches in a grid-like pattern. Can + optionally concatenate additional interpolated data to each patch. + Extracted patches are batched along the first dimension of the output. + The layout of the output assumes that for any i, `out[B * i: B * (i + 1)]` + corresponds to the same patch exacted from each batch element of + `input`. The patches can be reconstructed back into the original image + using the fuse method. + + Parameters + ---------- + input : Tensor + Input tensor of shape (batch_size, channels, img_shape_y, + img_shape_x). + additional_input : Optional[Tensor], optional + Additional data to concatenate to each patch. Will be interpolated + to match patch dimensions. Shape must be (batch_size, + additional_channels, H, W), by default None. + + Returns + ------- + Tensor + Tensor containing patches with shape (batch_size * patch_num, + channels [+ additional_channels], patch_shape_y, patch_shape_x). + If additional_input is provided, its channels are concatenated + along the channel dimension. + + See Also + -------- + :func:`physicsnemo.utils.patching.image_batching` + The underlying function used to perform the patching operation. + """ + if additional_input is not None: + add_input_interp = torch.nn.functional.interpolate( + input=additional_input, size=self.patch_shape, mode="bilinear" + ) + else: + add_input_interp = None + out = image_batching( + input=input, + patch_shape_y=self.patch_shape[0], + patch_shape_x=self.patch_shape[1], + overlap_pix=self.overlap_pix, + boundary_pix=self.boundary_pix, + input_interp=add_input_interp, + ) + return out + + def fuse(self, input: Tensor, batch_size: int) -> Tensor: + """ + Fuse patches back into a complete image. + + Reconstructs the original image by stitching together patches, + accounting for overlapping regions and boundary pixels. In overlapping + regions, values are averaged. + + Parameters + ---------- + input : Tensor + Input tensor containing patches with shape (batch_size * patch_num, + channels, patch_shape_y, patch_shape_x). + batch_size : int + The original batch size before patching. + + Returns + ------- + Tensor + Reconstructed image tensor with shape (batch_size, channels, + img_shape_y, img_shape_x). + + See Also + -------- + :func:`physicsnemo.utils.patching.image_fuse` + The underlying function used to perform the fusion operation. + """ + out = image_fuse( + input=input, + img_shape_y=self.img_shape[0], + img_shape_x=self.img_shape[1], + batch_size=batch_size, + overlap_pix=self.overlap_pix, + boundary_pix=self.boundary_pix, + ) + return out + + +def image_batching( + input: Tensor, + patch_shape_y: int, + patch_shape_x: int, + overlap_pix: int, + boundary_pix: int, + input_interp: Optional[Tensor] = None, +) -> Tensor: + """ + Splits a full image into a batch of patched images. + + This function takes a full image and splits it into patches, adding padding + where necessary. It can also concatenate additional interpolated data to + each patch if provided. + + Parameters + ---------- + input : Tensor + The input tensor representing the full image with shape (batch_size, + channels, img_shape_y, img_shape_x). + patch_shape_y : int + The height (y-dimension) of each image patch. + patch_shape_x : int + The width (x-dimension) of each image patch. + overlap_pix : int + The number of overlapping pixels between adjacent patches. + boundary_pix : int + The number of pixels to crop as a boundary from each patch. + input_interp : Optional[Tensor], optional + Optional additional data to concatenate to each patch with shape + (batch_size, interp_channels, patch_shape_y, patch_shape_x). + By default None. + + Returns + ------- + Tensor + A tensor containing the image patches, with shape (total_patches * + batch_size, channels [+ interp_channels], patch_shape_x, + patch_shape_y). + """ + # Infer sizes from input image + batch_size, _, img_shape_y, img_shape_x = input.shape + + # Safety check: make sure patch_shapes are large enough to accommodate + # overlaps and boundaries pixels + if (patch_shape_x - overlap_pix - boundary_pix) < 1: + raise ValueError( + f"patch_shape_x must verify patch_shape_x ({patch_shape_x}) >= " + f"1 + overlap_pix ({overlap_pix}) + boundary_pix ({boundary_pix})" + ) + if (patch_shape_y - overlap_pix - boundary_pix) < 1: + raise ValueError( + f"patch_shape_y must verify patch_shape_y ({patch_shape_y}) >= " + f"1 + overlap_pix ({overlap_pix}) + boundary_pix ({boundary_pix})" + ) + # Safety check: validate input_interp dimensions if provided + if input_interp is not None: + if input_interp.shape[0] != batch_size: + raise ValueError( + f"input_interp batch size ({input_interp.shape[0]}) must match " + f"input batch size ({batch_size})" + ) + if (input_interp.shape[2] != patch_shape_y) or ( + input_interp.shape[3] != patch_shape_x + ): + raise ValueError( + f"input_interp patch shape ({input_interp.shape[2]}, {input_interp.shape[3]}) " + f"must match specified patch shape ({patch_shape_y}, {patch_shape_x})" + ) + + # Safety check: make sure patch_shape is large enough in comparison to + # overlap_pix and boundary_pix. Otherwise, number of patches extracted by + # unfold differs from the expected number of patches. + if patch_shape_x <= overlap_pix + 2 * boundary_pix: + raise ValueError( + f"patch_shape_x ({patch_shape_x}) must verify " + f"patch_shape_x ({patch_shape_x}) > " + f"overlap_pix ({overlap_pix}) + 2 * boundary_pix ({boundary_pix})" + ) + if patch_shape_y <= overlap_pix + 2 * boundary_pix: + raise ValueError( + f"patch_shape_y ({patch_shape_y}) must verify " + f"patch_shape_y ({patch_shape_y}) > " + f"overlap_pix ({overlap_pix}) + 2 * boundary_pix ({boundary_pix})" + ) + + patch_num_x = math.ceil(img_shape_x / (patch_shape_x - overlap_pix - boundary_pix)) + patch_num_y = math.ceil(img_shape_y / (patch_shape_y - overlap_pix - boundary_pix)) + padded_shape_x = ( + (patch_shape_x - overlap_pix - boundary_pix) * (patch_num_x - 1) + + patch_shape_x + + boundary_pix + ) + padded_shape_y = ( + (patch_shape_y - overlap_pix - boundary_pix) * (patch_num_y - 1) + + patch_shape_y + + boundary_pix + ) + pad_x_right = padded_shape_x - img_shape_x - boundary_pix + pad_y_right = padded_shape_y - img_shape_y - boundary_pix + image_padding = torch.nn.ReflectionPad2d( + (boundary_pix, pad_x_right, boundary_pix, pad_y_right) + ).to( + input.device + ) # (padding_left,padding_right,padding_top,padding_bottom) + input_padded = image_padding(input) + patch_num = patch_num_x * patch_num_y + x_unfold = torch.nn.functional.unfold( + input=input_padded.view(_cast_type(input_padded)), # Cast to float + kernel_size=(patch_shape_y, patch_shape_x), + stride=( + patch_shape_y - overlap_pix - boundary_pix, + patch_shape_x - overlap_pix - boundary_pix, + ), + ).to(input_padded.dtype) + x_unfold = rearrange( + x_unfold, + "b (c p_h p_w) (nb_p_h nb_p_w) -> (nb_p_w nb_p_h b) c p_h p_w", + p_h=patch_shape_y, + p_w=patch_shape_x, + nb_p_h=patch_num_y, + nb_p_w=patch_num_x, + ) + if input_interp is not None: + input_interp_repeated = rearrange( + torch.repeat_interleave( + input=input_interp, + repeats=patch_num, + dim=0, + output_size=x_unfold.shape[0], + ), + "(b p) c h w -> (p b) c h w", + p=patch_num, + ) + return torch.cat((x_unfold, input_interp_repeated), dim=1) + else: + return x_unfold + + +def image_fuse( + input: Tensor, + img_shape_y: int, + img_shape_x: int, + batch_size: int, + overlap_pix: int, + boundary_pix: int, +) -> Tensor: + """ + Reconstructs a full image from a batch of patched images. Reverts the patching + operation performed by image_batching(). + + This function takes a batch of image patches and reconstructs the full + image by stitching the patches together. The function accounts for + overlapping and boundary pixels, ensuring that overlapping areas are + averaged. + + Parameters + ---------- + input : Tensor + The input tensor containing the image patches with shape (patch_num * batch_size, channels, patch_shape_y, patch_shape_x). + img_shape_y : int + The height (y-dimension) of the original full image. + img_shape_x : int + The width (x-dimension) of the original full image. + batch_size : int + The original batch size before patching. + overlap_pix : int + The number of overlapping pixels between adjacent patches. + boundary_pix : int + The number of pixels to crop as a boundary from each patch. + + Returns + ------- + Tensor + The reconstructed full image tensor with shape (batch_size, channels, + img_shape_y, img_shape_x). + + See Also + -------- + :func:`physicsnemo.utils.patching.image_batching` + The function this reverses, which splits images into patches. + """ + + # Infer sizes from input image shape + patch_shape_y, patch_shape_x = input.shape[2], input.shape[3] + + # Calculate the number of patches in each dimension + patch_num_x = math.ceil(img_shape_x / (patch_shape_x - overlap_pix - boundary_pix)) + patch_num_y = math.ceil(img_shape_y / (patch_shape_y - overlap_pix - boundary_pix)) + + # Calculate the shape of the input after padding + padded_shape_x = ( + (patch_shape_x - overlap_pix - boundary_pix) * (patch_num_x - 1) + + patch_shape_x + + boundary_pix + ) + padded_shape_y = ( + (patch_shape_y - overlap_pix - boundary_pix) * (patch_num_y - 1) + + patch_shape_y + + boundary_pix + ) + # Calculate the shape of the padding to add to input + pad_x_right = padded_shape_x - img_shape_x - boundary_pix + pad_y_right = padded_shape_y - img_shape_y - boundary_pix + pad = (boundary_pix, pad_x_right, boundary_pix, pad_y_right) + + # Count local overlaps between patches + input_ones = torch.ones( + (batch_size, input.shape[1], padded_shape_y, padded_shape_x), + device=input.device, + ) + overlap_count = torch.nn.functional.unfold( + input=input_ones, + kernel_size=(patch_shape_y, patch_shape_x), + stride=( + patch_shape_y - overlap_pix - boundary_pix, + patch_shape_x - overlap_pix - boundary_pix, + ), + ) + overlap_count = torch.nn.functional.fold( + input=overlap_count, + output_size=(padded_shape_y, padded_shape_x), + kernel_size=(patch_shape_y, patch_shape_x), + stride=( + patch_shape_y - overlap_pix - boundary_pix, + patch_shape_x - overlap_pix - boundary_pix, + ), + ) + + # Reshape input to make it 3D to apply fold + x = rearrange( + input, + "(nb_p_w nb_p_h b) c p_h p_w -> b (c p_h p_w) (nb_p_h nb_p_w)", + p_h=patch_shape_y, + p_w=patch_shape_x, + nb_p_h=patch_num_y, + nb_p_w=patch_num_x, + ) + # Stitch patches together (by summing over overlapping patches) + x_folded = torch.nn.functional.fold( + input=x, + output_size=(padded_shape_y, padded_shape_x), + kernel_size=(patch_shape_y, patch_shape_x), + stride=( + patch_shape_y - overlap_pix - boundary_pix, + patch_shape_x - overlap_pix - boundary_pix, + ), + ) + + # Remove padding + x_no_padding = x_folded[ + ..., pad[2] : pad[2] + img_shape_y, pad[0] : pad[0] + img_shape_x + ] + overlap_count_no_padding = overlap_count[ + ..., pad[2] : pad[2] + img_shape_y, pad[0] : pad[0] + img_shape_x + ] + + # Normalize by overlap count + return x_no_padding / overlap_count_no_padding + + +def _cast_type(input: Tensor) -> torch.dtype: + """Return float type based on input tensor type. + + Parameters + ---------- + input : Tensor + Input tensor to determine float type from + + Returns + ------- + torch.dtype + Float type corresponding to input tensor type for int32/64, + otherwise returns original dtype + """ + if input.dtype == torch.int32: + return torch.float32 + elif input.dtype == torch.int64: + return torch.float64 + else: + return input.dtype diff --git a/src/hirad/utils/stochastic_sampler.py b/src/hirad/utils/stochastic_sampler.py index ac5c13ba..198fde43 100644 --- a/src/hirad/utils/stochastic_sampler.py +++ b/src/hirad/utils/stochastic_sampler.py @@ -15,290 +15,23 @@ # limitations under the License. -import math -from typing import Any, Callable, Optional +from typing import Callable, Optional import torch from torch import Tensor - -def image_batching( - input: Tensor, - img_shape_y: int, - img_shape_x: int, - patch_shape_y: int, - patch_shape_x: int, - batch_size: int, - overlap_pix: int, - boundary_pix: int, - input_interp: Optional[Tensor] = None, -) -> Tensor: - """ - Splits a full image into a batch of patched images. - - This function takes a full image and splits it into patches, adding padding where necessary. - It can also concatenate additional interpolated data to each patch if provided. - - Parameters - ---------- - input : Tensor - The input tensor representing the full image with shape (batch_size, channels, img_shape_x, img_shape_y). - img_shape_x : int - The width (x-dimension) of the original full image. - img_shape_y : int - The height (y-dimension) of the original full image. - patch_shape_x : int - The width (x-dimension) of each image patch. - patch_shape_y : int - The height (y-dimension) of each image patch. - batch_size : int - The original batch size before patching. - overlap_pix : int - The number of overlapping pixels between adjacent patches. - boundary_pix : int - The number of pixels to crop as a boundary from each patch. - input_interp : Optional[Tensor], optional - Optional additional data to concatenate to each patch with shape (batch_size, interp_channels, patch_shape_x, patch_shape_y). - By default None. - - Returns - ------- - Tensor - A tensor containing the image patches, with shape (total_patches * batch_size, channels [+ interp_channels], patch_shape_x, patch_shape_y). - """ - patch_num_x = math.ceil(img_shape_x / (patch_shape_x - overlap_pix - boundary_pix)) - patch_num_y = math.ceil(img_shape_y / (patch_shape_y - overlap_pix - boundary_pix)) - padded_shape_x = ( - (patch_shape_x - overlap_pix - boundary_pix) * (patch_num_x - 1) - + patch_shape_x - + boundary_pix - ) - padded_shape_y = ( - (patch_shape_y - overlap_pix - boundary_pix) * (patch_num_y - 1) - + patch_shape_y - + boundary_pix - ) - pad_x_right = padded_shape_x - img_shape_x - boundary_pix - pad_y_right = padded_shape_y - img_shape_y - boundary_pix - input_padded = torch.zeros( - input.shape[0], input.shape[1], padded_shape_y, padded_shape_x - ).to(input.device) - image_padding = torch.nn.ReflectionPad2d( - (boundary_pix, pad_x_right, boundary_pix, pad_y_right) - ).to( - input.device - ) # (padding_left,padding_right,padding_top,padding_bottom) - input_padded = image_padding(input) - patch_num = patch_num_x * patch_num_y - if input_interp is not None: - output = torch.zeros( - patch_num * batch_size, - input.shape[1] + input_interp.shape[1], - patch_shape_y, - patch_shape_x, - ).to(input.device) - else: - output = torch.zeros( - patch_num * batch_size, input.shape[1], patch_shape_y, patch_shape_x - ).to(input.device) - for x_index in range(patch_num_x): - for y_index in range(patch_num_y): - x_start = x_index * (patch_shape_x - overlap_pix - boundary_pix) - y_start = y_index * (patch_shape_y - overlap_pix - boundary_pix) - if input_interp is not None: - output[ - (x_index * patch_num_y + y_index) - * batch_size : (x_index * patch_num_y + y_index + 1) - * batch_size, - ] = torch.cat( - ( - input_padded[ - :, - :, - y_start : y_start + patch_shape_y, - x_start : x_start + patch_shape_x, - ], - input_interp, - ), - dim=1, - ) - else: - output[ - (x_index * patch_num_y + y_index) - * batch_size : (x_index * patch_num_y + y_index + 1) - * batch_size, - ] = input_padded[ - :, - :, - y_start : y_start + patch_shape_y, - x_start : x_start + patch_shape_x, - ] - return output - - -def image_fuse( - input: Tensor, - img_shape_y: int, - img_shape_x: int, - patch_shape_y: int, - patch_shape_x: int, - batch_size: int, - overlap_pix: int, - boundary_pix: int, -) -> Tensor: - """ - Reconstructs a full image from a batch of patched images. - - This function takes a batch of image patches and reconstructs the full image - by stitching the patches together. The function accounts for overlapping and - boundary pixels, ensuring that overlapping areas are averaged. - - Parameters - ---------- - input : Tensor - The input tensor containing the image patches with shape (total_patches * batch_size, channels, patch_shape_x, patch_shape_y). - img_shape_x : int - The width (x-dimension) of the original full image. - img_shape_y : int - The height (y-dimension) of the original full image. - patch_shape_x : int - The width (x-dimension) of each image patch. - patch_shape_y : int - The height (y-dimension) of each image patch. - batch_size : int - The original batch size before patching. - overlap_pix : int - The number of overlapping pixels between adjacent patches. - boundary_pix : int - The number of pixels to crop as a boundary from each patch. - - Returns - ------- - Tensor - The reconstructed full image tensor with shape (batch_size, channels, img_shape_x, img_shape_y). - - """ - patch_num_x = math.ceil(img_shape_x / (patch_shape_x - overlap_pix - boundary_pix)) - patch_num_y = math.ceil(img_shape_y / (patch_shape_y - overlap_pix - boundary_pix)) - padded_shape_x = ( - (patch_shape_x - overlap_pix - boundary_pix) * (patch_num_x - 1) - + patch_shape_x - + boundary_pix - ) - padded_shape_y = ( - (patch_shape_y - overlap_pix - boundary_pix) * (patch_num_y - 1) - + patch_shape_y - + boundary_pix - ) - pad_x_right = padded_shape_x - img_shape_x - boundary_pix - pad_y_right = padded_shape_y - img_shape_y - boundary_pix - residual_x = patch_shape_x - pad_x_right # residual pixels in the last patch - residual_y = patch_shape_y - pad_y_right # residual pixels in the last patch - output = torch.zeros( - batch_size, input.shape[1], img_shape_y, img_shape_x, device=input.device - ) - one_map = torch.ones(1, 1, input.shape[2], input.shape[3], device=input.device) - count_map = torch.zeros( - 1, 1, img_shape_y, img_shape_x, device=input.device - ) # to count the overlapping times - for x_index in range(patch_num_x): - for y_index in range(patch_num_y): - x_start = x_index * (patch_shape_x - overlap_pix - boundary_pix) - y_start = y_index * (patch_shape_y - overlap_pix - boundary_pix) - if (x_index == patch_num_x - 1) and (y_index != patch_num_y - 1): - output[ - :, :, y_start : y_start + patch_shape_y - 2 * boundary_pix, x_start: - ] += input[ - (x_index * patch_num_y + y_index) - * batch_size : (x_index * patch_num_y + y_index + 1) - * batch_size, - :, - boundary_pix : patch_shape_y - boundary_pix, - boundary_pix : residual_x + boundary_pix, - ] - count_map[ - :, :, y_start : y_start + patch_shape_y - 2 * boundary_pix, x_start: - ] += one_map[ - :, - :, - boundary_pix : patch_shape_y - boundary_pix, - boundary_pix : residual_x + boundary_pix, - ] - elif (y_index == patch_num_y - 1) and ((x_index != patch_num_x - 1)): - output[ - :, :, y_start:, x_start : x_start + patch_shape_x - 2 * boundary_pix - ] += input[ - (x_index * patch_num_y + y_index) - * batch_size : (x_index * patch_num_y + y_index + 1) - * batch_size, - :, - boundary_pix : residual_y + boundary_pix, - boundary_pix : patch_shape_x - boundary_pix, - ] - count_map[ - :, :, y_start:, x_start : x_start + patch_shape_x - 2 * boundary_pix - ] += one_map[ - :, - :, - boundary_pix : residual_y + boundary_pix, - boundary_pix : patch_shape_x - boundary_pix, - ] - elif x_index == patch_num_x - 1 and y_index == patch_num_y - 1: - output[:, :, y_start:, x_start:] += input[ - (x_index * patch_num_y + y_index) - * batch_size : (x_index * patch_num_y + y_index + 1) - * batch_size, - :, - boundary_pix : residual_y + boundary_pix, - boundary_pix : residual_x + boundary_pix, - ] - count_map[:, :, y_start:, x_start:] += one_map[ - :, - :, - boundary_pix : residual_y + boundary_pix, - boundary_pix : residual_x + boundary_pix, - ] - else: - output[ - :, - :, - y_start : y_start + patch_shape_y - 2 * boundary_pix, - x_start : x_start + patch_shape_x - 2 * boundary_pix, - ] += input[ - (x_index * patch_num_y + y_index) - * batch_size : (x_index * patch_num_y + y_index + 1) - * batch_size, - :, - boundary_pix : patch_shape_y - boundary_pix, - boundary_pix : patch_shape_x - boundary_pix, - ] - count_map[ - :, - :, - y_start : y_start + patch_shape_y - 2 * boundary_pix, - x_start : x_start + patch_shape_x - 2 * boundary_pix, - ] += one_map[ - :, - :, - boundary_pix : patch_shape_y - boundary_pix, - boundary_pix : patch_shape_x - boundary_pix, - ] - return output / count_map +from hirad.utils.patching import GridPatching2D def stochastic_sampler( - net: Any, - latents: Tensor, - img_lr: Tensor, + net: torch.nn.Module, + latents: torch.Tensor, + img_lr: torch.Tensor, class_labels: Optional[Tensor] = None, randn_like: Callable[[Tensor], Tensor] = torch.randn_like, - img_shape: tuple[int,int] = (448,448), - patch_shape_x: int = 448, - patch_shape_y: int = 448, - overlap_pix: int = 4, - boundary_pix: int = 2, - mean_hr: Optional[Tensor] = None, - lead_time_label: Optional[Tensor] = None, + patching: Optional[GridPatching2D] = None, + mean_hr: Optional[torch.Tensor] = None, + lead_time_label: Optional[torch.Tensor] = None, num_steps: int = 18, sigma_min: float = 0.002, sigma_max: float = 800, @@ -307,33 +40,63 @@ def stochastic_sampler( S_min: float = 0, S_max: float = float("inf"), S_noise: float = 1, -) -> Tensor: +) -> torch.Tensor: """ - Proposed EDM sampler (Algorithm 2) with minor changes to enable super-resolution and patch-based diffusion. + Proposed EDM sampler (Algorithm 2) with minor changes to enable + super-resolution and patch-based diffusion. Parameters ---------- - net : Any - The neural network model that generates denoised images from noisy inputs. + net : torch.nn.Module + The neural network model that generates denoised images from noisy + inputs. + Expected signature: `net(x, x_lr, t_hat, class_labels, + lead_time_label=lead_time_label, embedding_selector=embedding_selector)`, + where: + x (torch.Tensor): Noisy input of shape (batch_size, C_out, H, W) + x_lr (torch.Tensor): Conditioning input of shape (batch_size, C_cond, H, W) + t_hat (torch.Tensor): Noise level of shape (batch_size, 1, 1, 1) or scalar + class_labels (torch.Tensor, optional): Optional class labels + lead_time_label (torch.Tensor, optional): Optional lead time labels + embedding_selector (callable, optional): Function to select + positional embeddings. Used for patch-based diffusion. + Returns: + torch.Tensor: Denoised prediction of shape (batch_size, C_out, H, W) + + Required attributes: + sigma_min (float): Minimum supported noise level for the model + sigma_max (float): Maximum supported noise level for the model + round_sigma (callable): Method to convert sigma values to tensor representation latents : Tensor - The latent variables (e.g., noise) used as the initial input for the sampler. + The latent variables (e.g., noise) used as the initial input for the + sampler. Has shape (batch_size, C_out, img_shape_y, img_shape_x). img_lr : Tensor - Low-resolution input image for conditioning the super-resolution process. + Low-resolution input image for conditioning the super-resolution + process. Must have shape (batch_size, C_lr, img_lr_ shape_y, + img_lr_shape_x). class_labels : Optional[Tensor], optional - Class labels for conditional generation, if required by the model. By default None. + Class labels for conditional generation, if required by the model. By + default None. randn_like : Callable[[Tensor], Tensor] - Function to generate random noise with the same shape as the input tensor. + Function to generate random noise with the same shape as the input + tensor. By default torch.randn_like. - img_shape : int - The height and width of the full image (assumed to be square). By default 448. - patch_shape : int - The height and width of each patch (assumed to be square). By default 448. - overlap_pix : int - Number of overlapping pixels between adjacent patches. By default 4. - boundary_pix : int - Number of pixels to be cropped as a boundary from each patch. By default 2. + patching : Optional[GridPatching2D], optional + A patching utility for patch-based diffusion. Implements methods to + extract patches from an image and batch the patches along `dim=0`. + Should also implement a `fuse` method to reconstruct the original image + from a batch of patches. See + :class:`physicsnemo.utils.patching.GridPatching2D` for details. By + default None, in which case non-patched diffusion is used. mean_hr : Optional[Tensor], optional - Optional tensor containing mean high-resolution images for conditioning. By default None. + Optional tensor containing mean high-resolution images for + conditioning. Must have same height and width as `img_lr`, with shape + (B_hr, C_hr, img_lr_shape_y, img_lr_shape_x) where the batch dimension + B_hr can be either 1, either equal to batch_size, or can be omitted. If + B_hr = 1 or is omitted, `mean_hr` will be expanded to match the shape + of `img_lr`. By default None. + lead_time_label : Optional[Tensor], optional + Optional lead time labels. By default None. num_steps : int Number of time steps for the sampler. By default 18. sigma_min : float @@ -343,7 +106,8 @@ def stochastic_sampler( rho : float Exponent used in the time step discretization. By default 7. S_churn : float - Churn parameter controlling the level of noise added in each step. By default 0. + Churn parameter controlling the level of noise added in each step. By + default 0. S_min : float Minimum time step for applying churn. By default 0. S_max : float @@ -354,20 +118,40 @@ def stochastic_sampler( Returns ------- Tensor - The final denoised image produced by the sampler. + The final denoised image produced by the sampler. Same shape as + `latents`: (batch_size, C_out, img_shape_y, img_shape_x). + + See Also + -------- + :class:`physicsnemo.models.diffusion.EDMPrecondSuperResolution`: A model + wrapper that provides preconditioning for super-resolution diffusion + models and implements the required interface for this sampler. """ # Adjust noise levels based on what's supported by the network. - "Proposed EDM sampler (Algorithm 2) with minor changes to enable super-resolution." + # Proposed EDM sampler (Algorithm 2) with minor changes to enable super-resolution. sigma_min = max(sigma_min, net.sigma_min) sigma_max = min(sigma_max, net.sigma_max) - # if isinstance(img_shape, tuple): - # img_shape_y, img_shape_x = img_shape - # else: - # img_shape_x = img_shape_y = img_shape - img_shape_x, img_shape_y = img_shape - patch_shape_x = min(img_shape_x, patch_shape_x) - patch_shape_y = min(img_shape_y, patch_shape_y) + + if patching is not None and not isinstance(patching, GridPatching2D): + raise ValueError("patching must be an instance of GridPatching2D.") + + # Safety check: if patching is used then img_lr and latents must have same + # height and width, otherwise there is mismatch in the number + # of patches extracted to form the final batch_size. + if patching: + if img_lr.shape[-2:] != latents.shape[-2:]: + raise ValueError( + f"img_lr and latents must have the same height and width, " + f"but found {img_lr.shape[-2:]} vs {latents.shape[-2:]}. " + ) + # img_lr and latents must also have the same batch_size, otherwise mismatch + # when processed by the network + if img_lr.shape[0] != latents.shape[0]: + raise ValueError( + f"img_lr and latents must have the same batch size, but found " + f"{img_lr.shape[0]} vs {latents.shape[0]}." + ) # Time step discretization. step_indices = torch.arange(num_steps, dtype=torch.float64, device=latents.device) @@ -381,46 +165,32 @@ def stochastic_sampler( [net.round_sigma(t_steps), torch.zeros_like(t_steps[:1])] ) # t_N = 0 - b = latents.shape[0] - Nx = torch.arange(img_shape_x) - Ny = torch.arange(img_shape_y) - grid = torch.stack(torch.meshgrid(Ny, Nx, indexing="ij"), dim=0)[ - None, - ].expand(b, -1, -1, -1) + batch_size = img_lr.shape[0] # conditioning = [mean_hr, img_lr, global_lr, pos_embd] - batch_size = img_lr.shape[0] x_lr = img_lr if mean_hr is not None: + if mean_hr.shape[-2:] != img_lr.shape[-2:]: + raise ValueError( + f"mean_hr and img_lr must have the same height and width, " + f"but found {mean_hr.shape[-2:]} vs {img_lr.shape[-2:]}." + ) x_lr = torch.cat((mean_hr.expand(x_lr.shape[0], -1, -1, -1), x_lr), dim=1) - global_index = None # input and position padding + patching - if patch_shape_x != img_shape_x or patch_shape_y != img_shape_y: - input_interp = torch.nn.functional.interpolate( - img_lr, (patch_shape_x, patch_shape_y), mode="bilinear" - ) - x_lr = image_batching( - x_lr, - img_shape_y, - img_shape_x, - patch_shape_x, - patch_shape_y, - batch_size, - overlap_pix, - boundary_pix, - input_interp, - ) - global_index = image_batching( - grid.float(), - img_shape_y, - img_shape_x, - patch_shape_x, - patch_shape_y, - batch_size, - overlap_pix, - boundary_pix, - ).int() + if patching: + # Patched conditioning [x_lr, mean_hr] + # (batch_size * patch_num, C_in + C_out, patch_shape_y, patch_shape_x) + x_lr = patching.apply(input=x_lr, additional_input=img_lr) + + # Function to select the correct positional embedding for each patch + def patch_embedding_selector(emb): + # emb: (N_pe, image_shape_y, image_shape_x) + # return: (batch_size * patch_num, N_pe, patch_shape_y, patch_shape_x) + return patching.apply(emb[None].expand(batch_size, -1, -1, -1)) + + else: + patch_embedding_selector = None # Main sampling loop. x_next = latents.to(torch.float64) * t_steps[0] @@ -432,26 +202,14 @@ def stochastic_sampler( x_hat = x_cur + (t_hat**2 - t_cur**2).sqrt() * S_noise * randn_like(x_cur) - # Euler step. Perform patching operation on score tensor if patch-based generation is used - # denoised = net(x_hat, t_hat, class_labels,lead_time_label=lead_time_label).to(torch.float64) #x_lr + # Euler step. Perform patching operation on score tensor if patch-based + # generation is used denoised = net(x_hat, t_hat, + # class_labels,lead_time_label=lead_time_label).to(torch.float64) - if patch_shape_x != img_shape_x or patch_shape_y != img_shape_y: - x_hat_batch = image_batching( - x_hat, - img_shape_y, - img_shape_x, - patch_shape_x, - patch_shape_y, - batch_size, - overlap_pix, - boundary_pix, - ) - else: - x_hat_batch = x_hat - x_hat_batch = x_hat_batch.to(latents.device) + x_hat_batch = (patching.apply(input=x_hat) if patching else x_hat).to( + latents.device + ) x_lr = x_lr.to(latents.device) - if global_index is not None: - global_index = global_index.to(latents.device) if lead_time_label is not None: denoised = net( @@ -460,7 +218,7 @@ def stochastic_sampler( t_hat, class_labels, lead_time_label=lead_time_label, - global_index=global_index, + embedding_selector=patch_embedding_selector, ).to(torch.float64) else: # print("Sizes") @@ -474,40 +232,24 @@ def stochastic_sampler( x_lr, t_hat, class_labels, - global_index=global_index, + embedding_selector=patch_embedding_selector, ).to(torch.float64) - if patch_shape_x != img_shape_x or patch_shape_y != img_shape_y: + if patching: + # Un-patch the denoised image + # (batch_size, C_out, img_shape_y, img_shape_x) + denoised = patching.fuse(input=denoised, batch_size=batch_size) - denoised = image_fuse( - denoised, - img_shape_y, - img_shape_x, - patch_shape_x, - patch_shape_y, - batch_size, - overlap_pix, - boundary_pix, - ) d_cur = (x_hat - denoised) / t_hat x_next = x_hat + (t_next - t_hat) * d_cur # Apply 2nd order correction. if i < num_steps - 1: - if patch_shape_x != img_shape_x or patch_shape_y != img_shape_y: - x_next_batch = image_batching( - x_next, - img_shape_y, - img_shape_x, - patch_shape_x, - patch_shape_y, - batch_size, - overlap_pix, - boundary_pix, - ) - else: - x_next_batch = x_next - # ask about this fix - x_next_batch = x_next_batch.to(latents.device) + # Patched input + # (batch_size * patch_num, C_out, patch_shape_y, patch_shape_x) + x_next_batch = (patching.apply(input=x_next) if patching else x_next).to( + latents.device + ) + if lead_time_label is not None: denoised = net( x_next_batch, @@ -515,7 +257,7 @@ def stochastic_sampler( t_next, class_labels, lead_time_label=lead_time_label, - global_index=global_index, + embedding_selector=patch_embedding_selector, ).to(torch.float64) else: denoised = net( @@ -523,19 +265,13 @@ def stochastic_sampler( x_lr, t_next, class_labels, - global_index=global_index, + embedding_selector=patch_embedding_selector, ).to(torch.float64) - if patch_shape_x != img_shape_x or patch_shape_y != img_shape_y: - denoised = image_fuse( - denoised, - img_shape_y, - img_shape_x, - patch_shape_x, - patch_shape_y, - batch_size, - overlap_pix, - boundary_pix, - ) + if patching: + # Un-patch the denoised image + # (batch_size, C_out, img_shape_y, img_shape_x) + denoised = patching.fuse(input=denoised, batch_size=batch_size) + d_prime = (x_next - denoised) / t_next x_next = x_hat + (t_next - t_hat) * (0.5 * d_cur + 0.5 * d_prime) return x_next diff --git a/src/hirad/utils/train_helpers.py b/src/hirad/utils/train_helpers.py index d4529ac8..218d6f19 100644 --- a/src/hirad/utils/train_helpers.py +++ b/src/hirad/utils/train_helpers.py @@ -17,6 +17,7 @@ import torch import numpy as np from omegaconf import ListConfig +import warnings def set_patch_shape(img_shape, patch_shape): @@ -26,12 +27,21 @@ def set_patch_shape(img_shape, patch_shape): patch_shape_x = img_shape_x if (patch_shape_y is None) or (patch_shape_y > img_shape_y): patch_shape_y = img_shape_y - if patch_shape_x != img_shape_x or patch_shape_y != img_shape_y: + if patch_shape_x == img_shape_x and patch_shape_y == img_shape_y: + use_patching = False + else: + use_patching = True + if use_patching: if patch_shape_x != patch_shape_y: + warnings.warn( + f"You are using rectangular patches " + f"of shape {(patch_shape_y, patch_shape_x)}, " + f"which are an experimental feature." + ) raise NotImplementedError("Rectangular patch not supported yet") if patch_shape_x % 32 != 0 or patch_shape_y % 32 != 0: raise ValueError("Patch shape needs to be a multiple of 32") - return (img_shape_y, img_shape_x), (patch_shape_y, patch_shape_x) + return use_patching, (img_shape_y, img_shape_x), (patch_shape_y, patch_shape_x) def set_seed(rank): From 5db5e47088b534b5ddc0362439fa6699f8361263 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 26 May 2025 18:22:20 +0200 Subject: [PATCH 048/302] small config fix --- src/hirad/conf/training_era_cosmo_diffusion.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hirad/conf/training_era_cosmo_diffusion.yaml b/src/hirad/conf/training_era_cosmo_diffusion.yaml index 4271e446..0a069e9c 100644 --- a/src/hirad/conf/training_era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training_era_cosmo_diffusion.yaml @@ -15,5 +15,7 @@ defaults: # Model - model/era_cosmo_diffusion + - model_size/normal + # Training - training/era_cosmo_diffusion \ No newline at end of file From e8bd5cd7b3b9ae61f000f5fd676cda0780526f7d Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 27 May 2025 12:09:35 +0200 Subject: [PATCH 049/302] fix generation on distributed --- src/hirad/inference/generate.py | 4 +- src/hirad/training/train.py | 83 ++++++++++++++------------------- 2 files changed, 38 insertions(+), 49 deletions(-) diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index 8fed809f..ec385dcd 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -269,9 +269,11 @@ def generate_fn(image_lr, lead_time_label): ) if dist.rank == 0: + if cfg.generation.inference_mode != "regression": + return torch.cat(gathered_tensors), image_reg[0:1,::] return torch.cat(gathered_tensors) else: - return None + return None, None else: #TODO do this for multi-gpu setting above too if cfg.generation.inference_mode != "regression": diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 794dd553..39b36537 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -227,21 +227,6 @@ def main(cfg: DictConfig) -> None: model.train().requires_grad_(True).to(dist.device) - # param_to_name = {} - # ppp = False - # for name, param in model.named_parameters(): - # pid = id(param) - # if pid in param_to_name: - # print(f"[SHARED PARAM] {name} == {param_to_name[pid]}") - # ppp = True - # break - # else: - # param_to_name[pid] = name - # print(f'There are shared parameters: {ppp}') - - # TODO write summry from rank=0 possibly - # summary(model, input_size=[(1,img_out_channels,*img_shape),(1,img_in_channels,*img_shape),(1,1)]) - if dist.rank==0 and not os.path.exists(os.path.join(checkpoint_dir, 'model_args.json')): with open(os.path.join(checkpoint_dir, f'model_args.json'), 'w') as f: json.dump(model_args, f) @@ -572,6 +557,41 @@ def main(cfg: DictConfig) -> None: cur_nimg += cfg.training.hp.total_batch_size done = cur_nimg >= cfg.training.hp.training_duration + if is_time_for_periodic_task( + cur_nimg, + cfg.training.io.print_progress_freq, + done, + cfg.training.hp.total_batch_size, + dist.rank, + rank_0_only=True, + ): + # Print stats if we crossed the printing threshold with this batch + tick_end_time = time.time() + fields = [] + fields += [f"samples {cur_nimg:<9.1f}"] + fields += [f"training_loss {average_loss:<7.2f}"] + fields += [f"training_loss_running_mean {average_loss_running_mean:<7.2f}"] + fields += [f"learning_rate {current_lr:<7.8f}"] + fields += [f"total_sec {(tick_end_time - start_time):<7.1f}"] + fields += [f"sec_per_tick {(tick_end_time - tick_start_time):<7.1f}"] + fields += [ + f"sec_per_sample {((tick_end_time - tick_start_time) / (cur_nimg - tick_start_nimg)):<7.2f}" + ] + fields += [ + f"cpu_mem_gb {(psutil.Process(os.getpid()).memory_info().rss / 2**30):<6.2f}" + ] + if torch.cuda.is_available(): + fields += [ + f"peak_gpu_mem_gb {(torch.cuda.max_memory_allocated(dist.device) / 2**30):<6.2f}" + ] + fields += [ + f"peak_gpu_mem_reserved_gb {(torch.cuda.max_memory_reserved(dist.device) / 2**30):<6.2f}" + ] + torch.cuda.reset_peak_memory_stats() + logger0.info(" ".join(fields)) + logger0.info(img_clean.shape) + logger0.info(img_lr.shape) + with nvtx.annotate("validation", color="red"): # Validation if validation_dataset_iterator is not None: @@ -671,39 +691,6 @@ def main(cfg: DictConfig) -> None: "validation_loss", average_valid_loss, cur_nimg ) - if is_time_for_periodic_task( - cur_nimg, - cfg.training.io.print_progress_freq, - done, - cfg.training.hp.total_batch_size, - dist.rank, - rank_0_only=True, - ): - # Print stats if we crossed the printing threshold with this batch - tick_end_time = time.time() - fields = [] - fields += [f"samples {cur_nimg:<9.1f}"] - fields += [f"training_loss {average_loss:<7.2f}"] - fields += [f"training_loss_running_mean {average_loss_running_mean:<7.2f}"] - fields += [f"learning_rate {current_lr:<7.8f}"] - fields += [f"total_sec {(tick_end_time - start_time):<7.1f}"] - fields += [f"sec_per_tick {(tick_end_time - tick_start_time):<7.1f}"] - fields += [ - f"sec_per_sample {((tick_end_time - tick_start_time) / (cur_nimg - tick_start_nimg)):<7.2f}" - ] - fields += [ - f"cpu_mem_gb {(psutil.Process(os.getpid()).memory_info().rss / 2**30):<6.2f}" - ] - if torch.cuda.is_available(): - fields += [ - f"peak_gpu_mem_gb {(torch.cuda.max_memory_allocated(dist.device) / 2**30):<6.2f}" - ] - fields += [ - f"peak_gpu_mem_reserved_gb {(torch.cuda.max_memory_reserved(dist.device) / 2**30):<6.2f}" - ] - torch.cuda.reset_peak_memory_stats() - logger0.info(" ".join(fields)) - # Save checkpoints if dist.world_size > 1: From 692dfe25d91af282d4f0b72f3a05237720d1fb76 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 27 May 2025 12:18:08 +0200 Subject: [PATCH 050/302] delete unnecessary logging --- src/hirad/training/train.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 39b36537..3a2fe2ea 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -589,8 +589,6 @@ def main(cfg: DictConfig) -> None: ] torch.cuda.reset_peak_memory_stats() logger0.info(" ".join(fields)) - logger0.info(img_clean.shape) - logger0.info(img_lr.shape) with nvtx.annotate("validation", color="red"): # Validation From f16b71d5fae2e528fa7fd72171ec6f069262dbd1 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Thu, 5 Jun 2025 15:23:40 +0200 Subject: [PATCH 051/302] add patch_num fix to loss calcualtion --- src/hirad/conf/dataset/era_cosmo.yaml | 3 +- .../conf/training/era_cosmo_diffusion.yaml | 18 ++++---- .../conf/training/era_cosmo_regression.yaml | 17 ++++--- src/hirad/datasets/dataset.py | 2 +- src/hirad/inference/generate.py | 46 +++++++++++++------ src/hirad/training/train.py | 13 ++++-- 6 files changed, 64 insertions(+), 35 deletions(-) diff --git a/src/hirad/conf/dataset/era_cosmo.yaml b/src/hirad/conf/dataset/era_cosmo.yaml index 63d7361a..b1e21e6f 100644 --- a/src/hirad/conf/dataset/era_cosmo.yaml +++ b/src/hirad/conf/dataset/era_cosmo.yaml @@ -1,2 +1,3 @@ type: era5_cosmo -dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/trim_19_overfit \ No newline at end of file +dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/trim_19_full +validation_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/trim_19_full/validation \ No newline at end of file diff --git a/src/hirad/conf/training/era_cosmo_diffusion.yaml b/src/hirad/conf/training/era_cosmo_diffusion.yaml index f8d19e66..07cbb03f 100644 --- a/src/hirad/conf/training/era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training/era_cosmo_diffusion.yaml @@ -1,8 +1,8 @@ # Hyperparameters hp: - training_duration: 16 + training_duration: 5000000 # Training duration based on the number of processed samples - total_batch_size: 4 + total_batch_size: 128 # Total batch size batch_size_per_gpu: "auto" # Batch size per GPU @@ -14,28 +14,30 @@ hp: # LR decay rate lr_rampup: 0 # Rampup for learning rate, in number of samples + lr_decay_rate: 5e5 + # Learning rate decay threshold in number of samples, applied every lr_decay_rate samples. # Performance perf: fp_optimizations: amp-bf16 # Floating point mode, one of ["fp32", "fp16", "amp-fp16", "amp-bf16"] # "amp-{fp16,bf16}" activates Automatic Mixed Precision (AMP) with {float16,bfloat16} - dataloader_workers: 8 + dataloader_workers: 10 # DataLoader worker processes songunet_checkpoint_level: 0 # 0 means no checkpointing # Gradient checkpointing level, value is number of layers to checkpoint # I/O io: - regression_checkpoint_path: /iopsstor/scratch/cscs/pstamenk/outputs/regression_overfit/checkpoints_regression + regression_checkpoint_path: /capstor/scratch/cscs/boeschf/HiRAD-Gen/outputs_full/regression/checkpoints_regression/ # Where to load the regression checkpoint - print_progress_freq: 128 + print_progress_freq: 5000 # How often to print progress - save_checkpoint_freq: 5000 + save_checkpoint_freq: 250000 # How often to save the checkpoints, measured in number of processed samples - validation_freq: 5000 + validation_freq: 25000 # how often to record the validation loss, measured in number of processed samples - validation_steps: 10 + validation_steps: 4 # how many loss evaluations are used to compute the validation loss per checkpoint # how many loss evaluations are used to compute the validation loss per checkpoint checkpoint_dir: . \ No newline at end of file diff --git a/src/hirad/conf/training/era_cosmo_regression.yaml b/src/hirad/conf/training/era_cosmo_regression.yaml index 76bdc4eb..98c6c249 100644 --- a/src/hirad/conf/training/era_cosmo_regression.yaml +++ b/src/hirad/conf/training/era_cosmo_regression.yaml @@ -1,13 +1,12 @@ # Hyperparameters hp: - training_duration: 8 + training_duration: 500000 # Training duration based on the number of processed samples - total_batch_size: 4 + total_batch_size: 64 # Total batch size batch_size_per_gpu: "auto" # Batch size per GPU - lr: 0.001 - #0.0002 + lr: 0.0002 # Learning rate grad_clip_threshold: null # no gradient clipping for defualt non-patch-based training @@ -15,22 +14,26 @@ hp: # LR decay rate lr_rampup: 0 # Rampup for learning rate, in number of samples + lr_decay_rate: 5e5 + # Learning rate decay threshold in number of samples, applied every lr_decay_rate samples. # Performance perf: fp_optimizations: amp-bf16 # Floating point mode, one of ["fp32", "fp16", "amp-fp16", "amp-bf16"] # "amp-{fp16,bf16}" activates Automatic Mixed Precision (AMP) with {float16,bfloat16} - dataloader_workers: 4 + dataloader_workers: 10 # DataLoader worker processes songunet_checkpoint_level: 0 # 0 means no checkpointing # Gradient checkpointing level, value is number of layers to checkpoint + # torch_compile: True + # use_apex_gn: True # I/O io: - print_progress_freq: 128 + print_progress_freq: 1024 # How often to print progress - save_checkpoint_freq: 5000 + save_checkpoint_freq: 25000 # How often to save the checkpoints, measured in number of processed samples validation_freq: 5000 # how often to record the validation loss, measured in number of processed samples diff --git a/src/hirad/datasets/dataset.py b/src/hirad/datasets/dataset.py index 7ba88330..c09a4f23 100644 --- a/src/hirad/datasets/dataset.py +++ b/src/hirad/datasets/dataset.py @@ -58,7 +58,7 @@ def init_train_valid_datasets_from_config( """ config = copy.deepcopy(dataset_cfg) - if 'validation_path': + if 'validation_path' in config: del config['validation_path'] (dataset, dataset_iter) = init_dataset_from_config( config, dataloader_cfg, batch_size=batch_size, seed=seed diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index ec385dcd..3cc1eb41 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -271,7 +271,7 @@ def generate_fn(image_lr, lead_time_label): if dist.rank == 0: if cfg.generation.inference_mode != "regression": return torch.cat(gathered_tensors), image_reg[0:1,::] - return torch.cat(gathered_tensors) + return torch.cat(gathered_tensors), None else: return None, None else: @@ -408,16 +408,31 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, target = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze()),1) #.reshape(len(output_channels),-1) prediction = np.flip(dataset.denormalize_output(image_pred[-1,::].squeeze()),1) #.reshape(len(output_channels),-1) baseline = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze()),1)# .reshape(len(input_channels),-1) + if mean_pred is not None: + mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze()),1) #.reshape(len(output_channels),-1) + freqs = {} power = {} for idx, channel in enumerate(output_channels): input_channel_idx = input_channels.index(channel) + + _plot_projection(longitudes, latitudes, target[idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-target.jpg')) + _plot_projection(longitudes, latitudes, prediction[idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-prediction.jpg')) + _plot_projection(longitudes, latitudes, baseline[input_channel_idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-input.jpg')) + if mean_pred is not None: + _plot_projection(longitudes, latitudes, mean_pred[idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-mean_prediction.jpg')) + _, baseline_errors = compute_mae(baseline[input_channel_idx,:,:], target[idx,:,:]) _, prediction_errors = compute_mae(prediction[idx,:,:], target[idx,:,:]) + if mean_pred is not None: + _, mean_prediction_errors = compute_mae(mean_pred[idx,:,:], target[idx,:,:]) + plot_error_projection(baseline_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path, f'{time_step}-{channel.name}-baseline-error.jpg')) plot_error_projection(prediction_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path, f'{time_step}-{channel.name}-prediction-error.jpg')) + if mean_pred is not None: + plot_error_projection(mean_prediction_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path, f'{time_step}-{channel.name}-mean-prediction-error.jpg')) b_freq, b_power = average_power_spectrum(baseline[input_channel_idx,:,:].squeeze(), 2.0) freqs['baseline'] = b_freq @@ -429,6 +444,10 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, p_freq, p_power = average_power_spectrum(prediction[idx,:,:].squeeze(), 2.0) freqs['prediction'] = p_freq power['prediction'] = p_power + if mean_pred is not None: + mp_freq, mp_power = average_power_spectrum(mean_pred[idx,:,:].squeeze(), 2.0) + freqs['mean_prediction'] = mp_freq + power['mean_prediction'] = mp_power plot_power_spectra(freqs, power, channel.name, os.path.join(output_path, f'{time_step}-{channel.name}-spectra.jpg')) # def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): @@ -459,19 +478,18 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, # if mean_pred is not None: # _plot_projection(longitudes,latitudes,mean_pred[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-mean-pred.jpg')) -# def _plot_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, cmap=None, vmin = None, vmax = None): +def _plot_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, cmap=None, vmin = None, vmax = None): -# """Plot observed or interpolated data in a scatter plot.""" -# # TODO: Refactor this somehow, it's not really generalizing well across variables. -# fig = plt.figure() -# fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) -# p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) -# ax.coastlines() -# ax.gridlines(draw_labels=True) -# plt.colorbar(p, label="K", orientation="horizontal") -# plt.savefig(filename) -# plt.close('all') + """Plot observed or interpolated data in a scatter plot.""" + # TODO: Refactor this somehow, it's not really generalizing well across variables. + fig = plt.figure() + fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) + p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) + ax.coastlines() + ax.gridlines(draw_labels=True) + plt.colorbar(p, label="K", orientation="horizontal") + plt.savefig(filename) + plt.close('all') if __name__ == "__main__": - main() - \ No newline at end of file + main() \ No newline at end of file diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 3a2fe2ea..1b43a6e2 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -494,8 +494,12 @@ def main(cfg: DictConfig) -> None: ): loss = loss_fn(**loss_fn_kwargs) - loss = loss.sum() / batch_size_per_gpu - loss_accum += loss / num_accumulation_rounds + loss_accum += ( + loss + / num_accumulation_rounds + / len(patch_nums_iter) + ) + loss_accum += loss / num_accumulation_rounds with nvtx.annotate(f"loss backward", color="yellow"): loss.backward() @@ -544,7 +548,7 @@ def main(cfg: DictConfig) -> None: if lr_rampup > 0: g["lr"] = cfg.training.hp.lr * min(cur_nimg / lr_rampup, 1) if cur_nimg >= lr_rampup: - g["lr"] *= cfg.training.hp.lr_decay ** ((cur_nimg - lr_rampup) // 5e6) + g["lr"] *= cfg.training.hp.lr_decay ** ((cur_nimg - lr_rampup) // cfg.training.hp.lr_decay_rate) current_lr = g["lr"] if dist.rank == 0: writer.add_scalar("learning_rate", current_lr, cur_nimg) @@ -658,7 +662,7 @@ def main(cfg: DictConfig) -> None: for patch_num_per_iter in patch_nums_iter: if patching is not None: patching.set_patch_num(patch_num_per_iter) - loss_fn_kwargs.update( + loss_valid_kwargs.update( {"patching": patching} ) with torch.autocast( @@ -674,6 +678,7 @@ def main(cfg: DictConfig) -> None: valid_loss_accum += ( loss_valid / cfg.training.io.validation_steps + / len(patch_nums_iter) ) valid_loss_sum = torch.tensor( [valid_loss_accum], device=dist.device From 4337ba5fbd095baf4f0613b2cd94079321db7e52 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Thu, 12 Jun 2025 10:19:54 +0200 Subject: [PATCH 052/302] add log scale for precipitation plotting --- src/hirad/eval/__init__.py | 2 ++ src/hirad/inference/generate.py | 45 +++++++++++++-------------------- 2 files changed, 20 insertions(+), 27 deletions(-) create mode 100644 src/hirad/eval/__init__.py diff --git a/src/hirad/eval/__init__.py b/src/hirad/eval/__init__.py new file mode 100644 index 00000000..a3228eec --- /dev/null +++ b/src/hirad/eval/__init__.py @@ -0,0 +1,2 @@ +from .metrics import compute_mae, average_power_spectrum +from .plotting import plot_error_projection, plot_power_spectra \ No newline at end of file diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index 3cc1eb41..35f856f3 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -396,6 +396,7 @@ def elapsed_time(self, _): f.close() logger0.info("Generation Completed.") + def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): os.makedirs(output_path, exist_ok=True) @@ -417,6 +418,13 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, for idx, channel in enumerate(output_channels): input_channel_idx = input_channels.index(channel) + if channel.name=="tp": + target[idx,::] = prepare_precipitaiton(target[idx,:,:]) + prediction[idx,::] = prepare_precipitaiton(prediction[idx,:,:]) + baseline[input_channel_idx,:,:] = prepare_precipitaiton(baseline[input_channel_idx]) + if mean_pred is not None: + mean_pred[idx,::] = prepare_precipitaiton(mean_pred[idx,::]) + _plot_projection(longitudes, latitudes, target[idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-target.jpg')) _plot_projection(longitudes, latitudes, prediction[idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-prediction.jpg')) _plot_projection(longitudes, latitudes, baseline[input_channel_idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-input.jpg')) @@ -450,33 +458,16 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, power['mean_prediction'] = mp_power plot_power_spectra(freqs, power, channel.name, os.path.join(output_path, f'{time_step}-{channel.name}-spectra.jpg')) -# def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): -# longitudes = dataset.longitude() -# latitudes = dataset.latitude() -# input_channels = dataset.input_channels() -# output_channels = dataset.output_channels() -# image_pred = image_pred.numpy() -# image_pred_final = np.flip(dataset.denormalize_output(image_pred[-1,::].squeeze()),1).reshape(len(output_channels),-1) -# if image_pred.shape[0]>1: -# image_pred_mean = np.flip(dataset.denormalize_output(image_pred.mean(axis=0)),1).reshape(len(output_channels),-1) -# image_pred_first_step = np.flip(dataset.denormalize_output(image_pred[0,::].squeeze()),1).reshape(len(output_channels),-1) -# image_pred_mid_step = np.flip(dataset.denormalize_output(image_pred[image_pred.shape[0]//2,::].squeeze()),1).reshape(len(output_channels),-1) -# image_hr = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze().numpy()),1).reshape(len(output_channels),-1) -# image_lr = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze().numpy()),1).reshape(len(input_channels),-1) -# if mean_pred is not None: -# mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze().numpy()),1).reshape(len(output_channels),-1) -# os.makedirs(output_path, exist_ok=True) -# for idx, channel in enumerate(output_channels): -# input_channel_idx = input_channels.index(channel) -# _plot_projection(longitudes,latitudes,image_lr[input_channel_idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-lr.jpg')) -# _plot_projection(longitudes,latitudes,image_hr[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr.jpg')) -# _plot_projection(longitudes,latitudes,image_pred_final[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred.jpg')) -# if image_pred.shape[0]>1: -# _plot_projection(longitudes,latitudes,image_pred_mean[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred-mean.jpg')) -# _plot_projection(longitudes,latitudes,image_pred_first_step[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred-0.jpg')) -# _plot_projection(longitudes,latitudes,image_pred_mid_step[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-hr-pred-mid.jpg')) -# if mean_pred is not None: -# _plot_projection(longitudes,latitudes,mean_pred[idx,:],os.path.join(output_path,f'{time_step}-{channel.name}-mean-pred.jpg')) + +def prepare_precipitaiton(precip_array): + precip_array = np.clip(precip_array, 0, None) + epsilon = 1e-2 + precip_array = precip_array + epsilon + precip_array = np.log(precip_array) + # log_min, log_max = precip_array.min(), precip_array.max() + # precip_array = (precip_array-log_min)/(log_max-log_min) + return precip_array + def _plot_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, cmap=None, vmin = None, vmax = None): From 2ebf7e14bf1e21bd402fa20cce8af3189b447965 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 13 Jun 2025 12:52:54 +0200 Subject: [PATCH 053/302] add missing loss sum --- src/hirad/training/train.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 1b43a6e2..12b6942c 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -11,7 +11,7 @@ from hydra.utils import to_absolute_path from torch.utils.tensorboard import SummaryWriter from torch.nn.parallel import DistributedDataParallel -from torchinfo import summary +# from torchinfo import summary from hirad.distributed import DistributedManager from hirad.utils.console import PythonLogger, RankZeroLoggingWrapper @@ -494,6 +494,7 @@ def main(cfg: DictConfig) -> None: ): loss = loss_fn(**loss_fn_kwargs) + loss = loss.sum() / batch_size_per_gpu loss_accum += ( loss / num_accumulation_rounds From c63ca0659772fa1d87d79ba24170048d3a15ed23 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 13 Jun 2025 14:37:32 +0200 Subject: [PATCH 054/302] fix small things for merging --- src/hirad/conf/model_size/mini.yaml | 2 ++ src/hirad/conf/model_size/normal.yaml | 1 + src/hirad/conf/sampler/deterministic.yaml | 8 ++++++-- src/hirad/conf/sampler/stochastic.yaml | 6 ++++-- src/hirad/datasets/__init__.py | 2 +- src/hirad/distributed/__init__.py | 2 +- src/hirad/eval/__init__.py | 2 +- src/hirad/losses/__init__.py | 2 +- 8 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/hirad/conf/model_size/mini.yaml b/src/hirad/conf/model_size/mini.yaml index 2eb8f8ab..a5847f58 100644 --- a/src/hirad/conf/model_size/mini.yaml +++ b/src/hirad/conf/model_size/mini.yaml @@ -16,6 +16,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +# Smaller model size (10 million parameters), lower learning capacity, should be used only for testing or for small datasets and small grid size. +# Learning capacity is reduced and final models are not recommmended to be used in production. model_args: # Base multiplier for the number of channels across the network. diff --git a/src/hirad/conf/model_size/normal.yaml b/src/hirad/conf/model_size/normal.yaml index b81fe153..96c29fbc 100644 --- a/src/hirad/conf/model_size/normal.yaml +++ b/src/hirad/conf/model_size/normal.yaml @@ -16,6 +16,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +# Normal model size (80 million parameters) should be used by default for full datasets and higher grid size. model_args: # Base multiplier for the number of channels across the network. diff --git a/src/hirad/conf/sampler/deterministic.yaml b/src/hirad/conf/sampler/deterministic.yaml index 35bc0f63..856906b6 100644 --- a/src/hirad/conf/sampler/deterministic.yaml +++ b/src/hirad/conf/sampler/deterministic.yaml @@ -1,4 +1,8 @@ +# Deterministic sampler is generally faster than stochastic sampler, but should theoretically produce worse results. +# Deterministic sampler is not implemented correctly in this codebase and shouldn't be used. + type: deterministic -num_steps: 9 +num_steps: 9 # Number of denoising steps -solver: euler \ No newline at end of file +solver: euler + # ODE solver type: euler is the simplest solver \ No newline at end of file diff --git a/src/hirad/conf/sampler/stochastic.yaml b/src/hirad/conf/sampler/stochastic.yaml index 2481cd37..808270c9 100644 --- a/src/hirad/conf/sampler/stochastic.yaml +++ b/src/hirad/conf/sampler/stochastic.yaml @@ -1,3 +1,5 @@ +# Stochastic sampler is slower, but should give better results than deterministic sampler. + type: stochastic -# boundary_pix: 2 -# overlap_pix: 4 \ No newline at end of file +# boundary_pix: 2 # set for patched diffusion +# overlap_pix: 4 # set for patched diffusion \ No newline at end of file diff --git a/src/hirad/datasets/__init__.py b/src/hirad/datasets/__init__.py index 706284e6..53e791e5 100644 --- a/src/hirad/datasets/__init__.py +++ b/src/hirad/datasets/__init__.py @@ -1,3 +1,3 @@ from .dataset import init_train_valid_datasets_from_config, init_dataset_from_config from .era5_cosmo import ERA5_COSMO -from .base import DownscalingDataset \ No newline at end of file +from .base import DownscalingDataset diff --git a/src/hirad/distributed/__init__.py b/src/hirad/distributed/__init__.py index 0da01f3c..d953a85c 100644 --- a/src/hirad/distributed/__init__.py +++ b/src/hirad/distributed/__init__.py @@ -1 +1 @@ -from .manager import DistributedManager \ No newline at end of file +from .manager import DistributedManager diff --git a/src/hirad/eval/__init__.py b/src/hirad/eval/__init__.py index a3228eec..13d9eb37 100644 --- a/src/hirad/eval/__init__.py +++ b/src/hirad/eval/__init__.py @@ -1,2 +1,2 @@ from .metrics import compute_mae, average_power_spectrum -from .plotting import plot_error_projection, plot_power_spectra \ No newline at end of file +from .plotting import plot_error_projection, plot_power_spectra diff --git a/src/hirad/losses/__init__.py b/src/hirad/losses/__init__.py index 868ffdf2..1494a54c 100644 --- a/src/hirad/losses/__init__.py +++ b/src/hirad/losses/__init__.py @@ -1 +1 @@ -from .loss import ResidualLoss, RegressionLoss, RegressionLossCE \ No newline at end of file +from .loss import ResidualLoss, RegressionLoss, RegressionLossCE From 4a3a352b7df0c1dfd7fa5c9722fd545fd9385a76 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 16 Jun 2025 14:13:06 +0200 Subject: [PATCH 055/302] add crps code --- src/hirad/eval/crps.py | 348 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 src/hirad/eval/crps.py diff --git a/src/hirad/eval/crps.py b/src/hirad/eval/crps.py new file mode 100644 index 00000000..2ccdb424 --- /dev/null +++ b/src/hirad/eval/crps.py @@ -0,0 +1,348 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Union + +import numpy as np +import torch + +from .histogram import cdf as cdf_function + +Tensor = torch.Tensor + + +@torch.jit.script +def _kernel_crps_implementation(pred: Tensor, obs: Tensor, biased: bool) -> Tensor: + """An O(m log m) implementation of the kernel CRPS formulas""" + skill = torch.abs(pred - obs[..., None]).mean(-1) + pred, _ = torch.sort(pred) + + # derivation of fast implementation of spread-portion of CRPS formula when x is sorted + # sum_(i,j=1)^m |x_i - x_j| = sum_(i j) |x_i - x_j| + # = 2 sum_(i <= j) |x_i -x_j| + # = 2 sum_(i <= j) (x_j - x_i) + # = 2 sum_(i <= j) x_j - 2 sum_(i <= j) x_i + # = 2 sum_(j=1)^m j x_j - 2 sum (m - i + 1) x_i + # = 2 sum_(i=1)^m (2i - m - 1) x_i + m = pred.size(-1) + i = torch.arange(1, m + 1, device=pred.device, dtype=pred.dtype) + denom = m * m if biased else m * (m - 1) + factor = (2 * i - m - 1) / denom + spread = torch.sum(factor * pred, dim=-1) + return skill - spread + + +def kcrps(pred: Tensor, obs: Tensor, dim: int = 0, biased: bool = True): + """Estimate the CRPS from a finite ensemble + + Computes the local Continuous Ranked Probability Score (CRPS) by using + the kernel version of CRPS. The cost is O(m log m). + + Creates a map of CRPS and does not accumulate over lat/lon regions. + Approximates: + .. math:: + CRPS(X, y) = E[X - y] - 0.5 E[X-X'] + + with + .. math:: + sum_i=1^m |X_i - y| / m - 1/(2m^2) sum_i,j=1^m |x_i - x_j| + + Parameters + ---------- + pred : Tensor + Tensor containing the ensemble predictions. The ensemble dimension + is assumed to be the leading dimension unless 'dim' is specified. + obs : Union[Tensor, np.ndarray] + Tensor or array containing an observation over which the CRPS is computed + with respect to. + dim : int, optional + The dimension over which to compute the CRPS, assumed to be 0. + biased : + When False, uses the unbiased estimators described in (Zamo and Naveau, 2018):: + + E|X-y|/m - 1/(2m(m-1)) sum_(i,j=1)|x_i - x_j| + + Unlike ``crps`` this is fair for finite ensembles. Non-fair ``crps`` favors less + dispersive ensembles since it is biased high by E|X- X'|/ m where m is the + ensemble size. + + Returns + ------- + Tensor + Map of CRPS + """ + pred = torch.movedim(pred, dim, -1) + return _kernel_crps_implementation(pred, obs, biased=biased) + + +def _crps_gaussian(mean: Tensor, std: Tensor, obs: Union[Tensor, np.ndarray]) -> Tensor: + """ + Computes the local Continuous Ranked Probability Score (CRPS) + using assuming that the forecast distribution is normal. + + Creates a map of CRPS and does not accumulate over lat/lon regions. + + Computes: + + .. math:: + + CRPS(mean, std, y) = std * [ \\frac{1}{\\sqrt{\\pi}}} - 2 \\phi ( \\frac{x-mean}{std} ) - + ( \\frac{x-mean}{std} ) * (2 \\Phi(\\frac{x-mean}{std}) - 1) ] + + where \\phi and \\Phi are the normal gaussian pdf/cdf respectively. + + Parameters + ---------- + mean : Tensor + Tensor of mean of forecast distribution. + std : Tensor + Tensor of standard deviation of forecast distribution. + obs : Union[Tensor, np.ndarray] + Tensor or array containing an observation over which the CRPS is computed + with respect to. Broadcasting dimensions must be compatible with the non-zeroth + dimensions of bins and cdf. + + Returns + ------- + Tensor + Map of CRPS + """ + if isinstance(obs, np.ndarray): + obs = torch.from_numpy(obs).to(mean.device) + # Check shape compatibility + if mean.shape != std.shape: + raise ValueError( + "Mean and standard deviation must have" + + "compatible shapes but found" + + str(mean.shape) + + " and " + + str(std.shape) + + "." + ) + if mean.shape != obs.shape: + raise ValueError( + "Mean and obs must have" + + "compatible shapes but found" + + str(mean.shape) + + " and " + + str(obs.shape) + + "." + ) + + d = (obs - mean) / std + phi = torch.exp(-0.5 * d**2) / torch.sqrt(torch.as_tensor(2 * torch.pi)) + + # Note, simplified expression below is not exactly Gaussian CDF + Phi = torch.erf(d / torch.sqrt(torch.as_tensor(2.0))) + + return std * (2 * phi + d * Phi - 1.0 / torch.sqrt(torch.as_tensor(torch.pi))) + + +def _crps_from_cdf( + bin_edges: Tensor, cdf: Tensor, obs: Union[Tensor, np.ndarray] +) -> Tensor: + """Computes the local Continuous Ranked Probability Score (CRPS) + using a cumulative distribution function. + + Creates a map of CRPS and does not accumulate over lat/lon regions. + + Computes: + + .. math:: + + CRPS(X, y) = \\int[ (F(x) - 1[x - y])^2 ] dx + + where F is the empirical cdf of X. + + Parameters + ---------- + bins_edges : Tensor + Tensor [N+1, ...] containing bin edges. The leading dimension must represent the + N+1 bin edges. + cdf : Tensor + Tensor [N, ...] containing a cdf, defined over bins. The non-zeroth dimensions + of bins and cdf must be compatible. + obs : Union[Tensor, np.ndarray] + Tensor or array containing an observation over which the CRPS is computed + with respect to. Broadcasting dimensions must be compatible with the non-zeroth + dimensions of bins and cdf. + + Returns + ------- + Tensor + Map of CRPS + """ + if isinstance(obs, np.ndarray): + obs = torch.from_numpy(obs).to(cdf.device) + if bin_edges.shape[1:] != cdf.shape[1:]: + raise ValueError( + "Expected bins and cdf to have compatible non-zeroth dimensions but have shapes" + + str(bin_edges.shape[1:]) + + " and " + + str(cdf.shape[1:]) + + "." + ) + if bin_edges.shape[1:] != obs.shape: + raise ValueError( + "Expected bins and observations to have compatible broadcasting dimensions but have shapes" + + str(bin_edges.shape[1:]) + + " and " + + str(obs.shape) + + "." + ) + if bin_edges.shape[0] != cdf.shape[0] + 1: + raise ValueError( + "Expected zeroth dimension of cdf to be equal to the zeroth dimension of bins + 1 but have shapes" + + str(bin_edges.shape[0]) + + " and " + + str(cdf.shape[0]) + + "+1." + ) + dbins = bin_edges[1, ...] - bin_edges[0, ...] + bin_mids = 0.5 * (bin_edges[1:] + bin_edges[:-1]) + obs = torch.ge(bin_mids, obs).int() + return torch.sum(torch.abs(cdf - obs) ** 2 * dbins, dim=0) + + +def _crps_from_counts( + bin_edges: Tensor, counts: Tensor, obs: Union[Tensor, np.ndarray] +) -> Tensor: + """Computes the local Continuous Ranked Probability Score (CRPS) + using a histogram of counts. + + Creates a map of CRPS and does not accumulate over lat/lon regions. + + Computes: + + .. math:: + + CRPS(X, y) = int[ (F(x) - 1[x - y])^2 ] dx + + where F is the empirical cdf of X. + + Parameters + ---------- + bins_edges : Tensor + Tensor [N+1, ...] containing bin edges. The leading dimension must represent the + N+1 bin edges. + counts : Tensor + Tensor [N, ...] containing counts, defined over bins. The non-zeroth dimensions + of bins and counts must be compatible. + obs : Union[Tensor, np.ndarray] + Tensor or array containing an observation over which the CRPS is computed + with respect to. Broadcasting dimensions must be compatible with the non-zeroth + dimensions of bins and counts. + + Returns + ------- + Tensor + Map of CRPS + """ + if isinstance(obs, np.ndarray): + obs = torch.from_numpy(obs).to(counts.device) + if bin_edges.shape[1:] != counts.shape[1:]: + raise ValueError( + "Expected bins and cdf to have compatible non-zeroth dimensions but have shapes" + + str(bin_edges.shape[1:]) + + " and " + + str(counts.shape[1:]) + + "." + ) + if bin_edges.shape[1:] != obs.shape: + raise ValueError( + "Expected bins and observations to have compatible broadcasting dimensions but have shapes" + + str(bin_edges.shape[1:]) + + " and " + + str(obs.shape) + + "." + ) + if bin_edges.shape[0] != counts.shape[0] + 1: + raise ValueError( + "Expected zeroth dimension of cdf to be equal to the zeroth dimension of bins + 1 but have shapes" + + str(bin_edges.shape[0]) + + " and " + + str(counts.shape[0]) + + "+1." + ) + cdf_hat = torch.cumsum(counts / torch.sum(counts, dim=0), dim=0) + return _crps_from_cdf(bin_edges, cdf_hat, obs) + + +def crps( + pred: Tensor, obs: Union[Tensor, np.ndarray], dim: int = 0, method: str = "kernel" +) -> Tensor: + """ + Computes the local Continuous Ranked Probability Score (CRPS). + + Creates a map of CRPS and does not accumulate over any other dimensions (e.g., lat/lon regions). + + Parameters + ---------- + pred : Tensor + Tensor containing the ensemble predictions. + obs : Union[Tensor, np.ndarray] + Tensor or array containing an observation over which the CRPS is computed + with respect to. + dim : int, Optional + Dimension with which to calculate the CRPS over, the ensemble dimension. + Assumed to be zero. + method: str, Optional + The method to calculate the crps. Can either be "kernel", "sort" or "histogram". + + The "kernel" method implements + .. math:: + CRPS(x, y) = E[X-y] - 0.5*E[X-X'] + + This method scales as O(n^2) where n is the number of ensemble members and + can potentially induce large memory consumption as the algorithm attempts + to vectorize over this O(n^2) operation. + + The "sort" method compute the exact CRPS using the CDF method + .. math:: + CRPS(x, y) = int [F(x) - 1(x-y)]^2 dx + + where F is the empirical CDF and 1(x-y) = 1 if x > y. + + This method is more memory efficient than the kernel method, and uses O(n + log n) compute instead of O(n^2), where n is the number of ensemble members. + + The "histogram" method computes an approximate CRPS using the CDF method + .. math:: + CRPS(x, y) = int [F(x) - 1(x-y)]^2 dx + + where F is the empirical CDF, estimated via a histogram of the samples. The + number of bins used is the lesser of the square root of the number of samples + and 100. For more control over the implementation of this method consider using + `cdf_function` to construct a cdf and `_crps_from_cdf` to compute CRPS. + + Returns + ------- + Tensor + Map of CRPS + """ + if method not in ["kernel", "sort", "histogram"]: + raise ValueError("Method must either be 'kernel', 'sort' or 'histogram'.") + + n = pred.shape[dim] + obs = torch.as_tensor(obs, device=pred.device, dtype=pred.dtype) + if method in ["kernel", "sort"]: + return kcrps(pred, obs, dim=dim) + else: + pred = pred.unsqueeze(0).transpose(0, dim + 1).squeeze(dim + 1) + number_of_bins = max(int(np.sqrt(n)), 100) + bin_edges, cdf = cdf_function(pred, bins=number_of_bins) + _crps = _crps_from_cdf(bin_edges, cdf, obs) + return _crps \ No newline at end of file From eb408f01e0ded58d4d8ca2df228bcf409b0e0fea Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 17 Jun 2025 16:14:41 +0200 Subject: [PATCH 056/302] restructure utils --- src/hirad/datasets/__init__.py | 2 +- src/hirad/datasets/dataset.py | 19 + src/hirad/inference/generate.py | 12 +- src/hirad/models/__init__.py | 1 + src/hirad/models/layers.py | 2 +- src/hirad/utils/capture.py | 513 ---------------------- src/hirad/utils/function_utils.py | 656 +---------------------------- src/hirad/utils/generate_utils.py | 22 - src/hirad/utils/inference_utils.py | 114 ----- src/hirad/utils/model_utils.py | 66 --- src/hirad/utils/train_helpers.py | 6 - 11 files changed, 44 insertions(+), 1369 deletions(-) delete mode 100644 src/hirad/utils/capture.py delete mode 100644 src/hirad/utils/generate_utils.py delete mode 100644 src/hirad/utils/model_utils.py diff --git a/src/hirad/datasets/__init__.py b/src/hirad/datasets/__init__.py index 53e791e5..2fb3d5dc 100644 --- a/src/hirad/datasets/__init__.py +++ b/src/hirad/datasets/__init__.py @@ -1,3 +1,3 @@ -from .dataset import init_train_valid_datasets_from_config, init_dataset_from_config +from .dataset import init_train_valid_datasets_from_config, init_dataset_from_config, get_dataset_and_sampler_inference from .era5_cosmo import ERA5_COSMO from .base import DownscalingDataset diff --git a/src/hirad/datasets/dataset.py b/src/hirad/datasets/dataset.py index c09a4f23..1d36bc22 100644 --- a/src/hirad/datasets/dataset.py +++ b/src/hirad/datasets/dataset.py @@ -111,3 +111,22 @@ def init_dataset_from_config( ) return (dataset_obj, dataset_iterator) + + +def get_dataset_and_sampler_inference(dataset_cfg, times, has_lead_time=False): + """ + Get a dataset and sampler for generation. + """ + (dataset, _) = init_dataset_from_config(dataset_cfg, batch_size=1) + # if has_lead_time: + # plot_times = times + # else: + # plot_times = [ + # datetime.datetime.strptime(time, "%Y-%m-%dT%H:%M:%S") + # for time in times + # ] + all_times = dataset.time() + time_indices = [all_times.index(t) for t in times] + sampler = time_indices + + return dataset, sampler diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index 35f856f3..b60d6d01 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -15,26 +15,20 @@ import cartopy.crs as ccrs from matplotlib import pyplot as plt -from einops import rearrange from torch.distributed import gather - -from hydra.utils import to_absolute_path from hirad.models import EDMPrecondSuperResolution, UNet from hirad.utils.patching import GridPatching2D from hirad.utils.stochastic_sampler import stochastic_sampler from hirad.utils.deterministic_sampler import deterministic_sampler from hirad.utils.inference_utils import ( - get_time_from_range, regression_step, diffusion_step, ) +from hirad.utils.function_utils import get_time_from_range from hirad.utils.checkpoint import load_checkpoint - -from hirad.utils.generate_utils import ( - get_dataset_and_sampler -) +from hirad.datasets import get_dataset_and_sampler_inference from hirad.utils.train_helpers import set_patch_shape @@ -81,7 +75,7 @@ def main(cfg: DictConfig) -> None: has_lead_time = cfg.generation["has_lead_time"] else: has_lead_time = False - dataset, sampler = get_dataset_and_sampler( + dataset, sampler = get_dataset_and_sampler_inference( dataset_cfg=dataset_cfg, times=times, has_lead_time=has_lead_time ) img_shape = dataset.image_shape() diff --git a/src/hirad/models/__init__.py b/src/hirad/models/__init__.py index b00a4777..c8fb3067 100644 --- a/src/hirad/models/__init__.py +++ b/src/hirad/models/__init__.py @@ -1,3 +1,4 @@ +from .utils import weight_init from .layers import ( Linear, Conv2d, diff --git a/src/hirad/models/layers.py b/src/hirad/models/layers.py index d7e63d7b..96bb37f3 100644 --- a/src/hirad/models/layers.py +++ b/src/hirad/models/layers.py @@ -30,7 +30,7 @@ from einops import rearrange from torch.nn.functional import elu, gelu, leaky_relu, relu, sigmoid, silu, tanh -from hirad.utils.model_utils import weight_init +from hirad.models import weight_init _is_apex_available = False if torch.cuda.is_available(): diff --git a/src/hirad/utils/capture.py b/src/hirad/utils/capture.py deleted file mode 100644 index 9c38d5aa..00000000 --- a/src/hirad/utils/capture.py +++ /dev/null @@ -1,513 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. -# SPDX-FileCopyrightText: All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import functools -import logging -import os -import time -from contextlib import nullcontext -from logging import Logger -from typing import Any, Callable, Dict, NewType, Optional, Union - -import torch - -from hirad.distributed import DistributedManager - -float16 = NewType("float16", torch.float16) -bfloat16 = NewType("bfloat16", torch.bfloat16) -optim = NewType("optim", torch.optim) - - -class _StaticCapture(object): - """Base class for StaticCapture decorator. - - This class should not be used, rather StaticCaptureTraining and StaticCaptureEvaluate - should be used instead for training and evaluation functions. - """ - - # Grad scaler and checkpoint class variables use for checkpoint saving and loading - # Since an instance of Static capture does not exist for checkpoint functions - # one must use class functions to access state dicts - _amp_scalers = {} - _amp_scaler_checkpoints = {} - _logger = logging.getLogger("capture") - - def __new__(cls, *args, **kwargs): - obj = super(_StaticCapture, cls).__new__(cls) - obj.amp_scalers = cls._amp_scalers - obj.amp_scaler_checkpoints = cls._amp_scaler_checkpoints - obj.logger = cls._logger - return obj - - def __init__( - self, - model: "physicsnemo.Module", - optim: Optional[optim] = None, - logger: Optional[Logger] = None, - use_graphs: bool = True, - use_autocast: bool = True, - use_gradscaler: bool = True, - compile: bool = False, - cuda_graph_warmup: int = 11, - amp_type: Union[float16, bfloat16] = torch.float16, - gradient_clip_norm: Optional[float] = None, - label: Optional[str] = None, - ): - self.logger = logger if logger else self.logger - # Checkpoint label (used for gradscaler) - self.label = label if label else f"scaler_{len(self.amp_scalers.keys())}" - - # DDP fix - if not isinstance(model, physicsnemo.models.Module) and hasattr( - model, "module" - ): - model = model.module - - if not isinstance(model, physicsnemo.models.Module): - self.logger.error("Model not a PhysicsNeMo Module!") - raise ValueError("Model not a PhysicsNeMo Module!") - if compile: - model = torch.compile(model) - - self.model = model - - self.optim = optim - self.eval = False - self.no_grad = False - self.gradient_clip_norm = gradient_clip_norm - - # Set up toggles for optimizations - if not (amp_type == torch.float16 or amp_type == torch.bfloat16): - raise ValueError("AMP type must be torch.float16 or torch.bfloat16") - # CUDA device - if "cuda" in str(self.model.device): - # CUDA graphs - if use_graphs and not self.model.meta.cuda_graphs: - self.logger.warning( - f"Model {model.meta.name} does not support CUDA graphs, turning off" - ) - use_graphs = False - self.cuda_graphs_enabled = use_graphs - - # AMP GPU - if not self.model.meta.amp_gpu: - self.logger.warning( - f"Model {model.meta.name} does not support AMP on GPUs, turning off" - ) - use_autocast = False - use_gradscaler = False - self.use_gradscaler = use_gradscaler - self.use_autocast = use_autocast - - self.amp_device = "cuda" - # Check if bfloat16 is suppored on the GPU - if amp_type == torch.bfloat16 and not torch.cuda.is_bf16_supported(): - self.logger.warning( - "Current CUDA device does not support bfloat16, falling back to float16" - ) - amp_type = torch.float16 - self.amp_dtype = amp_type - # Gradient Scaler - scaler_enabled = self.use_gradscaler and amp_type == torch.float16 - self.scaler = self._init_amp_scaler(scaler_enabled, self.logger) - - self.replay_stream = torch.cuda.Stream(self.model.device) - # CPU device - else: - self.cuda_graphs_enabled = False - # AMP CPU - if use_autocast and not self.model.meta.amp_cpu: - self.logger.warning( - f"Model {model.meta.name} does not support AMP on CPUs, turning off" - ) - use_autocast = False - - self.use_autocast = use_autocast - self.amp_device = "cpu" - # Only float16 is supported on CPUs - # https://pytorch.org/docs/stable/amp.html#cpu-op-specific-behavior - if amp_type == torch.float16 and use_autocast: - self.logger.warning( - "torch.float16 not supported for CPU AMP, switching to torch.bfloat16" - ) - amp_type = torch.bfloat16 - self.amp_dtype = torch.bfloat16 - # Gradient Scaler (not enabled) - self.scaler = self._init_amp_scaler(False, self.logger) - self.replay_stream = None - - if self.cuda_graphs_enabled: - self.graph = torch.cuda.CUDAGraph() - - self.output = None - self.iteration = 0 - self.cuda_graph_warmup = cuda_graph_warmup # Default for DDP = 11 - - def __call__(self, fn: Callable) -> Callable: - self.function = fn - - @functools.wraps(fn) - def decorated(*args: Any, **kwds: Any) -> Any: - """Training step decorator function""" - - with torch.no_grad() if self.no_grad else nullcontext(): - if self.cuda_graphs_enabled: - self._cuda_graph_forward(*args, **kwds) - else: - self._zero_grads() - self.output = self._amp_forward(*args, **kwds) - - if not self.eval: - # Update model parameters - self.scaler.step(self.optim) - self.scaler.update() - - return self.output - - return decorated - - def _cuda_graph_forward(self, *args: Any, **kwargs: Any) -> Any: - """Forward training step with CUDA graphs - - Returns - ------- - Any - Output of neural network forward - """ - # Graph warm up - if self.iteration < self.cuda_graph_warmup: - self.replay_stream.wait_stream(torch.cuda.current_stream()) - self._zero_grads() - with torch.cuda.stream(self.replay_stream): - output = self._amp_forward(*args, **kwargs) - self.output = output.detach() - torch.cuda.current_stream().wait_stream(self.replay_stream) - # CUDA Graphs - else: - # Graph record - if self.iteration == self.cuda_graph_warmup: - self.logger.warning(f"Recording graph of '{self.function.__name__}'") - self._zero_grads() - torch.cuda.synchronize() - if DistributedManager().distributed: - torch.distributed.barrier() - # TODO: temporary workaround till this issue is fixed: - # https://github.com/pytorch/pytorch/pull/104487#issuecomment-1638665876 - delay = os.environ.get("PHYSICSNEMO_CUDA_GRAPH_CAPTURE_DELAY", "10") - time.sleep(int(delay)) - with torch.cuda.graph(self.graph): - output = self._amp_forward(*args, **kwargs) - self.output = output.detach() - # Graph replay - self.graph.replay() - - self.iteration += 1 - return self.output - - def _zero_grads(self): - """Zero gradients - - Default to `set_to_none` since this will in general have lower memory - footprint, and can modestly improve performance. - - Note - ---- - Zeroing gradients can potentially cause an invalid CUDA memory access in another - graph. However if your graph involves gradients, you much set your gradients to none. - If there is already a graph recorded that includes these gradients, this will error. - Use the `NoGrad` version of capture to avoid this issue for inferencers / validators. - """ - # Skip zeroing if no grad is being used - if self.no_grad: - return - - try: - self.optim.zero_grad(set_to_none=True) - except Exception: - if self.optim: - self.optim.zero_grad() - # For apex optim support and eval mode (need to reset model grads) - self.model.zero_grad(set_to_none=True) - - def _amp_forward(self, *args, **kwargs) -> Any: - """Compute loss and gradients (if training) with AMP - - Returns - ------- - Any - Output of neural network forward - """ - with torch.autocast( - self.amp_device, enabled=self.use_autocast, dtype=self.amp_dtype - ): - output = self.function(*args, **kwargs) - - if not self.eval: - # In training mode output should be the loss - self.scaler.scale(output).backward() - if self.gradient_clip_norm is not None: - self.scaler.unscale_(self.optim) - torch.nn.utils.clip_grad_norm_( - self.model.parameters(), self.gradient_clip_norm - ) - - return output - - def _init_amp_scaler( - self, scaler_enabled: bool, logger: Logger - ) -> torch.cuda.amp.GradScaler: - # Create gradient scaler - scaler = torch.cuda.amp.GradScaler(enabled=scaler_enabled) - # Store scaler in class variable - self.amp_scalers[self.label] = scaler - logging.debug(f"Created gradient scaler {self.label}") - - # If our checkpoint dictionary has weights for this scaler lets load - if self.label in self.amp_scaler_checkpoints: - try: - scaler.load_state_dict(self.amp_scaler_checkpoints[self.label]) - del self.amp_scaler_checkpoints[self.label] - self.logger.info(f"Loaded grad scaler state dictionary {self.label}.") - except Exception as e: - self.logger.error( - f"Failed to load grad scaler {self.label} state dict from saved " - + "checkpoints. Did you switch the ordering of declared static captures?" - ) - raise ValueError(e) - return scaler - - @classmethod - def state_dict(cls) -> Dict[str, Any]: - """Class method for accsessing the StaticCapture state dictionary. - Use this in a training checkpoint function. - - Returns - ------- - Dict[str, Any] - Dictionary of states to save for file - """ - scaler_states = {} - for key, value in cls._amp_scalers.items(): - scaler_states[key] = value.state_dict() - - return scaler_states - - @classmethod - def load_state_dict(cls, state_dict: Dict[str, Any]) -> None: - """Class method for loading a StaticCapture state dictionary. - Use this in a training checkpoint function. - - Returns - ------- - Dict[str, Any] - Dictionary of states to save for file - """ - for key, value in state_dict.items(): - # If scaler has been created already load the weights - if key in cls._amp_scalers: - try: - cls._amp_scalers[key].load_state_dict(value) - cls._logger.info(f"Loaded grad scaler state dictionary {key}.") - except Exception as e: - cls._logger.error( - f"Failed to load grad scaler state dict with id {key}." - + " Something went wrong!" - ) - raise ValueError(e) - # Otherwise store in checkpoints for later use - else: - cls._amp_scaler_checkpoints[key] = value - - @classmethod - def reset_state(cls): - cls._amp_scalers = {} - cls._amp_scaler_checkpoints = {} - - -class StaticCaptureTraining(_StaticCapture): - """A performance optimization decorator for PyTorch training functions. - - This class should be initialized as a decorator on a function that computes the - forward pass of the neural network and loss function. The user should only call the - defind training step function. This will apply optimizations including: AMP and - Cuda Graphs. - - Parameters - ---------- - model : physicsnemo.models.Module - PhysicsNeMo Model - optim : torch.optim - Optimizer - logger : Optional[Logger], optional - PhysicsNeMo Launch Logger, by default None - use_graphs : bool, optional - Toggle CUDA graphs if supported by model, by default True - use_amp : bool, optional - Toggle AMP if supported by mode, by default True - cuda_graph_warmup : int, optional - Number of warmup steps for cuda graphs, by default 11 - amp_type : Union[float16, bfloat16], optional - Auto casting type for AMP, by default torch.float16 - gradient_clip_norm : Optional[float], optional - Threshold for gradient clipping - label : Optional[str], optional - Static capture checkpoint label, by default None - - Raises - ------ - ValueError - If the model provided is not a physicsnemo.models.Module. I.e. has no meta data. - - Example - ------- - >>> # Create model - >>> model = physicsnemo.models.mlp.FullyConnected(2, 64, 2) - >>> input = torch.rand(8, 2) - >>> output = torch.rand(8, 2) - >>> # Create optimizer - >>> optim = torch.optim.Adam(model.parameters(), lr=0.001) - >>> # Create training step function with optimization wrapper - >>> @StaticCaptureTraining(model=model, optim=optim) - ... def training_step(model, invar, outvar): - ... predvar = model(invar) - ... loss = torch.sum(torch.pow(predvar - outvar, 2)) - ... return loss - ... - >>> # Sample training loop - >>> for i in range(3): - ... loss = training_step(model, input, output) - ... - - Note - ---- - Static captures must be checkpointed when training using the `state_dict()` if AMP - is being used with gradient scaler. By default, this requires static captures to be - instantiated in the same order as when they were checkpointed. The label parameter - can be used to relax/circumvent this ordering requirement. - - Note - ---- - Capturing multiple cuda graphs in a single program can lead to potential invalid CUDA - memory access errors on some systems. Prioritize capturing training graphs when this - occurs. - """ - - def __init__( - self, - model: "physicsnemo.Module", - optim: torch.optim, - logger: Optional[Logger] = None, - use_graphs: bool = True, - use_amp: bool = True, - compile: bool = False, - cuda_graph_warmup: int = 11, - amp_type: Union[float16, bfloat16] = torch.float16, - gradient_clip_norm: Optional[float] = None, - label: Optional[str] = None, - ): - super().__init__( - model, - optim, - logger, - use_graphs, - use_amp, - use_amp, - compile, - cuda_graph_warmup, - amp_type, - gradient_clip_norm, - label, - ) - - -class StaticCaptureEvaluateNoGrad(_StaticCapture): - - """An performance optimization decorator for PyTorch no grad evaluation. - - This class should be initialized as a decorator on a function that computes run the - forward pass of the model that does not require gradient calculations. This is the - recommended method to use for inference and validation methods. - - Parameters - ---------- - model : physicsnemo.models.Module - PhysicsNeMo Model - logger : Optional[Logger], optional - PhysicsNeMo Launch Logger, by default None - use_graphs : bool, optional - Toggle CUDA graphs if supported by model, by default True - use_amp : bool, optional - Toggle AMP if supported by mode, by default True - cuda_graph_warmup : int, optional - Number of warmup steps for cuda graphs, by default 11 - amp_type : Union[float16, bfloat16], optional - Auto casting type for AMP, by default torch.float16 - label : Optional[str], optional - Static capture checkpoint label, by default None - - Raises - ------ - ValueError - If the model provided is not a physicsnemo.models.Module. I.e. has no meta data. - - Example - ------- - >>> # Create model - >>> model = physicsnemo.models.mlp.FullyConnected(2, 64, 2) - >>> input = torch.rand(8, 2) - >>> # Create evaluate function with optimization wrapper - >>> @StaticCaptureEvaluateNoGrad(model=model) - ... def eval_step(model, invar): - ... predvar = model(invar) - ... return predvar - ... - >>> output = eval_step(model, input) - >>> output.size() - torch.Size([8, 2]) - - Note - ---- - Capturing multiple cuda graphs in a single program can lead to potential invalid CUDA - memory access errors on some systems. Prioritize capturing training graphs when this - occurs. - """ - - def __init__( - self, - model: "physicsnemo.Module", - logger: Optional[Logger] = None, - use_graphs: bool = True, - use_amp: bool = True, - compile: bool = False, - cuda_graph_warmup: int = 11, - amp_type: Union[float16, bfloat16] = torch.float16, - label: Optional[str] = None, - ): - super().__init__( - model, - None, - logger, - use_graphs, - use_amp, - compile, - False, - cuda_graph_warmup, - amp_type, - None, - label, - ) - self.eval = True # No optimizer/scaler calls - self.no_grad = True # No grad context and no grad zeroing diff --git a/src/hirad/utils/function_utils.py b/src/hirad/utils/function_utils.py index 347457c3..2da05dbe 100644 --- a/src/hirad/utils/function_utils.py +++ b/src/hirad/utils/function_utils.py @@ -17,19 +17,8 @@ """Miscellaneous utility classes and functions.""" -import contextlib -import ctypes import datetime -import fnmatch -import importlib -import inspect -import os -import re -import shutil -import sys -import types -import warnings -from typing import Any, Iterator, List, Tuple, Union +from typing import Iterator import cftime import numpy as np @@ -38,25 +27,6 @@ # ruff: noqa: E722 PERF203 S110 E713 S324 -class EasyDict(dict): # pragma: no cover - """ - Convenience class that behaves like a dict but allows access with the attribute - syntax. - """ - - def __getattr__(self, name: str) -> Any: - try: - return self[name] - except KeyError: - raise AttributeError(name) - - def __setattr__(self, name: str, value: Any) -> None: - self[name] = value - - def __delattr__(self, name: str) -> None: - del self[name] - - class StackedRandomGenerator: # pragma: no cover """ Wrapper for torch.Generator that allows specifying a different random seed @@ -96,32 +66,8 @@ def randint(self, *args, size, **kwargs): ) -def parse_int_list(s): # pragma: no cover - """ - Parse a comma separated list of numbers or ranges and return a list of ints. - Example: '1,2,5-10' returns [1, 2, 5, 6, 7, 8, 9, 10] - """ - if isinstance(s, list): - return s - ranges = [] - range_re = re.compile(r"^(\d+)-(\d+)$") - for p in s.split(","): - m = range_re.match(p) - if m: - ranges.extend(range(int(m.group(1)), int(m.group(2)) + 1)) - else: - ranges.append(int(p)) - return ranges - - # Small util functions # ------------------------------------------------------------------------------------- -def convert_datetime_to_cftime( - time: datetime.datetime, cls=cftime.DatetimeGregorian -) -> cftime.DatetimeGregorian: - """Convert a Python datetime object to a cftime DatetimeGregorian object.""" - return cls(time.year, time.month, time.day, time.hour, time.minute, time.second) - def time_range( start_time: datetime.datetime, @@ -135,417 +81,30 @@ def time_range( yield t t += step +def get_time_from_range(times_range, time_format="%Y-%m-%dT%H:%M:%S"): + """Generates a list of times within a given range. -def format_time(seconds: Union[int, float]) -> str: # pragma: no cover - """Convert the seconds to human readable string with days, hours, minutes and seconds.""" - s = int(np.rint(seconds)) - - if s < 60: - return "{0}s".format(s) - elif s < 60 * 60: - return "{0}m {1:02}s".format(s // 60, s % 60) - elif s < 24 * 60 * 60: - return "{0}h {1:02}m {2:02}s".format(s // (60 * 60), (s // 60) % 60, s % 60) - else: - return "{0}d {1:02}h {2:02}m".format( - s // (24 * 60 * 60), (s // (60 * 60)) % 24, (s // 60) % 60 - ) - - -def format_time_brief(seconds: Union[int, float]) -> str: # pragma: no cover - """Convert the seconds to human readable string with days, hours, minutes and seconds.""" - s = int(np.rint(seconds)) - - if s < 60: - return "{0}s".format(s) - elif s < 60 * 60: - return "{0}m {1:02}s".format(s // 60, s % 60) - elif s < 24 * 60 * 60: - return "{0}h {1:02}m".format(s // (60 * 60), (s // 60) % 60) - else: - return "{0}d {1:02}h".format(s // (24 * 60 * 60), (s // (60 * 60)) % 24) - - -def tuple_product(t: Tuple) -> Any: # pragma: no cover - """Calculate the product of the tuple elements.""" - result = 1 - - for v in t: - result *= v - - return result - - -_str_to_ctype = { - "uint8": ctypes.c_ubyte, - "uint16": ctypes.c_uint16, - "uint32": ctypes.c_uint32, - "uint64": ctypes.c_uint64, - "int8": ctypes.c_byte, - "int16": ctypes.c_int16, - "int32": ctypes.c_int32, - "int64": ctypes.c_int64, - "float32": ctypes.c_float, - "float64": ctypes.c_double, -} - - -def get_dtype_and_ctype(type_obj: Any) -> Tuple[np.dtype, Any]: # pragma: no cover - """ - Given a type name string (or an object having a __name__ attribute), return - matching Numpy and ctypes types that have the same size in bytes. - """ - type_str = None - - if isinstance(type_obj, str): - type_str = type_obj - elif hasattr(type_obj, "__name__"): - type_str = type_obj.__name__ - elif hasattr(type_obj, "name"): - type_str = type_obj.name - else: - raise RuntimeError("Cannot infer type name from input") - - if type_str not in _str_to_ctype.keys(): - raise ValueError("Unknown type name: " + type_str) - - my_dtype = np.dtype(type_str) - my_ctype = _str_to_ctype[type_str] - - if my_dtype.itemsize != ctypes.sizeof(my_ctype): - raise ValueError( - "Numpy and ctypes types for '{}' have different sizes!".format(type_str) - ) - - return my_dtype, my_ctype - - -# Functionality to import modules/objects by name, and call functions by name -# ------------------------------------------------------------------------------------- - - -def get_module_from_obj_name( - obj_name: str, -) -> Tuple[types.ModuleType, str]: # pragma: no cover - """ - Searches for the underlying module behind the name to some python object. - Returns the module and the object name (original name with module part removed). - """ - - # allow convenience shorthands, substitute them by full names - obj_name = re.sub("^np.", "numpy.", obj_name) - obj_name = re.sub("^tf.", "tensorflow.", obj_name) - - # list alternatives for (module_name, local_obj_name) - parts = obj_name.split(".") - name_pairs = [ - (".".join(parts[:i]), ".".join(parts[i:])) for i in range(len(parts), 0, -1) - ] - - # try each alternative in turn - for module_name, local_obj_name in name_pairs: - try: - module = importlib.import_module(module_name) # may raise ImportError - get_obj_from_module(module, local_obj_name) # may raise AttributeError - return module, local_obj_name - except: - pass - - # maybe some of the modules themselves contain errors? - for module_name, _local_obj_name in name_pairs: - try: - importlib.import_module(module_name) # may raise ImportError - except ImportError: - if not str(sys.exc_info()[1]).startswith( - "No module named '" + module_name + "'" - ): - raise - - # maybe the requested attribute is missing? - for module_name, local_obj_name in name_pairs: - try: - module = importlib.import_module(module_name) # may raise ImportError - get_obj_from_module(module, local_obj_name) # may raise AttributeError - except ImportError: - pass - - # we are out of luck, but we have no idea why - raise ImportError(obj_name) - - -def get_obj_from_module( - module: types.ModuleType, obj_name: str -) -> Any: # pragma: no cover - """ - Traverses the object name and returns the last (rightmost) python object. - """ - if obj_name == "": - return module - obj = module - for part in obj_name.split("."): - obj = getattr(obj, part) - return obj - - -def get_obj_by_name(name: str) -> Any: # pragma: no cover - """ - Finds the python object with the given name. - """ - module, obj_name = get_module_from_obj_name(name) - return get_obj_from_module(module, obj_name) - - -def call_func_by_name( - *args, func_name: str = None, **kwargs -) -> Any: # pragma: no cover - """ - Finds the python object with the given name and calls it as a function. - """ - if func_name is None: - raise ValueError("func_name must be specified") - func_obj = get_obj_by_name(func_name) - if not callable(func_obj): - raise ValueError(func_name + " is not callable") - return func_obj(*args, **kwargs) - - -def construct_class_by_name( - *args, class_name: str = None, **kwargs -) -> Any: # pragma: no cover - """ - Finds the python class with the given name and constructs it with the given - arguments. - """ - return call_func_by_name(*args, func_name=class_name, **kwargs) - - -def get_module_dir_by_obj_name(obj_name: str) -> str: # pragma: no cover - """ - Get the directory path of the module containing the given object name. - """ - module, _ = get_module_from_obj_name(obj_name) - return os.path.dirname(inspect.getfile(module)) - - -def is_top_level_function(obj: Any) -> bool: # pragma: no cover - """ - Determine whether the given object is a top-level function, i.e., defined at module - scope using 'def'. - """ - return callable(obj) and obj.__name__ in sys.modules[obj.__module__].__dict__ - - -def get_top_level_function_name(obj: Any) -> str: # pragma: no cover - """ - Return the fully-qualified name of a top-level function. - """ - if not is_top_level_function(obj): - raise ValueError("Object is not a top-level function") - module = obj.__module__ - if module == "__main__": - module = os.path.splitext(os.path.basename(sys.modules[module].__file__))[0] - return module + "." + obj.__name__ - - -# File system helpers -# ------------------------------------------------------------------------------------------ - - -def list_dir_recursively_with_ignore( - dir_path: str, ignores: List[str] = None, add_base_to_relative: bool = False -) -> List[Tuple[str, str]]: # pragma: no cover - """ - List all files recursively in a given directory while ignoring given file and - directory names. Returns list of tuples containing both absolute and relative paths. - """ - if not os.path.isdir(dir_path): - raise RuntimeError(f"Directory does not exist: {dir_path}") - base_name = os.path.basename(os.path.normpath(dir_path)) - - if ignores is None: - ignores = [] - - result = [] - - for root, dirs, files in os.walk(dir_path, topdown=True): - for ignore_ in ignores: - dirs_to_remove = [d for d in dirs if fnmatch.fnmatch(d, ignore_)] - - # dirs need to be edited in-place - for d in dirs_to_remove: - dirs.remove(d) - - files = [f for f in files if not fnmatch.fnmatch(f, ignore_)] - - absolute_paths = [os.path.join(root, f) for f in files] - relative_paths = [os.path.relpath(p, dir_path) for p in absolute_paths] - - if add_base_to_relative: - relative_paths = [os.path.join(base_name, p) for p in relative_paths] - - if len(absolute_paths) != len(relative_paths): - raise ValueError("Number of absolute and relative paths do not match") - result += zip(absolute_paths, relative_paths) + Args: + times_range: A list containing start time, end time, and optional interval (hours). + time_format: The format of the input times (default: "%Y-%m-%dT%H:%M:%S"). - return result - - -def copy_files_and_create_dirs( - files: List[Tuple[str, str]] -) -> None: # pragma: no cover - """ - Takes in a list of tuples of (src, dst) paths and copies files. - Will create all necessary directories. + Returns: + A list of times within the specified range. """ - for file in files: - target_dir_name = os.path.dirname(file[1]) - - # will create all intermediate-level directories - if not os.path.exists(target_dir_name): - os.makedirs(target_dir_name) - - shutil.copyfile(file[0], file[1]) - -# ---------------------------------------------------------------------------- -# Cached construction of constant tensors. Avoids CPU=>GPU copy when the -# same constant is used multiple times. - -_constant_cache = dict() - - -def constant( - value, shape=None, dtype=None, device=None, memory_format=None -): # pragma: no cover - """Cached construction of constant tensors""" - value = np.asarray(value) - if shape is not None: - shape = tuple(shape) - if dtype is None: - dtype = torch.get_default_dtype() - if device is None: - device = torch.device("cpu") - if memory_format is None: - memory_format = torch.contiguous_format - - key = ( - value.shape, - value.dtype, - value.tobytes(), - shape, - dtype, - device, - memory_format, + start_time = datetime.datetime.strptime(times_range[0], time_format) + end_time = datetime.datetime.strptime(times_range[1], time_format) + interval = ( + datetime.timedelta(hours=times_range[2]) + if len(times_range) > 2 + else datetime.timedelta(hours=1) ) - tensor = _constant_cache.get(key, None) - if tensor is None: - tensor = torch.as_tensor(value.copy(), dtype=dtype, device=device) - if shape is not None: - tensor, _ = torch.broadcast_tensors(tensor, torch.empty(shape)) - tensor = tensor.contiguous(memory_format=memory_format) - _constant_cache[key] = tensor - return tensor - - -# ---------------------------------------------------------------------------- -# Replace NaN/Inf with specified numerical values. - -try: - nan_to_num = torch.nan_to_num # 1.8.0a0 -except AttributeError: - - def nan_to_num( - input, nan=0.0, posinf=None, neginf=None, *, out=None - ): # pylint: disable=redefined-builtin # pragma: no cover - """Replace NaN/Inf with specified numerical values""" - if not isinstance(input, torch.Tensor): - raise TypeError("input should be a Tensor") - if posinf is None: - posinf = torch.finfo(input.dtype).max - if neginf is None: - neginf = torch.finfo(input.dtype).min - if nan != 0: - raise ValueError("nan_to_num only supports nan=0") - return torch.clamp( - input.unsqueeze(0).nansum(0), min=neginf, max=posinf, out=out - ) - - -# ---------------------------------------------------------------------------- -# Symbolic assert. - -try: - symbolic_assert = torch._assert # 1.8.0a0 # pylint: disable=protected-access -except AttributeError: - symbolic_assert = torch.Assert # 1.7.0 - -# ---------------------------------------------------------------------------- -# Context manager to temporarily suppress known warnings in torch.jit.trace(). -# Note: Cannot use catch_warnings because of https://bugs.python.org/issue29672 - -@contextlib.contextmanager -def suppress_tracer_warnings(): # pragma: no cover - """ - Context manager to temporarily suppress known warnings in torch.jit.trace(). - Note: Cannot use catch_warnings because of https://bugs.python.org/issue29672 - """ - flt = ("ignore", None, torch.jit.TracerWarning, None, 0) - warnings.filters.insert(0, flt) - yield - warnings.filters.remove(flt) - - -# ---------------------------------------------------------------------------- -# Assert that the shape of a tensor matches the given list of integers. -# None indicates that the size of a dimension is allowed to vary. -# Performs symbolic assertion when used in torch.jit.trace(). - - -def assert_shape(tensor, ref_shape): # pragma: no cover - """ - Assert that the shape of a tensor matches the given list of integers. - None indicates that the size of a dimension is allowed to vary. - Performs symbolic assertion when used in torch.jit.trace(). - """ - if tensor.ndim != len(ref_shape): - raise AssertionError( - f"Wrong number of dimensions: got {tensor.ndim}, expected {len(ref_shape)}" - ) - for idx, (size, ref_size) in enumerate(zip(tensor.shape, ref_shape)): - if ref_size is None: - pass - elif isinstance(ref_size, torch.Tensor): - with suppress_tracer_warnings(): # as_tensor results are registered as constants - symbolic_assert( - torch.equal(torch.as_tensor(size), ref_size), - f"Wrong size for dimension {idx}", - ) - elif isinstance(size, torch.Tensor): - with suppress_tracer_warnings(): # as_tensor results are registered as constants - symbolic_assert( - torch.equal(size, torch.as_tensor(ref_size)), - f"Wrong size for dimension {idx}: expected {ref_size}", - ) - elif size != ref_size: - raise AssertionError( - f"Wrong size for dimension {idx}: got {size}, expected {ref_size}" - ) - - -# ---------------------------------------------------------------------------- -# Function decorator that calls torch.autograd.profiler.record_function(). - - -def profiled_function(fn): # pragma: no cover - """Function decorator that calls torch.autograd.profiler.record_function().""" - - def decorator(*args, **kwargs): - with torch.autograd.profiler.record_function(fn.__name__): - return fn(*args, **kwargs) - - decorator.__name__ = fn.__name__ - return decorator + times = [ + t.strftime(time_format) + for t in time_range(start_time, end_time, interval, inclusive=True) + ] + return times # ---------------------------------------------------------------------------- @@ -619,180 +178,3 @@ def __iter__(self) -> Iterator[int]: j = (i - rnd.randint(window)) % order.size order[i], order[j] = order[j], order[i] idx += 1 - - -# ---------------------------------------------------------------------------- -# Utilities for operating with torch.nn.Module parameters and buffers. - - -def params_and_buffers(module): # pragma: no cover - """Get parameters and buffers of a nn.Module""" - if not isinstance(module, torch.nn.Module): - raise TypeError("module must be a torch.nn.Module instance") - return list(module.parameters()) + list(module.buffers()) - - -def named_params_and_buffers(module): # pragma: no cover - """Get named parameters and buffers of a nn.Module""" - if not isinstance(module, torch.nn.Module): - raise TypeError("module must be a torch.nn.Module instance") - return list(module.named_parameters()) + list(module.named_buffers()) - - -@torch.no_grad() -def copy_params_and_buffers( - src_module, dst_module, require_all=False -): # pragma: no cover - """Copy parameters and buffers from a source module to target module""" - if not isinstance(src_module, torch.nn.Module): - raise TypeError("src_module must be a torch.nn.Module instance") - if not isinstance(dst_module, torch.nn.Module): - raise TypeError("dst_module must be a torch.nn.Module instance") - src_tensors = dict(named_params_and_buffers(src_module)) - for name, tensor in named_params_and_buffers(dst_module): - if not ((name in src_tensors) or (not require_all)): - raise ValueError(f"Missing source tensor for {name}") - if name in src_tensors: - tensor.copy_(src_tensors[name]) - - -# ---------------------------------------------------------------------------- -# Context manager for easily enabling/disabling DistributedDataParallel -# synchronization. - - -@contextlib.contextmanager -def ddp_sync(module, sync): # pragma: no cover - """ - Context manager for easily enabling/disabling DistributedDataParallel - synchronization. - """ - if not isinstance(module, torch.nn.Module): - raise TypeError("module must be a torch.nn.Module instance") - if sync or not isinstance(module, torch.nn.parallel.DistributedDataParallel): - yield - else: - with module.no_sync(): - yield - - -# ---------------------------------------------------------------------------- -# Check DistributedDataParallel consistency across processes. - - -def check_ddp_consistency(module, ignore_regex=None): # pragma: no cover - """Check DistributedDataParallel consistency across processes.""" - if not isinstance(module, torch.nn.Module): - raise TypeError("module must be a torch.nn.Module instance") - for name, tensor in named_params_and_buffers(module): - fullname = type(module).__name__ + "." + name - if ignore_regex is not None and re.fullmatch(ignore_regex, fullname): - continue - tensor = tensor.detach() - if tensor.is_floating_point(): - tensor = nan_to_num(tensor) - other = tensor.clone() - torch.distributed.broadcast(tensor=other, src=0) - if not (tensor == other).all(): - raise RuntimeError(f"DDP consistency check failed for {fullname}") - - -# ---------------------------------------------------------------------------- -# Print summary table of module hierarchy. - - -def print_module_summary( - module, inputs, max_nesting=3, skip_redundant=True -): # pragma: no cover - """Print summary table of module hierarchy.""" - if not isinstance(module, torch.nn.Module): - raise TypeError("module must be a torch.nn.Module instance") - if isinstance(module, torch.jit.ScriptModule): - raise TypeError("module must not be a torch.jit.ScriptModule instance") - if not isinstance(inputs, (tuple, list)): - raise TypeError("inputs must be a tuple or list") - - # Register hooks. - entries = [] - nesting = [0] - - def pre_hook(_mod, _inputs): - nesting[0] += 1 - - def post_hook(mod, _inputs, outputs): - nesting[0] -= 1 - if nesting[0] <= max_nesting: - outputs = list(outputs) if isinstance(outputs, (tuple, list)) else [outputs] - outputs = [t for t in outputs if isinstance(t, torch.Tensor)] - entries.append(EasyDict(mod=mod, outputs=outputs)) - - hooks = [mod.register_forward_pre_hook(pre_hook) for mod in module.modules()] - hooks += [mod.register_forward_hook(post_hook) for mod in module.modules()] - - # Run module. - outputs = module(*inputs) - for hook in hooks: - hook.remove() - - # Identify unique outputs, parameters, and buffers. - tensors_seen = set() - for e in entries: - e.unique_params = [t for t in e.mod.parameters() if id(t) not in tensors_seen] - e.unique_buffers = [t for t in e.mod.buffers() if id(t) not in tensors_seen] - e.unique_outputs = [t for t in e.outputs if id(t) not in tensors_seen] - tensors_seen |= { - id(t) for t in e.unique_params + e.unique_buffers + e.unique_outputs - } - - # Filter out redundant entries. - if skip_redundant: - entries = [ - e - for e in entries - if len(e.unique_params) or len(e.unique_buffers) or len(e.unique_outputs) - ] - - # Construct table. - rows = [ - [type(module).__name__, "Parameters", "Buffers", "Output shape", "Datatype"] - ] - rows += [["---"] * len(rows[0])] - param_total = 0 - buffer_total = 0 - submodule_names = {mod: name for name, mod in module.named_modules()} - for e in entries: - name = "" if e.mod is module else submodule_names[e.mod] - param_size = sum(t.numel() for t in e.unique_params) - buffer_size = sum(t.numel() for t in e.unique_buffers) - output_shapes = [str(list(t.shape)) for t in e.outputs] - output_dtypes = [str(t.dtype).split(".")[-1] for t in e.outputs] - rows += [ - [ - name + (":0" if len(e.outputs) >= 2 else ""), - str(param_size) if param_size else "-", - str(buffer_size) if buffer_size else "-", - (output_shapes + ["-"])[0], - (output_dtypes + ["-"])[0], - ] - ] - for idx in range(1, len(e.outputs)): - rows += [ - [name + f":{idx}", "-", "-", output_shapes[idx], output_dtypes[idx]] - ] - param_total += param_size - buffer_total += buffer_size - rows += [["---"] * len(rows[0])] - rows += [["Total", str(param_total), str(buffer_total), "-", "-"]] - - # Print table. - widths = [max(len(cell) for cell in column) for column in zip(*rows)] - for row in rows: - print( - " ".join( - cell + " " * (width - len(cell)) for cell, width in zip(row, widths) - ) - ) - return outputs - - -# ---------------------------------------------------------------------------- diff --git a/src/hirad/utils/generate_utils.py b/src/hirad/utils/generate_utils.py deleted file mode 100644 index 43f83b63..00000000 --- a/src/hirad/utils/generate_utils.py +++ /dev/null @@ -1,22 +0,0 @@ -import datetime -from hirad.datasets import init_dataset_from_config -from .function_utils import convert_datetime_to_cftime - - -def get_dataset_and_sampler(dataset_cfg, times, has_lead_time=False): - """ - Get a dataset and sampler for generation. - """ - (dataset, _) = init_dataset_from_config(dataset_cfg, batch_size=1) - # if has_lead_time: - # plot_times = times - # else: - # plot_times = [ - # datetime.datetime.strptime(time, "%Y-%m-%dT%H:%M:%S") - # for time in times - # ] - all_times = dataset.time() - time_indices = [all_times.index(t) for t in times] - sampler = time_indices - - return dataset, sampler \ No newline at end of file diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index 8665536b..ee4b55ac 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -203,117 +203,3 @@ def diffusion_step( def generate(): pass - -############################################################################ -# CorrDiff writer utilities # -############################################################################ - - -class NetCDFWriter: - """NetCDF Writer""" - - def __init__( - self, f, lat, lon, input_channels, output_channels, has_lead_time=False - ): - self._f = f - self.has_lead_time = has_lead_time - # create unlimited dimensions - f.createDimension("time") - f.createDimension("ensemble") - - if lat.shape != lon.shape: - raise ValueError("lat and lon must have the same shape") - ny, nx = lat.shape - - # create lat/lon grid - f.createDimension("x", nx) - f.createDimension("y", ny) - - v = f.createVariable("lat", "f", dimensions=("y", "x")) - # NOTE rethink this for datasets whose samples don't have constant lat-lon. - v[:] = lat - v.standard_name = "latitude" - v.units = "degrees_north" - - v = f.createVariable("lon", "f", dimensions=("y", "x")) - v[:] = lon - v.standard_name = "longitude" - v.units = "degrees_east" - - # create time dimension - if has_lead_time: - v = f.createVariable("time", "str", ("time")) - else: - v = f.createVariable("time", "i8", ("time")) - v.calendar = "standard" - v.units = "hours since 1990-01-01 00:00:00" - - self.truth_group = f.createGroup("truth") - self.prediction_group = f.createGroup("prediction") - self.input_group = f.createGroup("input") - - for variable in output_channels: - name = variable.name + variable.level - self.truth_group.createVariable(name, "f", dimensions=("time", "y", "x")) - self.prediction_group.createVariable( - name, "f", dimensions=("ensemble", "time", "y", "x") - ) - - # setup input data in netCDF - - for variable in input_channels: - name = variable.name + variable.level - self.input_group.createVariable(name, "f", dimensions=("time", "y", "x")) - - def write_input(self, channel_name, time_index, val): - """Write input data to NetCDF file.""" - self.input_group[channel_name][time_index] = val - - def write_truth(self, channel_name, time_index, val): - """Write ground truth data to NetCDF file.""" - self.truth_group[channel_name][time_index] = val - - def write_prediction(self, channel_name, time_index, ensemble_index, val): - """Write prediction data to NetCDF file.""" - self.prediction_group[channel_name][ensemble_index, time_index] = val - - def write_time(self, time_index, time): - """Write time information to NetCDF file.""" - if self.has_lead_time: - self._f["time"][time_index] = time - else: - time_v = self._f["time"] - self._f["time"][time_index] = cftime.date2num( - time, time_v.units, time_v.calendar - ) - - -############################################################################ -# CorrDiff time utilities # -############################################################################ - - -def get_time_from_range(times_range, time_format="%Y-%m-%dT%H:%M:%S"): - """Generates a list of times within a given range. - - Args: - times_range: A list containing start time, end time, and optional interval (hours). - time_format: The format of the input times (default: "%Y-%m-%dT%H:%M:%S"). - - Returns: - A list of times within the specified range. - """ - - start_time = datetime.datetime.strptime(times_range[0], time_format) - end_time = datetime.datetime.strptime(times_range[1], time_format) - interval = ( - datetime.timedelta(hours=times_range[2]) - if len(times_range) > 2 - else datetime.timedelta(hours=1) - ) - - times = [ - t.strftime(time_format) - for t in time_range(start_time, end_time, interval, inclusive=True) - ] - return times diff --git a/src/hirad/utils/model_utils.py b/src/hirad/utils/model_utils.py deleted file mode 100644 index e1cde9d8..00000000 --- a/src/hirad/utils/model_utils.py +++ /dev/null @@ -1,66 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. -# SPDX-FileCopyrightText: All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import numpy as np -import torch - - -def weight_init(shape: tuple, mode: str, fan_in: int, fan_out: int): - """ - Unified routine for initializing weights and biases. - This function provides a unified interface for various weight initialization - strategies like Xavier (Glorot) and Kaiming (He) initializations. - - Parameters - ---------- - shape : tuple - The shape of the tensor to initialize. It could represent weights or biases - of a layer in a neural network. - mode : str - The mode/type of initialization to use. Supported values are: - - "xavier_uniform": Xavier (Glorot) uniform initialization. - - "xavier_normal": Xavier (Glorot) normal initialization. - - "kaiming_uniform": Kaiming (He) uniform initialization. - - "kaiming_normal": Kaiming (He) normal initialization. - fan_in : int - The number of input units in the weight tensor. For convolutional layers, - this typically represents the number of input channels times the kernel height - times the kernel width. - fan_out : int - The number of output units in the weight tensor. For convolutional layers, - this typically represents the number of output channels times the kernel height - times the kernel width. - - Returns - ------- - torch.Tensor - The initialized tensor based on the specified mode. - - Raises - ------ - ValueError - If the provided `mode` is not one of the supported initialization modes. - """ - if mode == "xavier_uniform": - return np.sqrt(6 / (fan_in + fan_out)) * (torch.rand(*shape) * 2 - 1) - if mode == "xavier_normal": - return np.sqrt(2 / (fan_in + fan_out)) * torch.randn(*shape) - if mode == "kaiming_uniform": - return np.sqrt(3 / fan_in) * (torch.rand(*shape) * 2 - 1) - if mode == "kaiming_normal": - return np.sqrt(1 / fan_in) * torch.randn(*shape) - raise ValueError(f'Invalid init mode "{mode}"') diff --git a/src/hirad/utils/train_helpers.py b/src/hirad/utils/train_helpers.py index 218d6f19..dc1a5b9f 100644 --- a/src/hirad/utils/train_helpers.py +++ b/src/hirad/utils/train_helpers.py @@ -16,7 +16,6 @@ import torch import numpy as np -from omegaconf import ListConfig import warnings @@ -100,11 +99,6 @@ def handle_and_clip_gradients(model, grad_clip_threshold=None): torch.nn.utils.clip_grad_norm_(model.parameters(), grad_clip_threshold) -def parse_model_args(args): - """Convert ListConfig values in args to tuples.""" - return {k: tuple(v) if isinstance(v, ListConfig) else v for k, v in args.items()} - - def is_time_for_periodic_task( cur_nimg, freq, done, batch_size, rank, rank_0_only=False ): From 054bdf495758299a9aa72015c691ab5700cfdd49 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 17 Jun 2025 16:57:23 +0200 Subject: [PATCH 057/302] restructure inference utils and samplers --- src/hirad/inference/__init__.py | 2 ++ .../{utils => inference}/deterministic_sampler.py | 0 src/hirad/inference/generate.py | 4 ++-- src/hirad/{utils => inference}/stochastic_sampler.py | 0 src/hirad/utils/inference_utils.py | 11 +---------- 5 files changed, 5 insertions(+), 12 deletions(-) create mode 100644 src/hirad/inference/__init__.py rename src/hirad/{utils => inference}/deterministic_sampler.py (100%) rename src/hirad/{utils => inference}/stochastic_sampler.py (100%) diff --git a/src/hirad/inference/__init__.py b/src/hirad/inference/__init__.py new file mode 100644 index 00000000..95edb484 --- /dev/null +++ b/src/hirad/inference/__init__.py @@ -0,0 +1,2 @@ +from .deterministic_sampler import deterministic_sampler +from .stochastic_sampler import stochastic_sampler \ No newline at end of file diff --git a/src/hirad/utils/deterministic_sampler.py b/src/hirad/inference/deterministic_sampler.py similarity index 100% rename from src/hirad/utils/deterministic_sampler.py rename to src/hirad/inference/deterministic_sampler.py diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index b60d6d01..40d42bcf 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -19,8 +19,8 @@ from hirad.models import EDMPrecondSuperResolution, UNet from hirad.utils.patching import GridPatching2D -from hirad.utils.stochastic_sampler import stochastic_sampler -from hirad.utils.deterministic_sampler import deterministic_sampler +from hirad.inference import stochastic_sampler +from hirad.inference import deterministic_sampler from hirad.utils.inference_utils import ( regression_step, diffusion_step, diff --git a/src/hirad/utils/stochastic_sampler.py b/src/hirad/inference/stochastic_sampler.py similarity index 100% rename from src/hirad/utils/stochastic_sampler.py rename to src/hirad/inference/stochastic_sampler.py diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index ee4b55ac..1dcfe5ec 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -14,18 +14,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import datetime from typing import Optional -import cftime import nvtx import torch import tqdm -from .function_utils import StackedRandomGenerator, time_range - -from .stochastic_sampler import stochastic_sampler -from .deterministic_sampler import deterministic_sampler +from .function_utils import StackedRandomGenerator ############################################################################ # CorrDiff Generation Utilities # @@ -199,7 +194,3 @@ def diffusion_step( ) all_images.append(images) return torch.cat(all_images) - - -def generate(): - pass From a02d5a7852e621563b7aad1fc51bacec51b90fea Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 1 Jul 2025 14:36:38 +0200 Subject: [PATCH 058/302] refactor generation --- pyproject.toml | 12 +- src/hirad/conf/dataset/era_cosmo.yaml | 4 +- src/hirad/conf/generate_era_cosmo.yaml | 4 +- src/hirad/conf/generation/era_cosmo.yaml | 8 +- src/hirad/conf/sampler/deterministic.yaml | 5 +- src/hirad/generate.sh | 34 +-- src/hirad/inference/__init__.py | 3 +- src/hirad/inference/generate.py | 253 +++------------------- src/hirad/inference/generator.py | 141 ++++++++++++ src/hirad/train_diffusion.sh | 36 +-- src/hirad/train_regression.sh | 41 ++-- src/hirad/training/train.py | 1 - src/hirad/utils/inference_utils.py | 91 ++++++++ 13 files changed, 334 insertions(+), 299 deletions(-) create mode 100644 src/hirad/inference/generator.py diff --git a/pyproject.toml b/pyproject.toml index 1477899a..ff966f76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,17 +13,7 @@ readme = "README.md" requires-python = ">=3.12" license = {file = "LICENSE"} -dependencies = [ - "cartopy>=0.24.1", - "cftime>=1.6.4", - "hydra-core>=1.3.2", - "matplotlib>=3.10.1", - "omegaconf>=2.3.0", - "tensorboard>=2.19.0", - "termcolor>=3.1.0", - "torchinfo>=1.8.0", - "treelib>=1.7.1" -] +dependencies = [] [tool.setuptools] package-dir = {"" = "src"} diff --git a/src/hirad/conf/dataset/era_cosmo.yaml b/src/hirad/conf/dataset/era_cosmo.yaml index b1e21e6f..5d32f4ed 100644 --- a/src/hirad/conf/dataset/era_cosmo.yaml +++ b/src/hirad/conf/dataset/era_cosmo.yaml @@ -1,3 +1,3 @@ type: era5_cosmo -dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/trim_19_full -validation_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/trim_19_full/validation \ No newline at end of file +dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/train +validation_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/validation \ No newline at end of file diff --git a/src/hirad/conf/generate_era_cosmo.yaml b/src/hirad/conf/generate_era_cosmo.yaml index 5d7649de..90a59484 100644 --- a/src/hirad/conf/generate_era_cosmo.yaml +++ b/src/hirad/conf/generate_era_cosmo.yaml @@ -1,9 +1,9 @@ hydra: job: chdir: true - name: generation_full + name: generation_regression_valid run: - dir: /iopsstor/scratch/cscs/pstamenk/outputs/${hydra:job.name} + dir: /capstor/scratch/cscs/pstamenk/outputs/${hydra:job.name} # Get defaults defaults: diff --git a/src/hirad/conf/generation/era_cosmo.yaml b/src/hirad/conf/generation/era_cosmo.yaml index be4219d2..2ccbb719 100644 --- a/src/hirad/conf/generation/era_cosmo.yaml +++ b/src/hirad/conf/generation/era_cosmo.yaml @@ -2,7 +2,7 @@ num_ensembles: 8 # Number of ensembles to generate per input seed_batch_size: 4 # Size of the batched inference -inference_mode: all +inference_mode: regression # Choose between "all" (regression + diffusion), "regression" or "diffusion" # Patch size. Patch-based sampling will be utilized if these dimensions differ from # img_shape_x and img_shape_y @@ -17,7 +17,7 @@ hr_mean_conditioning: True # Sampling resolution times_range: null times: - - 20160101-0000 + - 20200926-1800 # - 20160101-0600 # - 20160101-1200 has_laed_time: False @@ -35,10 +35,10 @@ perf: # To support multiple workers a threadsafe version of the netCDF library must be used io: - res_ckpt_path: /iopsstor/scratch/cscs/pstamenk/outputs/diffusion_refactoring/checkpoints_diffusion + res_ckpt_path: /capstor/scratch/cscs/pstamenk/diffusion_checkpoints # res_ckpt_path: null # Checkpoint filename for the diffusion model - reg_ckpt_path: /iopsstor/scratch/cscs/pstamenk/outputs/regression_refactoring/checkpoints_regression + reg_ckpt_path: /capstor/scratch/cscs/pstamenk/regression_checkpoints # reg_ckpt_path: /iopsstor/scratch/cscs/pstamenk/outputs/regression_test/checkpoints_regression # Checkpoint filename for the mean predictor model output_path: ./images \ No newline at end of file diff --git a/src/hirad/conf/sampler/deterministic.yaml b/src/hirad/conf/sampler/deterministic.yaml index 856906b6..f65e7384 100644 --- a/src/hirad/conf/sampler/deterministic.yaml +++ b/src/hirad/conf/sampler/deterministic.yaml @@ -2,7 +2,8 @@ # Deterministic sampler is not implemented correctly in this codebase and shouldn't be used. type: deterministic -num_steps: 9 +params: + num_steps: 9 # Number of denoising steps -solver: euler + solver: euler # ODE solver type: euler is the simplest solver \ No newline at end of file diff --git a/src/hirad/generate.sh b/src/hirad/generate.sh index 87c8979c..2607431b 100644 --- a/src/hirad/generate.sh +++ b/src/hirad/generate.sh @@ -7,19 +7,16 @@ #SBATCH --nodes=1 #SBATCH --ntasks-per-node=1 #SBATCH --gpus-per-node=1 -#SBATCH --cpus-per-task=72 #SBATCH --time=00:30:00 #SBATCH --no-requeue #SBATCH --exclusive ### OUTPUT ### -#SBATCH --output=/iopsstor/scratch/cscs/pstamenk/logs/full_generation.log -#SBATCH --error=/iopsstor/scratch/cscs/pstamenk/logs/full_generation.err +#SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/regression_generation.log +#SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/regression_generation.err ### ENVIRONMENT #### -#SBATCH --uenv=pytorch/v2.6.0:/user-environment -#SBATCH --view=default -#SBATCH -A a-a122 +#SBATCH -A c38 # Choose method to initialize dist in pythorch export DISTRIBUTED_INITIALIZATION_METHOD=SLURM @@ -34,18 +31,21 @@ export MASTER_PORT=29500 echo "Master port: $MASTER_PORT" # Get number of physical cores using Python -PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") -# Use SLURM_NTASKS (number of processes to be launched by torchrun) -LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} -# Compute threads per process -OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) -export OMP_NUM_THREADS=$OMP_THREADS -echo "Physical cores: $PHYSICAL_CORES" -echo "Local processes: $LOCAL_PROCS" -echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" +# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") +# # Use SLURM_NTASKS (number of processes to be launched by torchrun) +# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} +# # Compute threads per process +# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) +# export OMP_NUM_THREADS=$OMP_THREADS +export OMP_NUM_THREADS=72 +# echo "Physical cores: $PHYSICAL_CORES" +# echo "Local processes: $LOCAL_PROCS" +# echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" # python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml -srun bash -c " - . ./train_env/bin/activate +srun --container-writable --environment=modulus_env bash -c " + cd HiRAD-Gen + pip install -e . --no-dependencies + pip install Cartopy==0.22.0 python src/hirad/inference/generate.py --config-name=generate_era_cosmo.yaml " \ No newline at end of file diff --git a/src/hirad/inference/__init__.py b/src/hirad/inference/__init__.py index 95edb484..1593b3a8 100644 --- a/src/hirad/inference/__init__.py +++ b/src/hirad/inference/__init__.py @@ -1,2 +1,3 @@ from .deterministic_sampler import deterministic_sampler -from .stochastic_sampler import stochastic_sampler \ No newline at end of file +from .stochastic_sampler import stochastic_sampler +from .generator import Generator \ No newline at end of file diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index 40d42bcf..77e78b22 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -4,27 +4,16 @@ from omegaconf import OmegaConf, DictConfig import torch import torch._dynamo -import nvtx import numpy as np import contextlib from hirad.distributed import DistributedManager from hirad.utils.console import PythonLogger, RankZeroLoggingWrapper from concurrent.futures import ThreadPoolExecutor -from functools import partial - -import cartopy.crs as ccrs -from matplotlib import pyplot as plt -from torch.distributed import gather from hirad.models import EDMPrecondSuperResolution, UNet -from hirad.utils.patching import GridPatching2D -from hirad.inference import stochastic_sampler -from hirad.inference import deterministic_sampler -from hirad.utils.inference_utils import ( - regression_step, - diffusion_step, -) +from hirad.inference import Generator +from hirad.utils.inference_utils import save_images from hirad.utils.function_utils import get_time_from_range from hirad.utils.checkpoint import load_checkpoint @@ -32,14 +21,13 @@ from hirad.utils.train_helpers import set_patch_shape -from hirad.eval import compute_mae, average_power_spectrum, plot_error_projection, plot_power_spectra @hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") def main(cfg: DictConfig) -> None: """Generate random dowscaled atmospheric states using the techniques described in the paper "Elucidating the Design Space of Diffusion-Based Generative Models". """ - torch.backends.cudnn.enabled = False + # torch.backends.cudnn.enabled = False # Initialize distributed manager DistributedManager.initialize() dist = DistributedManager() @@ -49,14 +37,6 @@ def main(cfg: DictConfig) -> None: logger = PythonLogger("generate") # General python logger logger0 = RankZeroLoggingWrapper(logger, dist) - # Handle the batch size - seeds = list(np.arange(cfg.generation.num_ensembles)) - num_batches = ( - (len(seeds) - 1) // (cfg.generation.seed_batch_size * dist.world_size) + 1 - ) * dist.world_size - all_batches = torch.as_tensor(seeds).tensor_split(num_batches) - rank_batches = all_batches[dist.rank :: dist.world_size] - # Synchronize if dist.world_size > 1: torch.distributed.barrier() @@ -81,26 +61,6 @@ def main(cfg: DictConfig) -> None: img_shape = dataset.image_shape() img_out_channels = len(dataset.output_channels()) - # Parse the patch shape - if cfg.generation.patching: - patch_shape_x = cfg.generation.patch_shape_x - patch_shape_y = cfg.generation.patch_shape_y - else: - patch_shape_x, patch_shape_y = None, None - patch_shape = (patch_shape_y, patch_shape_x) - use_patching, img_shape, patch_shape = set_patch_shape(img_shape, patch_shape) - if use_patching: - patching = GridPatching2D( - img_shape=img_shape, - patch_shape=patch_shape, - boundary_pix=cfg.generation.boundary_pix, - overlap_pix=cfg.generation.overlap_pix, - ) - logger0.info("Patch-based training enabled") - else: - patching = None - logger0.info("Patch-based training disabled") - # Parse the inference mode if cfg.generation.inference_mode == "regression": load_net_reg, load_net_res = True, False @@ -178,101 +138,34 @@ def main(cfg: DictConfig) -> None: # Overhead of compiling regression network outweights any benefits if net_res: net_res = torch.compile(net_res, mode="reduce-overhead") - - # Partially instantiate the sampler based on the configs - if cfg.sampler.type == "deterministic": - if cfg.generation.hr_mean_conditioning: - raise NotImplementedError( - "High-res mean conditioning is not yet implemented for the deterministic sampler" - ) - sampler_fn = partial( - deterministic_sampler, - num_steps=cfg.sampler.num_steps, - # num_ensembles=cfg.generation.num_ensembles, - solver=cfg.sampler.solver, - ) - elif cfg.sampler.type == "stochastic": - sampler_fn = partial(stochastic_sampler, patching=patching) - else: - raise ValueError(f"Unknown sampling method {cfg.sampling.type}") + generator = Generator( + net_reg=net_reg, + net_res=net_res, + batch_size=cfg.generation.seed_batch_size, + ensemble_size=cfg.generation.num_ensembles, + hr_mean_conditioning=cfg.generation.hr_mean_conditioning, + n_out_channels=img_out_channels, + inference_mode=cfg.generation.inference_mode, + dist=dist, + ) - # Main generation definition - def generate_fn(image_lr, lead_time_label): - with nvtx.annotate("generate_fn", color="green"): - # (1, C, H, W) - image_lr = image_lr.to(memory_format=torch.channels_last) - - if net_reg: - with nvtx.annotate("regression_model", color="yellow"): - image_reg = regression_step( - net=net_reg, - img_lr=image_lr, - latents_shape=( - cfg.generation.seed_batch_size, - img_out_channels, - img_shape[0], - img_shape[1], - ), # (batch_size, C, H, W) - lead_time_label=lead_time_label, - ) - if net_res: - if cfg.generation.hr_mean_conditioning: - mean_hr = image_reg[0:1] - else: - mean_hr = None - with nvtx.annotate("diffusion model", color="purple"): - image_res = diffusion_step( - net=net_res, - sampler_fn=sampler_fn, - img_shape=img_shape, - img_out_channels=img_out_channels, - rank_batches=rank_batches, - img_lr=image_lr.expand( - cfg.generation.seed_batch_size, -1, -1, -1 - ), #.to(memory_format=torch.channels_last), - rank=dist.rank, - device=device, - mean_hr=mean_hr, - lead_time_label=lead_time_label, - ) - if cfg.generation.inference_mode == "regression": - image_out = image_reg - elif cfg.generation.inference_mode == "diffusion": - image_out = image_res - else: - image_out = image_reg[0:1,::] + image_res - - # Gather tensors on rank 0 - if dist.world_size > 1: - if dist.rank == 0: - gathered_tensors = [ - torch.zeros_like( - image_out, dtype=image_out.dtype, device=image_out.device - ) - for _ in range(dist.world_size) - ] - else: - gathered_tensors = None - - torch.distributed.barrier() - gather( - image_out, - gather_list=gathered_tensors if dist.rank == 0 else None, - dst=0, - ) - - if dist.rank == 0: - if cfg.generation.inference_mode != "regression": - return torch.cat(gathered_tensors), image_reg[0:1,::] - return torch.cat(gathered_tensors), None - else: - return None, None - else: - #TODO do this for multi-gpu setting above too - if cfg.generation.inference_mode != "regression": - return image_out, image_reg - return image_out, None + # Parse the patch shape + if cfg.generation.patching: + patch_shape_x = cfg.generation.patch_shape_x + patch_shape_y = cfg.generation.patch_shape_y + else: + patch_shape_x, patch_shape_y = None, None + patch_shape = (patch_shape_y, patch_shape_x) + use_patching, img_shape, patch_shape = set_patch_shape(img_shape, patch_shape) + if use_patching: + generator.initialize_patching(img_shape=img_shape, + patch_shape=patch_shape, + boundary_pix=cfg.generation.boundary_pix, + overlap_pix=cfg.generation.overlap_pix, + ) + sampler_params = cfg.sampler.params if "params" in cfg.sampler else {} + generator.initialize_sampler(cfg.sampler.type, **sampler_params) # generate images output_path = getattr(cfg.generation.io, "output_path", "./outputs") @@ -348,7 +241,8 @@ def elapsed_time(self, _): .to(memory_format=torch.channels_last) ) image_tar = image_tar.to(device=device).to(torch.float32) - image_out, image_reg = generate_fn(image_lr,lead_time_label) + # image_out, image_reg = generate_fn(image_lr,lead_time_label) + image_out, image_reg = generator.generate(image_lr,lead_time_label) if dist.rank == 0: batch_size = image_out.shape[0] # write out data in a seperate thread so we don't hold up inferencing @@ -391,90 +285,5 @@ def elapsed_time(self, _): logger0.info("Generation Completed.") -def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): - - os.makedirs(output_path, exist_ok=True) - - longitudes = dataset.longitude() - latitudes = dataset.latitude() - input_channels = dataset.input_channels() - output_channels = dataset.output_channels() - - target = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze()),1) #.reshape(len(output_channels),-1) - prediction = np.flip(dataset.denormalize_output(image_pred[-1,::].squeeze()),1) #.reshape(len(output_channels),-1) - baseline = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze()),1)# .reshape(len(input_channels),-1) - if mean_pred is not None: - mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze()),1) #.reshape(len(output_channels),-1) - - - freqs = {} - power = {} - for idx, channel in enumerate(output_channels): - input_channel_idx = input_channels.index(channel) - - if channel.name=="tp": - target[idx,::] = prepare_precipitaiton(target[idx,:,:]) - prediction[idx,::] = prepare_precipitaiton(prediction[idx,:,:]) - baseline[input_channel_idx,:,:] = prepare_precipitaiton(baseline[input_channel_idx]) - if mean_pred is not None: - mean_pred[idx,::] = prepare_precipitaiton(mean_pred[idx,::]) - - _plot_projection(longitudes, latitudes, target[idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-target.jpg')) - _plot_projection(longitudes, latitudes, prediction[idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-prediction.jpg')) - _plot_projection(longitudes, latitudes, baseline[input_channel_idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-input.jpg')) - if mean_pred is not None: - _plot_projection(longitudes, latitudes, mean_pred[idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-mean_prediction.jpg')) - - _, baseline_errors = compute_mae(baseline[input_channel_idx,:,:], target[idx,:,:]) - _, prediction_errors = compute_mae(prediction[idx,:,:], target[idx,:,:]) - if mean_pred is not None: - _, mean_prediction_errors = compute_mae(mean_pred[idx,:,:], target[idx,:,:]) - - - plot_error_projection(baseline_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path, f'{time_step}-{channel.name}-baseline-error.jpg')) - plot_error_projection(prediction_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path, f'{time_step}-{channel.name}-prediction-error.jpg')) - if mean_pred is not None: - plot_error_projection(mean_prediction_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path, f'{time_step}-{channel.name}-mean-prediction-error.jpg')) - - b_freq, b_power = average_power_spectrum(baseline[input_channel_idx,:,:].squeeze(), 2.0) - freqs['baseline'] = b_freq - power['baseline'] = b_power - #plotting.plot_power_spectrum(b_freq, b_power, target_channels[t_c], os.path.join('plots/spectra/baseline2dt', target_channels[t_c] + '-all_dates')) - t_freq, t_power = average_power_spectrum(target[idx,:,:].squeeze(), 2.0) - freqs['target'] = t_freq - power['target'] = t_power - p_freq, p_power = average_power_spectrum(prediction[idx,:,:].squeeze(), 2.0) - freqs['prediction'] = p_freq - power['prediction'] = p_power - if mean_pred is not None: - mp_freq, mp_power = average_power_spectrum(mean_pred[idx,:,:].squeeze(), 2.0) - freqs['mean_prediction'] = mp_freq - power['mean_prediction'] = mp_power - plot_power_spectra(freqs, power, channel.name, os.path.join(output_path, f'{time_step}-{channel.name}-spectra.jpg')) - - -def prepare_precipitaiton(precip_array): - precip_array = np.clip(precip_array, 0, None) - epsilon = 1e-2 - precip_array = precip_array + epsilon - precip_array = np.log(precip_array) - # log_min, log_max = precip_array.min(), precip_array.max() - # precip_array = (precip_array-log_min)/(log_max-log_min) - return precip_array - - -def _plot_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, cmap=None, vmin = None, vmax = None): - - """Plot observed or interpolated data in a scatter plot.""" - # TODO: Refactor this somehow, it's not really generalizing well across variables. - fig = plt.figure() - fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) - p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) - ax.coastlines() - ax.gridlines(draw_labels=True) - plt.colorbar(p, label="K", orientation="horizontal") - plt.savefig(filename) - plt.close('all') - if __name__ == "__main__": main() \ No newline at end of file diff --git a/src/hirad/inference/generator.py b/src/hirad/inference/generator.py new file mode 100644 index 00000000..3b9a5388 --- /dev/null +++ b/src/hirad/inference/generator.py @@ -0,0 +1,141 @@ +from typing import Callable +from functools import partial +import nvtx +import numpy as np +import torch +from torch.distributed import gather +from hirad.utils.inference_utils import regression_step, diffusion_step +from hirad.distributed import DistributedManager +from hirad.utils.patching import GridPatching2D +from hirad.inference import stochastic_sampler, deterministic_sampler + +class Generator(): + def __init__(self, + net_reg: torch.nn.Module, + net_res: torch.nn.Module, + batch_size: int, + ensemble_size: int, + hr_mean_conditioning: bool, + n_out_channels: int, + inference_mode: str, + dist: DistributedManager, + ): + + self.net_reg = net_reg + self.net_res = net_res + self.batch_size = batch_size + self.hr_mean_conditioning = hr_mean_conditioning + self.n_out_channels = n_out_channels + self.inference_mode = inference_mode + self.ensemble_size = ensemble_size + self.dist = dist + self.get_rank_batches() + self.patching = None + + def get_rank_batches(self): + seeds = list(np.arange(self.ensemble_size)) + num_batches = ( + (len(seeds) - 1) // (self.batch_size * self.dist.world_size) + 1 + ) * self.dist.world_size + all_batches = torch.as_tensor(seeds).tensor_split(num_batches) + self.rank_batches = all_batches[self.dist.rank :: self.dist.world_size] + + def initialize_sampler(self, sampler_type, **sampler_args): + if sampler_type == "deterministic": + if self.hr_mean_conditioning: + raise NotImplementedError( + "High-res mean conditioning is not yet implemented for the deterministic sampler" + ) + self.sampler = partial( + deterministic_sampler, + **sampler_args + ) + elif sampler_type == "stochastic": + self.sampler = partial(stochastic_sampler, patching=self.patching) + else: + raise ValueError(f"Unknown sampling method {sampler_type}") + + def initialize_patching(self, img_shape, patch_shape, boundary_pix, overlap_pix): + self.patching = GridPatching2D( + img_shape=img_shape, + patch_shape=patch_shape, + boundary_pix=boundary_pix, + overlap_pix=overlap_pix, + ) + + def generate(self, image_lr, lead_time_label=None): + with nvtx.annotate("generate_fn", color="green"): + # (1, C, H, W) + image_lr = image_lr.to(memory_format=torch.channels_last) + img_shape = image_lr.shape[-2:] + + if self.net_reg: + with nvtx.annotate("regression_model", color="yellow"): + image_reg = regression_step( + net=self.net_reg, + img_lr=image_lr, + latents_shape=( + self.batch_size, + self.n_out_channels, + img_shape[0], + img_shape[1], + ), # (batch_size, C, H, W) + lead_time_label=lead_time_label, + ) + if self.net_res: + if self.hr_mean_conditioning: + mean_hr = image_reg[0:1] + else: + mean_hr = None + with nvtx.annotate("diffusion model", color="purple"): + image_res = diffusion_step( + net=self.net_res, + sampler_fn=self.sampler, + img_shape=img_shape, + img_out_channels=self.n_out_channels, + rank_batches=self.rank_batches, + img_lr=image_lr.expand( + self.batch_size, -1, -1, -1 + ).to(memory_format=torch.channels_last), #.to(memory_format=torch.channels_last), + rank=self.dist.rank, + device=image_lr.device, + mean_hr=mean_hr, + lead_time_label=lead_time_label, + ) + if self.inference_mode == "regression": + image_out = image_reg + elif self.inference_mode == "diffusion": + image_out = image_res + else: + image_out = image_reg[0:1,::] + image_res + + # Gather tensors on rank 0 + if self.dist.world_size > 1: + if self.dist.rank == 0: + gathered_tensors = [ + torch.zeros_like( + image_out, dtype=image_out.dtype, device=image_out.device + ) + for _ in range(self.dist.world_size) + ] + else: + gathered_tensors = None + + torch.distributed.barrier() + gather( + image_out, + gather_list=gathered_tensors if self.dist.rank == 0 else None, + dst=0, + ) + + if self.dist.rank == 0: + if self.inference_mode != "regression": + return torch.cat(gathered_tensors), image_reg[0:1,::] + return torch.cat(gathered_tensors), None + else: + return None, None + else: + #TODO do this for multi-gpu setting above too + if self.inference_mode != "regression": + return image_out, image_reg + return image_out, None diff --git a/src/hirad/train_diffusion.sh b/src/hirad/train_diffusion.sh index cf2f88f3..d8515db3 100644 --- a/src/hirad/train_diffusion.sh +++ b/src/hirad/train_diffusion.sh @@ -1,25 +1,23 @@ #!/bin/bash -#SBATCH --job-name="testrun" +#SBATCH --job-name="corrdiff-second-stage" ### HARDWARE ### -#SBATCH --partition=debug -#SBATCH --nodes=1 -#SBATCH --ntasks-per-node=1 -#SBATCH --gpus-per-node=1 +#SBATCH --partition=normal +#SBATCH --nodes=2 +#SBATCH --ntasks-per-node=4 +#SBATCH --gpus-per-node=4 #SBATCH --cpus-per-task=72 -#SBATCH --time=00:30:00 +#SBATCH --time=24:00:00 #SBATCH --no-requeue #SBATCH --exclusive ### OUTPUT ### -#SBATCH --output=/iopsstor/scratch/cscs/pstamenk/logs/diffusion.log -#SBATCH --error=/iopsstor/scratch/cscs/pstamenk/logs/diffusion.err +#SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/diffusion_full.log +#SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/diffusion_full.err ### ENVIRONMENT #### -#SBATCH --uenv=pytorch/v2.6.0:/user-environment -#SBATCH --view=default -#SBATCH -A a-a122 +#SBATCH -A c38 # Choose method to initialize dist in pythorch export DISTRIBUTED_INITIALIZATION_METHOD=SLURM @@ -32,14 +30,16 @@ export MASTER_ADDR export MASTER_PORT=29500 # Get number of physical cores using Python -PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") -LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} -# Compute cores per process -OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) -export OMP_NUM_THREADS=$OMP_THREADS +# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") +# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} +# # Compute cores per process +# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) +# export OMP_NUM_THREADS=$OMP_THREADS +export OMP_NUM_THREADS=72 # python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml -srun bash -c " - . ./train_env/bin/activate +srun --container-writable --environment=modulus_env bash -c " + cd HiRAD-Gen + pip install -e . --no-dependencies python src/hirad/training/train.py --config-name=training_era_cosmo_diffusion.yaml " \ No newline at end of file diff --git a/src/hirad/train_regression.sh b/src/hirad/train_regression.sh index c0654773..7499bcfd 100644 --- a/src/hirad/train_regression.sh +++ b/src/hirad/train_regression.sh @@ -1,25 +1,23 @@ #!/bin/bash -#SBATCH --job-name="testrun" +#SBATCH --job-name="corrdiff-first-stage" ### HARDWARE ### -#SBATCH --partition=debug -#SBATCH --nodes=1 -#SBATCH --ntasks-per-node=1 -#SBATCH --gpus-per-node=1 +#SBATCH --partition=normal +#SBATCH --nodes=2 +#SBATCH --ntasks-per-node=4 +#SBATCH --gpus-per-node=4 #SBATCH --cpus-per-task=72 -#SBATCH --time=00:30:00 +#SBATCH --time=06:00:00 #SBATCH --no-requeue #SBATCH --exclusive ### OUTPUT ### -#SBATCH --output=/iopsstor/scratch/cscs/pstamenk/logs/regression.log -#SBATCH --error=/iopsstor/scratch/cscs/pstamenk/logs/regression.err +#SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/regression_full_run.log +#SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/regression_full_run.err ### ENVIRONMENT #### -#SBATCH --uenv=pytorch/v2.6.0:/user-environment -#SBATCH --view=default -#SBATCH -A a-a122 +#SBATCH -A c38 # Choose method to initialize dist in pythorch export DISTRIBUTED_INITIALIZATION_METHOD=SLURM @@ -32,14 +30,19 @@ export MASTER_ADDR export MASTER_PORT=29500 # Get number of physical cores using Python -PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") -LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} -# Compute cores per process -OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) -export OMP_NUM_THREADS=$OMP_THREADS - +# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") +# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} +# # Compute cores per process +# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) +# export OMP_NUM_THREADS=$OMP_THREADS +export OMP_NUM_THREADS=72 # python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml -srun bash -c " - . ./train_env/bin/activate +# srun bash -c " +# . ./train_env/bin/activate +# python src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml +# " +srun --container-writable --environment=modulus_env bash -c " + cd HiRAD-Gen + pip install -e . --no-dependencies python src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml " \ No newline at end of file diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 12b6942c..7ea7a71c 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -500,7 +500,6 @@ def main(cfg: DictConfig) -> None: / num_accumulation_rounds / len(patch_nums_iter) ) - loss_accum += loss / num_accumulation_rounds with nvtx.annotate(f"loss backward", color="yellow"): loss.backward() diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index 1dcfe5ec..7698a82a 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -15,12 +15,17 @@ # limitations under the License. from typing import Optional +import os import nvtx +import numpy as np import torch import tqdm +from matplotlib import pyplot as plt +import cartopy.crs as ccrs from .function_utils import StackedRandomGenerator +from hirad.eval import compute_mae, average_power_spectrum, plot_error_projection, plot_power_spectra ############################################################################ # CorrDiff Generation Utilities # @@ -194,3 +199,89 @@ def diffusion_step( ) all_images.append(images) return torch.cat(all_images) + + +def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): + + os.makedirs(output_path, exist_ok=True) + + longitudes = dataset.longitude() + latitudes = dataset.latitude() + input_channels = dataset.input_channels() + output_channels = dataset.output_channels() + + target = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze()),1) #.reshape(len(output_channels),-1) + prediction = np.flip(dataset.denormalize_output(image_pred[-1,::].squeeze()),1) #.reshape(len(output_channels),-1) + baseline = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze()),1)# .reshape(len(input_channels),-1) + if mean_pred is not None: + mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze()),1) #.reshape(len(output_channels),-1) + + + freqs = {} + power = {} + for idx, channel in enumerate(output_channels): + input_channel_idx = input_channels.index(channel) + + if channel.name=="tp": + target[idx,::] = _prepare_precipitaiton(target[idx,:,:]) + prediction[idx,::] = _prepare_precipitaiton(prediction[idx,:,:]) + baseline[input_channel_idx,:,:] = _prepare_precipitaiton(baseline[input_channel_idx]) + if mean_pred is not None: + mean_pred[idx,::] = _prepare_precipitaiton(mean_pred[idx,::]) + + _plot_projection(longitudes, latitudes, target[idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-target.jpg')) + _plot_projection(longitudes, latitudes, prediction[idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-prediction.jpg')) + _plot_projection(longitudes, latitudes, baseline[input_channel_idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-input.jpg')) + if mean_pred is not None: + _plot_projection(longitudes, latitudes, mean_pred[idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-mean_prediction.jpg')) + + _, baseline_errors = compute_mae(baseline[input_channel_idx,:,:], target[idx,:,:]) + _, prediction_errors = compute_mae(prediction[idx,:,:], target[idx,:,:]) + if mean_pred is not None: + _, mean_prediction_errors = compute_mae(mean_pred[idx,:,:], target[idx,:,:]) + + + plot_error_projection(baseline_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path, f'{time_step}-{channel.name}-baseline-error.jpg')) + plot_error_projection(prediction_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path, f'{time_step}-{channel.name}-prediction-error.jpg')) + if mean_pred is not None: + plot_error_projection(mean_prediction_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path, f'{time_step}-{channel.name}-mean-prediction-error.jpg')) + + b_freq, b_power = average_power_spectrum(baseline[input_channel_idx,:,:].squeeze(), 2.0) + freqs['baseline'] = b_freq + power['baseline'] = b_power + #plotting.plot_power_spectrum(b_freq, b_power, target_channels[t_c], os.path.join('plots/spectra/baseline2dt', target_channels[t_c] + '-all_dates')) + t_freq, t_power = average_power_spectrum(target[idx,:,:].squeeze(), 2.0) + freqs['target'] = t_freq + power['target'] = t_power + p_freq, p_power = average_power_spectrum(prediction[idx,:,:].squeeze(), 2.0) + freqs['prediction'] = p_freq + power['prediction'] = p_power + if mean_pred is not None: + mp_freq, mp_power = average_power_spectrum(mean_pred[idx,:,:].squeeze(), 2.0) + freqs['mean_prediction'] = mp_freq + power['mean_prediction'] = mp_power + plot_power_spectra(freqs, power, channel.name, os.path.join(output_path, f'{time_step}-{channel.name}-spectra.jpg')) + + +def _prepare_precipitaiton(precip_array): + precip_array = np.clip(precip_array, 0, None) + epsilon = 1e-2 + precip_array = precip_array + epsilon + precip_array = np.log(precip_array) + # log_min, log_max = precip_array.min(), precip_array.max() + # precip_array = (precip_array-log_min)/(log_max-log_min) + return precip_array + + +def _plot_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, cmap=None, vmin = None, vmax = None): + + """Plot observed or interpolated data in a scatter plot.""" + # TODO: Refactor this somehow, it's not really generalizing well across variables. + fig = plt.figure() + fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) + p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) + ax.coastlines() + ax.gridlines(draw_labels=True) + plt.colorbar(p, label="K", orientation="horizontal") + plt.savefig(filename) + plt.close('all') \ No newline at end of file From be6170dfd5302157f021254dd3b2200c0a772c14 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 1 Jul 2025 18:34:13 +0200 Subject: [PATCH 059/302] Add some initial CRPS code --- src/hirad/eval/__init__.py | 2 +- src/hirad/eval/metrics.py | 23 +++++++++++++++++++++++ src/hirad/generate.sh | 5 +++-- src/hirad/utils/inference_utils.py | 9 ++++++++- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/hirad/eval/__init__.py b/src/hirad/eval/__init__.py index 13d9eb37..a4b94751 100644 --- a/src/hirad/eval/__init__.py +++ b/src/hirad/eval/__init__.py @@ -1,2 +1,2 @@ -from .metrics import compute_mae, average_power_spectrum +from .metrics import compute_mae, average_power_spectrum, crps from .plotting import plot_error_projection, plot_power_spectra diff --git a/src/hirad/eval/metrics.py b/src/hirad/eval/metrics.py index 1170ea16..e508cf2f 100644 --- a/src/hirad/eval/metrics.py +++ b/src/hirad/eval/metrics.py @@ -4,6 +4,8 @@ import torch from scipy.signal import periodogram +import xskillscore +import xarray as xr # set up MAE calculation to be run for each channel for a given date/time (for target COSMO, prediction, and ERA interpolated) @@ -55,3 +57,24 @@ def average_power_spectrum(data: np.ndarray, d=2.0): # d=2km by default logging.info(f'power spectra shape={power_spectra.shape}') return freqs, power_spectra + +def crps(prediction_ensemble, target, average_over_area=True, average_over_channels=True): + # Plot CRPS + observations = xr.DataArray(target, + coords = [('channel', np.arange(target.shape[0])), + ('x', np.arange(target.shape[1])), + ('y', np.arange(target.shape[2]))]) + + forecasts = xr.DataArray(prediction_ensemble, + coords = [('member', np.arange(prediction_ensemble.shape[0])), + ('channel', np.arange(prediction_ensemble.shape[1])), + ('x', np.arange(prediction_ensemble.shape[2])), + ('y', np.arange(prediction_ensemble.shape[3]))]) + dim = [] + if average_over_area: + dim.append('x') + dim.append('y') + if average_over_channels: + dim.append('channel') + crps = xskillscore.crps_ensemble(observations=observations, forecasts=forecasts, dim=dim) + crps = crps.to_numpy() diff --git a/src/hirad/generate.sh b/src/hirad/generate.sh index 2607431b..7237d2ad 100644 --- a/src/hirad/generate.sh +++ b/src/hirad/generate.sh @@ -12,8 +12,8 @@ #SBATCH --exclusive ### OUTPUT ### -#SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/regression_generation.log -#SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/regression_generation.err +#SBATCH --output=/capstor/scratch/cscs/$USER/logs/regression_generation.log +#SBATCH --error=/capstor/scratch/cscs/$USER/logs/regression_generation.err ### ENVIRONMENT #### #SBATCH -A c38 @@ -47,5 +47,6 @@ srun --container-writable --environment=modulus_env bash -c " cd HiRAD-Gen pip install -e . --no-dependencies pip install Cartopy==0.22.0 + pip install xskillscore python src/hirad/inference/generate.py --config-name=generate_era_cosmo.yaml " \ No newline at end of file diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index 7698a82a..515e0009 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -25,7 +25,7 @@ import cartopy.crs as ccrs from .function_utils import StackedRandomGenerator -from hirad.eval import compute_mae, average_power_spectrum, plot_error_projection, plot_power_spectra +from hirad.eval import compute_mae, average_power_spectrum, plot_error_projection, plot_power_spectra, crps ############################################################################ # CorrDiff Generation Utilities # @@ -211,12 +211,19 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, output_channels = dataset.output_channels() target = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze()),1) #.reshape(len(output_channels),-1) + # prediction.shape = (num_channels, X, Y) prediction = np.flip(dataset.denormalize_output(image_pred[-1,::].squeeze()),1) #.reshape(len(output_channels),-1) + # prediction_ensemble.shape = (num_ensembles, num_channels, X, Y) + prediction_ensemble = np.flip(dataset.denormalize_output(image_pred.squeeze()),1) #.reshape(len(output_channels),-1) baseline = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze()),1)# .reshape(len(input_channels),-1) if mean_pred is not None: mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze()),1) #.reshape(len(output_channels),-1) + # Plot CRPS + crps_score = crps(prediction_ensemble, target, average_over_area=False, average_over_channels=True) + _plot_projection(longitudes, latitudes, crps_score, os.path.join(output_path, f'{time_step}-crps.jpg')) + # Plot power spectra freqs = {} power = {} for idx, channel in enumerate(output_channels): From 833242b2021cff35b382cdbb904779d2efce3586 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 2 Jul 2025 12:11:24 +0200 Subject: [PATCH 060/302] Fixes to CRPS --- src/hirad/eval/metrics.py | 1 + src/hirad/generate.sh | 4 ++-- src/hirad/utils/inference_utils.py | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/hirad/eval/metrics.py b/src/hirad/eval/metrics.py index e508cf2f..6b2a4b2d 100644 --- a/src/hirad/eval/metrics.py +++ b/src/hirad/eval/metrics.py @@ -78,3 +78,4 @@ def crps(prediction_ensemble, target, average_over_area=True, average_over_chann dim.append('channel') crps = xskillscore.crps_ensemble(observations=observations, forecasts=forecasts, dim=dim) crps = crps.to_numpy() + return crps diff --git a/src/hirad/generate.sh b/src/hirad/generate.sh index 7237d2ad..3876ade3 100644 --- a/src/hirad/generate.sh +++ b/src/hirad/generate.sh @@ -12,8 +12,8 @@ #SBATCH --exclusive ### OUTPUT ### -#SBATCH --output=/capstor/scratch/cscs/$USER/logs/regression_generation.log -#SBATCH --error=/capstor/scratch/cscs/$USER/logs/regression_generation.err +#SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/regression_generation.log +#SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/regression_generation.err ### ENVIRONMENT #### #SBATCH -A c38 diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index 515e0009..699d7b67 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -214,7 +214,10 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, # prediction.shape = (num_channels, X, Y) prediction = np.flip(dataset.denormalize_output(image_pred[-1,::].squeeze()),1) #.reshape(len(output_channels),-1) # prediction_ensemble.shape = (num_ensembles, num_channels, X, Y) - prediction_ensemble = np.flip(dataset.denormalize_output(image_pred.squeeze()),1) #.reshape(len(output_channels),-1) + prediction_ensemble = np.ndarray(image_pred.shape) + for i in range(image_pred.shape[0]): + prediction_ensemble[i,::] = np.flip(dataset.denormalize_output(image_pred[i,::].squeeze()),1) + prediction_ensemble = np.flip(dataset.denormalize_output(image_pred.squeeze()),2) #.reshape(len(output_channels),-1) baseline = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze()),1)# .reshape(len(input_channels),-1) if mean_pred is not None: mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze()),1) #.reshape(len(output_channels),-1) From 68ab63e4efb09765e84d06776b53ed8a2fd2686a Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 2 Jul 2025 12:49:48 +0200 Subject: [PATCH 061/302] Adds time capability to crps --- src/hirad/eval/metrics.py | 28 +++++++++++++++++++++------- src/hirad/utils/inference_utils.py | 1 - 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/hirad/eval/metrics.py b/src/hirad/eval/metrics.py index 6b2a4b2d..01594d62 100644 --- a/src/hirad/eval/metrics.py +++ b/src/hirad/eval/metrics.py @@ -58,19 +58,33 @@ def average_power_spectrum(data: np.ndarray, d=2.0): # d=2km by default return freqs, power_spectra -def crps(prediction_ensemble, target, average_over_area=True, average_over_channels=True): - # Plot CRPS +def crps(prediction_ensemble, target, average_over_area=True, average_over_channels=True, average_over_time=True): + # Assumes that prediction_ensemble is in form: + # (member, channel, x, y) or + # (time, member, channel, x, y) + # Returns: a k-dimensional array of continuous ranked probability scores, + # where k is the number of dimensions that were not averaged over. + # For example, if average_over_area is False (and all others true), will + # return an ndarray of shape (X,Y) observations = xr.DataArray(target, coords = [('channel', np.arange(target.shape[0])), ('x', np.arange(target.shape[1])), ('y', np.arange(target.shape[2]))]) - forecasts = xr.DataArray(prediction_ensemble, - coords = [('member', np.arange(prediction_ensemble.shape[0])), - ('channel', np.arange(prediction_ensemble.shape[1])), - ('x', np.arange(prediction_ensemble.shape[2])), - ('y', np.arange(prediction_ensemble.shape[3]))]) + forecasts_coords = [('member', np.arange(prediction_ensemble.shape[-4])), + ('channel', np.arange(prediction_ensemble.shape[-3])), + ('x', np.arange(prediction_ensemble.shape[-2])), + ('y', np.arange(prediction_ensemble.shape[-1]))] + + if prediction_ensemble.ndim > 4: + forecasts.coords.insert(0, ('time', np.arange(prediction_ensemble.shape[-5]))) + + + forecasts = xr.DataArray(prediction_ensemble, coords = forecasts_coords) + dim = [] + if prediction_ensemble.ndim > 4 and average_over_time: + dim.append('time') if average_over_area: dim.append('x') dim.append('y') diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index 699d7b67..af6fda5f 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -292,6 +292,5 @@ def _plot_projection(longitudes: np.array, latitudes: np.array, values: np.array p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) ax.coastlines() ax.gridlines(draw_labels=True) - plt.colorbar(p, label="K", orientation="horizontal") plt.savefig(filename) plt.close('all') \ No newline at end of file From 826601537092d05a706214bc8e1c8bc5a58112b0 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 2 Jul 2025 13:23:11 +0200 Subject: [PATCH 062/302] Plot CRPS per channel --- src/hirad/utils/inference_utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index af6fda5f..9047bcd1 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -224,7 +224,10 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, # Plot CRPS crps_score = crps(prediction_ensemble, target, average_over_area=False, average_over_channels=True) - _plot_projection(longitudes, latitudes, crps_score, os.path.join(output_path, f'{time_step}-crps.jpg')) + _plot_projection(longitudes, latitudes, crps_score, os.path.join(output_path, f'{time_step}-crps-all.jpg')) + crps_score_channels = crps(prediction_ensemble, target, average_over_area=False, average_over_channels=False) + for channel_num in range(crps_score_channels.shape[0]): + _plot_projection(longitudes, latitudes, crps_score_channels[channel_num,::], os.path.join(output_path, f'{time_step}-crps-{output_channels[channel_num].name}.jpg')) # Plot power spectra freqs = {} @@ -292,5 +295,6 @@ def _plot_projection(longitudes: np.array, latitudes: np.array, values: np.array p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) ax.coastlines() ax.gridlines(draw_labels=True) + plt.colorbar(p, orientation="horizontal") plt.savefig(filename) plt.close('all') \ No newline at end of file From a89fa4684fe25fd513ad69e839db023124bcbc56 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Fri, 4 Jul 2025 10:01:00 +0200 Subject: [PATCH 063/302] Adds plotting CRPS over time (not yet tested) --- src/hirad/eval/metrics.py | 15 +++++++----- src/hirad/utils/inference_utils.py | 38 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/hirad/eval/metrics.py b/src/hirad/eval/metrics.py index 01594d62..add4023f 100644 --- a/src/hirad/eval/metrics.py +++ b/src/hirad/eval/metrics.py @@ -66,21 +66,24 @@ def crps(prediction_ensemble, target, average_over_area=True, average_over_chann # where k is the number of dimensions that were not averaged over. # For example, if average_over_area is False (and all others true), will # return an ndarray of shape (X,Y) - observations = xr.DataArray(target, - coords = [('channel', np.arange(target.shape[0])), - ('x', np.arange(target.shape[1])), - ('y', np.arange(target.shape[2]))]) + target_coords = [('channel', np.arange(target.shape[-3])), + ('x', np.arange(target.shape[-2])), + ('y', np.arange(target.shape[-1]))] + forecasts_coords = [('member', np.arange(prediction_ensemble.shape[-4])), ('channel', np.arange(prediction_ensemble.shape[-3])), ('x', np.arange(prediction_ensemble.shape[-2])), ('y', np.arange(prediction_ensemble.shape[-1]))] - if prediction_ensemble.ndim > 4: - forecasts.coords.insert(0, ('time', np.arange(prediction_ensemble.shape[-5]))) + if prediction_ensemble.ndim > 4 and target.ndim > 3: + forecasts_coords.insert(0, ('time', np.arange(prediction_ensemble.shape[-5]))) + target_coords.insert(0, ('time', np.arange(target.shape[-4]))) + forecasts = xr.DataArray(prediction_ensemble, coords = forecasts_coords) + observations = xr.DataArray(target, coords = target_coords) dim = [] if prediction_ensemble.ndim > 4 and average_over_time: diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index 9047bcd1..f90b8a0b 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -201,6 +201,26 @@ def diffusion_step( return torch.cat(all_images) +def save_results_as_torch(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): + + os.makedirs(output_path, exist_ok=True) + + target = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze()),1) #.reshape(len(output_channels),-1) + # prediction.shape = (num_channels, X, Y) + # prediction = np.flip(dataset.denormalize_output(image_pred[-1,::].squeeze()),1) #.reshape(len(output_channels),-1) + # prediction_ensemble.shape = (num_ensembles, num_channels, X, Y) + prediction_ensemble = np.ndarray(image_pred.shape) + for i in range(image_pred.shape[0]): + prediction_ensemble[i,::] = np.flip(dataset.denormalize_output(image_pred[i,::].squeeze()),1) + prediction_ensemble = np.flip(dataset.denormalize_output(image_pred.squeeze()),2) #.reshape(len(output_channels),-1) + baseline = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze()),1)# .reshape(len(input_channels),-1) + if mean_pred is not None: + mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze()),1) #.reshape(len(output_channels),-1) + torch.save(target, os.path.join(output_path, f'{time_step}-target')) + torch.save(prediction_ensemble, os.path.join(output_path, f'{time_step}-predictions')) + torch.save(baseline, os.path.join(output_path, f'{time_step}-baseline')) + + def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): os.makedirs(output_path, exist_ok=True) @@ -275,6 +295,24 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, power['mean_prediction'] = mp_power plot_power_spectra(freqs, power, channel.name, os.path.join(output_path, f'{time_step}-{channel.name}-spectra.jpg')) +def plot_crps_over_time(times, dataset, output_path): + longitudes = dataset.longitude() + latitudes = dataset.latitude() + input_channels = dataset.input_channels() + output_channels = dataset.output_channels() + + prediction_ensemble = torch.load(os.join(output_path, f'{times[0]}-predictions')) + all_predictions = np.ndarray((len(times), prediction_ensemble.shape[0], prediction_ensemble.shape[1], prediction_ensemble.shape[2], prediction_ensemble.shape[3])) + all_targets = np.ndarray(len(times), prediction_ensemble.shape[1], prediction_ensemble.shape[2], prediction_ensemble.shape[3]) + for i in range(len(times)): + prediction_ensemble = torch.load(os.join(output_path, f'{times[i]}-predictions')) + all_predictions[i,::] = prediction_ensemble + target = torch.load(os.join(output_path, f'{times[i]}-target')) + all_targets[i,::] = target + score_over_time_channels = crps_score = crps(all_predictions, all_targets, average_over_area=True, average_over_channels=False, average_over_time=False) + score_over_area_channels = crps(all_predictions, all_targets, average_over_area=False, average_over_channels=False, average_over_time=True) + for channel_num in range(score_over_area_channels.shape[0]): + _plot_projection(longitudes, latitudes, score_over_area_channels[channel_num,::], os.path.join(output_path, f'all-time-crps-{output_channels[channel_num].name}.jpg')) def _prepare_precipitaiton(precip_array): precip_array = np.clip(precip_array, 0, None) From 99b3b3770673fc57ef91e36a968290ce93637c98 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 8 Jul 2025 12:58:06 +0200 Subject: [PATCH 064/302] Add plotting CRPS across time as well as area. --- src/hirad/inference/generate.py | 23 +++++++++++++++++++++-- src/hirad/utils/inference_utils.py | 28 +++++++++++++++++++++------- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index 77e78b22..7696f7f6 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -13,7 +13,7 @@ from hirad.models import EDMPrecondSuperResolution, UNet from hirad.inference import Generator -from hirad.utils.inference_utils import save_images +from hirad.utils.inference_utils import save_images, save_results_as_torch, plot_crps_over_time from hirad.utils.function_utils import get_time_from_range from hirad.utils.checkpoint import load_checkpoint @@ -246,9 +246,23 @@ def elapsed_time(self, _): if dist.rank == 0: batch_size = image_out.shape[0] # write out data in a seperate thread so we don't hold up inferencing + + if not cfg.generation.times_range: + writer_threads.append( + writer_executor.submit( + save_images, + output_path, + times[sampler[time_index]], + dataset, + image_out.cpu().numpy(), + image_tar.cpu().numpy(), + image_lr.cpu().numpy(), + image_reg.cpu().numpy() if image_reg is not None else None, + ) + ) writer_threads.append( writer_executor.submit( - save_images, + save_results_as_torch, output_path, times[sampler[time_index]], dataset, @@ -284,6 +298,11 @@ def elapsed_time(self, _): f.close() logger0.info("Generation Completed.") + if cfg.generation.times_range: + # reassign times + times = get_time_from_range(cfg.generation.times_range, time_format="%Y%m%d-%H%M") #TODO check what time formats we are using and adapt + plot_crps_over_time(times, dataset, output_path) + if __name__ == "__main__": main() \ No newline at end of file diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index f90b8a0b..a57a107c 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -16,6 +16,7 @@ from typing import Optional import os +import logging import nvtx import numpy as np @@ -295,24 +296,37 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, power['mean_prediction'] = mp_power plot_power_spectra(freqs, power, channel.name, os.path.join(output_path, f'{time_step}-{channel.name}-spectra.jpg')) -def plot_crps_over_time(times, dataset, output_path): +def plot_crps_over_time(times, dataset, output_path): longitudes = dataset.longitude() latitudes = dataset.latitude() input_channels = dataset.input_channels() output_channels = dataset.output_channels() + start_time=times[0] + end_time=times[-1] - prediction_ensemble = torch.load(os.join(output_path, f'{times[0]}-predictions')) + prediction_ensemble = torch.load(os.path.join(output_path, f'{times[0]}-predictions'), weights_only=False) all_predictions = np.ndarray((len(times), prediction_ensemble.shape[0], prediction_ensemble.shape[1], prediction_ensemble.shape[2], prediction_ensemble.shape[3])) - all_targets = np.ndarray(len(times), prediction_ensemble.shape[1], prediction_ensemble.shape[2], prediction_ensemble.shape[3]) + all_targets = np.ndarray((len(times), prediction_ensemble.shape[1], prediction_ensemble.shape[2], prediction_ensemble.shape[3])) for i in range(len(times)): - prediction_ensemble = torch.load(os.join(output_path, f'{times[i]}-predictions')) + prediction_ensemble = torch.load(os.path.join(output_path, f'{times[i]}-predictions'), weights_only=False) all_predictions[i,::] = prediction_ensemble - target = torch.load(os.join(output_path, f'{times[i]}-target')) + target = torch.load(os.path.join(output_path, f'{times[i]}-target'), weights_only=False) all_targets[i,::] = target - score_over_time_channels = crps_score = crps(all_predictions, all_targets, average_over_area=True, average_over_channels=False, average_over_time=False) + score_over_time_channels = crps(all_predictions, all_targets, average_over_area=True, average_over_channels=False, average_over_time=False) score_over_area_channels = crps(all_predictions, all_targets, average_over_area=False, average_over_channels=False, average_over_time=True) for channel_num in range(score_over_area_channels.shape[0]): - _plot_projection(longitudes, latitudes, score_over_area_channels[channel_num,::], os.path.join(output_path, f'all-time-crps-{output_channels[channel_num].name}.jpg')) + _plot_projection(longitudes, latitudes, score_over_area_channels[channel_num,::], os.path.join(output_path, f'crps-time-{start_time}-{end_time}-{output_channels[channel_num].name}.jpg')) + _plot_score_vs_t(score_over_time_channels[:, channel_num], times, os.path.join(output_path, f'crps-area-{start_time}-{end_time}-{output_channels[channel_num].name}.jpg')) + +def _plot_score_vs_t(score: np.array, times: np.array, filename: str): + fig = plt.figure() + ax = plt.subplot() + p = plt.plot(times, score) + #plt.ylabel('CRPS') + #plt.xlabel('time') + plt.xticks([times[0],times[-1]]) + plt.savefig(filename) + plt.close('all') def _prepare_precipitaiton(precip_array): precip_array = np.clip(precip_array, 0, None) From 88ee662293e1bf3606652d59049eb9e369fed1e6 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 8 Jul 2025 13:08:37 +0200 Subject: [PATCH 065/302] add visualization during training --- src/hirad/inference/generate.py | 4 +- src/hirad/inference/generator.py | 1 - src/hirad/inference/stochastic_sampler.py | 9 +- src/hirad/models/layers.py | 4 +- src/hirad/training/train.py | 139 +++++++++++++++++++++- src/hirad/utils/inference_utils.py | 84 +++++++++---- 6 files changed, 207 insertions(+), 34 deletions(-) diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index 77e78b22..804e1414 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -230,6 +230,8 @@ def elapsed_time(self, _): if time_index == warmup_steps: start.record() + savedir = os.path.join(output_path,f"{times[sampler[time_index]]}") + os.makedirs(savedir,exist_ok=True) # continue if lead_time_label: lead_time_label = lead_time_label[0].to(dist.device).contiguous() @@ -249,7 +251,7 @@ def elapsed_time(self, _): writer_threads.append( writer_executor.submit( save_images, - output_path, + savedir, times[sampler[time_index]], dataset, image_out.cpu().numpy(), diff --git a/src/hirad/inference/generator.py b/src/hirad/inference/generator.py index 3b9a5388..b1e10e1f 100644 --- a/src/hirad/inference/generator.py +++ b/src/hirad/inference/generator.py @@ -66,7 +66,6 @@ def initialize_patching(self, img_shape, patch_shape, boundary_pix, overlap_pix) def generate(self, image_lr, lead_time_label=None): with nvtx.annotate("generate_fn", color="green"): # (1, C, H, W) - image_lr = image_lr.to(memory_format=torch.channels_last) img_shape = image_lr.shape[-2:] if self.net_reg: diff --git a/src/hirad/inference/stochastic_sampler.py b/src/hirad/inference/stochastic_sampler.py index 198fde43..606c911b 100644 --- a/src/hirad/inference/stochastic_sampler.py +++ b/src/hirad/inference/stochastic_sampler.py @@ -130,8 +130,8 @@ def stochastic_sampler( # Adjust noise levels based on what's supported by the network. # Proposed EDM sampler (Algorithm 2) with minor changes to enable super-resolution. - sigma_min = max(sigma_min, net.sigma_min) - sigma_max = min(sigma_max, net.sigma_max) + sigma_min = max(sigma_min, net.module.sigma_min if hasattr(net, "module") else net.sigma_min) + sigma_max = min(sigma_max, net.module.sigma_max if hasattr(net, "module") else net.sigma_max) if patching is not None and not isinstance(patching, GridPatching2D): raise ValueError("patching must be an instance of GridPatching2D.") @@ -162,7 +162,8 @@ def stochastic_sampler( * (sigma_min ** (1 / rho) - sigma_max ** (1 / rho)) ) ** rho t_steps = torch.cat( - [net.round_sigma(t_steps), torch.zeros_like(t_steps[:1])] + [net.module.round_sigma(t_steps) if hasattr(net, "module") else net.round_sigma(t_steps), + torch.zeros_like(t_steps[:1])] ) # t_N = 0 batch_size = img_lr.shape[0] @@ -198,7 +199,7 @@ def patch_embedding_selector(emb): x_cur = x_next # Increase noise temporarily. gamma = S_churn / num_steps if S_min <= t_cur <= S_max else 0 - t_hat = net.round_sigma(t_cur + gamma * t_cur) + t_hat = net.module.round_sigma(t_cur + gamma * t_cur) if hasattr(net, "module") else net.round_sigma(t_cur + gamma * t_cur) x_hat = x_cur + (t_hat**2 - t_cur**2).sqrt() * S_noise * randn_like(x_cur) diff --git a/src/hirad/models/layers.py b/src/hirad/models/layers.py index 96bb37f3..4da26b1f 100644 --- a/src/hirad/models/layers.py +++ b/src/hirad/models/layers.py @@ -26,7 +26,7 @@ import numpy as np import nvtx import torch -import torch.cuda.amp as amp +import torch.amp as amp from einops import rearrange from torch.nn.functional import elu, gelu, leaky_relu, relu, sigmoid, silu, tanh @@ -700,7 +700,7 @@ def forward(self, x, emb): # w = AttentionOp.apply(q, k) # a = torch.einsum("nqk,nck->ncq", w, v) # Compute attention in one step - with amp.autocast(enabled=self.amp_mode): + with amp.autocast(x.device.type, enabled=self.amp_mode): attn = torch.nn.functional.scaled_dot_product_attention(q, k, v) x = self.proj(attn.reshape(*x.shape)).add_(x) x = x * self.skip_scale diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 7ea7a71c..931fa664 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -1,6 +1,8 @@ import os import time +from concurrent.futures import ThreadPoolExecutor + import psutil import hydra from omegaconf import DictConfig, OmegaConf @@ -20,11 +22,12 @@ is_time_for_periodic_task, handle_and_clip_gradients from hirad.utils.checkpoint import load_checkpoint, save_checkpoint from hirad.utils.patching import RandomPatching2D -from hirad.models import UNet, EDMPrecondSuperResolution, EDMPrecondSR +from hirad.utils.function_utils import get_time_from_range +from hirad.utils.inference_utils import save_images +from hirad.models import UNet, EDMPrecondSuperResolution from hirad.losses import ResidualLoss, RegressionLoss, RegressionLossCE -from hirad.datasets import init_train_valid_datasets_from_config - -from matplotlib import pyplot as plt +from hirad.datasets import init_train_valid_datasets_from_config, get_dataset_and_sampler_inference +from hirad.inference import Generator torch._dynamo.reset() # Increase the cache size limit @@ -85,6 +88,14 @@ def main(cfg: DictConfig) -> None: ) if dist.rank==0 and not os.path.exists(checkpoint_dir): os.makedirs(checkpoint_dir) # added creating checkpoint dir + visualize_checkpoints = False + if hasattr(cfg, "generation"): + visualization_dir = os.path.join( + cfg.training.io.get("checkpoint_dir", "."), "visualization" + ) + if dist.rank==0 and not os.path.exists(visualization_dir): + os.makedirs(visualization_dir) # added creating checkpoint dir + visualize_checkpoints = True if cfg.training.hp.batch_size_per_gpu == "auto": cfg.training.hp.batch_size_per_gpu = ( cfg.training.hp.total_batch_size // dist.world_size @@ -127,6 +138,22 @@ def main(cfg: DictConfig) -> None: else: prob_channels = None + if visualize_checkpoints: + # Parse the inference input times + if cfg.generation.times_range and cfg.generation.times: + raise ValueError("Either times_range or times must be provided, but not both") + if cfg.generation.times_range: + times = get_time_from_range(cfg.generation.times_range, time_format="%Y%m%d-%H%M") #TODO check what time formats we are using and adapt + else: + times = cfg.generation.times + viz_dataset_cfg = OmegaConf.to_container(cfg.generation.dataset) + visualization_dataset, visualization_sampler = get_dataset_and_sampler_inference( + dataset_cfg=viz_dataset_cfg, times=times + ) + visualization_data_loader = torch.utils.data.DataLoader( + dataset=visualization_dataset, sampler=visualization_sampler, batch_size=1, pin_memory=True + ) + # Parse the patch shape #TODO figure out patched diffusion and how to use it if ( @@ -395,6 +422,27 @@ def main(cfg: DictConfig) -> None: except: cur_nimg = 0 + # init the generator for inference to visualize checkpoint results + if visualize_checkpoints: + generator = Generator( + net_reg= model if regression_net is None else regression_net, + net_res= model if regression_net is not None else None, + batch_size=int(cfg.generation.num_ensembles//dist.world_size), + ensemble_size=cfg.generation.num_ensembles, + hr_mean_conditioning=cfg.model.hr_mean_conditioning, + n_out_channels=img_out_channels, + inference_mode="all" if regression_net is not None else "regression", + dist=dist, + ) + if use_patching: + generator.initialize_patching(img_shape=img_shape, + patch_shape=patch_shape, + boundary_pix=cfg.generation.boundary_pix, + overlap_pix=cfg.generation.overlap_pix, + ) + sampler_params = cfg.generation.sampler.params if "params" in cfg.generation.sampler else {} + generator.initialize_sampler(cfg.generation.sampler.type, **sampler_params) + ############################################################################ # MAIN TRAINING LOOP # ############################################################################ @@ -713,6 +761,89 @@ def main(cfg: DictConfig) -> None: epoch=cur_nimg, ) + # Visualize samples + if dist.world_size > 1: + torch.distributed.barrier() + if is_time_for_periodic_task( + cur_nimg, + cfg.training.io.save_checkpoint_freq, + done, + cfg.training.hp.total_batch_size, + dist.rank, + ): + if visualize_checkpoints: + with nvtx.annotate("validation", color="red"): + if dist.rank == 0: + writer_executor = ThreadPoolExecutor( + max_workers=cfg.generation.perf.num_writer_workers + ) + writer_threads = [] + + times = visualization_dataset.time() + time_index = -1 + for index, (img_clean_viz, img_lr_viz, *lead_time_label_viz) in enumerate( + iter(visualization_data_loader) + ): + time_index += 1 + logger0.info(f"starting index: {time_index}") + + # continue + if lead_time_label_viz: + lead_time_label_viz = lead_time_label_viz[0].to(dist.device).contiguous() + else: + lead_time_label_viz = None + + if use_apex_gn: + img_clean_viz = img_clean_viz.to( + dist.device, + dtype=input_dtype, + non_blocking=True, + ).to(memory_format=torch.channels_last) + img_lr_viz = img_lr_viz.to( + dist.device, + dtype=input_dtype, + non_blocking=True, + ).to(memory_format=torch.channels_last) + else: + img_clean_viz = ( + img_clean_viz.to(dist.device) + .to(input_dtype) + .contiguous() + ) + img_lr_viz = ( + img_lr_viz.to(dist.device) + .to(input_dtype) + .contiguous() + ) + with torch.autocast( + "cuda", dtype=amp_dtype, enabled=enable_amp + ): + image_pred_viz, image_reg_viz = generator.generate(img_lr_viz, lead_time_label_viz) + if dist.rank == 0: + # write out data in a seperate thread so we don't hold up inferencing + output_path = os.path.join(visualization_dir, f"{cur_nimg}_{times[visualization_sampler[time_index]]}") + if dist.rank==0 and not os.path.exists(output_path): + os.makedirs(output_path) + writer_threads.append( + writer_executor.submit( + save_images, + output_path, + times[visualization_sampler[time_index]], + visualization_dataset, + image_pred_viz.cpu().numpy(), + img_clean_viz.cpu().numpy(), + img_lr_viz.cpu().numpy(), + image_reg_viz.cpu().numpy() if image_reg_viz is not None else None, + ) + ) + # make sure all the workers are done writing + if dist.rank == 0: + for thread in list(writer_threads): + thread.result() + writer_threads.remove(thread) + writer_executor.shutdown() + + # Done. logger0.info("Training Completed.") diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index 7698a82a..50534485 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -84,7 +84,7 @@ def regression_step( if lead_time_label is not None: x = net(x=x_hat[0:1], img_lr=img_lr, lead_time_label=lead_time_label) else: - x = net(x=x_hat[0:1], img_lr=img_lr) + x = net(x=x_hat[0:1], img_lr=img_lr, force_fp32=False) # If the batch size is greater than 1, repeat the prediction if x_hat.shape[0] > 1: @@ -201,6 +201,11 @@ def diffusion_step( return torch.cat(all_images) +############################################################################ +# Visualization Utilities # +############################################################################ + + def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): os.makedirs(output_path, exist_ok=True) @@ -211,7 +216,7 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, output_channels = dataset.output_channels() target = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze()),1) #.reshape(len(output_channels),-1) - prediction = np.flip(dataset.denormalize_output(image_pred[-1,::].squeeze()),1) #.reshape(len(output_channels),-1) + prediction = np.flip(dataset.denormalize_output(image_pred.squeeze()),-2) #.reshape(len(output_channels),-1) baseline = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze()),1)# .reshape(len(input_channels),-1) if mean_pred is not None: mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze()),1) #.reshape(len(output_channels),-1) @@ -220,31 +225,60 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, freqs = {} power = {} for idx, channel in enumerate(output_channels): + channel_dir = channel.name + "_" + channel.level if channel.level else channel.name + output_path_channel = os.path.join(output_path, channel_dir) + if not os.path.exists(output_path_channel): + os.makedirs(output_path_channel) input_channel_idx = input_channels.index(channel) if channel.name=="tp": target[idx,::] = _prepare_precipitaiton(target[idx,:,:]) - prediction[idx,::] = _prepare_precipitaiton(prediction[idx,:,:]) - baseline[input_channel_idx,:,:] = _prepare_precipitaiton(baseline[input_channel_idx]) + prediction[:,idx,::] = _prepare_precipitaiton(prediction[:,idx,:,:]) + baseline[input_channel_idx,:,:] = _prepare_precipitaiton(baseline[input_channel_idx,::]) if mean_pred is not None: mean_pred[idx,::] = _prepare_precipitaiton(mean_pred[idx,::]) - - _plot_projection(longitudes, latitudes, target[idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-target.jpg')) - _plot_projection(longitudes, latitudes, prediction[idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-prediction.jpg')) - _plot_projection(longitudes, latitudes, baseline[input_channel_idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-input.jpg')) + + if mean_pred is not None: + vmin, vmax = calculate_bounds(target[idx,:,:], + prediction[:,idx,:,:], + baseline[input_channel_idx,:,:], + mean_pred[idx,:,:]) + else: + vmin, vmax = calculate_bounds(target[idx,:,:], + prediction[:,idx,:,:], + baseline[input_channel_idx,:,:]) + _plot_projection(longitudes, latitudes, target[idx,:,:], + os.path.join(output_path_channel, f'{time_step}-{channel.name}-target.jpg'), + vmin=vmin, vmax=vmax) if mean_pred is not None: - _plot_projection(longitudes, latitudes, mean_pred[idx,:,:], os.path.join(output_path, f'{time_step}-{channel.name}-mean_prediction.jpg')) + for member_idx in range(prediction.shape[0]): + _plot_projection(longitudes, latitudes, prediction[member_idx,idx,:,:], + os.path.join(output_path_channel, f'{time_step}-{channel.name}-prediction_{member_idx}.jpg'), + vmin=vmin, vmax=vmax) + else: + _plot_projection(longitudes, latitudes, + prediction[0,idx,:,:], os.path.join(output_path_channel, f'{time_step}-{channel.name}-prediction.jpg'), + vmin=vmin, vmax=vmax) + _plot_projection(longitudes, latitudes, baseline[input_channel_idx,:,:], + os.path.join(output_path_channel, f'{time_step}-{channel.name}-input.jpg'), + vmin=vmin, vmax=vmax) + if mean_pred is not None: + _plot_projection(longitudes, latitudes, mean_pred[idx,:,:], + os.path.join(output_path_channel, f'{time_step}-{channel.name}-mean_prediction.jpg'), + vmin=vmin, vmax=vmax) _, baseline_errors = compute_mae(baseline[input_channel_idx,:,:], target[idx,:,:]) - _, prediction_errors = compute_mae(prediction[idx,:,:], target[idx,:,:]) + plot_error_projection(baseline_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path_channel, f'{time_step}-{channel.name}-baseline-error.jpg')) if mean_pred is not None: - _, mean_prediction_errors = compute_mae(mean_pred[idx,:,:], target[idx,:,:]) - - - plot_error_projection(baseline_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path, f'{time_step}-{channel.name}-baseline-error.jpg')) - plot_error_projection(prediction_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path, f'{time_step}-{channel.name}-prediction-error.jpg')) + for member_idx in range(prediction.shape[0]): + _, prediction_errors = compute_mae(prediction[member_idx,idx,:,:], target[idx,:,:]) + plot_error_projection(prediction_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path_channel, f'{time_step}-{channel.name}-prediction_{member_idx}-error.jpg')) + else: + _, prediction_errors = compute_mae(prediction[0,idx,:,:], target[idx,:,:]) + plot_error_projection(prediction_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path_channel, f'{time_step}-{channel.name}-prediction-error.jpg')) if mean_pred is not None: - plot_error_projection(mean_prediction_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path, f'{time_step}-{channel.name}-mean-prediction-error.jpg')) + _, mean_prediction_errors = compute_mae(mean_pred[idx,:,:], target[idx,:,:]) + plot_error_projection(mean_prediction_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path_channel, f'{time_step}-{channel.name}-mean-prediction-error.jpg')) b_freq, b_power = average_power_spectrum(baseline[input_channel_idx,:,:].squeeze(), 2.0) freqs['baseline'] = b_freq @@ -253,21 +287,22 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, t_freq, t_power = average_power_spectrum(target[idx,:,:].squeeze(), 2.0) freqs['target'] = t_freq power['target'] = t_power - p_freq, p_power = average_power_spectrum(prediction[idx,:,:].squeeze(), 2.0) + p_freq, p_power = average_power_spectrum(prediction[-1,idx,:,:].squeeze(), 2.0) freqs['prediction'] = p_freq power['prediction'] = p_power if mean_pred is not None: mp_freq, mp_power = average_power_spectrum(mean_pred[idx,:,:].squeeze(), 2.0) freqs['mean_prediction'] = mp_freq power['mean_prediction'] = mp_power - plot_power_spectra(freqs, power, channel.name, os.path.join(output_path, f'{time_step}-{channel.name}-spectra.jpg')) + plot_power_spectra(freqs, power, channel.name, os.path.join(output_path_channel, f'{time_step}-{channel.name}-spectra.jpg')) def _prepare_precipitaiton(precip_array): precip_array = np.clip(precip_array, 0, None) - epsilon = 1e-2 - precip_array = precip_array + epsilon - precip_array = np.log(precip_array) + precip_array = np.where(precip_array == 0, 1e-6, precip_array) + # epsilon = 1e-2 + # precip_array = precip_array + epsilon + precip_array = np.log10(precip_array) # log_min, log_max = precip_array.min(), precip_array.max() # precip_array = (precip_array-log_min)/(log_max-log_min) return precip_array @@ -284,4 +319,9 @@ def _plot_projection(longitudes: np.array, latitudes: np.array, values: np.array ax.gridlines(draw_labels=True) plt.colorbar(p, label="K", orientation="horizontal") plt.savefig(filename) - plt.close('all') \ No newline at end of file + plt.close('all') + +def calculate_bounds(*arrays: np.ndarray) -> tuple[float]: + vmin = min(*[np.min(array).item() for array in arrays]) + vmax = max(*[np.max(array).item() for array in arrays]) + return vmin, vmax \ No newline at end of file From 48ae52baa379425ce1f1aaad5d067861413d97b3 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 9 Jul 2025 14:05:43 +0200 Subject: [PATCH 066/302] fix calculating crps only for diffusion output --- .../conf/dataset/era_cosmo_inference.yaml | 2 ++ src/hirad/conf/generate_era_cosmo.yaml | 6 ++--- src/hirad/conf/generation/era_cosmo.yaml | 13 +++++----- .../conf/generation/era_cosmo_training.yaml | 18 +++++++++++++ .../conf/training/era_cosmo_diffusion.yaml | 15 +++++------ .../conf/training/era_cosmo_regression.yaml | 10 +++---- .../conf/training_era_cosmo_diffusion.yaml | 9 ++++--- .../conf/training_era_cosmo_regression.yaml | 9 ++++--- src/hirad/generate.sh | 13 +++++----- src/hirad/inference/generate.py | 1 + src/hirad/inference/generator.py | 7 +++-- src/hirad/train_diffusion.sh | 8 +++--- src/hirad/train_regression.sh | 8 +++--- src/hirad/training/train_dummy.py | 14 ++++++++++ src/hirad/utils/inference_utils.py | 26 +++++++++---------- 15 files changed, 98 insertions(+), 61 deletions(-) create mode 100644 src/hirad/conf/dataset/era_cosmo_inference.yaml create mode 100644 src/hirad/conf/generation/era_cosmo_training.yaml create mode 100644 src/hirad/training/train_dummy.py diff --git a/src/hirad/conf/dataset/era_cosmo_inference.yaml b/src/hirad/conf/dataset/era_cosmo_inference.yaml new file mode 100644 index 00000000..f819b18f --- /dev/null +++ b/src/hirad/conf/dataset/era_cosmo_inference.yaml @@ -0,0 +1,2 @@ +type: era5_cosmo +dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/validation \ No newline at end of file diff --git a/src/hirad/conf/generate_era_cosmo.yaml b/src/hirad/conf/generate_era_cosmo.yaml index 90a59484..f9f629c2 100644 --- a/src/hirad/conf/generate_era_cosmo.yaml +++ b/src/hirad/conf/generate_era_cosmo.yaml @@ -1,15 +1,15 @@ hydra: job: chdir: true - name: generation_regression_valid + name: diffusion_era5_cosmo_7500000_test run: - dir: /capstor/scratch/cscs/pstamenk/outputs/${hydra:job.name} + dir: /capstor/scratch/cscs/pstamenk/outputs/generation/${hydra:job.name} # Get defaults defaults: - _self_ # Dataset - - dataset/era_cosmo + - dataset/era_cosmo_inference # Sampler - sampler/stochastic diff --git a/src/hirad/conf/generation/era_cosmo.yaml b/src/hirad/conf/generation/era_cosmo.yaml index 2ccbb719..70618897 100644 --- a/src/hirad/conf/generation/era_cosmo.yaml +++ b/src/hirad/conf/generation/era_cosmo.yaml @@ -2,7 +2,7 @@ num_ensembles: 8 # Number of ensembles to generate per input seed_batch_size: 4 # Size of the batched inference -inference_mode: regression +inference_mode: all # Choose between "all" (regression + diffusion), "regression" or "diffusion" # Patch size. Patch-based sampling will be utilized if these dimensions differ from # img_shape_x and img_shape_y @@ -18,8 +18,8 @@ hr_mean_conditioning: True times_range: null times: - 20200926-1800 - # - 20160101-0600 - # - 20160101-1200 + - 20200927-0000 + has_laed_time: False perf: @@ -30,15 +30,16 @@ perf: # whether to use torch.compile on the diffusion model # this will make the first time stamp generation very slow due to compilation overheads # but will significantly speed up subsequent inference runs - num_writer_workers: 1 + num_writer_workers: 8 # number of workers to use for writing file # To support multiple workers a threadsafe version of the netCDF library must be used io: - res_ckpt_path: /capstor/scratch/cscs/pstamenk/diffusion_checkpoints + # res_ckpt_path: /capstor/scratch/cscs/pstamenk/outputs/diffusion_full/checkpoints_diffusion + res_ckpt_path: /capstor/scratch/cscs/pstamenk/outputs/training/diffusion_era5_cosmo/checkpoints_diffusion # res_ckpt_path: null # Checkpoint filename for the diffusion model - reg_ckpt_path: /capstor/scratch/cscs/pstamenk/regression_checkpoints + reg_ckpt_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_era5_cosmo/checkpoints_regression # reg_ckpt_path: /iopsstor/scratch/cscs/pstamenk/outputs/regression_test/checkpoints_regression # Checkpoint filename for the mean predictor model output_path: ./images \ No newline at end of file diff --git a/src/hirad/conf/generation/era_cosmo_training.yaml b/src/hirad/conf/generation/era_cosmo_training.yaml new file mode 100644 index 00000000..4374fc88 --- /dev/null +++ b/src/hirad/conf/generation/era_cosmo_training.yaml @@ -0,0 +1,18 @@ +defaults: + - ../sampler@sampler: stochastic + - ../dataset@dataset: era_cosmo_inference + +num_ensembles: 16 + # Number of ensembles to generate per input +# overlap_pixels: 0 + # Number of overlapping pixels between adjacent patches +# boundary_pixels: 0 + # Number of boundary pixels to be cropped out. 2 is recommanded to address the boundary + # artifact. +times_range: null +times: + - 20200926-1800 + - 20200927-0000 + +perf: + num_writer_workers: 10 \ No newline at end of file diff --git a/src/hirad/conf/training/era_cosmo_diffusion.yaml b/src/hirad/conf/training/era_cosmo_diffusion.yaml index 07cbb03f..e0d096cb 100644 --- a/src/hirad/conf/training/era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training/era_cosmo_diffusion.yaml @@ -1,8 +1,8 @@ # Hyperparameters hp: - training_duration: 5000000 + training_duration: 1000 # Training duration based on the number of processed samples - total_batch_size: 128 + total_batch_size: 64 # Total batch size batch_size_per_gpu: "auto" # Batch size per GPU @@ -29,15 +29,14 @@ perf: # I/O io: - regression_checkpoint_path: /capstor/scratch/cscs/boeschf/HiRAD-Gen/outputs_full/regression/checkpoints_regression/ + regression_checkpoint_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_era5_cosmo/checkpoints_regression # Where to load the regression checkpoint - print_progress_freq: 5000 + print_progress_freq: 128 # How often to print progress - save_checkpoint_freq: 250000 + save_checkpoint_freq: 512 # How often to save the checkpoints, measured in number of processed samples - validation_freq: 25000 + validation_freq: 256 # how often to record the validation loss, measured in number of processed samples - validation_steps: 4 - # how many loss evaluations are used to compute the validation loss per checkpoint + validation_steps: 2 # how many loss evaluations are used to compute the validation loss per checkpoint checkpoint_dir: . \ No newline at end of file diff --git a/src/hirad/conf/training/era_cosmo_regression.yaml b/src/hirad/conf/training/era_cosmo_regression.yaml index 98c6c249..caa94ab5 100644 --- a/src/hirad/conf/training/era_cosmo_regression.yaml +++ b/src/hirad/conf/training/era_cosmo_regression.yaml @@ -1,6 +1,6 @@ # Hyperparameters hp: - training_duration: 500000 + training_duration: 1000 # Training duration based on the number of processed samples total_batch_size: 64 # Total batch size @@ -31,12 +31,12 @@ perf: # I/O io: - print_progress_freq: 1024 + print_progress_freq: 128 # How often to print progress - save_checkpoint_freq: 25000 + save_checkpoint_freq: 512 # How often to save the checkpoints, measured in number of processed samples - validation_freq: 5000 + validation_freq: 256 # how often to record the validation loss, measured in number of processed samples - validation_steps: 10 + validation_steps: 2 # how many loss evaluations are used to compute the validation loss per checkpoint checkpoint_dir: . \ No newline at end of file diff --git a/src/hirad/conf/training_era_cosmo_diffusion.yaml b/src/hirad/conf/training_era_cosmo_diffusion.yaml index 0a069e9c..7a38fbd9 100644 --- a/src/hirad/conf/training_era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training_era_cosmo_diffusion.yaml @@ -1,9 +1,9 @@ hydra: job: chdir: true - name: diffusion + name: diffusion_era5_cosmo_test run: - dir: /iopsstor/scratch/cscs/pstamenk/outputs/${hydra:job.name} + dir: /capstor/scratch/cscs/pstamenk/outputs/training/${hydra:job.name} # Get defaults defaults: @@ -18,4 +18,7 @@ defaults: - model_size/normal # Training - - training/era_cosmo_diffusion \ No newline at end of file + - training/era_cosmo_diffusion + + # Inference visualization + - generation/era_cosmo_training \ No newline at end of file diff --git a/src/hirad/conf/training_era_cosmo_regression.yaml b/src/hirad/conf/training_era_cosmo_regression.yaml index 1de83d91..045eba49 100644 --- a/src/hirad/conf/training_era_cosmo_regression.yaml +++ b/src/hirad/conf/training_era_cosmo_regression.yaml @@ -1,9 +1,9 @@ hydra: job: chdir: true - name: regression + name: regression_era5_cosmo_test run: - dir: /iopsstor/scratch/cscs/pstamenk/outputs/${hydra:job.name} + dir: /capstor/scratch/cscs/pstamenk/outputs/training/${hydra:job.name} # Get defaults defaults: @@ -18,4 +18,7 @@ defaults: - model_size/normal # Training - - training/era_cosmo_regression \ No newline at end of file + - training/era_cosmo_regression + + # Inference visualization + - generation/era_cosmo_training \ No newline at end of file diff --git a/src/hirad/generate.sh b/src/hirad/generate.sh index 3876ade3..5ca8b97c 100644 --- a/src/hirad/generate.sh +++ b/src/hirad/generate.sh @@ -5,18 +5,19 @@ ### HARDWARE ### #SBATCH --partition=debug #SBATCH --nodes=1 -#SBATCH --ntasks-per-node=1 -#SBATCH --gpus-per-node=1 +#SBATCH --ntasks-per-node=2 +#SBATCH --gpus-per-node=2 +#SBATCH --cpus-per-task=72 #SBATCH --time=00:30:00 #SBATCH --no-requeue #SBATCH --exclusive ### OUTPUT ### -#SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/regression_generation.log -#SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/regression_generation.err +#SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/generation_diffusion_test.log +#SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/generation_diffusion_test.err ### ENVIRONMENT #### -#SBATCH -A c38 +#SBATCH -A a122 # Choose method to initialize dist in pythorch export DISTRIBUTED_INITIALIZATION_METHOD=SLURM @@ -46,7 +47,5 @@ export OMP_NUM_THREADS=72 srun --container-writable --environment=modulus_env bash -c " cd HiRAD-Gen pip install -e . --no-dependencies - pip install Cartopy==0.22.0 - pip install xskillscore python src/hirad/inference/generate.py --config-name=generate_era_cosmo.yaml " \ No newline at end of file diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index ae6d9d96..cea82a24 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -245,6 +245,7 @@ def elapsed_time(self, _): image_tar = image_tar.to(device=device).to(torch.float32) # image_out, image_reg = generate_fn(image_lr,lead_time_label) image_out, image_reg = generator.generate(image_lr,lead_time_label) + if dist.rank == 0: batch_size = image_out.shape[0] # write out data in a seperate thread so we don't hold up inferencing diff --git a/src/hirad/inference/generator.py b/src/hirad/inference/generator.py index b1e10e1f..ad051a95 100644 --- a/src/hirad/inference/generator.py +++ b/src/hirad/inference/generator.py @@ -102,7 +102,7 @@ def generate(self, image_lr, lead_time_label=None): lead_time_label=lead_time_label, ) if self.inference_mode == "regression": - image_out = image_reg + image_out = image_reg[0:1,::] elif self.inference_mode == "diffusion": image_out = image_res else: @@ -130,11 +130,10 @@ def generate(self, image_lr, lead_time_label=None): if self.dist.rank == 0: if self.inference_mode != "regression": return torch.cat(gathered_tensors), image_reg[0:1,::] - return torch.cat(gathered_tensors), None + return torch.cat(gathered_tensors)[0:1,::], None else: return None, None else: - #TODO do this for multi-gpu setting above too if self.inference_mode != "regression": - return image_out, image_reg + return image_out, image_reg[0:1,::] return image_out, None diff --git a/src/hirad/train_diffusion.sh b/src/hirad/train_diffusion.sh index d8515db3..badeb46f 100644 --- a/src/hirad/train_diffusion.sh +++ b/src/hirad/train_diffusion.sh @@ -3,18 +3,18 @@ #SBATCH --job-name="corrdiff-second-stage" ### HARDWARE ### -#SBATCH --partition=normal +#SBATCH --partition=debug #SBATCH --nodes=2 #SBATCH --ntasks-per-node=4 #SBATCH --gpus-per-node=4 #SBATCH --cpus-per-task=72 -#SBATCH --time=24:00:00 +#SBATCH --time=00:30:00 #SBATCH --no-requeue #SBATCH --exclusive ### OUTPUT ### -#SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/diffusion_full.log -#SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/diffusion_full.err +#SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/training_diffusion_test.log +#SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/training_diffusion_test.err ### ENVIRONMENT #### #SBATCH -A c38 diff --git a/src/hirad/train_regression.sh b/src/hirad/train_regression.sh index 7499bcfd..31d80145 100644 --- a/src/hirad/train_regression.sh +++ b/src/hirad/train_regression.sh @@ -3,18 +3,18 @@ #SBATCH --job-name="corrdiff-first-stage" ### HARDWARE ### -#SBATCH --partition=normal +#SBATCH --partition=debug #SBATCH --nodes=2 #SBATCH --ntasks-per-node=4 #SBATCH --gpus-per-node=4 #SBATCH --cpus-per-task=72 -#SBATCH --time=06:00:00 +#SBATCH --time=00:30:00 #SBATCH --no-requeue #SBATCH --exclusive ### OUTPUT ### -#SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/regression_full_run.log -#SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/regression_full_run.err +#SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/training_regression_test.log +#SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/training_regression_test.err ### ENVIRONMENT #### #SBATCH -A c38 diff --git a/src/hirad/training/train_dummy.py b/src/hirad/training/train_dummy.py new file mode 100644 index 00000000..cc47ea9f --- /dev/null +++ b/src/hirad/training/train_dummy.py @@ -0,0 +1,14 @@ +import hydra +from omegaconf import DictConfig, OmegaConf +import json + + +@hydra.main(version_base=None, config_path="../conf", config_name="training") +def main(cfg: DictConfig) -> None: + OmegaConf.resolve(cfg) + cfg = OmegaConf.to_container(cfg) + print(json.dumps(cfg, indent=2)) + # print(cfg.pretty()) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index f3c9f74f..ed50b051 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -215,10 +215,7 @@ def save_results_as_torch(output_path, time_step, dataset, image_pred, image_hr, # prediction.shape = (num_channels, X, Y) # prediction = np.flip(dataset.denormalize_output(image_pred[-1,::].squeeze()),1) #.reshape(len(output_channels),-1) # prediction_ensemble.shape = (num_ensembles, num_channels, X, Y) - prediction_ensemble = np.ndarray(image_pred.shape) - for i in range(image_pred.shape[0]): - prediction_ensemble[i,::] = np.flip(dataset.denormalize_output(image_pred[i,::].squeeze()),1) - prediction_ensemble = np.flip(dataset.denormalize_output(image_pred.squeeze()),2) #.reshape(len(output_channels),-1) + prediction_ensemble = np.flip(dataset.denormalize_output(image_pred.squeeze()),-2) baseline = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze()),1)# .reshape(len(input_channels),-1) if mean_pred is not None: mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze()),1) #.reshape(len(output_channels),-1) @@ -227,8 +224,8 @@ def save_results_as_torch(output_path, time_step, dataset, image_pred, image_hr, torch.save(baseline, os.path.join(output_path, f'{time_step}-baseline')) -def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): - +def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): + os.makedirs(output_path, exist_ok=True) longitudes = dataset.longitude() @@ -237,17 +234,18 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, output_channels = dataset.output_channels() target = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze()),1) #.reshape(len(output_channels),-1) - prediction = np.flip(dataset.denormalize_output(image_pred.squeeze()),-2) #.reshape(len(output_channels),-1) + prediction = np.flip(dataset.denormalize_output(image_pred),-2) #.reshape(len(output_channels),-1) baseline = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze()),1)# .reshape(len(input_channels),-1) if mean_pred is not None: mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze()),1) #.reshape(len(output_channels),-1) # Plot CRPS - crps_score = crps(prediction, target, average_over_area=False, average_over_channels=True) - _plot_projection(longitudes, latitudes, crps_score, os.path.join(output_path, f'{time_step}-crps-all.jpg')) - crps_score_channels = crps(prediction, target, average_over_area=False, average_over_channels=False) - for channel_num in range(crps_score_channels.shape[0]): - _plot_projection(longitudes, latitudes, crps_score_channels[channel_num,::], os.path.join(output_path, f'{time_step}-crps-{output_channels[channel_num].name}.jpg')) + if prediction.shape[0] > 1: + crps_score = crps(prediction, target, average_over_area=False, average_over_channels=True) + _plot_projection(longitudes, latitudes, crps_score, os.path.join(output_path, f'{time_step}-crps-all.jpg')) + crps_score_channels = crps(prediction, target, average_over_area=False, average_over_channels=False) + for channel_num in range(crps_score_channels.shape[0]): + _plot_projection(longitudes, latitudes, crps_score_channels[channel_num,::], os.path.join(output_path, f'{time_step}-crps-{output_channels[channel_num].name}.jpg')) # Plot power spectra freqs = {} @@ -278,7 +276,7 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, _plot_projection(longitudes, latitudes, target[idx,:,:], os.path.join(output_path_channel, f'{time_step}-{channel.name}-target.jpg'), vmin=vmin, vmax=vmax) - if mean_pred is not None: + if prediction.shape[0] > 1: for member_idx in range(prediction.shape[0]): _plot_projection(longitudes, latitudes, prediction[member_idx,idx,:,:], os.path.join(output_path_channel, f'{time_step}-{channel.name}-prediction_{member_idx}.jpg'), @@ -297,7 +295,7 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, _, baseline_errors = compute_mae(baseline[input_channel_idx,:,:], target[idx,:,:]) plot_error_projection(baseline_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path_channel, f'{time_step}-{channel.name}-baseline-error.jpg')) - if mean_pred is not None: + if prediction.shape[0] > 1: for member_idx in range(prediction.shape[0]): _, prediction_errors = compute_mae(prediction[member_idx,idx,:,:], target[idx,:,:]) plot_error_projection(prediction_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path_channel, f'{time_step}-{channel.name}-prediction_{member_idx}-error.jpg')) From b92abde6e052981a85f7210ca167edf5d894154b Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 9 Jul 2025 15:46:08 +0200 Subject: [PATCH 067/302] environment files --- Dockerfile | 10 ++++++++++ modulus_env.toml | 10 ++++++++++ src/hirad/generate.sh | 5 ++--- 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 Dockerfile create mode 100644 modulus_env.toml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..93f389c0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM nvcr.io/nvidia/physicsnemo/physicsnemo:25.06 + +# setup +RUN apt-get update && apt-get install python3-pip python3-venv -y +RUN pip install --upgrade pip + +# Install the rest of dependencies. +RUN pip install \ + Cartopy==0.22.0 \ + xskillscore diff --git a/modulus_env.toml b/modulus_env.toml new file mode 100644 index 00000000..f44a7a70 --- /dev/null +++ b/modulus_env.toml @@ -0,0 +1,10 @@ +image = "/capstor/scratch/cscs/pstamenk/hirad.sqsh" + +mounts = ["/capstor", "/iopsstor”, “/users”] + +# The initial directory in the container. +workdir = "${PWD}" + +[annotations] +com.hooks.aws_ofi_nccl.enabled = "true" +com.hooks.aws_ofi_nccl.variant = "cuda12" \ No newline at end of file diff --git a/src/hirad/generate.sh b/src/hirad/generate.sh index 5ca8b97c..57320bf9 100644 --- a/src/hirad/generate.sh +++ b/src/hirad/generate.sh @@ -17,7 +17,7 @@ #SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/generation_diffusion_test.err ### ENVIRONMENT #### -#SBATCH -A a122 +#SBATCH -A c38 # Choose method to initialize dist in pythorch export DISTRIBUTED_INITIALIZATION_METHOD=SLURM @@ -44,8 +44,7 @@ export OMP_NUM_THREADS=72 # echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" # python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml -srun --container-writable --environment=modulus_env bash -c " - cd HiRAD-Gen +srun --environment=./modulus_env.toml bash -c " pip install -e . --no-dependencies python src/hirad/inference/generate.py --config-name=generate_era_cosmo.yaml " \ No newline at end of file From 03386edeea614b44794d7b5a2381e302cbb4ef09 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 9 Jul 2025 18:09:46 +0200 Subject: [PATCH 068/302] small fix environment --- modulus_env.toml | 2 +- src/hirad/conf/training/era_cosmo_diffusion.yaml | 4 ++-- src/hirad/conf/training/era_cosmo_regression.yaml | 2 +- src/hirad/generate.sh | 2 +- src/hirad/inference/README.md | 0 src/hirad/train_diffusion.sh | 5 ++--- src/hirad/train_regression.sh | 5 ++--- 7 files changed, 9 insertions(+), 11 deletions(-) create mode 100644 src/hirad/inference/README.md diff --git a/modulus_env.toml b/modulus_env.toml index f44a7a70..55f43d8f 100644 --- a/modulus_env.toml +++ b/modulus_env.toml @@ -1,6 +1,6 @@ image = "/capstor/scratch/cscs/pstamenk/hirad.sqsh" -mounts = ["/capstor", "/iopsstor”, “/users”] +mounts = ["/capstor", "/iopsstor", "/users"] # The initial directory in the container. workdir = "${PWD}" diff --git a/src/hirad/conf/training/era_cosmo_diffusion.yaml b/src/hirad/conf/training/era_cosmo_diffusion.yaml index e0d096cb..5a14a6a9 100644 --- a/src/hirad/conf/training/era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training/era_cosmo_diffusion.yaml @@ -1,6 +1,6 @@ # Hyperparameters hp: - training_duration: 1000 + training_duration: 1024 # Training duration based on the number of processed samples total_batch_size: 64 # Total batch size @@ -33,7 +33,7 @@ io: # Where to load the regression checkpoint print_progress_freq: 128 # How often to print progress - save_checkpoint_freq: 512 + save_checkpoint_freq: 1024 # How often to save the checkpoints, measured in number of processed samples validation_freq: 256 # how often to record the validation loss, measured in number of processed samples diff --git a/src/hirad/conf/training/era_cosmo_regression.yaml b/src/hirad/conf/training/era_cosmo_regression.yaml index caa94ab5..6d7216ed 100644 --- a/src/hirad/conf/training/era_cosmo_regression.yaml +++ b/src/hirad/conf/training/era_cosmo_regression.yaml @@ -1,6 +1,6 @@ # Hyperparameters hp: - training_duration: 1000 + training_duration: 1024 # Training duration based on the number of processed samples total_batch_size: 64 # Total batch size diff --git a/src/hirad/generate.sh b/src/hirad/generate.sh index 57320bf9..a9843e03 100644 --- a/src/hirad/generate.sh +++ b/src/hirad/generate.sh @@ -17,7 +17,7 @@ #SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/generation_diffusion_test.err ### ENVIRONMENT #### -#SBATCH -A c38 +#SBATCH -A a122 # Choose method to initialize dist in pythorch export DISTRIBUTED_INITIALIZATION_METHOD=SLURM diff --git a/src/hirad/inference/README.md b/src/hirad/inference/README.md new file mode 100644 index 00000000..e69de29b diff --git a/src/hirad/train_diffusion.sh b/src/hirad/train_diffusion.sh index badeb46f..58e6ccbe 100644 --- a/src/hirad/train_diffusion.sh +++ b/src/hirad/train_diffusion.sh @@ -17,7 +17,7 @@ #SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/training_diffusion_test.err ### ENVIRONMENT #### -#SBATCH -A c38 +#SBATCH -A a122 # Choose method to initialize dist in pythorch export DISTRIBUTED_INITIALIZATION_METHOD=SLURM @@ -38,8 +38,7 @@ export MASTER_PORT=29500 export OMP_NUM_THREADS=72 # python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml -srun --container-writable --environment=modulus_env bash -c " - cd HiRAD-Gen +srun --environment=./modulus_env.toml bash -c " pip install -e . --no-dependencies python src/hirad/training/train.py --config-name=training_era_cosmo_diffusion.yaml " \ No newline at end of file diff --git a/src/hirad/train_regression.sh b/src/hirad/train_regression.sh index 31d80145..8c681dba 100644 --- a/src/hirad/train_regression.sh +++ b/src/hirad/train_regression.sh @@ -17,7 +17,7 @@ #SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/training_regression_test.err ### ENVIRONMENT #### -#SBATCH -A c38 +#SBATCH -A a122 # Choose method to initialize dist in pythorch export DISTRIBUTED_INITIALIZATION_METHOD=SLURM @@ -41,8 +41,7 @@ export OMP_NUM_THREADS=72 # . ./train_env/bin/activate # python src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml # " -srun --container-writable --environment=modulus_env bash -c " - cd HiRAD-Gen +srun --environment=./modulus_env.toml bash -c " pip install -e . --no-dependencies python src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml " \ No newline at end of file From f7a0b4d624a29257e3b951b23486233158e1699e Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Fri, 11 Jul 2025 12:31:47 +0200 Subject: [PATCH 069/302] Make some paths relative --- src/hirad/conf/dataset/era_cosmo.yaml | 2 +- src/hirad/conf/generate_era_cosmo.yaml | 4 ++++ src/hirad/conf/generation/era_cosmo.yaml | 14 +++++++------- src/hirad/conf/training/era_cosmo_regression.yaml | 4 ++++ src/hirad/conf/training_era_cosmo_diffusion.yaml | 2 +- src/hirad/conf/training_era_cosmo_regression.yaml | 2 +- src/hirad/generate.sh | 5 +++++ src/hirad/train_regression.sh | 3 +-- 8 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/hirad/conf/dataset/era_cosmo.yaml b/src/hirad/conf/dataset/era_cosmo.yaml index 5d32f4ed..beec8a2e 100644 --- a/src/hirad/conf/dataset/era_cosmo.yaml +++ b/src/hirad/conf/dataset/era_cosmo.yaml @@ -1,3 +1,3 @@ type: era5_cosmo -dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/train +dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/validation validation_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/validation \ No newline at end of file diff --git a/src/hirad/conf/generate_era_cosmo.yaml b/src/hirad/conf/generate_era_cosmo.yaml index f9f629c2..cc427514 100644 --- a/src/hirad/conf/generate_era_cosmo.yaml +++ b/src/hirad/conf/generate_era_cosmo.yaml @@ -3,7 +3,11 @@ hydra: chdir: true name: diffusion_era5_cosmo_7500000_test run: +<<<<<<< Updated upstream dir: /capstor/scratch/cscs/pstamenk/outputs/generation/${hydra:job.name} +======= + dir: /capstor/scratch/cscs/mmcgloho/outputs/${hydra:job.name} +>>>>>>> Stashed changes # Get defaults defaults: diff --git a/src/hirad/conf/generation/era_cosmo.yaml b/src/hirad/conf/generation/era_cosmo.yaml index 70618897..3891b7b1 100644 --- a/src/hirad/conf/generation/era_cosmo.yaml +++ b/src/hirad/conf/generation/era_cosmo.yaml @@ -15,12 +15,12 @@ patching: False hr_mean_conditioning: True # sample_res: full # Sampling resolution -times_range: null -times: - - 20200926-1800 - - 20200927-0000 - -has_laed_time: False +times_range: ['20200101-0000','20200102-0000',1] +times: null +# - 20200926-1800 + #- 20160101-0600 + # - 20160101-1200 +has_lead_time: False perf: force_fp16: False @@ -42,4 +42,4 @@ io: reg_ckpt_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_era5_cosmo/checkpoints_regression # reg_ckpt_path: /iopsstor/scratch/cscs/pstamenk/outputs/regression_test/checkpoints_regression # Checkpoint filename for the mean predictor model - output_path: ./images \ No newline at end of file + output_path: /capstor/scratch/cscs/mmcgloho/outputs/era-cosmo-1h/ \ No newline at end of file diff --git a/src/hirad/conf/training/era_cosmo_regression.yaml b/src/hirad/conf/training/era_cosmo_regression.yaml index 6d7216ed..db647261 100644 --- a/src/hirad/conf/training/era_cosmo_regression.yaml +++ b/src/hirad/conf/training/era_cosmo_regression.yaml @@ -1,6 +1,10 @@ # Hyperparameters hp: +<<<<<<< Updated upstream training_duration: 1024 +======= + training_duration: 1000 +>>>>>>> Stashed changes # Training duration based on the number of processed samples total_batch_size: 64 # Total batch size diff --git a/src/hirad/conf/training_era_cosmo_diffusion.yaml b/src/hirad/conf/training_era_cosmo_diffusion.yaml index 7a38fbd9..08c8d6a2 100644 --- a/src/hirad/conf/training_era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training_era_cosmo_diffusion.yaml @@ -15,7 +15,7 @@ defaults: # Model - model/era_cosmo_diffusion - - model_size/normal + - model_size/mini # Training - training/era_cosmo_diffusion diff --git a/src/hirad/conf/training_era_cosmo_regression.yaml b/src/hirad/conf/training_era_cosmo_regression.yaml index 045eba49..83a4f944 100644 --- a/src/hirad/conf/training_era_cosmo_regression.yaml +++ b/src/hirad/conf/training_era_cosmo_regression.yaml @@ -15,7 +15,7 @@ defaults: # Model - model/era_cosmo_regression - - model_size/normal + - model_size/mini # Training - training/era_cosmo_regression diff --git a/src/hirad/generate.sh b/src/hirad/generate.sh index a9843e03..a27a7277 100644 --- a/src/hirad/generate.sh +++ b/src/hirad/generate.sh @@ -13,8 +13,13 @@ #SBATCH --exclusive ### OUTPUT ### +<<<<<<< Updated upstream #SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/generation_diffusion_test.log #SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/generation_diffusion_test.err +======= +#SBATCH --output=/capstor/scratch/cscs/mmcgloho/logs/regression_generation.log +#SBATCH --error=/capstor/scratch/cscs/mmcgloho/logs/regression_generation.err +>>>>>>> Stashed changes ### ENVIRONMENT #### #SBATCH -A a122 diff --git a/src/hirad/train_regression.sh b/src/hirad/train_regression.sh index 8c681dba..a75cc06d 100644 --- a/src/hirad/train_regression.sh +++ b/src/hirad/train_regression.sh @@ -13,8 +13,7 @@ #SBATCH --exclusive ### OUTPUT ### -#SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/training_regression_test.log -#SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/training_regression_test.err +#SBATCH --output=./logs/regression_full_run.log ### ENVIRONMENT #### #SBATCH -A a122 From 0224692916323416c89771053b7903c51cd99ddb Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Fri, 11 Jul 2025 12:47:44 +0200 Subject: [PATCH 070/302] Fix errors from merge process --- src/hirad/conf/dataset/era_cosmo.yaml | 2 +- src/hirad/conf/generate_era_cosmo.yaml | 6 +----- src/hirad/conf/generation/era_cosmo.yaml | 2 +- src/hirad/conf/training/era_cosmo_regression.yaml | 6 +----- src/hirad/generate.sh | 12 ++++-------- 5 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/hirad/conf/dataset/era_cosmo.yaml b/src/hirad/conf/dataset/era_cosmo.yaml index beec8a2e..5d32f4ed 100644 --- a/src/hirad/conf/dataset/era_cosmo.yaml +++ b/src/hirad/conf/dataset/era_cosmo.yaml @@ -1,3 +1,3 @@ type: era5_cosmo -dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/validation +dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/train validation_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/validation \ No newline at end of file diff --git a/src/hirad/conf/generate_era_cosmo.yaml b/src/hirad/conf/generate_era_cosmo.yaml index cc427514..d448b583 100644 --- a/src/hirad/conf/generate_era_cosmo.yaml +++ b/src/hirad/conf/generate_era_cosmo.yaml @@ -3,11 +3,7 @@ hydra: chdir: true name: diffusion_era5_cosmo_7500000_test run: -<<<<<<< Updated upstream - dir: /capstor/scratch/cscs/pstamenk/outputs/generation/${hydra:job.name} -======= - dir: /capstor/scratch/cscs/mmcgloho/outputs/${hydra:job.name} ->>>>>>> Stashed changes + dir: ./outputs/generation/${hydra:job.name} # Get defaults defaults: diff --git a/src/hirad/conf/generation/era_cosmo.yaml b/src/hirad/conf/generation/era_cosmo.yaml index 3891b7b1..c4f71dc4 100644 --- a/src/hirad/conf/generation/era_cosmo.yaml +++ b/src/hirad/conf/generation/era_cosmo.yaml @@ -42,4 +42,4 @@ io: reg_ckpt_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_era5_cosmo/checkpoints_regression # reg_ckpt_path: /iopsstor/scratch/cscs/pstamenk/outputs/regression_test/checkpoints_regression # Checkpoint filename for the mean predictor model - output_path: /capstor/scratch/cscs/mmcgloho/outputs/era-cosmo-1h/ \ No newline at end of file + output_path: ./outputs/evaluation \ No newline at end of file diff --git a/src/hirad/conf/training/era_cosmo_regression.yaml b/src/hirad/conf/training/era_cosmo_regression.yaml index db647261..b45b206c 100644 --- a/src/hirad/conf/training/era_cosmo_regression.yaml +++ b/src/hirad/conf/training/era_cosmo_regression.yaml @@ -1,13 +1,9 @@ # Hyperparameters hp: -<<<<<<< Updated upstream training_duration: 1024 -======= - training_duration: 1000 ->>>>>>> Stashed changes # Training duration based on the number of processed samples total_batch_size: 64 - # Total batch size + # Total batch size -- based 8 per GPU -- 2 nodes is 2x8x4 -- see sbatch vars for how many gpus. diffusion need to point to the rgression. batch_size_per_gpu: "auto" # Batch size per GPU lr: 0.0002 diff --git a/src/hirad/generate.sh b/src/hirad/generate.sh index a27a7277..adb9b30e 100644 --- a/src/hirad/generate.sh +++ b/src/hirad/generate.sh @@ -13,13 +13,7 @@ #SBATCH --exclusive ### OUTPUT ### -<<<<<<< Updated upstream -#SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/generation_diffusion_test.log -#SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/generation_diffusion_test.err -======= -#SBATCH --output=/capstor/scratch/cscs/mmcgloho/logs/regression_generation.log -#SBATCH --error=/capstor/scratch/cscs/mmcgloho/logs/regression_generation.err ->>>>>>> Stashed changes +#SBATCH --output=./logs/regression_generation.log ### ENVIRONMENT #### #SBATCH -A a122 @@ -52,4 +46,6 @@ export OMP_NUM_THREADS=72 srun --environment=./modulus_env.toml bash -c " pip install -e . --no-dependencies python src/hirad/inference/generate.py --config-name=generate_era_cosmo.yaml -" \ No newline at end of file +" + #pip install Cartopy==0.22.0 + #pip install xskillscore \ No newline at end of file From 9cf4d18e3692e834f3719ee38974649d03162610 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Fri, 11 Jul 2025 17:12:39 +0200 Subject: [PATCH 071/302] Move environments to ci/ directory --- .gitignore | 1 + ci/cscs.yml | 2 +- ci/docker/Dockerfile.ci | 15 +++++++++++++++ Dockerfile => ci/docker/Dockerfile.corrdiff | 0 modulus_env.toml => ci/edf/modulus_env.toml | 0 src/hirad/generate.sh | 2 +- 6 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 ci/docker/Dockerfile.ci rename Dockerfile => ci/docker/Dockerfile.corrdiff (100%) rename modulus_env.toml => ci/edf/modulus_env.toml (100%) diff --git a/.gitignore b/.gitignore index 7189efd7..17c4ea5d 100644 --- a/.gitignore +++ b/.gitignore @@ -175,6 +175,7 @@ pyrightconfig.json *.torch plots/* *.npz +outputs/* # conda .conda/* diff --git a/ci/cscs.yml b/ci/cscs.yml index fc926459..b96e65af 100644 --- a/ci/cscs.yml +++ b/ci/cscs.yml @@ -12,7 +12,7 @@ build_job: stage: build extends: .container-builder-cscs-gh200 variables: - DOCKERFILE: ci/docker/Dockerfile + DOCKERFILE: ci/docker/Dockerfile.ci #test_job: # stage: test diff --git a/ci/docker/Dockerfile.ci b/ci/docker/Dockerfile.ci new file mode 100644 index 00000000..80925d3f --- /dev/null +++ b/ci/docker/Dockerfile.ci @@ -0,0 +1,15 @@ +FROM nvcr.io/nvidia/physicsnemo/physicsnemo:25.06 + +# setup +RUN apt-get update && apt-get install python3-pip python3-venv -y +RUN pip install --upgrade pip + +# Install the rest of dependencies. +RUN pip install \ + Cartopy==0.22.0 \ + xskillscore + + + + + diff --git a/Dockerfile b/ci/docker/Dockerfile.corrdiff similarity index 100% rename from Dockerfile rename to ci/docker/Dockerfile.corrdiff diff --git a/modulus_env.toml b/ci/edf/modulus_env.toml similarity index 100% rename from modulus_env.toml rename to ci/edf/modulus_env.toml diff --git a/src/hirad/generate.sh b/src/hirad/generate.sh index adb9b30e..8f383266 100644 --- a/src/hirad/generate.sh +++ b/src/hirad/generate.sh @@ -43,7 +43,7 @@ export OMP_NUM_THREADS=72 # echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" # python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml -srun --environment=./modulus_env.toml bash -c " +srun --environment=./ci/edf/modulus_env.toml bash -c " pip install -e . --no-dependencies python src/hirad/inference/generate.py --config-name=generate_era_cosmo.yaml " From 482938812490badd0e47f864e8b603b60d8ddf69 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Fri, 11 Jul 2025 17:16:28 +0200 Subject: [PATCH 072/302] Update paths for CRPS plotting --- src/hirad/utils/inference_utils.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index ed50b051..8e95f1c3 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -330,19 +330,20 @@ def plot_crps_over_time(times, dataset, output_path): start_time=times[0] end_time=times[-1] - prediction_ensemble = torch.load(os.path.join(output_path, f'{times[0]}-predictions'), weights_only=False) + # Load one prediction ensemble to get the shape + prediction_ensemble = torch.load(os.path.join(output_path, times[0], f'{times[0]}-predictions'), weights_only=False) all_predictions = np.ndarray((len(times), prediction_ensemble.shape[0], prediction_ensemble.shape[1], prediction_ensemble.shape[2], prediction_ensemble.shape[3])) all_targets = np.ndarray((len(times), prediction_ensemble.shape[1], prediction_ensemble.shape[2], prediction_ensemble.shape[3])) for i in range(len(times)): - prediction_ensemble = torch.load(os.path.join(output_path, f'{times[i]}-predictions'), weights_only=False) + prediction_ensemble = torch.load(os.path.join(output_path, times[i], f'{times[i]}-predictions'), weights_only=False) all_predictions[i,::] = prediction_ensemble - target = torch.load(os.path.join(output_path, f'{times[i]}-target'), weights_only=False) + target = torch.load(os.path.join(output_path, times[i], f'{times[i]}-target'), weights_only=False) all_targets[i,::] = target score_over_time_channels = crps(all_predictions, all_targets, average_over_area=True, average_over_channels=False, average_over_time=False) score_over_area_channels = crps(all_predictions, all_targets, average_over_area=False, average_over_channels=False, average_over_time=True) for channel_num in range(score_over_area_channels.shape[0]): - _plot_projection(longitudes, latitudes, score_over_area_channels[channel_num,::], os.path.join(output_path, f'crps-time-{start_time}-{end_time}-{output_channels[channel_num].name}.jpg')) - _plot_score_vs_t(score_over_time_channels[:, channel_num], times, os.path.join(output_path, f'crps-area-{start_time}-{end_time}-{output_channels[channel_num].name}.jpg')) + _plot_projection(longitudes, latitudes, score_over_area_channels[channel_num,::], os.path.join(output_path, f'crps-area-{start_time}-{end_time}-{output_channels[channel_num].name}.jpg')) + _plot_score_vs_t(score_over_time_channels[:, channel_num], times, os.path.join(output_path, f'crps-time-{start_time}-{end_time}-{output_channels[channel_num].name}.jpg')) def _plot_score_vs_t(score: np.array, times: np.array, filename: str): fig = plt.figure() From 07db9f397b7b563566c3e938bcea725017c7baf4 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 14 Jul 2025 09:48:29 +0200 Subject: [PATCH 073/302] =?UTF-8?q?Change=20slurm=20account=20to=20new=20p?= =?UTF-8?q?roject=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hirad/generate.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/hirad/generate.sh b/src/hirad/generate.sh index 8f383266..1e3e9f5c 100644 --- a/src/hirad/generate.sh +++ b/src/hirad/generate.sh @@ -16,7 +16,7 @@ #SBATCH --output=./logs/regression_generation.log ### ENVIRONMENT #### -#SBATCH -A a122 +#SBATCH -A a161 # Choose method to initialize dist in pythorch export DISTRIBUTED_INITIALIZATION_METHOD=SLURM @@ -46,6 +46,4 @@ export OMP_NUM_THREADS=72 srun --environment=./ci/edf/modulus_env.toml bash -c " pip install -e . --no-dependencies python src/hirad/inference/generate.py --config-name=generate_era_cosmo.yaml -" - #pip install Cartopy==0.22.0 - #pip install xskillscore \ No newline at end of file +" \ No newline at end of file From 36953f84cc574173ccc6052042ed7acb0c79c2f8 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 16 Jul 2025 10:04:53 +0200 Subject: [PATCH 074/302] Starting plotting script --- src/hirad/conf/compute_eval.yaml | 12 ++ src/hirad/eval/__init__.py | 4 +- src/hirad/eval/compute_eval.py | 63 +++++++++++ src/hirad/eval/plotting.py | 183 ++++++++++++++++++++++++++++++- 4 files changed, 257 insertions(+), 5 deletions(-) create mode 100644 src/hirad/conf/compute_eval.yaml create mode 100644 src/hirad/eval/compute_eval.py diff --git a/src/hirad/conf/compute_eval.yaml b/src/hirad/conf/compute_eval.yaml new file mode 100644 index 00000000..069ad1e0 --- /dev/null +++ b/src/hirad/conf/compute_eval.yaml @@ -0,0 +1,12 @@ +hydra: + job: + chdir: true + name: diffusion_era5_cosmo_7500000_test + run: + dir: ./outputs/generation/${hydra:job.name} + +# Get defaults +defaults: + - _self_ + # Dataset + - dataset/era_cosmo_inference diff --git a/src/hirad/eval/__init__.py b/src/hirad/eval/__init__.py index a4b94751..db34154b 100644 --- a/src/hirad/eval/__init__.py +++ b/src/hirad/eval/__init__.py @@ -1,2 +1,2 @@ -from .metrics import compute_mae, average_power_spectrum, crps -from .plotting import plot_error_projection, plot_power_spectra +from .metrics import absolute_error, compute_mae, average_power_spectrum, crps +from .plotting import plot_error_projection, plot_power_spectra, compute_crps_over_time, compute_crps_over_time_and_area, plot_crps_over_time_and_area diff --git a/src/hirad/eval/compute_eval.py b/src/hirad/eval/compute_eval.py new file mode 100644 index 00000000..a8c11d86 --- /dev/null +++ b/src/hirad/eval/compute_eval.py @@ -0,0 +1,63 @@ +import hydra +import os +import json +from omegaconf import OmegaConf, DictConfig +import torch +import torch._dynamo +import numpy as np +import contextlib + +from hirad.distributed import DistributedManager +from hirad.utils.console import PythonLogger, RankZeroLoggingWrapper +from concurrent.futures import ThreadPoolExecutor + +from hirad.eval import compute_crps_over_time, plot_crps_over_time_and_area, compute_crps_over_time_and_area +from hirad.models import EDMPrecondSuperResolution, UNet +from hirad.inference import Generator +from hirad.utils.inference_utils import save_images, save_results_as_torch +from hirad.utils.function_utils import get_time_from_range +from hirad.utils.checkpoint import load_checkpoint + +from hirad.datasets import get_dataset_and_sampler_inference + +from hirad.utils.train_helpers import set_patch_shape + +@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") +def main(cfg: DictConfig) -> None: + + + # Initialize distributed manager + DistributedManager.initialize() + dist = DistributedManager() + device = dist.device + + + # Initialize logger + logger = PythonLogger("generate") # General python logger + logger0 = RankZeroLoggingWrapper(logger, dist) + + + if cfg.generation.times_range: + times = get_time_from_range(cfg.generation.times_range, time_format="%Y%m%d-%H%M") + + dataset_cfg = OmegaConf.to_container(cfg.dataset) + if "has_lead_time" in cfg.generation: + has_lead_time = cfg.generation["has_lead_time"] + else: + has_lead_time = False + dataset, sampler = get_dataset_and_sampler_inference( + dataset_cfg=dataset_cfg, times=times, has_lead_time=has_lead_time + ) + img_shape = dataset.image_shape() + img_out_channels = len(dataset.output_channels()) + output_path = getattr(cfg.generation.io, "output_path", "./outputs") + + #plot_crps_over_time_and_area(times, dataset, output_path) + #compute_crps_over_time(times, dataset, output_path) + compute_crps_over_time_and_area(times, dataset, output_path) + + + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index 1ca11c2e..58d37a30 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -1,20 +1,197 @@ import logging +import os + +from hirad.eval import crps, absolute_error import cartopy.crs as ccrs import matplotlib.pyplot as plt import numpy as np +import torch -def plot_error_projection(values: np.array, latitudes: np.array, longitudes: np.array, filename: str): +def plot_error_projection(values: np.array, latitudes: np.array, longitudes: np.array, filename: str, label: str, vmin=None, vmax=None): + """Plot observed or interpolated data in a scatter plot.""" fig = plt.figure() fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) logging.info(f'plotting values to {filename}') - p = ax.scatter(x=longitudes, y=latitudes, c=values) + p = ax.scatter(x=longitudes, y=latitudes, c=values, vmin=vmin, vmax=vmax) ax.coastlines() ax.gridlines(draw_labels=True) - plt.colorbar(p, label="absolute error", orientation="horizontal") + plt.colorbar(p, label=label, orientation="horizontal") plt.savefig(filename) plt.close('all') +def compute_crps_over_time(times, dataset, output_path): + logging.info('computing crps') + longitudes = dataset.longitude() + latitudes = dataset.latitude() + input_channels = dataset.input_channels() + output_channels = dataset.output_channels() + start_time=times[0] + end_time=times[-1] + + # Load one prediction ensemble to get the shape + prediction_ensemble = torch.load(os.path.join(output_path, times[0], f'{times[0]}-predictions'), weights_only=False) + #all_predictions = np.ndarray((len(times), prediction_ensemble.shape[0], prediction_ensemble.shape[1], prediction_ensemble.shape[2], prediction_ensemble.shape[3])) + #all_targets = np.ndarray((len(times), prediction_ensemble.shape[1], prediction_ensemble.shape[2], prediction_ensemble.shape[3])) + + for i in range(len(times)): + logging.info(f'computing crps for {times[i]}') + prediction_ensemble = torch.load(os.path.join(output_path, times[i], f'{times[i]}-predictions'), weights_only=False) + baseline = torch.load(os.path.join(output_path, times[i], f'{times[i]}-baseline'), weights_only=False) + target = torch.load(os.path.join(output_path, times[i], f'{times[i]}-target'), weights_only=False) + + # Calculate CRPS + crps_area = crps(prediction_ensemble, target, average_over_area=False, average_over_channels=False) + + # Calculate ensemble mean error + ensemble_mean = np.mean(prediction_ensemble, 0) + ensemble_mean_error = absolute_error(ensemble_mean, target) + + # Calculate interpolation error (baseline #1) + interpolation_error = np.zeros(baseline.shape) + for j in range(len(output_channels)): + index = -1 + for k in range(len(input_channels)): + if input_channels[k].name == output_channels[j].name: + index = k + if k > -1: + interpolation_error[j,::] = baseline[k,::] - target[j,::] + + # Calculate persistence error (baseline #2) + persistence_error = np.zeros(baseline.shape) + if i > 0: + prev = torch.load(os.path.join(output_path, times[i-1], f'{times[i-1]}-target'), weights_only=False) + persistence_error = absolute_error(prev, target) + else: + persistence_error = absolute_error(target, target) + + + torch.save(crps_area, os.path.join(output_path, times[i], f'{times[i]}-crps')) + torch.save(ensemble_mean_error, os.path.join(output_path, times[i], f'{times[i]}-ensemble-mean-error')) + torch.save(interpolation_error, os.path.join(output_path, times[i], f'{times[i]}-interpolation-error')) + torch.save(persistence_error, os.path.join(output_path, times[i], f'{times[i]}-persistence-error')) + +def plot_crps_over_time_and_area(times, dataset, output_path): + logging.info('plotting crps') + longitudes = dataset.longitude() + latitudes = dataset.latitude() + input_channels = dataset.input_channels() + output_channels = dataset.output_channels() + start_time=times[0] + end_time=times[-1] + + maxes = torch.load(os.path.join(output_path, f'crps-maxes-{start_time}-{end_time}'), weights_only=False) + mins = torch.load(os.path.join(output_path, f'crps-mins-{start_time}-{end_time}'), weights_only=False) + crps_time = torch.load(os.path.join(output_path, f'crps-time-{start_time}-{end_time}'), weights_only=False) + crps_area = torch.load(os.path.join(output_path, f'crps-area-{start_time}-{end_time}'), weights_only=False) + interpolation_time = torch.load(os.path.join(output_path, f'interpolation-time-{start_time}-{end_time}'), weights_only=False) + persistence_time = torch.load(os.path.join(output_path, f'persistence-time-{start_time}-{end_time}'), weights_only=False) + + + for j in range(crps_area.shape[0]): + plot_error_projection(crps_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-crps-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + label=output_channels[j].name) + plot_error_projection(interpolation_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-crps-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + label=output_channels[j].name) + plot_error_projection(crps_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-crps-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + label=output_channels[j].name) + _plot_score_vs_t(crps_time[j,::], times, os.path.join(output_path, f'NEW-crps-time-{start_time}-{end_time}-{output_channels[j].name}.jpg')) + + for j in range(len(output_channels)): + for i in range(len(times)): + plot_error_projection(crps_area[j,::], latitudes, longitudes, + os.path.join(output_path, 'animations', output_channels[j].name, f'{times[i]}.jpg'), + label=output_channels[j].name) + + +def compute_crps_over_time_and_area(times, dataset, output_path): + logging.info('computing crps and errors') + longitudes = dataset.longitude() + latitudes = dataset.latitude() + input_channels = dataset.input_channels() + output_channels = dataset.output_channels() + start_time=times[0] + end_time=times[-1] + + logging.info('calculating min/max') + + crps_area = torch.load(os.path.join(output_path, times[0], f'{times[0]}-crps'), weights_only=False) + mins = np.ones((crps_area.shape[0])) * 999999999 + maxes = np.zeros((crps_area.shape[0])) + + for i in range(len(times)): + if i % (24*5) == 0: + logging.info(f'on time {times[i]}') + # Shape = channels, x, y + crps_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-crps'), weights_only=False) + for j in range(crps_area.shape[0]): + max = np.max(crps_area[j,::]) + min = np.min(crps_area[j,::]) + if max > maxes[j]: + maxes[j] = max + if min < mins[j]: + mins[j] = min + logging.info(f'maxes are {maxes}') + logging.info(f'mins are {mins}') + + torch.save(maxes, os.path.join(output_path, f'crps-maxes-{times[0]}-{times[len(times)-1]}')) + torch.save(mins, os.path.join(output_path, f'crps-mins-{times[0]}-{times[len(times)-1]}')) + + # make area and time plot + total_crps_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) + total_ensemble_mean_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) + total_interpolation_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) + total_persistence_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) + + crps_over_time = np.zeros((crps_area.shape[0], len(times))) + ensemble_mean_over_time = np.zeros((crps_area.shape[0], len(times))) + interpolation_over_time = np.zeros((crps_area.shape[0], len(times))) + persistence_over_time = np.zeros((crps_area.shape[0], len(times))) + for i in range(len(times)): + if i % (24*5) == 0: + logging.info(f'on time {times[i]}') + crps_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-crps'), weights_only=False) + total_crps_area = total_crps_area + crps_area + ensemble_mean_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-ensemble-mean-error'), weights_only=False) + total_ensemble_mean_area = total_ensemble_mean_area + ensemble_mean_area + interpolation_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-interpolation-error'), weights_only=False) + total_interpolation_area = total_interpolation_area + interpolation_area + persistence_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-persistence-error'), weights_only=False) + total_persistence_area = total_persistence_area + persistence_area + + for j in range(crps_area.shape[0]): + crps_over_time[j,i] = np.mean(crps_area[j,::]) + ensemble_mean_over_time[j,i] = np.mean(ensemble_mean_area[j,::]) + interpolation_over_time[j,i] = np.mean(interpolation_area[j,::]) + persistence_over_time[j,i] = np.mean(persistence_area[j,::]) + mean_crps_area = total_crps_area / len(times) + mean_ensemble_mean_area = total_ensemble_mean_area / len(times) + mean_interpolation_area = total_interpolation_area / len(times) + mean_persistence_area = total_persistence_area / len(times) + torch.save(mean_crps_area, os.path.join(output_path, f'crps-area-{times[0]}-{times[len(times)-1]}')) + torch.save(mean_ensemble_mean_area, os.path.join(output_path, f'mae-ensemble-mean-area-{times[0]}-{times[len(times)-1]}')) + torch.save(mean_interpolation_area, os.path.join(output_path, f'mae-interpolation-area-{times[0]}-{times[len(times)-1]}')) + torch.save(mean_persistence_area, os.path.join(output_path, f'mae-persistence-area-{times[0]}-{times[len(times)-1]}')) + + + torch.save(crps_over_time, os.path.join(output_path, f'crps-time-{times[0]}-{times[len(times)-1]}')) + torch.save(ensemble_mean_over_time, os.path.join(output_path, f'mae-ensemble-mean-time-{times[0]}-{times[len(times)-1]}')) + torch.save(interpolation_over_time, os.path.join(output_path, f'mae-interpolation-time-{times[0]}-{times[len(times)-1]}')) + torch.save(persistence_over_time, os.path.join(output_path, f'mae-persistence-time-{times[0]}-{times[len(times)-1]}')) + +def _plot_score_vs_t(score: np.array, times: np.array, filename: str): + fig = plt.figure() + ax = plt.subplot() + p = plt.plot(times, score) + #plt.ylabel('CRPS') + #plt.xlabel('time') + plt.xticks([times[0],times[-1]]) + plt.savefig(filename) + plt.close('all') + + + + def plot_power_spectra(freqs: dict, spec: dict, channel_name, filename): fig = plt.figure() for k in freqs.keys(): From 872ab702781d6762a15ea2b01ccf5868dc0b7745 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 16 Jul 2025 10:05:07 +0200 Subject: [PATCH 075/302] add time point to logging --- src/hirad/inference/generate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index cea82a24..d26a1681 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -225,7 +225,7 @@ def elapsed_time(self, _): ): time_index += 1 if dist.rank == 0: - logger0.info(f"starting index: {time_index}") + logger0.info(f"starting index: {time_index} time: {times[sampler[time_index]]}") if time_index == warmup_steps: start.record() From 38d1d88d6da8759e12f20ac656d4adb7c88b0de0 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 16 Jul 2025 10:05:19 +0200 Subject: [PATCH 076/302] eval script --- src/hirad/eval.sh | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/hirad/eval.sh diff --git a/src/hirad/eval.sh b/src/hirad/eval.sh new file mode 100644 index 00000000..64bb9959 --- /dev/null +++ b/src/hirad/eval.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +#SBATCH --job-name="testrun" + +### HARDWARE ### +#SBATCH --partition=normal +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=2 +#SBATCH --gpus-per-node=2 +#SBATCH --cpus-per-task=72 +##SBATCH --time=00:30:00 +#SBATCH --no-requeue +#SBATCH --exclusive + +### OUTPUT ### +#SBATCH --output=./logs/eval_compute.log + +### ENVIRONMENT #### +#SBATCH -A a161 + +# Choose method to initialize dist in pythorch +export DISTRIBUTED_INITIALIZATION_METHOD=SLURM + +MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" +echo "Master node : $MASTER_ADDR" +# Get IP for hostname. +MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" +echo "Master address : $MASTER_ADDR" +export MASTER_ADDR +export MASTER_PORT=29500 +echo "Master port: $MASTER_PORT" + +# Get number of physical cores using Python +# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") +# # Use SLURM_NTASKS (number of processes to be launched by torchrun) +# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} +# # Compute threads per process +# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) +# export OMP_NUM_THREADS=$OMP_THREADS +export OMP_NUM_THREADS=72 +# echo "Physical cores: $PHYSICAL_CORES" +# echo "Local processes: $LOCAL_PROCS" +# echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" + +# python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml +srun --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . --no-dependencies + python src/hirad/eval/compute_eval.py --config-name=generate_era_cosmo.yaml +" \ No newline at end of file From 97434911b3479b6fb54f54fab8f654b07f2062ad Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 16 Jul 2025 10:33:47 +0200 Subject: [PATCH 077/302] add ensemble mean MAE, and add a comparison plot over time --- src/hirad/eval/compute_eval.py | 4 +- src/hirad/eval/plotting.py | 133 +++++++++++++++++++-------------- 2 files changed, 78 insertions(+), 59 deletions(-) diff --git a/src/hirad/eval/compute_eval.py b/src/hirad/eval/compute_eval.py index a8c11d86..e6113732 100644 --- a/src/hirad/eval/compute_eval.py +++ b/src/hirad/eval/compute_eval.py @@ -52,9 +52,9 @@ def main(cfg: DictConfig) -> None: img_out_channels = len(dataset.output_channels()) output_path = getattr(cfg.generation.io, "output_path", "./outputs") - #plot_crps_over_time_and_area(times, dataset, output_path) - #compute_crps_over_time(times, dataset, output_path) + compute_crps_over_time(times, dataset, output_path) compute_crps_over_time_and_area(times, dataset, output_path) + plot_crps_over_time_and_area(times, dataset, output_path) diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index 58d37a30..0924e3d8 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -20,6 +20,44 @@ def plot_error_projection(values: np.array, latitudes: np.array, longitudes: np. plt.savefig(filename) plt.close('all') +def _plot_score_vs_t(score: np.array, times: np.array, filename: str): + fig = plt.figure() + ax = plt.subplot() + p = plt.plot(times, score) + #plt.ylabel('CRPS') + #plt.xlabel('time') + plt.xticks([times[0],times[-1]]) + plt.savefig(filename) + plt.close('all') + +def plot_scores_vs_t(scores: dict[str,np.ndarray], times: np.array, filename: str): + fig = plt.figure() + ax = plt.subplot() + colors = {'red', 'green', 'blue', 'orange'} # TODO, add more + for k in scores.keys(): + p = ax.plot(times, scores[k], color=colors[k]) + p.set_label(k) + ax.legend() + #plt.ylabel('CRPS') + #plt.xlabel('time') + ax.set_xticks([times[0],times[-1]]) + plt.savefig(filename) + plt.close('all') + +def plot_power_spectra(freqs: dict, spec: dict, channel_name, filename): + fig = plt.figure() + for k in freqs.keys(): + plt.loglog(freqs[k], spec[k], label=k) + plt.title(channel_name) + plt.legend() + plt.xlabel("Frequency (1/km)") + plt.ylabel("Power Spectrum") + plt.ylim(bottom=1e-1) + #plt.psd(x) + logging.info(f'plotting values to {filename}') + plt.savefig(filename) + plt.close('all') + def compute_crps_over_time(times, dataset, output_path): logging.info('computing crps') longitudes = dataset.longitude() @@ -71,39 +109,6 @@ def compute_crps_over_time(times, dataset, output_path): torch.save(interpolation_error, os.path.join(output_path, times[i], f'{times[i]}-interpolation-error')) torch.save(persistence_error, os.path.join(output_path, times[i], f'{times[i]}-persistence-error')) -def plot_crps_over_time_and_area(times, dataset, output_path): - logging.info('plotting crps') - longitudes = dataset.longitude() - latitudes = dataset.latitude() - input_channels = dataset.input_channels() - output_channels = dataset.output_channels() - start_time=times[0] - end_time=times[-1] - - maxes = torch.load(os.path.join(output_path, f'crps-maxes-{start_time}-{end_time}'), weights_only=False) - mins = torch.load(os.path.join(output_path, f'crps-mins-{start_time}-{end_time}'), weights_only=False) - crps_time = torch.load(os.path.join(output_path, f'crps-time-{start_time}-{end_time}'), weights_only=False) - crps_area = torch.load(os.path.join(output_path, f'crps-area-{start_time}-{end_time}'), weights_only=False) - interpolation_time = torch.load(os.path.join(output_path, f'interpolation-time-{start_time}-{end_time}'), weights_only=False) - persistence_time = torch.load(os.path.join(output_path, f'persistence-time-{start_time}-{end_time}'), weights_only=False) - - - for j in range(crps_area.shape[0]): - plot_error_projection(crps_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-crps-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), - label=output_channels[j].name) - plot_error_projection(interpolation_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-crps-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), - label=output_channels[j].name) - plot_error_projection(crps_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-crps-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), - label=output_channels[j].name) - _plot_score_vs_t(crps_time[j,::], times, os.path.join(output_path, f'NEW-crps-time-{start_time}-{end_time}-{output_channels[j].name}.jpg')) - - for j in range(len(output_channels)): - for i in range(len(times)): - plot_error_projection(crps_area[j,::], latitudes, longitudes, - os.path.join(output_path, 'animations', output_channels[j].name, f'{times[i]}.jpg'), - label=output_channels[j].name) - - def compute_crps_over_time_and_area(times, dataset, output_path): logging.info('computing crps and errors') longitudes = dataset.longitude() @@ -157,7 +162,8 @@ def compute_crps_over_time_and_area(times, dataset, output_path): interpolation_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-interpolation-error'), weights_only=False) total_interpolation_area = total_interpolation_area + interpolation_area persistence_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-persistence-error'), weights_only=False) - total_persistence_area = total_persistence_area + persistence_area + if i>0: + total_persistence_area = total_persistence_area + persistence_area for j in range(crps_area.shape[0]): crps_over_time[j,i] = np.mean(crps_area[j,::]) @@ -167,7 +173,7 @@ def compute_crps_over_time_and_area(times, dataset, output_path): mean_crps_area = total_crps_area / len(times) mean_ensemble_mean_area = total_ensemble_mean_area / len(times) mean_interpolation_area = total_interpolation_area / len(times) - mean_persistence_area = total_persistence_area / len(times) + mean_persistence_area = total_persistence_area / (len(times)-1) torch.save(mean_crps_area, os.path.join(output_path, f'crps-area-{times[0]}-{times[len(times)-1]}')) torch.save(mean_ensemble_mean_area, os.path.join(output_path, f'mae-ensemble-mean-area-{times[0]}-{times[len(times)-1]}')) torch.save(mean_interpolation_area, os.path.join(output_path, f'mae-interpolation-area-{times[0]}-{times[len(times)-1]}')) @@ -179,29 +185,42 @@ def compute_crps_over_time_and_area(times, dataset, output_path): torch.save(interpolation_over_time, os.path.join(output_path, f'mae-interpolation-time-{times[0]}-{times[len(times)-1]}')) torch.save(persistence_over_time, os.path.join(output_path, f'mae-persistence-time-{times[0]}-{times[len(times)-1]}')) -def _plot_score_vs_t(score: np.array, times: np.array, filename: str): - fig = plt.figure() - ax = plt.subplot() - p = plt.plot(times, score) - #plt.ylabel('CRPS') - #plt.xlabel('time') - plt.xticks([times[0],times[-1]]) - plt.savefig(filename) - plt.close('all') +def plot_crps_over_time_and_area(times, dataset, output_path): + logging.info('plotting crps and errors') + longitudes = dataset.longitude() + latitudes = dataset.latitude() + input_channels = dataset.input_channels() + output_channels = dataset.output_channels() + start_time=times[0] + end_time=times[-1] + maxes = torch.load(os.path.join(output_path, f'crps-maxes-{start_time}-{end_time}'), weights_only=False) + mins = torch.load(os.path.join(output_path, f'crps-mins-{start_time}-{end_time}'), weights_only=False) + crps_time = torch.load(os.path.join(output_path, f'crps-time-{start_time}-{end_time}'), weights_only=False) + crps_area = torch.load(os.path.join(output_path, f'crps-area-{start_time}-{end_time}'), weights_only=False) + ensemble_mean_time = torch.load(os.path.join(output_path, f'mae-ensemble-mean-time-{start_time}-{end_time}'), weights_only=False) + ensemble_mean_area = torch.load(os.path.join(output_path, f'mae-ensemble-mean-area-{start_time}-{end_time}'), weights_only=False) + interpolation_time = torch.load(os.path.join(output_path, f'mae-interpolation-time-{start_time}-{end_time}'), weights_only=False) + interpolation_area = torch.load(os.path.join(output_path, f'mae-interpolation-area-{start_time}-{end_time}'), weights_only=False) + persistence_time = torch.load(os.path.join(output_path, f'mae-persistence-time-{start_time}-{end_time}'), weights_only=False) + persistence_area = torch.load(os.path.join(output_path, f'mae-persistence-area-{start_time}-{end_time}'), weights_only=False) + for j in range(crps_area.shape[0]): + plot_error_projection(crps_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-crps-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + label=output_channels[j].name) + plot_error_projection(ensemble_mean_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-mae-ensemble-mean-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + label=output_channels[j].name) + plot_error_projection(interpolation_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-mae-interpolation-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + label=output_channels[j].name) + plot_error_projection(persistence_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-mae-persistence-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + label=output_channels[j].name) + + _plot_score_vs_t(crps_time[j,::], times, os.path.join(output_path, f'NEW-crps-time-{start_time}-{end_time}-{output_channels[j].name}.jpg')) -def plot_power_spectra(freqs: dict, spec: dict, channel_name, filename): - fig = plt.figure() - for k in freqs.keys(): - plt.loglog(freqs[k], spec[k], label=k) - plt.title(channel_name) - plt.legend() - plt.xlabel("Frequency (1/km)") - plt.ylabel("Power Spectrum") - plt.ylim(bottom=1e-1) - #plt.psd(x) - logging.info(f'plotting values to {filename}') - plt.savefig(filename) - plt.close('all') \ No newline at end of file + maes = {} + maes['ensemble mean'] = ensemble_mean_time[j,::] + maes['interpolation'] = interpolation_time[j,::] + maes['persistence'] = persistence_time[j,::] + plot_scores_vs_t(maes, times, os.path.join(output_path, f'NEW-mae-time-{start_time}-{end_time}-{output_channels[j].name}.jpg')) + From ebdec81e42785e7f53f255c2ba54caa0e4d5c692 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 16 Jul 2025 13:12:23 +0200 Subject: [PATCH 078/302] Do some extra plotting tests to ensure that CRPS reduces to MAE --- src/hirad/eval/plotting.py | 121 +++++++++++++++++++++---------------- 1 file changed, 68 insertions(+), 53 deletions(-) diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index 0924e3d8..56cc69fe 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -20,27 +20,22 @@ def plot_error_projection(values: np.array, latitudes: np.array, longitudes: np. plt.savefig(filename) plt.close('all') -def _plot_score_vs_t(score: np.array, times: np.array, filename: str): +def plot_scores_vs_t(scores: dict[str,np.ndarray], times: np.array, filename: str, xlabel='', ylabel='', title=''): fig = plt.figure() ax = plt.subplot() - p = plt.plot(times, score) - #plt.ylabel('CRPS') - #plt.xlabel('time') - plt.xticks([times[0],times[-1]]) - plt.savefig(filename) - plt.close('all') - -def plot_scores_vs_t(scores: dict[str,np.ndarray], times: np.array, filename: str): - fig = plt.figure() - ax = plt.subplot() - colors = {'red', 'green', 'blue', 'orange'} # TODO, add more + colors = ['red', 'green', 'blue', 'orange'] # TODO, add more + i=0 for k in scores.keys(): - p = ax.plot(times, scores[k], color=colors[k]) + p, = ax.plot(times, scores[k], color=colors[i]) + i=i+1 p.set_label(k) ax.legend() #plt.ylabel('CRPS') #plt.xlabel('time') ax.set_xticks([times[0],times[-1]]) + plt.xlabel(xlabel) + plt.ylabel(ylabel) + plt.title(title) plt.savefig(filename) plt.close('all') @@ -72,39 +67,55 @@ def compute_crps_over_time(times, dataset, output_path): #all_predictions = np.ndarray((len(times), prediction_ensemble.shape[0], prediction_ensemble.shape[1], prediction_ensemble.shape[2], prediction_ensemble.shape[3])) #all_targets = np.ndarray((len(times), prediction_ensemble.shape[1], prediction_ensemble.shape[2], prediction_ensemble.shape[3])) + output_to_input_channel_map = {} + for j in range(len(output_channels)): + index = -1 + for k in range(len(input_channels)): + if input_channels[k].name == output_channels[j].name: + logging.info(f'found index of {j}:{output_channels[j].name} at {k}') + index = k + output_to_input_channel_map[j] = index + + for i in range(len(times)): - logging.info(f'computing crps for {times[i]}') prediction_ensemble = torch.load(os.path.join(output_path, times[i], f'{times[i]}-predictions'), weights_only=False) baseline = torch.load(os.path.join(output_path, times[i], f'{times[i]}-baseline'), weights_only=False) target = torch.load(os.path.join(output_path, times[i], f'{times[i]}-target'), weights_only=False) - # Calculate CRPS - crps_area = crps(prediction_ensemble, target, average_over_area=False, average_over_channels=False) - # Calculate ensemble mean error ensemble_mean = np.mean(prediction_ensemble, 0) ensemble_mean_error = absolute_error(ensemble_mean, target) # Calculate interpolation error (baseline #1) - interpolation_error = np.zeros(baseline.shape) + interpolation_error = np.zeros(target.shape) for j in range(len(output_channels)): - index = -1 - for k in range(len(input_channels)): - if input_channels[k].name == output_channels[j].name: - index = k + k = output_to_input_channel_map[j] if k > -1: - interpolation_error[j,::] = baseline[k,::] - target[j,::] + interpolation_error[j,::] = absolute_error(baseline[k,::], target[j,::]) # Calculate persistence error (baseline #2) persistence_error = np.zeros(baseline.shape) + prev=[] if i > 0: prev = torch.load(os.path.join(output_path, times[i-1], f'{times[i-1]}-target'), weights_only=False) persistence_error = absolute_error(prev, target) else: + # persist the next-time-point target-- this is fiction but it keeps the plots from looking weird. + prev = torch.load(os.path.join(output_path, times[i+1], f'{times[i+1]}-target'), weights_only=False) persistence_error = absolute_error(target, target) + + # Calculate CRPS + crps_diffusion_area = crps(prediction_ensemble, target, average_over_area=False, average_over_channels=False) + crps_interpolation_area = crps(np.broadcast_to(baseline, np.insert(baseline.shape, 0, 8)), target, average_over_area=False, average_over_channels=False) + crps_ensemble_mean_area = crps(np.broadcast_to(ensemble_mean, np.insert(ensemble_mean.shape, 0, 8)), target, average_over_area=False, average_over_channels=False) + crps_persistence_area = crps(np.broadcast_to(prev, np.insert(prev.shape, 0, 8)), target, average_over_area=False, average_over_channels=False) - torch.save(crps_area, os.path.join(output_path, times[i], f'{times[i]}-crps')) + + torch.save(crps_diffusion_area, os.path.join(output_path, times[i], f'{times[i]}-crps-ensemble')) + torch.save(crps_interpolation_area, os.path.join(output_path, times[i], f'{times[i]}-crps-interpolation')) + torch.save(crps_ensemble_mean_area, os.path.join(output_path, times[i], f'{times[i]}-crps-ensemble-mean')) + torch.save(crps_persistence_area, os.path.join(output_path, times[i], f'{times[i]}-crps-persistence')) torch.save(ensemble_mean_error, os.path.join(output_path, times[i], f'{times[i]}-ensemble-mean-error')) torch.save(interpolation_error, os.path.join(output_path, times[i], f'{times[i]}-interpolation-error')) torch.save(persistence_error, os.path.join(output_path, times[i], f'{times[i]}-persistence-error')) @@ -121,26 +132,6 @@ def compute_crps_over_time_and_area(times, dataset, output_path): logging.info('calculating min/max') crps_area = torch.load(os.path.join(output_path, times[0], f'{times[0]}-crps'), weights_only=False) - mins = np.ones((crps_area.shape[0])) * 999999999 - maxes = np.zeros((crps_area.shape[0])) - - for i in range(len(times)): - if i % (24*5) == 0: - logging.info(f'on time {times[i]}') - # Shape = channels, x, y - crps_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-crps'), weights_only=False) - for j in range(crps_area.shape[0]): - max = np.max(crps_area[j,::]) - min = np.min(crps_area[j,::]) - if max > maxes[j]: - maxes[j] = max - if min < mins[j]: - mins[j] = min - logging.info(f'maxes are {maxes}') - logging.info(f'mins are {mins}') - - torch.save(maxes, os.path.join(output_path, f'crps-maxes-{times[0]}-{times[len(times)-1]}')) - torch.save(mins, os.path.join(output_path, f'crps-mins-{times[0]}-{times[len(times)-1]}')) # make area and time plot total_crps_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) @@ -149,14 +140,22 @@ def compute_crps_over_time_and_area(times, dataset, output_path): total_persistence_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) crps_over_time = np.zeros((crps_area.shape[0], len(times))) + crps_ensemble_mean_over_time = np.zeros((crps_area.shape[0], len(times))) + crps_persistence_over_time = np.zeros((crps_area.shape[0], len(times))) + crps_interpolation_over_time = np.zeros((crps_area.shape[0], len(times))) ensemble_mean_over_time = np.zeros((crps_area.shape[0], len(times))) interpolation_over_time = np.zeros((crps_area.shape[0], len(times))) persistence_over_time = np.zeros((crps_area.shape[0], len(times))) for i in range(len(times)): if i % (24*5) == 0: logging.info(f'on time {times[i]}') - crps_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-crps'), weights_only=False) + crps_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-crps-ensemble'), weights_only=False) total_crps_area = total_crps_area + crps_area + + crps_ensemble_mean_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-crps-ensemble-mean'), weights_only=False) + crps_interpolation_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-crps-interpolation'), weights_only=False) + crps_persistence_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-crps-persistence'), weights_only=False) + ensemble_mean_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-ensemble-mean-error'), weights_only=False) total_ensemble_mean_area = total_ensemble_mean_area + ensemble_mean_area interpolation_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-interpolation-error'), weights_only=False) @@ -167,6 +166,9 @@ def compute_crps_over_time_and_area(times, dataset, output_path): for j in range(crps_area.shape[0]): crps_over_time[j,i] = np.mean(crps_area[j,::]) + crps_ensemble_mean_over_time[j,i] = np.mean(crps_ensemble_mean_area[j,::]) + crps_interpolation_over_time[j,i] = np.mean(crps_interpolation_area[j,::]) + crps_persistence_over_time[j,i] = np.mean(crps_persistence_area[j,::]) ensemble_mean_over_time[j,i] = np.mean(ensemble_mean_area[j,::]) interpolation_over_time[j,i] = np.mean(interpolation_area[j,::]) persistence_over_time[j,i] = np.mean(persistence_area[j,::]) @@ -174,13 +176,19 @@ def compute_crps_over_time_and_area(times, dataset, output_path): mean_ensemble_mean_area = total_ensemble_mean_area / len(times) mean_interpolation_area = total_interpolation_area / len(times) mean_persistence_area = total_persistence_area / (len(times)-1) - torch.save(mean_crps_area, os.path.join(output_path, f'crps-area-{times[0]}-{times[len(times)-1]}')) + torch.save(mean_crps_area, os.path.join(output_path, f'crps-ensemble-area-{times[0]}-{times[len(times)-1]}')) torch.save(mean_ensemble_mean_area, os.path.join(output_path, f'mae-ensemble-mean-area-{times[0]}-{times[len(times)-1]}')) torch.save(mean_interpolation_area, os.path.join(output_path, f'mae-interpolation-area-{times[0]}-{times[len(times)-1]}')) torch.save(mean_persistence_area, os.path.join(output_path, f'mae-persistence-area-{times[0]}-{times[len(times)-1]}')) + # Little hack to make the plots look nicer, without having to change dimensions. + persistence_over_time[:,0] = persistence_over_time[:,1] + + torch.save(crps_over_time, os.path.join(output_path, f'crps-ensemble-time-{times[0]}-{times[len(times)-1]}')) + torch.save(crps_ensemble_mean_over_time, os.path.join(output_path, f'crps-ensemble-mean-time-{times[0]}-{times[len(times)-1]}')) + torch.save(crps_interpolation_over_time, os.path.join(output_path, f'crps-interpolation-time-{times[0]}-{times[len(times)-1]}')) + torch.save(crps_persistence_over_time, os.path.join(output_path, f'crps-persistence-time-{times[0]}-{times[len(times)-1]}')) - torch.save(crps_over_time, os.path.join(output_path, f'crps-time-{times[0]}-{times[len(times)-1]}')) torch.save(ensemble_mean_over_time, os.path.join(output_path, f'mae-ensemble-mean-time-{times[0]}-{times[len(times)-1]}')) torch.save(interpolation_over_time, os.path.join(output_path, f'mae-interpolation-time-{times[0]}-{times[len(times)-1]}')) torch.save(persistence_over_time, os.path.join(output_path, f'mae-persistence-time-{times[0]}-{times[len(times)-1]}')) @@ -194,9 +202,10 @@ def plot_crps_over_time_and_area(times, dataset, output_path): start_time=times[0] end_time=times[-1] - maxes = torch.load(os.path.join(output_path, f'crps-maxes-{start_time}-{end_time}'), weights_only=False) - mins = torch.load(os.path.join(output_path, f'crps-mins-{start_time}-{end_time}'), weights_only=False) - crps_time = torch.load(os.path.join(output_path, f'crps-time-{start_time}-{end_time}'), weights_only=False) + crps_ensemble_time = torch.load(os.path.join(output_path, f'crps-time-{start_time}-{end_time}'), weights_only=False) + crps_ensemble_mean_time = torch.load(os.path.join(output_path, f'crps-ensemble-mean-time-{start_time}-{end_time}'), weights_only=False) + crps_interpolation_time = torch.load(os.path.join(output_path, f'crps-interpolation-time-{start_time}-{end_time}'), weights_only=False) + crps_persistence_time = torch.load(os.path.join(output_path, f'crps-persistence-time-{start_time}-{end_time}'), weights_only=False) crps_area = torch.load(os.path.join(output_path, f'crps-area-{start_time}-{end_time}'), weights_only=False) ensemble_mean_time = torch.load(os.path.join(output_path, f'mae-ensemble-mean-time-{start_time}-{end_time}'), weights_only=False) ensemble_mean_area = torch.load(os.path.join(output_path, f'mae-ensemble-mean-area-{start_time}-{end_time}'), weights_only=False) @@ -216,11 +225,17 @@ def plot_crps_over_time_and_area(times, dataset, output_path): plot_error_projection(persistence_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-mae-persistence-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), label=output_channels[j].name) - _plot_score_vs_t(crps_time[j,::], times, os.path.join(output_path, f'NEW-crps-time-{start_time}-{end_time}-{output_channels[j].name}.jpg')) + crps_scores = {} + crps_scores['ensemble predictions'] = crps_ensemble_time[j,::] + crps_scores['ensemble mean'] = crps_ensemble_mean_time[j,::] + crps_scores['interpolation'] = crps_interpolation_time[j,::] + crps_scores['persistence'] = crps_persistence_time[j,::] + plot_scores_vs_t(crps_scores, times, os.path.join(output_path, f'NEW-crps-time-{start_time}-{end_time}-{output_channels[j].name}.jpg'), title=f'CRPS: {output_channels[j].name}', xlabel='time', ylabel='CRPS') maes = {} - maes['ensemble mean'] = ensemble_mean_time[j,::] maes['interpolation'] = interpolation_time[j,::] + maes['ensemble mean'] = ensemble_mean_time[j,::] + maes['crps'] = crps_ensemble_time[j,:] maes['persistence'] = persistence_time[j,::] - plot_scores_vs_t(maes, times, os.path.join(output_path, f'NEW-mae-time-{start_time}-{end_time}-{output_channels[j].name}.jpg')) + plot_scores_vs_t(maes, times, os.path.join(output_path, f'NEW-mae-time-{start_time}-{end_time}-{output_channels[j].name}.jpg'), title=f'Mean absolute error: {output_channels[j].name}', xlabel='time', ylabel='MAE') From 02e8788665ae35ce46efaff8bf0d42832209f4c7 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 16 Jul 2025 13:15:11 +0200 Subject: [PATCH 079/302] Remove superfluous plotting, now that we know CRPS reduvces to MAE --- src/hirad/eval/plotting.py | 35 ++--------------------------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index 56cc69fe..cbb0eb00 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -107,15 +107,8 @@ def compute_crps_over_time(times, dataset, output_path): # Calculate CRPS crps_diffusion_area = crps(prediction_ensemble, target, average_over_area=False, average_over_channels=False) - crps_interpolation_area = crps(np.broadcast_to(baseline, np.insert(baseline.shape, 0, 8)), target, average_over_area=False, average_over_channels=False) - crps_ensemble_mean_area = crps(np.broadcast_to(ensemble_mean, np.insert(ensemble_mean.shape, 0, 8)), target, average_over_area=False, average_over_channels=False) - crps_persistence_area = crps(np.broadcast_to(prev, np.insert(prev.shape, 0, 8)), target, average_over_area=False, average_over_channels=False) - torch.save(crps_diffusion_area, os.path.join(output_path, times[i], f'{times[i]}-crps-ensemble')) - torch.save(crps_interpolation_area, os.path.join(output_path, times[i], f'{times[i]}-crps-interpolation')) - torch.save(crps_ensemble_mean_area, os.path.join(output_path, times[i], f'{times[i]}-crps-ensemble-mean')) - torch.save(crps_persistence_area, os.path.join(output_path, times[i], f'{times[i]}-crps-persistence')) torch.save(ensemble_mean_error, os.path.join(output_path, times[i], f'{times[i]}-ensemble-mean-error')) torch.save(interpolation_error, os.path.join(output_path, times[i], f'{times[i]}-interpolation-error')) torch.save(persistence_error, os.path.join(output_path, times[i], f'{times[i]}-persistence-error')) @@ -140,9 +133,6 @@ def compute_crps_over_time_and_area(times, dataset, output_path): total_persistence_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) crps_over_time = np.zeros((crps_area.shape[0], len(times))) - crps_ensemble_mean_over_time = np.zeros((crps_area.shape[0], len(times))) - crps_persistence_over_time = np.zeros((crps_area.shape[0], len(times))) - crps_interpolation_over_time = np.zeros((crps_area.shape[0], len(times))) ensemble_mean_over_time = np.zeros((crps_area.shape[0], len(times))) interpolation_over_time = np.zeros((crps_area.shape[0], len(times))) persistence_over_time = np.zeros((crps_area.shape[0], len(times))) @@ -152,10 +142,6 @@ def compute_crps_over_time_and_area(times, dataset, output_path): crps_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-crps-ensemble'), weights_only=False) total_crps_area = total_crps_area + crps_area - crps_ensemble_mean_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-crps-ensemble-mean'), weights_only=False) - crps_interpolation_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-crps-interpolation'), weights_only=False) - crps_persistence_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-crps-persistence'), weights_only=False) - ensemble_mean_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-ensemble-mean-error'), weights_only=False) total_ensemble_mean_area = total_ensemble_mean_area + ensemble_mean_area interpolation_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-interpolation-error'), weights_only=False) @@ -166,9 +152,6 @@ def compute_crps_over_time_and_area(times, dataset, output_path): for j in range(crps_area.shape[0]): crps_over_time[j,i] = np.mean(crps_area[j,::]) - crps_ensemble_mean_over_time[j,i] = np.mean(crps_ensemble_mean_area[j,::]) - crps_interpolation_over_time[j,i] = np.mean(crps_interpolation_area[j,::]) - crps_persistence_over_time[j,i] = np.mean(crps_persistence_area[j,::]) ensemble_mean_over_time[j,i] = np.mean(ensemble_mean_area[j,::]) interpolation_over_time[j,i] = np.mean(interpolation_area[j,::]) persistence_over_time[j,i] = np.mean(persistence_area[j,::]) @@ -185,10 +168,6 @@ def compute_crps_over_time_and_area(times, dataset, output_path): persistence_over_time[:,0] = persistence_over_time[:,1] torch.save(crps_over_time, os.path.join(output_path, f'crps-ensemble-time-{times[0]}-{times[len(times)-1]}')) - torch.save(crps_ensemble_mean_over_time, os.path.join(output_path, f'crps-ensemble-mean-time-{times[0]}-{times[len(times)-1]}')) - torch.save(crps_interpolation_over_time, os.path.join(output_path, f'crps-interpolation-time-{times[0]}-{times[len(times)-1]}')) - torch.save(crps_persistence_over_time, os.path.join(output_path, f'crps-persistence-time-{times[0]}-{times[len(times)-1]}')) - torch.save(ensemble_mean_over_time, os.path.join(output_path, f'mae-ensemble-mean-time-{times[0]}-{times[len(times)-1]}')) torch.save(interpolation_over_time, os.path.join(output_path, f'mae-interpolation-time-{times[0]}-{times[len(times)-1]}')) torch.save(persistence_over_time, os.path.join(output_path, f'mae-persistence-time-{times[0]}-{times[len(times)-1]}')) @@ -202,10 +181,7 @@ def plot_crps_over_time_and_area(times, dataset, output_path): start_time=times[0] end_time=times[-1] - crps_ensemble_time = torch.load(os.path.join(output_path, f'crps-time-{start_time}-{end_time}'), weights_only=False) - crps_ensemble_mean_time = torch.load(os.path.join(output_path, f'crps-ensemble-mean-time-{start_time}-{end_time}'), weights_only=False) - crps_interpolation_time = torch.load(os.path.join(output_path, f'crps-interpolation-time-{start_time}-{end_time}'), weights_only=False) - crps_persistence_time = torch.load(os.path.join(output_path, f'crps-persistence-time-{start_time}-{end_time}'), weights_only=False) + crps_ensemble_time = torch.load(os.path.join(output_path, f'crps-ensemble-time-{start_time}-{end_time}'), weights_only=False) crps_area = torch.load(os.path.join(output_path, f'crps-area-{start_time}-{end_time}'), weights_only=False) ensemble_mean_time = torch.load(os.path.join(output_path, f'mae-ensemble-mean-time-{start_time}-{end_time}'), weights_only=False) ensemble_mean_area = torch.load(os.path.join(output_path, f'mae-ensemble-mean-area-{start_time}-{end_time}'), weights_only=False) @@ -225,17 +201,10 @@ def plot_crps_over_time_and_area(times, dataset, output_path): plot_error_projection(persistence_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-mae-persistence-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), label=output_channels[j].name) - crps_scores = {} - crps_scores['ensemble predictions'] = crps_ensemble_time[j,::] - crps_scores['ensemble mean'] = crps_ensemble_mean_time[j,::] - crps_scores['interpolation'] = crps_interpolation_time[j,::] - crps_scores['persistence'] = crps_persistence_time[j,::] - plot_scores_vs_t(crps_scores, times, os.path.join(output_path, f'NEW-crps-time-{start_time}-{end_time}-{output_channels[j].name}.jpg'), title=f'CRPS: {output_channels[j].name}', xlabel='time', ylabel='CRPS') - maes = {} maes['interpolation'] = interpolation_time[j,::] maes['ensemble mean'] = ensemble_mean_time[j,::] maes['crps'] = crps_ensemble_time[j,:] maes['persistence'] = persistence_time[j,::] - plot_scores_vs_t(maes, times, os.path.join(output_path, f'NEW-mae-time-{start_time}-{end_time}-{output_channels[j].name}.jpg'), title=f'Mean absolute error: {output_channels[j].name}', xlabel='time', ylabel='MAE') + plot_scores_vs_t(maes, times, os.path.join(output_path, f'NEW-error-plot-time-{start_time}-{end_time}-{output_channels[j].name}.jpg'), title=f'Mean absolute error: {output_channels[j].name}', xlabel='time', ylabel='MAE') From 8401a248560171be472bb1517ad3718a8d9933af Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 16 Jul 2025 13:23:29 +0200 Subject: [PATCH 080/302] fix filename --- src/hirad/eval/plotting.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index cbb0eb00..2fbdab01 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -72,7 +72,6 @@ def compute_crps_over_time(times, dataset, output_path): index = -1 for k in range(len(input_channels)): if input_channels[k].name == output_channels[j].name: - logging.info(f'found index of {j}:{output_channels[j].name} at {k}') index = k output_to_input_channel_map[j] = index @@ -182,7 +181,7 @@ def plot_crps_over_time_and_area(times, dataset, output_path): end_time=times[-1] crps_ensemble_time = torch.load(os.path.join(output_path, f'crps-ensemble-time-{start_time}-{end_time}'), weights_only=False) - crps_area = torch.load(os.path.join(output_path, f'crps-area-{start_time}-{end_time}'), weights_only=False) + crps_area = torch.load(os.path.join(output_path, f'crps-ensemble-area-{start_time}-{end_time}'), weights_only=False) ensemble_mean_time = torch.load(os.path.join(output_path, f'mae-ensemble-mean-time-{start_time}-{end_time}'), weights_only=False) ensemble_mean_area = torch.load(os.path.join(output_path, f'mae-ensemble-mean-area-{start_time}-{end_time}'), weights_only=False) interpolation_time = torch.load(os.path.join(output_path, f'mae-interpolation-time-{start_time}-{end_time}'), weights_only=False) From e0f9de878889a86193063f3006ecfb0a1fde5c80 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 16 Jul 2025 15:53:17 +0200 Subject: [PATCH 081/302] Move script methods into script --- src/hirad/eval/__init__.py | 2 +- src/hirad/eval/compute_eval.py | 169 +++++++++++++++- src/hirad/eval/crps.py | 348 --------------------------------- src/hirad/eval/plotting.py | 159 +-------------- 4 files changed, 169 insertions(+), 509 deletions(-) delete mode 100644 src/hirad/eval/crps.py diff --git a/src/hirad/eval/__init__.py b/src/hirad/eval/__init__.py index db34154b..82e9073a 100644 --- a/src/hirad/eval/__init__.py +++ b/src/hirad/eval/__init__.py @@ -1,2 +1,2 @@ from .metrics import absolute_error, compute_mae, average_power_spectrum, crps -from .plotting import plot_error_projection, plot_power_spectra, compute_crps_over_time, compute_crps_over_time_and_area, plot_crps_over_time_and_area +from .plotting import plot_error_projection, plot_power_spectra, plot_scores_vs_t \ No newline at end of file diff --git a/src/hirad/eval/compute_eval.py b/src/hirad/eval/compute_eval.py index e6113732..305f6403 100644 --- a/src/hirad/eval/compute_eval.py +++ b/src/hirad/eval/compute_eval.py @@ -1,4 +1,5 @@ import hydra +import logging import os import json from omegaconf import OmegaConf, DictConfig @@ -11,7 +12,7 @@ from hirad.utils.console import PythonLogger, RankZeroLoggingWrapper from concurrent.futures import ThreadPoolExecutor -from hirad.eval import compute_crps_over_time, plot_crps_over_time_and_area, compute_crps_over_time_and_area +from hirad.eval import absolute_error, crps, plot_scores_vs_t from hirad.models import EDMPrecondSuperResolution, UNet from hirad.inference import Generator from hirad.utils.inference_utils import save_images, save_results_as_torch @@ -52,10 +53,174 @@ def main(cfg: DictConfig) -> None: img_out_channels = len(dataset.output_channels()) output_path = getattr(cfg.generation.io, "output_path", "./outputs") - compute_crps_over_time(times, dataset, output_path) + compute_crps_per_time(times, dataset, output_path) compute_crps_over_time_and_area(times, dataset, output_path) plot_crps_over_time_and_area(times, dataset, output_path) + +def _get_data_path(output_path, time=None, filename=None): + return os.path.join(output_path, time, filename) + +def load_data(output_path, time=None, filename=None): + return torch.load(_get_data_path(output_path, time, filename), weights_only=False) + +def save_data(data, output_path, time=None, filename=None): + path = _get_data_path(output_path, time, filename) + torch.save(data, path) +def compute_crps_per_time(times, dataset, output_path): + logging.info('Computing CRPS for each time point') + input_channels = dataset.input_channels() + output_channels = dataset.output_channels() + start_time=times[0] + end_time=times[-1] + + # Load one prediction ensemble to get the shape + prediction_ensemble = torch.load(os.path.join(output_path, start_time, f'{start_time}-predictions'), weights_only=False) + + # Get a map of output to input channel, for building baseline errors + output_to_input_channel_map = {} + for j in range(len(output_channels)): + index = -1 + for k in range(len(input_channels)): + if input_channels[k].name == output_channels[j].name: + index = k + output_to_input_channel_map[j] = index + + + for i in range(len(times)): + curr_time = times[i] + if i % (24*5) == 0: + logging.info(f'on time {curr_time}') + prediction_ensemble = torch.load(os.path.join(output_path, curr_time, f'{curr_time}-predictions'), weights_only=False) + baseline = torch.load(os.path.join(output_path, curr_time, f'{curr_time}-baseline'), weights_only=False) + target = torch.load(os.path.join(output_path, curr_time, f'{curr_time}-target'), weights_only=False) + + # Calculate ensemble mean error + ensemble_mean = np.mean(prediction_ensemble, 0) + ensemble_mean_error = absolute_error(ensemble_mean, target) + + # Calculate interpolation error (baseline #1) + interpolation_error = np.zeros(target.shape) + for j in range(len(output_channels)): + k = output_to_input_channel_map[j] + if k > -1: + interpolation_error[j,::] = absolute_error(baseline[k,::], target[j,::]) + + # Calculate persistence error (baseline #2) + persistence_error = np.zeros(baseline.shape) + if i > 0: + prev = torch.load(os.path.join(output_path, times[i-1], f'{times[i-1]}-target'), weights_only=False) + persistence_error = absolute_error(prev, target) + else: + # for the first time point, persist the next-time-point target. + # This is fiction but it keeps the plots from looking weird. + prev = torch.load(os.path.join(output_path, times[i+1], f'{times[i+1]}-target'), weights_only=False) + persistence_error = absolute_error(prev, target) + + + # Calculate CRPS + crps_diffusion_area = crps(prediction_ensemble, target, average_over_area=False, average_over_channels=False) + + torch.save(crps_diffusion_area, os.path.join(output_path, times[i], f'{times[i]}-crps-ensemble')) + torch.save(ensemble_mean_error, os.path.join(output_path, times[i], f'{times[i]}-ensemble-mean-error')) + torch.save(interpolation_error, os.path.join(output_path, times[i], f'{times[i]}-interpolation-error')) + torch.save(persistence_error, os.path.join(output_path, times[i], f'{times[i]}-persistence-error')) + +def compute_crps_over_time_and_area(times, dataset, output_path): + logging.info('computing crps and errors') + longitudes = dataset.longitude() + latitudes = dataset.latitude() + input_channels = dataset.input_channels() + output_channels = dataset.output_channels() + start_time=times[0] + end_time=times[-1] + + logging.info('calculating min/max') + + crps_area = torch.load(os.path.join(output_path, times[0], f'{times[0]}-crps'), weights_only=False) + + # make area and time plot + total_crps_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) + total_ensemble_mean_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) + total_interpolation_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) + total_persistence_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) + + crps_over_time = np.zeros((crps_area.shape[0], len(times))) + ensemble_mean_over_time = np.zeros((crps_area.shape[0], len(times))) + interpolation_over_time = np.zeros((crps_area.shape[0], len(times))) + persistence_over_time = np.zeros((crps_area.shape[0], len(times))) + for i in range(len(times)): + if i % (24*5) == 0: + logging.info(f'on time {times[i]}') + crps_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-crps-ensemble'), weights_only=False) + total_crps_area = total_crps_area + crps_area + + ensemble_mean_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-ensemble-mean-error'), weights_only=False) + total_ensemble_mean_area = total_ensemble_mean_area + ensemble_mean_area + interpolation_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-interpolation-error'), weights_only=False) + total_interpolation_area = total_interpolation_area + interpolation_area + persistence_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-persistence-error'), weights_only=False) + if i>0: + total_persistence_area = total_persistence_area + persistence_area + + for j in range(crps_area.shape[0]): + crps_over_time[j,i] = np.mean(crps_area[j,::]) + ensemble_mean_over_time[j,i] = np.mean(ensemble_mean_area[j,::]) + interpolation_over_time[j,i] = np.mean(interpolation_area[j,::]) + persistence_over_time[j,i] = np.mean(persistence_area[j,::]) + mean_crps_area = total_crps_area / len(times) + mean_ensemble_mean_area = total_ensemble_mean_area / len(times) + mean_interpolation_area = total_interpolation_area / len(times) + mean_persistence_area = total_persistence_area / (len(times)-1) + torch.save(mean_crps_area, os.path.join(output_path, f'crps-ensemble-area-{times[0]}-{times[len(times)-1]}')) + torch.save(mean_ensemble_mean_area, os.path.join(output_path, f'mae-ensemble-mean-area-{times[0]}-{times[len(times)-1]}')) + torch.save(mean_interpolation_area, os.path.join(output_path, f'mae-interpolation-area-{times[0]}-{times[len(times)-1]}')) + torch.save(mean_persistence_area, os.path.join(output_path, f'mae-persistence-area-{times[0]}-{times[len(times)-1]}')) + + # Little hack to make the plots look nicer, without having to change dimensions. + persistence_over_time[:,0] = persistence_over_time[:,1] + + torch.save(crps_over_time, os.path.join(output_path, f'crps-ensemble-time-{times[0]}-{times[len(times)-1]}')) + torch.save(ensemble_mean_over_time, os.path.join(output_path, f'mae-ensemble-mean-time-{times[0]}-{times[len(times)-1]}')) + torch.save(interpolation_over_time, os.path.join(output_path, f'mae-interpolation-time-{times[0]}-{times[len(times)-1]}')) + torch.save(persistence_over_time, os.path.join(output_path, f'mae-persistence-time-{times[0]}-{times[len(times)-1]}')) + +def plot_crps_over_time_and_area(times, dataset, output_path): + logging.info('plotting crps and errors') + longitudes = dataset.longitude() + latitudes = dataset.latitude() + input_channels = dataset.input_channels() + output_channels = dataset.output_channels() + start_time=times[0] + end_time=times[-1] + + crps_ensemble_time = torch.load(os.path.join(output_path, f'crps-ensemble-time-{start_time}-{end_time}'), weights_only=False) + crps_area = torch.load(os.path.join(output_path, f'crps-ensemble-area-{start_time}-{end_time}'), weights_only=False) + ensemble_mean_time = torch.load(os.path.join(output_path, f'mae-ensemble-mean-time-{start_time}-{end_time}'), weights_only=False) + ensemble_mean_area = torch.load(os.path.join(output_path, f'mae-ensemble-mean-area-{start_time}-{end_time}'), weights_only=False) + interpolation_time = torch.load(os.path.join(output_path, f'mae-interpolation-time-{start_time}-{end_time}'), weights_only=False) + interpolation_area = torch.load(os.path.join(output_path, f'mae-interpolation-area-{start_time}-{end_time}'), weights_only=False) + persistence_time = torch.load(os.path.join(output_path, f'mae-persistence-time-{start_time}-{end_time}'), weights_only=False) + persistence_area = torch.load(os.path.join(output_path, f'mae-persistence-area-{start_time}-{end_time}'), weights_only=False) + + + for j in range(crps_area.shape[0]): + plot_error_projection(crps_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-crps-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + label=output_channels[j].name, title=f'Mean absolute error: CRPS: {output_channels[j].name}') + plot_error_projection(ensemble_mean_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-mae-ensemble-mean-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + label=output_channels[j].name, title=f'Mean absolute error: Ensemble mean: {output_channels[j].name}') + plot_error_projection(interpolation_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-mae-interpolation-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + label=output_channels[j].name, title=f'Mean absolute error: Interpolation: {output_channels[j].name}') + plot_error_projection(persistence_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-mae-persistence-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + label=output_channels[j].name, title=f'Mean absolute error: Persistence: {output_channels[j].name}') + + maes = {} + maes['interpolation'] = interpolation_time[j,::] + maes['ensemble mean'] = ensemble_mean_time[j,::] + maes['crps'] = crps_ensemble_time[j,:] + maes['persistence'] = persistence_time[j,::] + plot_scores_vs_t(maes, times, os.path.join(output_path, f'NEW-error-plot-time-{start_time}-{end_time}-{output_channels[j].name}.jpg'), title=f'Mean absolute error: {output_channels[j].name}', xlabel='time', ylabel='MAE') + diff --git a/src/hirad/eval/crps.py b/src/hirad/eval/crps.py deleted file mode 100644 index 2ccdb424..00000000 --- a/src/hirad/eval/crps.py +++ /dev/null @@ -1,348 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. -# SPDX-FileCopyrightText: All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Union - -import numpy as np -import torch - -from .histogram import cdf as cdf_function - -Tensor = torch.Tensor - - -@torch.jit.script -def _kernel_crps_implementation(pred: Tensor, obs: Tensor, biased: bool) -> Tensor: - """An O(m log m) implementation of the kernel CRPS formulas""" - skill = torch.abs(pred - obs[..., None]).mean(-1) - pred, _ = torch.sort(pred) - - # derivation of fast implementation of spread-portion of CRPS formula when x is sorted - # sum_(i,j=1)^m |x_i - x_j| = sum_(i j) |x_i - x_j| - # = 2 sum_(i <= j) |x_i -x_j| - # = 2 sum_(i <= j) (x_j - x_i) - # = 2 sum_(i <= j) x_j - 2 sum_(i <= j) x_i - # = 2 sum_(j=1)^m j x_j - 2 sum (m - i + 1) x_i - # = 2 sum_(i=1)^m (2i - m - 1) x_i - m = pred.size(-1) - i = torch.arange(1, m + 1, device=pred.device, dtype=pred.dtype) - denom = m * m if biased else m * (m - 1) - factor = (2 * i - m - 1) / denom - spread = torch.sum(factor * pred, dim=-1) - return skill - spread - - -def kcrps(pred: Tensor, obs: Tensor, dim: int = 0, biased: bool = True): - """Estimate the CRPS from a finite ensemble - - Computes the local Continuous Ranked Probability Score (CRPS) by using - the kernel version of CRPS. The cost is O(m log m). - - Creates a map of CRPS and does not accumulate over lat/lon regions. - Approximates: - .. math:: - CRPS(X, y) = E[X - y] - 0.5 E[X-X'] - - with - .. math:: - sum_i=1^m |X_i - y| / m - 1/(2m^2) sum_i,j=1^m |x_i - x_j| - - Parameters - ---------- - pred : Tensor - Tensor containing the ensemble predictions. The ensemble dimension - is assumed to be the leading dimension unless 'dim' is specified. - obs : Union[Tensor, np.ndarray] - Tensor or array containing an observation over which the CRPS is computed - with respect to. - dim : int, optional - The dimension over which to compute the CRPS, assumed to be 0. - biased : - When False, uses the unbiased estimators described in (Zamo and Naveau, 2018):: - - E|X-y|/m - 1/(2m(m-1)) sum_(i,j=1)|x_i - x_j| - - Unlike ``crps`` this is fair for finite ensembles. Non-fair ``crps`` favors less - dispersive ensembles since it is biased high by E|X- X'|/ m where m is the - ensemble size. - - Returns - ------- - Tensor - Map of CRPS - """ - pred = torch.movedim(pred, dim, -1) - return _kernel_crps_implementation(pred, obs, biased=biased) - - -def _crps_gaussian(mean: Tensor, std: Tensor, obs: Union[Tensor, np.ndarray]) -> Tensor: - """ - Computes the local Continuous Ranked Probability Score (CRPS) - using assuming that the forecast distribution is normal. - - Creates a map of CRPS and does not accumulate over lat/lon regions. - - Computes: - - .. math:: - - CRPS(mean, std, y) = std * [ \\frac{1}{\\sqrt{\\pi}}} - 2 \\phi ( \\frac{x-mean}{std} ) - - ( \\frac{x-mean}{std} ) * (2 \\Phi(\\frac{x-mean}{std}) - 1) ] - - where \\phi and \\Phi are the normal gaussian pdf/cdf respectively. - - Parameters - ---------- - mean : Tensor - Tensor of mean of forecast distribution. - std : Tensor - Tensor of standard deviation of forecast distribution. - obs : Union[Tensor, np.ndarray] - Tensor or array containing an observation over which the CRPS is computed - with respect to. Broadcasting dimensions must be compatible with the non-zeroth - dimensions of bins and cdf. - - Returns - ------- - Tensor - Map of CRPS - """ - if isinstance(obs, np.ndarray): - obs = torch.from_numpy(obs).to(mean.device) - # Check shape compatibility - if mean.shape != std.shape: - raise ValueError( - "Mean and standard deviation must have" - + "compatible shapes but found" - + str(mean.shape) - + " and " - + str(std.shape) - + "." - ) - if mean.shape != obs.shape: - raise ValueError( - "Mean and obs must have" - + "compatible shapes but found" - + str(mean.shape) - + " and " - + str(obs.shape) - + "." - ) - - d = (obs - mean) / std - phi = torch.exp(-0.5 * d**2) / torch.sqrt(torch.as_tensor(2 * torch.pi)) - - # Note, simplified expression below is not exactly Gaussian CDF - Phi = torch.erf(d / torch.sqrt(torch.as_tensor(2.0))) - - return std * (2 * phi + d * Phi - 1.0 / torch.sqrt(torch.as_tensor(torch.pi))) - - -def _crps_from_cdf( - bin_edges: Tensor, cdf: Tensor, obs: Union[Tensor, np.ndarray] -) -> Tensor: - """Computes the local Continuous Ranked Probability Score (CRPS) - using a cumulative distribution function. - - Creates a map of CRPS and does not accumulate over lat/lon regions. - - Computes: - - .. math:: - - CRPS(X, y) = \\int[ (F(x) - 1[x - y])^2 ] dx - - where F is the empirical cdf of X. - - Parameters - ---------- - bins_edges : Tensor - Tensor [N+1, ...] containing bin edges. The leading dimension must represent the - N+1 bin edges. - cdf : Tensor - Tensor [N, ...] containing a cdf, defined over bins. The non-zeroth dimensions - of bins and cdf must be compatible. - obs : Union[Tensor, np.ndarray] - Tensor or array containing an observation over which the CRPS is computed - with respect to. Broadcasting dimensions must be compatible with the non-zeroth - dimensions of bins and cdf. - - Returns - ------- - Tensor - Map of CRPS - """ - if isinstance(obs, np.ndarray): - obs = torch.from_numpy(obs).to(cdf.device) - if bin_edges.shape[1:] != cdf.shape[1:]: - raise ValueError( - "Expected bins and cdf to have compatible non-zeroth dimensions but have shapes" - + str(bin_edges.shape[1:]) - + " and " - + str(cdf.shape[1:]) - + "." - ) - if bin_edges.shape[1:] != obs.shape: - raise ValueError( - "Expected bins and observations to have compatible broadcasting dimensions but have shapes" - + str(bin_edges.shape[1:]) - + " and " - + str(obs.shape) - + "." - ) - if bin_edges.shape[0] != cdf.shape[0] + 1: - raise ValueError( - "Expected zeroth dimension of cdf to be equal to the zeroth dimension of bins + 1 but have shapes" - + str(bin_edges.shape[0]) - + " and " - + str(cdf.shape[0]) - + "+1." - ) - dbins = bin_edges[1, ...] - bin_edges[0, ...] - bin_mids = 0.5 * (bin_edges[1:] + bin_edges[:-1]) - obs = torch.ge(bin_mids, obs).int() - return torch.sum(torch.abs(cdf - obs) ** 2 * dbins, dim=0) - - -def _crps_from_counts( - bin_edges: Tensor, counts: Tensor, obs: Union[Tensor, np.ndarray] -) -> Tensor: - """Computes the local Continuous Ranked Probability Score (CRPS) - using a histogram of counts. - - Creates a map of CRPS and does not accumulate over lat/lon regions. - - Computes: - - .. math:: - - CRPS(X, y) = int[ (F(x) - 1[x - y])^2 ] dx - - where F is the empirical cdf of X. - - Parameters - ---------- - bins_edges : Tensor - Tensor [N+1, ...] containing bin edges. The leading dimension must represent the - N+1 bin edges. - counts : Tensor - Tensor [N, ...] containing counts, defined over bins. The non-zeroth dimensions - of bins and counts must be compatible. - obs : Union[Tensor, np.ndarray] - Tensor or array containing an observation over which the CRPS is computed - with respect to. Broadcasting dimensions must be compatible with the non-zeroth - dimensions of bins and counts. - - Returns - ------- - Tensor - Map of CRPS - """ - if isinstance(obs, np.ndarray): - obs = torch.from_numpy(obs).to(counts.device) - if bin_edges.shape[1:] != counts.shape[1:]: - raise ValueError( - "Expected bins and cdf to have compatible non-zeroth dimensions but have shapes" - + str(bin_edges.shape[1:]) - + " and " - + str(counts.shape[1:]) - + "." - ) - if bin_edges.shape[1:] != obs.shape: - raise ValueError( - "Expected bins and observations to have compatible broadcasting dimensions but have shapes" - + str(bin_edges.shape[1:]) - + " and " - + str(obs.shape) - + "." - ) - if bin_edges.shape[0] != counts.shape[0] + 1: - raise ValueError( - "Expected zeroth dimension of cdf to be equal to the zeroth dimension of bins + 1 but have shapes" - + str(bin_edges.shape[0]) - + " and " - + str(counts.shape[0]) - + "+1." - ) - cdf_hat = torch.cumsum(counts / torch.sum(counts, dim=0), dim=0) - return _crps_from_cdf(bin_edges, cdf_hat, obs) - - -def crps( - pred: Tensor, obs: Union[Tensor, np.ndarray], dim: int = 0, method: str = "kernel" -) -> Tensor: - """ - Computes the local Continuous Ranked Probability Score (CRPS). - - Creates a map of CRPS and does not accumulate over any other dimensions (e.g., lat/lon regions). - - Parameters - ---------- - pred : Tensor - Tensor containing the ensemble predictions. - obs : Union[Tensor, np.ndarray] - Tensor or array containing an observation over which the CRPS is computed - with respect to. - dim : int, Optional - Dimension with which to calculate the CRPS over, the ensemble dimension. - Assumed to be zero. - method: str, Optional - The method to calculate the crps. Can either be "kernel", "sort" or "histogram". - - The "kernel" method implements - .. math:: - CRPS(x, y) = E[X-y] - 0.5*E[X-X'] - - This method scales as O(n^2) where n is the number of ensemble members and - can potentially induce large memory consumption as the algorithm attempts - to vectorize over this O(n^2) operation. - - The "sort" method compute the exact CRPS using the CDF method - .. math:: - CRPS(x, y) = int [F(x) - 1(x-y)]^2 dx - - where F is the empirical CDF and 1(x-y) = 1 if x > y. - - This method is more memory efficient than the kernel method, and uses O(n - log n) compute instead of O(n^2), where n is the number of ensemble members. - - The "histogram" method computes an approximate CRPS using the CDF method - .. math:: - CRPS(x, y) = int [F(x) - 1(x-y)]^2 dx - - where F is the empirical CDF, estimated via a histogram of the samples. The - number of bins used is the lesser of the square root of the number of samples - and 100. For more control over the implementation of this method consider using - `cdf_function` to construct a cdf and `_crps_from_cdf` to compute CRPS. - - Returns - ------- - Tensor - Map of CRPS - """ - if method not in ["kernel", "sort", "histogram"]: - raise ValueError("Method must either be 'kernel', 'sort' or 'histogram'.") - - n = pred.shape[dim] - obs = torch.as_tensor(obs, device=pred.device, dtype=pred.dtype) - if method in ["kernel", "sort"]: - return kcrps(pred, obs, dim=dim) - else: - pred = pred.unsqueeze(0).transpose(0, dim + 1).squeeze(dim + 1) - number_of_bins = max(int(np.sqrt(n)), 100) - bin_edges, cdf = cdf_function(pred, bins=number_of_bins) - _crps = _crps_from_cdf(bin_edges, cdf, obs) - return _crps \ No newline at end of file diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index 2fbdab01..0d437143 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -8,7 +8,7 @@ import numpy as np import torch -def plot_error_projection(values: np.array, latitudes: np.array, longitudes: np.array, filename: str, label: str, vmin=None, vmax=None): +def plot_error_projection(values: np.array, latitudes: np.array, longitudes: np.array, filename: str, label: str, title='', vmin=None, vmax=None): """Plot observed or interpolated data in a scatter plot.""" fig = plt.figure() fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) @@ -30,8 +30,6 @@ def plot_scores_vs_t(scores: dict[str,np.ndarray], times: np.array, filename: st i=i+1 p.set_label(k) ax.legend() - #plt.ylabel('CRPS') - #plt.xlabel('time') ax.set_xticks([times[0],times[-1]]) plt.xlabel(xlabel) plt.ylabel(ylabel) @@ -52,158 +50,3 @@ def plot_power_spectra(freqs: dict, spec: dict, channel_name, filename): logging.info(f'plotting values to {filename}') plt.savefig(filename) plt.close('all') - -def compute_crps_over_time(times, dataset, output_path): - logging.info('computing crps') - longitudes = dataset.longitude() - latitudes = dataset.latitude() - input_channels = dataset.input_channels() - output_channels = dataset.output_channels() - start_time=times[0] - end_time=times[-1] - - # Load one prediction ensemble to get the shape - prediction_ensemble = torch.load(os.path.join(output_path, times[0], f'{times[0]}-predictions'), weights_only=False) - #all_predictions = np.ndarray((len(times), prediction_ensemble.shape[0], prediction_ensemble.shape[1], prediction_ensemble.shape[2], prediction_ensemble.shape[3])) - #all_targets = np.ndarray((len(times), prediction_ensemble.shape[1], prediction_ensemble.shape[2], prediction_ensemble.shape[3])) - - output_to_input_channel_map = {} - for j in range(len(output_channels)): - index = -1 - for k in range(len(input_channels)): - if input_channels[k].name == output_channels[j].name: - index = k - output_to_input_channel_map[j] = index - - - for i in range(len(times)): - prediction_ensemble = torch.load(os.path.join(output_path, times[i], f'{times[i]}-predictions'), weights_only=False) - baseline = torch.load(os.path.join(output_path, times[i], f'{times[i]}-baseline'), weights_only=False) - target = torch.load(os.path.join(output_path, times[i], f'{times[i]}-target'), weights_only=False) - - # Calculate ensemble mean error - ensemble_mean = np.mean(prediction_ensemble, 0) - ensemble_mean_error = absolute_error(ensemble_mean, target) - - # Calculate interpolation error (baseline #1) - interpolation_error = np.zeros(target.shape) - for j in range(len(output_channels)): - k = output_to_input_channel_map[j] - if k > -1: - interpolation_error[j,::] = absolute_error(baseline[k,::], target[j,::]) - - # Calculate persistence error (baseline #2) - persistence_error = np.zeros(baseline.shape) - prev=[] - if i > 0: - prev = torch.load(os.path.join(output_path, times[i-1], f'{times[i-1]}-target'), weights_only=False) - persistence_error = absolute_error(prev, target) - else: - # persist the next-time-point target-- this is fiction but it keeps the plots from looking weird. - prev = torch.load(os.path.join(output_path, times[i+1], f'{times[i+1]}-target'), weights_only=False) - persistence_error = absolute_error(target, target) - - - # Calculate CRPS - crps_diffusion_area = crps(prediction_ensemble, target, average_over_area=False, average_over_channels=False) - - torch.save(crps_diffusion_area, os.path.join(output_path, times[i], f'{times[i]}-crps-ensemble')) - torch.save(ensemble_mean_error, os.path.join(output_path, times[i], f'{times[i]}-ensemble-mean-error')) - torch.save(interpolation_error, os.path.join(output_path, times[i], f'{times[i]}-interpolation-error')) - torch.save(persistence_error, os.path.join(output_path, times[i], f'{times[i]}-persistence-error')) - -def compute_crps_over_time_and_area(times, dataset, output_path): - logging.info('computing crps and errors') - longitudes = dataset.longitude() - latitudes = dataset.latitude() - input_channels = dataset.input_channels() - output_channels = dataset.output_channels() - start_time=times[0] - end_time=times[-1] - - logging.info('calculating min/max') - - crps_area = torch.load(os.path.join(output_path, times[0], f'{times[0]}-crps'), weights_only=False) - - # make area and time plot - total_crps_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) - total_ensemble_mean_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) - total_interpolation_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) - total_persistence_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) - - crps_over_time = np.zeros((crps_area.shape[0], len(times))) - ensemble_mean_over_time = np.zeros((crps_area.shape[0], len(times))) - interpolation_over_time = np.zeros((crps_area.shape[0], len(times))) - persistence_over_time = np.zeros((crps_area.shape[0], len(times))) - for i in range(len(times)): - if i % (24*5) == 0: - logging.info(f'on time {times[i]}') - crps_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-crps-ensemble'), weights_only=False) - total_crps_area = total_crps_area + crps_area - - ensemble_mean_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-ensemble-mean-error'), weights_only=False) - total_ensemble_mean_area = total_ensemble_mean_area + ensemble_mean_area - interpolation_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-interpolation-error'), weights_only=False) - total_interpolation_area = total_interpolation_area + interpolation_area - persistence_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-persistence-error'), weights_only=False) - if i>0: - total_persistence_area = total_persistence_area + persistence_area - - for j in range(crps_area.shape[0]): - crps_over_time[j,i] = np.mean(crps_area[j,::]) - ensemble_mean_over_time[j,i] = np.mean(ensemble_mean_area[j,::]) - interpolation_over_time[j,i] = np.mean(interpolation_area[j,::]) - persistence_over_time[j,i] = np.mean(persistence_area[j,::]) - mean_crps_area = total_crps_area / len(times) - mean_ensemble_mean_area = total_ensemble_mean_area / len(times) - mean_interpolation_area = total_interpolation_area / len(times) - mean_persistence_area = total_persistence_area / (len(times)-1) - torch.save(mean_crps_area, os.path.join(output_path, f'crps-ensemble-area-{times[0]}-{times[len(times)-1]}')) - torch.save(mean_ensemble_mean_area, os.path.join(output_path, f'mae-ensemble-mean-area-{times[0]}-{times[len(times)-1]}')) - torch.save(mean_interpolation_area, os.path.join(output_path, f'mae-interpolation-area-{times[0]}-{times[len(times)-1]}')) - torch.save(mean_persistence_area, os.path.join(output_path, f'mae-persistence-area-{times[0]}-{times[len(times)-1]}')) - - # Little hack to make the plots look nicer, without having to change dimensions. - persistence_over_time[:,0] = persistence_over_time[:,1] - - torch.save(crps_over_time, os.path.join(output_path, f'crps-ensemble-time-{times[0]}-{times[len(times)-1]}')) - torch.save(ensemble_mean_over_time, os.path.join(output_path, f'mae-ensemble-mean-time-{times[0]}-{times[len(times)-1]}')) - torch.save(interpolation_over_time, os.path.join(output_path, f'mae-interpolation-time-{times[0]}-{times[len(times)-1]}')) - torch.save(persistence_over_time, os.path.join(output_path, f'mae-persistence-time-{times[0]}-{times[len(times)-1]}')) - -def plot_crps_over_time_and_area(times, dataset, output_path): - logging.info('plotting crps and errors') - longitudes = dataset.longitude() - latitudes = dataset.latitude() - input_channels = dataset.input_channels() - output_channels = dataset.output_channels() - start_time=times[0] - end_time=times[-1] - - crps_ensemble_time = torch.load(os.path.join(output_path, f'crps-ensemble-time-{start_time}-{end_time}'), weights_only=False) - crps_area = torch.load(os.path.join(output_path, f'crps-ensemble-area-{start_time}-{end_time}'), weights_only=False) - ensemble_mean_time = torch.load(os.path.join(output_path, f'mae-ensemble-mean-time-{start_time}-{end_time}'), weights_only=False) - ensemble_mean_area = torch.load(os.path.join(output_path, f'mae-ensemble-mean-area-{start_time}-{end_time}'), weights_only=False) - interpolation_time = torch.load(os.path.join(output_path, f'mae-interpolation-time-{start_time}-{end_time}'), weights_only=False) - interpolation_area = torch.load(os.path.join(output_path, f'mae-interpolation-area-{start_time}-{end_time}'), weights_only=False) - persistence_time = torch.load(os.path.join(output_path, f'mae-persistence-time-{start_time}-{end_time}'), weights_only=False) - persistence_area = torch.load(os.path.join(output_path, f'mae-persistence-area-{start_time}-{end_time}'), weights_only=False) - - - for j in range(crps_area.shape[0]): - plot_error_projection(crps_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-crps-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), - label=output_channels[j].name) - plot_error_projection(ensemble_mean_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-mae-ensemble-mean-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), - label=output_channels[j].name) - plot_error_projection(interpolation_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-mae-interpolation-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), - label=output_channels[j].name) - plot_error_projection(persistence_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-mae-persistence-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), - label=output_channels[j].name) - - maes = {} - maes['interpolation'] = interpolation_time[j,::] - maes['ensemble mean'] = ensemble_mean_time[j,::] - maes['crps'] = crps_ensemble_time[j,:] - maes['persistence'] = persistence_time[j,::] - plot_scores_vs_t(maes, times, os.path.join(output_path, f'NEW-error-plot-time-{start_time}-{end_time}-{output_channels[j].name}.jpg'), title=f'Mean absolute error: {output_channels[j].name}', xlabel='time', ylabel='MAE') - From 2449db4a5d6c7b28d682672a280cce9c1ce25dbc Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 16 Jul 2025 17:43:10 +0200 Subject: [PATCH 082/302] Cleanup eval script a bit --- src/hirad/eval/compute_eval.py | 120 ++++++++++++++++----------------- 1 file changed, 58 insertions(+), 62 deletions(-) diff --git a/src/hirad/eval/compute_eval.py b/src/hirad/eval/compute_eval.py index 305f6403..b4570d55 100644 --- a/src/hirad/eval/compute_eval.py +++ b/src/hirad/eval/compute_eval.py @@ -4,7 +4,6 @@ import json from omegaconf import OmegaConf, DictConfig import torch -import torch._dynamo import numpy as np import contextlib @@ -12,7 +11,7 @@ from hirad.utils.console import PythonLogger, RankZeroLoggingWrapper from concurrent.futures import ThreadPoolExecutor -from hirad.eval import absolute_error, crps, plot_scores_vs_t +from hirad.eval import absolute_error, crps, plot_scores_vs_t, plot_error_projection from hirad.models import EDMPrecondSuperResolution, UNet from hirad.inference import Generator from hirad.utils.inference_utils import save_images, save_results_as_torch @@ -31,12 +30,9 @@ def main(cfg: DictConfig) -> None: DistributedManager.initialize() dist = DistributedManager() device = dist.device - # Initialize logger logger = PythonLogger("generate") # General python logger - logger0 = RankZeroLoggingWrapper(logger, dist) - if cfg.generation.times_range: times = get_time_from_range(cfg.generation.times_range, time_format="%Y%m%d-%H%M") @@ -49,8 +45,6 @@ def main(cfg: DictConfig) -> None: dataset, sampler = get_dataset_and_sampler_inference( dataset_cfg=dataset_cfg, times=times, has_lead_time=has_lead_time ) - img_shape = dataset.image_shape() - img_out_channels = len(dataset.output_channels()) output_path = getattr(cfg.generation.io, "output_path", "./outputs") compute_crps_per_time(times, dataset, output_path) @@ -91,9 +85,9 @@ def compute_crps_per_time(times, dataset, output_path): curr_time = times[i] if i % (24*5) == 0: logging.info(f'on time {curr_time}') - prediction_ensemble = torch.load(os.path.join(output_path, curr_time, f'{curr_time}-predictions'), weights_only=False) - baseline = torch.load(os.path.join(output_path, curr_time, f'{curr_time}-baseline'), weights_only=False) - target = torch.load(os.path.join(output_path, curr_time, f'{curr_time}-target'), weights_only=False) + prediction_ensemble = load_data(output_path, time=curr_time, filename=f'{curr_time}-predictions') + baseline = load_data(output_path, time=curr_time, filename=f'{curr_time}-baseline') + target = load_data(output_path, time=curr_time, filename=f'{curr_time}-target') # Calculate ensemble mean error ensemble_mean = np.mean(prediction_ensemble, 0) @@ -107,63 +101,60 @@ def compute_crps_per_time(times, dataset, output_path): interpolation_error[j,::] = absolute_error(baseline[k,::], target[j,::]) # Calculate persistence error (baseline #2) - persistence_error = np.zeros(baseline.shape) + persistence_error = np.zeros(target.shape) if i > 0: - prev = torch.load(os.path.join(output_path, times[i-1], f'{times[i-1]}-target'), weights_only=False) + prev = load_data(output_path, time=times[i-1], filename=f'{times[i-1]}-target') persistence_error = absolute_error(prev, target) else: # for the first time point, persist the next-time-point target. # This is fiction but it keeps the plots from looking weird. - prev = torch.load(os.path.join(output_path, times[i+1], f'{times[i+1]}-target'), weights_only=False) + prev = load_data(output_path, time=times[i+1], filename=f'{times[i+1]}-target') persistence_error = absolute_error(prev, target) # Calculate CRPS crps_diffusion_area = crps(prediction_ensemble, target, average_over_area=False, average_over_channels=False) - torch.save(crps_diffusion_area, os.path.join(output_path, times[i], f'{times[i]}-crps-ensemble')) - torch.save(ensemble_mean_error, os.path.join(output_path, times[i], f'{times[i]}-ensemble-mean-error')) - torch.save(interpolation_error, os.path.join(output_path, times[i], f'{times[i]}-interpolation-error')) - torch.save(persistence_error, os.path.join(output_path, times[i], f'{times[i]}-persistence-error')) + save_data(crps_diffusion_area, output_path, time=curr_time, filename=f'{curr_time}-crps-ensemble') + save_data(ensemble_mean_error, output_path, time=curr_time, filename=f'{curr_time}-ensemble-mean-error') + save_data(interpolation_error, output_path, time=curr_time, filename=f'{curr_time}-interpolation-error') + save_data(persistence_error, output_path, time=curr_time, filename=f'{curr_time}-persistence-error') def compute_crps_over_time_and_area(times, dataset, output_path): logging.info('computing crps and errors') - longitudes = dataset.longitude() - latitudes = dataset.latitude() - input_channels = dataset.input_channels() - output_channels = dataset.output_channels() start_time=times[0] end_time=times[-1] - logging.info('calculating min/max') - - crps_area = torch.load(os.path.join(output_path, times[0], f'{times[0]}-crps'), weights_only=False) + # shape = (channels, x, y) + crps_area = load_data(output_path, time=start_time, filename=f'{start_time}-crps-ensemble') + num_channels = crps_area.shape[0] # make area and time plot - total_crps_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) - total_ensemble_mean_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) - total_interpolation_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) - total_persistence_area = np.zeros((crps_area.shape[0],crps_area.shape[1],crps_area.shape[2])) - - crps_over_time = np.zeros((crps_area.shape[0], len(times))) - ensemble_mean_over_time = np.zeros((crps_area.shape[0], len(times))) - interpolation_over_time = np.zeros((crps_area.shape[0], len(times))) - persistence_over_time = np.zeros((crps_area.shape[0], len(times))) + total_crps_area = np.zeros_like(crps_area) + total_ensemble_mean_area = np.zeros_like(crps_area) + total_interpolation_area = np.zeros_like(crps_area) + total_persistence_area = np.zeros_like(crps_area) + + crps_over_time = np.zeros((num_channels, len(times))) + ensemble_mean_over_time = np.zeros_like(crps_over_time) + interpolation_over_time = np.zeros_like(crps_over_time) + persistence_over_time = np.zeros_like(crps_over_time) for i in range(len(times)): + curr_time = times[i] if i % (24*5) == 0: logging.info(f'on time {times[i]}') - crps_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-crps-ensemble'), weights_only=False) + crps_area = load_data(output_path, time=curr_time, filename=f'{curr_time}-crps-ensemble') total_crps_area = total_crps_area + crps_area - ensemble_mean_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-ensemble-mean-error'), weights_only=False) + ensemble_mean_area = load_data(output_path, time=curr_time, filename=f'{curr_time}-ensemble-mean-error') total_ensemble_mean_area = total_ensemble_mean_area + ensemble_mean_area - interpolation_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-interpolation-error'), weights_only=False) + interpolation_area = load_data(output_path, time=curr_time, filename=f'{curr_time}-interpolation-error') total_interpolation_area = total_interpolation_area + interpolation_area - persistence_area = torch.load(os.path.join(output_path, times[i], f'{times[i]}-persistence-error'), weights_only=False) + persistence_area = load_data(output_path, time=curr_time, filename=f'{curr_time}-persistence-error') if i>0: total_persistence_area = total_persistence_area + persistence_area - for j in range(crps_area.shape[0]): + for j in range(num_channels): crps_over_time[j,i] = np.mean(crps_area[j,::]) ensemble_mean_over_time[j,i] = np.mean(ensemble_mean_area[j,::]) interpolation_over_time[j,i] = np.mean(interpolation_area[j,::]) @@ -172,54 +163,59 @@ def compute_crps_over_time_and_area(times, dataset, output_path): mean_ensemble_mean_area = total_ensemble_mean_area / len(times) mean_interpolation_area = total_interpolation_area / len(times) mean_persistence_area = total_persistence_area / (len(times)-1) - torch.save(mean_crps_area, os.path.join(output_path, f'crps-ensemble-area-{times[0]}-{times[len(times)-1]}')) - torch.save(mean_ensemble_mean_area, os.path.join(output_path, f'mae-ensemble-mean-area-{times[0]}-{times[len(times)-1]}')) - torch.save(mean_interpolation_area, os.path.join(output_path, f'mae-interpolation-area-{times[0]}-{times[len(times)-1]}')) - torch.save(mean_persistence_area, os.path.join(output_path, f'mae-persistence-area-{times[0]}-{times[len(times)-1]}')) + save_data(mean_crps_area, output_path, filename=f'crps-ensemble-area-{start_time}-{end_time}') + save_data(mean_ensemble_mean_area, output_path, filename=f'mae-ensemble-mean-area-{start_time}-{end_time}') + save_data(mean_interpolation_area, output_path, filename=f'mae-interpolation-area-{start_time}-{end_time}') + save_data(mean_persistence_area, output_path, filename=f'mae-persistence-area-{start_time}-{end_time}') # Little hack to make the plots look nicer, without having to change dimensions. persistence_over_time[:,0] = persistence_over_time[:,1] - torch.save(crps_over_time, os.path.join(output_path, f'crps-ensemble-time-{times[0]}-{times[len(times)-1]}')) - torch.save(ensemble_mean_over_time, os.path.join(output_path, f'mae-ensemble-mean-time-{times[0]}-{times[len(times)-1]}')) - torch.save(interpolation_over_time, os.path.join(output_path, f'mae-interpolation-time-{times[0]}-{times[len(times)-1]}')) - torch.save(persistence_over_time, os.path.join(output_path, f'mae-persistence-time-{times[0]}-{times[len(times)-1]}')) + save_data(crps_over_time, output_path, filename=f'crps-ensemble-time-{start_time}-{end_time}') + save_data(ensemble_mean_over_time, output_path, filename=f'mae-ensemble-mean-time-{start_time}-{end_time}') + save_data(interpolation_over_time, output_path, filename=f'mae-interpolation-time-{start_time}-{end_time}') + save_data(persistence_over_time, output_path, filename=f'mae-persistence-time-{start_time}-{end_time}') def plot_crps_over_time_and_area(times, dataset, output_path): logging.info('plotting crps and errors') longitudes = dataset.longitude() latitudes = dataset.latitude() - input_channels = dataset.input_channels() output_channels = dataset.output_channels() start_time=times[0] end_time=times[-1] - crps_ensemble_time = torch.load(os.path.join(output_path, f'crps-ensemble-time-{start_time}-{end_time}'), weights_only=False) - crps_area = torch.load(os.path.join(output_path, f'crps-ensemble-area-{start_time}-{end_time}'), weights_only=False) - ensemble_mean_time = torch.load(os.path.join(output_path, f'mae-ensemble-mean-time-{start_time}-{end_time}'), weights_only=False) - ensemble_mean_area = torch.load(os.path.join(output_path, f'mae-ensemble-mean-area-{start_time}-{end_time}'), weights_only=False) - interpolation_time = torch.load(os.path.join(output_path, f'mae-interpolation-time-{start_time}-{end_time}'), weights_only=False) - interpolation_area = torch.load(os.path.join(output_path, f'mae-interpolation-area-{start_time}-{end_time}'), weights_only=False) - persistence_time = torch.load(os.path.join(output_path, f'mae-persistence-time-{start_time}-{end_time}'), weights_only=False) - persistence_area = torch.load(os.path.join(output_path, f'mae-persistence-area-{start_time}-{end_time}'), weights_only=False) + crps_area = load_data(output_path, filename=f'crps-ensemble-area-{start_time}-{end_time}') + ensemble_mean_area = load_data(output_path, filename=f'mae-ensemble-mean-area-{start_time}-{end_time}') + interpolation_area = load_data(output_path, filename=f'mae-interpolation-area-{start_time}-{end_time}') + persistence_area = load_data(output_path, filename=f'mae-persistence-area-{start_time}-{end_time}') + crps_ensemble_time = load_data(output_path, filename=f'crps-ensemble-time-{start_time}-{end_time}') + ensemble_mean_time = load_data(output_path, filename=f'mae-ensemble-mean-time-{start_time}-{end_time}') + interpolation_time = load_data(output_path, filename=f'mae-interpolation-time-{start_time}-{end_time}') + persistence_time = load_data(output_path, filename=f'mae-persistence-time-{start_time}-{end_time}') - for j in range(crps_area.shape[0]): - plot_error_projection(crps_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-crps-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + for j in range(len(output_channels)): + plot_error_projection(crps_area[j,::], latitudes, longitudes, + _get_data_path(output_path, filename=f'NEW-crps-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), label=output_channels[j].name, title=f'Mean absolute error: CRPS: {output_channels[j].name}') - plot_error_projection(ensemble_mean_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-mae-ensemble-mean-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + plot_error_projection(ensemble_mean_area[j,::], latitudes, longitudes, + _get_data_path(output_path, filename=f'NEW-mae-ensemble-mean-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), label=output_channels[j].name, title=f'Mean absolute error: Ensemble mean: {output_channels[j].name}') - plot_error_projection(interpolation_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-mae-interpolation-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + plot_error_projection(interpolation_area[j,::], latitudes, longitudes, + _get_data_path(output_path, filename=f'NEW-mae-interpolation-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), label=output_channels[j].name, title=f'Mean absolute error: Interpolation: {output_channels[j].name}') - plot_error_projection(persistence_area[j,::], latitudes, longitudes, os.path.join(output_path, f'NEW-mae-persistence-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + plot_error_projection(persistence_area[j,::], latitudes, longitudes, + _get_data_path(output_path, f'NEW-mae-persistence-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), label=output_channels[j].name, title=f'Mean absolute error: Persistence: {output_channels[j].name}') - + maes = {} maes['interpolation'] = interpolation_time[j,::] maes['ensemble mean'] = ensemble_mean_time[j,::] maes['crps'] = crps_ensemble_time[j,:] maes['persistence'] = persistence_time[j,::] - plot_scores_vs_t(maes, times, os.path.join(output_path, f'NEW-error-plot-time-{start_time}-{end_time}-{output_channels[j].name}.jpg'), title=f'Mean absolute error: {output_channels[j].name}', xlabel='time', ylabel='MAE') + plot_scores_vs_t(maes, times, + _get_data_path(output_path, filename=f'NEW-error-plot-time-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + title=f'Mean absolute error: {output_channels[j].name}', xlabel='time', ylabel='MAE') From 9aff70344d70cb89e640a975979d9312ddd67f75 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 16 Jul 2025 17:54:34 +0200 Subject: [PATCH 083/302] Fully separate CRPS and MAE plots for long time ranges --- src/hirad/eval/compute_eval.py | 10 +++--- src/hirad/inference/generate.py | 7 +--- src/hirad/utils/inference_utils.py | 51 +++--------------------------- 3 files changed, 11 insertions(+), 57 deletions(-) diff --git a/src/hirad/eval/compute_eval.py b/src/hirad/eval/compute_eval.py index b4570d55..b64a55de 100644 --- a/src/hirad/eval/compute_eval.py +++ b/src/hirad/eval/compute_eval.py @@ -52,7 +52,10 @@ def main(cfg: DictConfig) -> None: plot_crps_over_time_and_area(times, dataset, output_path) def _get_data_path(output_path, time=None, filename=None): - return os.path.join(output_path, time, filename) + if time: + return os.path.join(output_path, time, filename) + else: + return os.path.join(output_path, filename) def load_data(output_path, time=None, filename=None): return torch.load(_get_data_path(output_path, time, filename), weights_only=False) @@ -66,7 +69,6 @@ def compute_crps_per_time(times, dataset, output_path): input_channels = dataset.input_channels() output_channels = dataset.output_channels() start_time=times[0] - end_time=times[-1] # Load one prediction ensemble to get the shape prediction_ensemble = torch.load(os.path.join(output_path, start_time, f'{start_time}-predictions'), weights_only=False) @@ -205,7 +207,7 @@ def plot_crps_over_time_and_area(times, dataset, output_path): _get_data_path(output_path, filename=f'NEW-mae-interpolation-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), label=output_channels[j].name, title=f'Mean absolute error: Interpolation: {output_channels[j].name}') plot_error_projection(persistence_area[j,::], latitudes, longitudes, - _get_data_path(output_path, f'NEW-mae-persistence-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), + _get_data_path(output_path, filename=f'NEW-mae-persistence-area-{start_time}-{end_time}-{output_channels[j].name}.jpg'), label=output_channels[j].name, title=f'Mean absolute error: Persistence: {output_channels[j].name}') maes = {} @@ -218,7 +220,5 @@ def plot_crps_over_time_and_area(times, dataset, output_path): title=f'Mean absolute error: {output_channels[j].name}', xlabel='time', ylabel='MAE') - - if __name__ == "__main__": main() \ No newline at end of file diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index d26a1681..23206568 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -13,7 +13,7 @@ from hirad.models import EDMPrecondSuperResolution, UNet from hirad.inference import Generator -from hirad.utils.inference_utils import save_images, save_results_as_torch, plot_crps_over_time +from hirad.utils.inference_utils import save_images, save_results_as_torch from hirad.utils.function_utils import get_time_from_range from hirad.utils.checkpoint import load_checkpoint @@ -301,11 +301,6 @@ def elapsed_time(self, _): f.close() logger0.info("Generation Completed.") - if cfg.generation.times_range: - # reassign times - times = get_time_from_range(cfg.generation.times_range, time_format="%Y%m%d-%H%M") #TODO check what time formats we are using and adapt - plot_crps_over_time(times, dataset, output_path) - if __name__ == "__main__": main() \ No newline at end of file diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index 8e95f1c3..14be4466 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -239,14 +239,6 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, if mean_pred is not None: mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze()),1) #.reshape(len(output_channels),-1) - # Plot CRPS - if prediction.shape[0] > 1: - crps_score = crps(prediction, target, average_over_area=False, average_over_channels=True) - _plot_projection(longitudes, latitudes, crps_score, os.path.join(output_path, f'{time_step}-crps-all.jpg')) - crps_score_channels = crps(prediction, target, average_over_area=False, average_over_channels=False) - for channel_num in range(crps_score_channels.shape[0]): - _plot_projection(longitudes, latitudes, crps_score_channels[channel_num,::], os.path.join(output_path, f'{time_step}-crps-{output_channels[channel_num].name}.jpg')) - # Plot power spectra freqs = {} power = {} @@ -258,11 +250,11 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, input_channel_idx = input_channels.index(channel) if channel.name=="tp": - target[idx,::] = _prepare_precipitaiton(target[idx,:,:]) - prediction[:,idx,::] = _prepare_precipitaiton(prediction[:,idx,:,:]) - baseline[input_channel_idx,:,:] = _prepare_precipitaiton(baseline[input_channel_idx,::]) + target[idx,::] = _prepare_precipitation(target[idx,:,:]) + prediction[:,idx,::] = _prepare_precipitation(prediction[:,idx,:,:]) + baseline[input_channel_idx,:,:] = _prepare_precipitation(baseline[input_channel_idx,::]) if mean_pred is not None: - mean_pred[idx,::] = _prepare_precipitaiton(mean_pred[idx,::]) + mean_pred[idx,::] = _prepare_precipitation(mean_pred[idx,::]) if mean_pred is not None: vmin, vmax = calculate_bounds(target[idx,:,:], @@ -322,40 +314,7 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, power['mean_prediction'] = mp_power plot_power_spectra(freqs, power, channel.name, os.path.join(output_path_channel, f'{time_step}-{channel.name}-spectra.jpg')) -def plot_crps_over_time(times, dataset, output_path): - longitudes = dataset.longitude() - latitudes = dataset.latitude() - input_channels = dataset.input_channels() - output_channels = dataset.output_channels() - start_time=times[0] - end_time=times[-1] - - # Load one prediction ensemble to get the shape - prediction_ensemble = torch.load(os.path.join(output_path, times[0], f'{times[0]}-predictions'), weights_only=False) - all_predictions = np.ndarray((len(times), prediction_ensemble.shape[0], prediction_ensemble.shape[1], prediction_ensemble.shape[2], prediction_ensemble.shape[3])) - all_targets = np.ndarray((len(times), prediction_ensemble.shape[1], prediction_ensemble.shape[2], prediction_ensemble.shape[3])) - for i in range(len(times)): - prediction_ensemble = torch.load(os.path.join(output_path, times[i], f'{times[i]}-predictions'), weights_only=False) - all_predictions[i,::] = prediction_ensemble - target = torch.load(os.path.join(output_path, times[i], f'{times[i]}-target'), weights_only=False) - all_targets[i,::] = target - score_over_time_channels = crps(all_predictions, all_targets, average_over_area=True, average_over_channels=False, average_over_time=False) - score_over_area_channels = crps(all_predictions, all_targets, average_over_area=False, average_over_channels=False, average_over_time=True) - for channel_num in range(score_over_area_channels.shape[0]): - _plot_projection(longitudes, latitudes, score_over_area_channels[channel_num,::], os.path.join(output_path, f'crps-area-{start_time}-{end_time}-{output_channels[channel_num].name}.jpg')) - _plot_score_vs_t(score_over_time_channels[:, channel_num], times, os.path.join(output_path, f'crps-time-{start_time}-{end_time}-{output_channels[channel_num].name}.jpg')) - -def _plot_score_vs_t(score: np.array, times: np.array, filename: str): - fig = plt.figure() - ax = plt.subplot() - p = plt.plot(times, score) - #plt.ylabel('CRPS') - #plt.xlabel('time') - plt.xticks([times[0],times[-1]]) - plt.savefig(filename) - plt.close('all') - -def _prepare_precipitaiton(precip_array): +def _prepare_precipitation(precip_array): precip_array = np.clip(precip_array, 0, None) precip_array = np.where(precip_array == 0, 1e-6, precip_array) # epsilon = 1e-2 From 20a2ad827503955d474edb478a474ca3ab4542f6 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 16 Jul 2025 18:37:55 +0200 Subject: [PATCH 084/302] Minor improvements to plotting --- src/hirad/eval/compute_eval.py | 8 ++++++-- src/hirad/eval/plotting.py | 11 +++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/hirad/eval/compute_eval.py b/src/hirad/eval/compute_eval.py index b64a55de..8a01db60 100644 --- a/src/hirad/eval/compute_eval.py +++ b/src/hirad/eval/compute_eval.py @@ -6,6 +6,8 @@ import torch import numpy as np import contextlib +import datetime +from pandas import to_datetime from hirad.distributed import DistributedManager from hirad.utils.console import PythonLogger, RankZeroLoggingWrapper @@ -48,7 +50,7 @@ def main(cfg: DictConfig) -> None: output_path = getattr(cfg.generation.io, "output_path", "./outputs") compute_crps_per_time(times, dataset, output_path) - compute_crps_over_time_and_area(times, dataset, output_path) + compute_crps_over_time_and_area(times, output_path) plot_crps_over_time_and_area(times, dataset, output_path) def _get_data_path(output_path, time=None, filename=None): @@ -122,7 +124,7 @@ def compute_crps_per_time(times, dataset, output_path): save_data(interpolation_error, output_path, time=curr_time, filename=f'{curr_time}-interpolation-error') save_data(persistence_error, output_path, time=curr_time, filename=f'{curr_time}-persistence-error') -def compute_crps_over_time_and_area(times, dataset, output_path): +def compute_crps_over_time_and_area(times, output_path): logging.info('computing crps and errors') start_time=times[0] end_time=times[-1] @@ -215,6 +217,8 @@ def plot_crps_over_time_and_area(times, dataset, output_path): maes['ensemble mean'] = ensemble_mean_time[j,::] maes['crps'] = crps_ensemble_time[j,:] maes['persistence'] = persistence_time[j,::] + # TODO: consider casting times to datetime objects to avoid warnings. + # However, this seems to be working OK, and a direct cast causes plotting errors plot_scores_vs_t(maes, times, _get_data_path(output_path, filename=f'NEW-error-plot-time-{start_time}-{end_time}-{output_channels[j].name}.jpg'), title=f'Mean absolute error: {output_channels[j].name}', xlabel='time', ylabel='MAE') diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index 0d437143..8d6a5146 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -21,12 +21,19 @@ def plot_error_projection(values: np.array, latitudes: np.array, longitudes: np. plt.close('all') def plot_scores_vs_t(scores: dict[str,np.ndarray], times: np.array, filename: str, xlabel='', ylabel='', title=''): + fig = plt.figure() ax = plt.subplot() - colors = ['red', 'green', 'blue', 'orange'] # TODO, add more + colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'] # TODO, add more i=0 for k in scores.keys(): - p, = ax.plot(times, scores[k], color=colors[i]) + style = colors[i] + # If more than 50 points, don't connect lines + if len(times) > 50: + style = style + '.' + else: + style = style + '-' + p, = ax.plot(times, scores[k], style) i=i+1 p.set_label(k) ax.legend() From 734279f974964af2613bf632f407970d06ed5955 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 21 Jul 2025 10:13:42 +0200 Subject: [PATCH 085/302] Update paths to environment for training scripts --- src/hirad/train_diffusion.sh | 2 +- src/hirad/train_regression.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hirad/train_diffusion.sh b/src/hirad/train_diffusion.sh index 58e6ccbe..10eb13f7 100644 --- a/src/hirad/train_diffusion.sh +++ b/src/hirad/train_diffusion.sh @@ -38,7 +38,7 @@ export MASTER_PORT=29500 export OMP_NUM_THREADS=72 # python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml -srun --environment=./modulus_env.toml bash -c " +srun --environment=./ci/edf/modulus_env.toml bash -c " pip install -e . --no-dependencies python src/hirad/training/train.py --config-name=training_era_cosmo_diffusion.yaml " \ No newline at end of file diff --git a/src/hirad/train_regression.sh b/src/hirad/train_regression.sh index a75cc06d..f6e276f6 100644 --- a/src/hirad/train_regression.sh +++ b/src/hirad/train_regression.sh @@ -40,7 +40,7 @@ export OMP_NUM_THREADS=72 # . ./train_env/bin/activate # python src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml # " -srun --environment=./modulus_env.toml bash -c " +srun --environment=./ci/edf/modulus_env.toml bash -c " pip install -e . --no-dependencies python src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml " \ No newline at end of file From a618c2f29e07be1e6ec32d818a97a727d387708c Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 21 Jul 2025 10:40:42 +0200 Subject: [PATCH 086/302] Make label opt argument --- src/hirad/eval/plotting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index 8d6a5146..a7603b79 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -8,7 +8,7 @@ import numpy as np import torch -def plot_error_projection(values: np.array, latitudes: np.array, longitudes: np.array, filename: str, label: str, title='', vmin=None, vmax=None): +def plot_error_projection(values: np.array, latitudes: np.array, longitudes: np.array, filename: str, label='', title='', vmin=None, vmax=None): """Plot observed or interpolated data in a scatter plot.""" fig = plt.figure() fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) From 5fc44e63537df355838f69611ccb849bbc51043c Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 21 Jul 2025 18:03:25 +0200 Subject: [PATCH 087/302] Add test scripts/configs for regression, diffusion, and generation --- src/hirad/conf/generate_era_cosmo_test.yaml | 20 ++++++++ src/hirad/conf/generation/era_cosmo_test.yaml | 42 ++++++++++++++++ .../training_era_cosmo_diffusion_test.yaml | 24 ++++++++++ .../training_era_cosmo_regression_test.yaml | 25 ++++++++++ src/hirad/generate_test.sh | 48 +++++++++++++++++++ src/hirad/train_diffusion_test.sh | 42 ++++++++++++++++ src/hirad/train_regression_test.sh | 42 ++++++++++++++++ 7 files changed, 243 insertions(+) create mode 100644 src/hirad/conf/generate_era_cosmo_test.yaml create mode 100644 src/hirad/conf/generation/era_cosmo_test.yaml create mode 100644 src/hirad/conf/training_era_cosmo_diffusion_test.yaml create mode 100644 src/hirad/conf/training_era_cosmo_regression_test.yaml create mode 100644 src/hirad/generate_test.sh create mode 100644 src/hirad/train_diffusion_test.sh create mode 100644 src/hirad/train_regression_test.sh diff --git a/src/hirad/conf/generate_era_cosmo_test.yaml b/src/hirad/conf/generate_era_cosmo_test.yaml new file mode 100644 index 00000000..bb838902 --- /dev/null +++ b/src/hirad/conf/generate_era_cosmo_test.yaml @@ -0,0 +1,20 @@ +hydra: + job: + chdir: true + name: generation_era5_cosmo_test + run: + dir: ./outputs/generation/${hydra:job.name} + +# Get defaults +defaults: + - _self_ + # Dataset + - dataset/era_cosmo_inference + + # Sampler + - sampler/stochastic + #- sampler/deterministic + + # Generation + - generation/era_cosmo_test + #- generation/patched_based \ No newline at end of file diff --git a/src/hirad/conf/generation/era_cosmo_test.yaml b/src/hirad/conf/generation/era_cosmo_test.yaml new file mode 100644 index 00000000..9e10eb4a --- /dev/null +++ b/src/hirad/conf/generation/era_cosmo_test.yaml @@ -0,0 +1,42 @@ +# TODO: See if there's a way to inherit from era_cosmo.yaml +num_ensembles: 8 + # Number of ensembles to generate per input +seed_batch_size: 4 + # Size of the batched inference +inference_mode: all + # Choose between "all" (regression + diffusion), "regression" or "diffusion" + # Patch size. Patch-based sampling will be utilized if these dimensions differ from + # img_shape_x and img_shape_y +# overlap_pixels: 0 + # Number of overlapping pixels between adjacent patches +# boundary_pixels: 0 + # Number of boundary pixels to be cropped out. 2 is recommanded to address the boundary + # artifact. +patching: False +hr_mean_conditioning: True +# sample_res: full + # Sampling resolution +times_range: ['20200131-0000','20200131-2300',1] +times: null +has_lead_time: False + +perf: + force_fp16: False + # Whether to force fp16 precision for the model. If false, it'll use the precision + # specified upon training. + use_torch_compile: False + # whether to use torch.compile on the diffusion model + # this will make the first time stamp generation very slow due to compilation overheads + # but will significantly speed up subsequent inference runs + num_writer_workers: 8 + # number of workers to use for writing file + # To support multiple workers a threadsafe version of the netCDF library must be used + +io: + res_ckpt_path: /capstor/scratch/cscs/pstamenk/outputs/training/diffusion_test/checkpoints_diffusion + # Checkpoint filename for the diffusion model + reg_ckpt_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_test/checkpoints_regression + # Checkpoint filename for the mean predictor model + output_path: ./outputs/evaluation + + diff --git a/src/hirad/conf/training_era_cosmo_diffusion_test.yaml b/src/hirad/conf/training_era_cosmo_diffusion_test.yaml new file mode 100644 index 00000000..27230507 --- /dev/null +++ b/src/hirad/conf/training_era_cosmo_diffusion_test.yaml @@ -0,0 +1,24 @@ +hydra: + job: + chdir: true + name: diffusion_era5_cosmo_test + run: + dir: ./outputs/training/${hydra:job.name} + +# Get defaults +defaults: + - _self_ + + # Dataset + - dataset/era_cosmo + + # Model + - model/era_cosmo_diffusion + + - model_size/mini + + # Training + - training/era_cosmo_diffusion + + # Inference visualization + - generation/era_cosmo_training \ No newline at end of file diff --git a/src/hirad/conf/training_era_cosmo_regression_test.yaml b/src/hirad/conf/training_era_cosmo_regression_test.yaml new file mode 100644 index 00000000..96e88fa7 --- /dev/null +++ b/src/hirad/conf/training_era_cosmo_regression_test.yaml @@ -0,0 +1,25 @@ +hydra: + job: + chdir: true + name: regression_era5_cosmo_test + run: + dir: ./outputs/training/${hydra:job.name} + +# Get defaults +defaults: + - _self_ + + # Dataset + - dataset/era_cosmo + + # Model + - model/era_cosmo_regression + + - model_size/mini + + # Training + # Leave same as prod + - training/era_cosmo_regression + + # Inference visualization + - generation/era_cosmo_training \ No newline at end of file diff --git a/src/hirad/generate_test.sh b/src/hirad/generate_test.sh new file mode 100644 index 00000000..0f12f9ea --- /dev/null +++ b/src/hirad/generate_test.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +#SBATCH --job-name="corrdiff-test-genreate" + +### HARDWARE ### +#SBATCH --partition=debug +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=2 +#SBATCH --gpus-per-node=2 +#SBATCH --cpus-per-task=72 +#SBATCH --time=00:30:00 +#SBATCH --no-requeue +#SBATCH --exclusive + +### OUTPUT ### +#SBATCH --output=./logs/generation_test.log + +### ENVIRONMENT #### +#SBATCH -A a161 + +# Choose method to initialize dist in pythorch +export DISTRIBUTED_INITIALIZATION_METHOD=SLURM + +MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" +echo "Master node : $MASTER_ADDR" +# Get IP for hostname. +MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" +echo "Master address : $MASTER_ADDR" +export MASTER_ADDR +export MASTER_PORT=29500 +echo "Master port: $MASTER_PORT" + +# Get number of physical cores using Python +# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") +# # Use SLURM_NTASKS (number of processes to be launched by torchrun) +# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} +# # Compute threads per process +# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) +# export OMP_NUM_THREADS=$OMP_THREADS +export OMP_NUM_THREADS=72 +# echo "Physical cores: $PHYSICAL_CORES" +# echo "Local processes: $LOCAL_PROCS" +# echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" + +srun --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . --no-dependencies + python src/hirad/inference/generate.py --config-name=generate_era_cosmo_test.yaml +" \ No newline at end of file diff --git a/src/hirad/train_diffusion_test.sh b/src/hirad/train_diffusion_test.sh new file mode 100644 index 00000000..9c9831f4 --- /dev/null +++ b/src/hirad/train_diffusion_test.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +#SBATCH --job-name="corrdiff-test-second-stage" + +### HARDWARE ### +#SBATCH --partition=debug +#SBATCH --nodes=2 +#SBATCH --ntasks-per-node=4 +#SBATCH --gpus-per-node=4 +#SBATCH --cpus-per-task=72 +#SBATCH --time=00:30:00 +#SBATCH --no-requeue +#SBATCH --exclusive + +### OUTPUT ### +#SBATCH --output=./logs/training_diffusion_test.log + +### ENVIRONMENT #### +#SBATCH -A a161 + +# Choose method to initialize dist in pythorch +export DISTRIBUTED_INITIALIZATION_METHOD=SLURM + +# Get master node. +MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" +# Get IP for hostname. +MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" +export MASTER_ADDR +export MASTER_PORT=29500 + +# Get number of physical cores using Python +# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") +# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} +# # Compute cores per process +# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) +# export OMP_NUM_THREADS=$OMP_THREADS +export OMP_NUM_THREADS=72 + +srun --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . --no-dependencies + python src/hirad/training/train.py --config-name=training_era_cosmo_diffusion_test.yaml +" \ No newline at end of file diff --git a/src/hirad/train_regression_test.sh b/src/hirad/train_regression_test.sh new file mode 100644 index 00000000..4986fc08 --- /dev/null +++ b/src/hirad/train_regression_test.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +#SBATCH --job-name="corrdiff-test-first-stage" + +### HARDWARE ### +#SBATCH --partition=debug +#SBATCH --nodes=2 +#SBATCH --ntasks-per-node=4 +#SBATCH --gpus-per-node=4 +#SBATCH --cpus-per-task=72 +#SBATCH --time=00:30:00 +#SBATCH --no-requeue +#SBATCH --exclusive + +### OUTPUT ### +#SBATCH --output=./logs/training_regression_test.log + +### ENVIRONMENT #### +#SBATCH -A a161 + +# Choose method to initialize dist in pythorch +export DISTRIBUTED_INITIALIZATION_METHOD=SLURM + +# Get master node. +MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" +# Get IP for hostname. +MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" +export MASTER_ADDR +export MASTER_PORT=29500 + +# Get number of physical cores using Python +# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") +# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} +# # Compute cores per process +# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) +# export OMP_NUM_THREADS=$OMP_THREADS +export OMP_NUM_THREADS=72 + +srun --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . --no-dependencies + python src/hirad/training/train.py --config-name=training_era_cosmo_regression_test.yaml +" \ No newline at end of file From 284f778ee476aa79ffd3ecbd9e2ef731ba6d58bd Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 21 Jul 2025 18:16:56 +0200 Subject: [PATCH 088/302] Update CI to include regression test script --- ci/cscs.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ci/cscs.yml b/ci/cscs.yml index b96e65af..1be60ea0 100644 --- a/ci/cscs.yml +++ b/ci/cscs.yml @@ -14,12 +14,12 @@ build_job: variables: DOCKERFILE: ci/docker/Dockerfile.ci -#test_job: -# stage: test -# extends: .container-runner-clariden-gh200 -# image: $PERSIST_IMAGE_NAME -# script: -# - /opt/helloworld/bin/hello -# variables: -# SLURM_JOB_NUM_NODES: 2 -# SLURM_NTASKS: 2 +test_job: + stage: test + extends: .container-runner-clariden-gh200 + image: $PERSIST_IMAGE_NAME + script: + - hirad/train_regression_test.sh + variables: + SLURM_JOB_NUM_NODES: 2 + SLURM_NTASKS: 2 From 995e888a8aefaede077ec7e5ffd136b3ebe19ee6 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 24 Jul 2025 15:58:38 +0200 Subject: [PATCH 089/302] Test script should not be srun if it's going to work with ci/cd --- src/hirad/train_regression_test.sh | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/hirad/train_regression_test.sh b/src/hirad/train_regression_test.sh index 4986fc08..6cc04681 100644 --- a/src/hirad/train_regression_test.sh +++ b/src/hirad/train_regression_test.sh @@ -1,23 +1,8 @@ #!/bin/bash -#SBATCH --job-name="corrdiff-test-first-stage" - -### HARDWARE ### -#SBATCH --partition=debug -#SBATCH --nodes=2 -#SBATCH --ntasks-per-node=4 -#SBATCH --gpus-per-node=4 -#SBATCH --cpus-per-task=72 -#SBATCH --time=00:30:00 -#SBATCH --no-requeue -#SBATCH --exclusive - ### OUTPUT ### #SBATCH --output=./logs/training_regression_test.log -### ENVIRONMENT #### -#SBATCH -A a161 - # Choose method to initialize dist in pythorch export DISTRIBUTED_INITIALIZATION_METHOD=SLURM @@ -36,7 +21,5 @@ export MASTER_PORT=29500 # export OMP_NUM_THREADS=$OMP_THREADS export OMP_NUM_THREADS=72 -srun --environment=./ci/edf/modulus_env.toml bash -c " - pip install -e . --no-dependencies - python src/hirad/training/train.py --config-name=training_era_cosmo_regression_test.yaml -" \ No newline at end of file +pip install -e . --no-dependencies +python src/hirad/training/train.py --config-name=training_era_cosmo_regression_test.yaml \ No newline at end of file From ffc1bedf62628e31050ebf351bd59967f0f55621 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 24 Jul 2025 16:20:27 +0200 Subject: [PATCH 090/302] Try to directly run from cscs.yml --- ci/cscs.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/cscs.yml b/ci/cscs.yml index 1be60ea0..1668b814 100644 --- a/ci/cscs.yml +++ b/ci/cscs.yml @@ -19,7 +19,8 @@ test_job: extends: .container-runner-clariden-gh200 image: $PERSIST_IMAGE_NAME script: - - hirad/train_regression_test.sh + - pip install -e . --no-dependencies + - python src/hirad/training/train.py --config-name=training_era_cosmo_regression_test.yaml variables: SLURM_JOB_NUM_NODES: 2 SLURM_NTASKS: 2 From ebc104308bc92c7fbf5bccfe5df602aa56928768 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Thu, 24 Jul 2025 18:45:28 +0200 Subject: [PATCH 091/302] plot diurnal cycles of precip amount and wet-hour frequency over time period --- src/hirad/diurnal_cycle.sh | 49 +++++++++ src/hirad/eval/diurnal_cycle_precip.py | 147 +++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 src/hirad/diurnal_cycle.sh create mode 100644 src/hirad/eval/diurnal_cycle_precip.py diff --git a/src/hirad/diurnal_cycle.sh b/src/hirad/diurnal_cycle.sh new file mode 100644 index 00000000..4a021c06 --- /dev/null +++ b/src/hirad/diurnal_cycle.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +#SBATCH --job-name="plot_diurnal_cycle" + +### HARDWARE ### +#SBATCH --partition=debug +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=2 +#SBATCH --gpus-per-node=2 +#SBATCH --cpus-per-task=72 +##SBATCH --time=00:10:00 +#SBATCH --no-requeue +#SBATCH --exclusive + +### OUTPUT ### +#SBATCH --output=./logs/plot_diurnal_cycle.log + +### ENVIRONMENT #### +#SBATCH -A a161 + +# Choose method to initialize dist in pythorch +export DISTRIBUTED_INITIALIZATION_METHOD=SLURM + +MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" +echo "Master node : $MASTER_ADDR" +# Get IP for hostname. +MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" +echo "Master address : $MASTER_ADDR" +export MASTER_ADDR +export MASTER_PORT=29500 +echo "Master port: $MASTER_PORT" + +# Get number of physical cores using Python +# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") +# # Use SLURM_NTASKS (number of processes to be launched by torchrun) +# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} +# # Compute threads per process +# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) +# export OMP_NUM_THREADS=$OMP_THREADS +export OMP_NUM_THREADS=72 +# echo "Physical cores: $PHYSICAL_CORES" +# echo "Local processes: $LOCAL_PROCS" +# echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" + +# python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml +srun --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . --no-dependencies + python src/hirad/eval/diurnal_cycle_precip.py --config-name=generate_era_cosmo.yaml +" \ No newline at end of file diff --git a/src/hirad/eval/diurnal_cycle_precip.py b/src/hirad/eval/diurnal_cycle_precip.py new file mode 100644 index 00000000..231a5b33 --- /dev/null +++ b/src/hirad/eval/diurnal_cycle_precip.py @@ -0,0 +1,147 @@ +import logging +from datetime import datetime +from pathlib import Path +from collections import defaultdict + +import hydra +import numpy as np +torch = __import__('torch') +from omegaconf import DictConfig, OmegaConf +import matplotlib.pyplot as plt + +from hirad.datasets import get_dataset_and_sampler_inference +from hirad.distributed import DistributedManager +from hirad.utils.function_utils import get_time_from_range + +# Constants +CONV_FACTOR = 100 # Convert meters to mm/h +WET_THRESHOLD = 0.1 # Threshold for wet-hour in mm/h +LOG_INTERVAL = 1 # Log progress every N timesteps + + +def hour_of(dt: str, fmt: str = "%Y%m%d-%H%M") -> int: + return datetime.strptime(dt, fmt).hour + + +def compute_ensemble(hourly_values): + hours = sorted(hourly_values) + means = [np.mean(hourly_values[h]) for h in hours] + stds = [np.std(hourly_values[h]) for h in hours] + return hours, means, stds + + +def save_plot(hours, lines, labels, ylabel, title, out_path): + Path(out_path).parent.mkdir(parents=True, exist_ok=True) + plt.figure(figsize=(8,4)) + for data, label in zip(lines, labels): + if isinstance(data, tuple): # (mean, std) + mean, std = data + plt.plot(hours, mean, label=label) + plt.fill_between(hours, + np.array(mean)-std, + np.array(mean)+std, + alpha=0.3) + else: + plt.plot(hours, data, label=label) + plt.xlabel('Hour (UTC)') + plt.xticks(range(0,25,3)) + plt.xlim(0,24) + plt.ylabel(ylabel) + plt.title(title) + plt.grid(True) + plt.legend() + plt.tight_layout() + plt.savefig(out_path) + plt.close() + + +@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") +def main(cfg: DictConfig): + # Setup logging + DistributedManager.initialize() + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + + logger.info("Starting diurnal cycle computation") + times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + logger.info(f"Loaded {len(times)} timesteps to process") + + ds_cfg = OmegaConf.to_container(cfg.dataset) + dataset, _ = get_dataset_and_sampler_inference( + ds_cfg, times, cfg.generation.get('has_lead_time', False) + ) + logger.info("Dataset and sampler initialized") + + out_root = Path(cfg.generation.io.output_path or './outputs') + load = lambda ts, fn: torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR + + # Find channel indices + out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} + in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} + tp_out = out_ch['tp']; tp_in = in_ch.get('tp', tp_out) + logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") + + # Prepare data structures + stats = {mode: defaultdict(list) for mode in ['target','baseline','prediction']} + wet_stats = {mode: defaultdict(list) for mode in stats} + + # Collect data + for idx, ts in enumerate(times, 1): + hr = hour_of(ts) + target = load(ts, f"{ts}-target")[tp_out] + baseline = load(ts, f"{ts}-baseline")[tp_in] + stats['target'][hr].append(target) + stats['baseline'][hr].append(baseline) + wet_stats['target'][hr].append((target > WET_THRESHOLD).mean()) + wet_stats['baseline'][hr].append((baseline > WET_THRESHOLD).mean()) + + preds = load(ts, f"{ts}-predictions")[:, tp_out] + for member in preds: + stats['prediction'][hr].append(member.mean()) + wet_stats['prediction'][hr].append((member > WET_THRESHOLD).mean()) + + if idx % LOG_INTERVAL == 0 or idx == len(times): + logger.info(f"Processed {idx}/{len(times)} timesteps ({ts})") + + # Compute hourly means + mean_cycle = { + mode: [np.mean(stats[mode][h]) for h in sorted(stats[mode])] + for mode in ['target','baseline'] + } + wet_cycle = { + mode: [np.mean(wet_stats[mode][h]) for h in sorted(wet_stats[mode])] + for mode in ['target','baseline'] + } + logger.info("Computed hourly mean and wet-cycle statistics") + + # Ensemble cycles (mean ± std) + hrs, pred_mean, pred_std = compute_ensemble(stats['prediction']) + _, wet_mean, wet_std = compute_ensemble(wet_stats['prediction']) + logger.info("Computed ensemble statistics") + + # Prepare cyclic series + cycle = lambda x: x + [x[0]] + hrs_c = hrs + [24] + amount_lines = [cycle(mean_cycle['target']), cycle(mean_cycle['baseline']), (cycle(pred_mean), cycle(pred_std))] + wet_lines = [cycle(wet_cycle['target']), cycle(wet_cycle['baseline']), (cycle(wet_mean), cycle(wet_std))] + + # Log the lines to be plotted (debug) + logger.info(f"amount_lines: {amount_lines}") + logger.info(f"wet_lines: {wet_lines}") + + # Plot + plot_paths = [] + fn1 = out_root/'diurnal_cycle_precip_amount.png' + save_plot(hrs_c, amount_lines, ['COSMO-2','ERA5','CorrDiff ± Std(Members)'], 'Rain Rate (mm/h)', + 'Diurnal Cycle of Precip Amount', fn1) + plot_paths.append(fn1) + + fn2 = out_root/'diurnal_cycle_precip_wethours.png' + save_plot(hrs_c, wet_lines * 100., ['COSMO-2','ERA5','Pred Mean ± Std'], 'Wet-Hour Fraction [%]', + 'Diurnal Cycle of Wet-Hours (>0.1 mm/h)', fn2) + plot_paths.append(fn2) + + logger.info(f"Plots saved: {', '.join(str(p) for p in plot_paths)}") + +if __name__ == '__main__': + main() From 7470c99713e0eea84b271dccd09c5a080dc14d8a Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 25 Jul 2025 12:36:42 +0200 Subject: [PATCH 092/302] add mlflow logging --- ci/docker/Dockerfile.corrdiff | 3 +- .../conf/logging/era_cosmo_diffusion.yaml | 5 + .../conf/logging/era_cosmo_regression.yaml | 5 + .../conf/training_era_cosmo_diffusion.yaml | 5 +- .../conf/training_era_cosmo_regression.yaml | 7 +- src/hirad/training/train.py | 190 ++++++++++-------- src/hirad/utils/env_info.py | 166 +++++++++++++++ src/hirad/utils/train_helpers.py | 39 ++++ 8 files changed, 330 insertions(+), 90 deletions(-) create mode 100644 src/hirad/conf/logging/era_cosmo_diffusion.yaml create mode 100644 src/hirad/conf/logging/era_cosmo_regression.yaml create mode 100644 src/hirad/utils/env_info.py diff --git a/ci/docker/Dockerfile.corrdiff b/ci/docker/Dockerfile.corrdiff index 93f389c0..79081979 100644 --- a/ci/docker/Dockerfile.corrdiff +++ b/ci/docker/Dockerfile.corrdiff @@ -7,4 +7,5 @@ RUN pip install --upgrade pip # Install the rest of dependencies. RUN pip install \ Cartopy==0.22.0 \ - xskillscore + xskillscore \ + mlflow diff --git a/src/hirad/conf/logging/era_cosmo_diffusion.yaml b/src/hirad/conf/logging/era_cosmo_diffusion.yaml new file mode 100644 index 00000000..df7f4812 --- /dev/null +++ b/src/hirad/conf/logging/era_cosmo_diffusion.yaml @@ -0,0 +1,5 @@ +# set method to mlflow to log with mlflow +method: null +experiment_name: hirad-corrdiff-diffusion +run_name: era-cosmo-1h +uri: null \ No newline at end of file diff --git a/src/hirad/conf/logging/era_cosmo_regression.yaml b/src/hirad/conf/logging/era_cosmo_regression.yaml new file mode 100644 index 00000000..ba4095b2 --- /dev/null +++ b/src/hirad/conf/logging/era_cosmo_regression.yaml @@ -0,0 +1,5 @@ +# set method to mlflow to log with mlflow +method: null +experiment_name: hirad-corrdiff-regression +run_name: era-cosmo-1h +uri: null \ No newline at end of file diff --git a/src/hirad/conf/training_era_cosmo_diffusion.yaml b/src/hirad/conf/training_era_cosmo_diffusion.yaml index 08c8d6a2..fee76274 100644 --- a/src/hirad/conf/training_era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training_era_cosmo_diffusion.yaml @@ -21,4 +21,7 @@ defaults: - training/era_cosmo_diffusion # Inference visualization - - generation/era_cosmo_training \ No newline at end of file + - generation/era_cosmo_training + + # Logging + - logging/era_cosmo_diffusion \ No newline at end of file diff --git a/src/hirad/conf/training_era_cosmo_regression.yaml b/src/hirad/conf/training_era_cosmo_regression.yaml index 83a4f944..ce04119b 100644 --- a/src/hirad/conf/training_era_cosmo_regression.yaml +++ b/src/hirad/conf/training_era_cosmo_regression.yaml @@ -1,7 +1,7 @@ hydra: job: chdir: true - name: regression_era5_cosmo_test + name: regression_era5_cosmo_mlflow_test_x16 run: dir: /capstor/scratch/cscs/pstamenk/outputs/training/${hydra:job.name} @@ -21,4 +21,7 @@ defaults: - training/era_cosmo_regression # Inference visualization - - generation/era_cosmo_training \ No newline at end of file + - generation/era_cosmo_training + + # Logging + - logging/era_cosmo_regression \ No newline at end of file diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 931fa664..a60844c1 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -11,19 +11,22 @@ import nvtx import torch from hydra.utils import to_absolute_path -from torch.utils.tensorboard import SummaryWriter +# from torch.utils.tensorboard import SummaryWriter from torch.nn.parallel import DistributedDataParallel +import mlflow # from torchinfo import summary from hirad.distributed import DistributedManager from hirad.utils.console import PythonLogger, RankZeroLoggingWrapper from hirad.utils.train_helpers import set_seed, configure_cuda_for_consistent_precision, \ set_patch_shape, compute_num_accumulation_rounds, \ - is_time_for_periodic_task, handle_and_clip_gradients + is_time_for_periodic_task, handle_and_clip_gradients, \ + init_mlflow from hirad.utils.checkpoint import load_checkpoint, save_checkpoint from hirad.utils.patching import RandomPatching2D from hirad.utils.function_utils import get_time_from_range from hirad.utils.inference_utils import save_images +from hirad.utils.env_info import get_env_info, flatten_dict from hirad.models import UNet, EDMPrecondSuperResolution from hirad.losses import ResidualLoss, RegressionLoss, RegressionLossCE from hirad.datasets import init_train_valid_datasets_from_config, get_dataset_and_sampler_inference @@ -66,12 +69,19 @@ def main(cfg: DictConfig) -> None: DistributedManager.initialize() dist = DistributedManager() - if dist.rank==0: - writer = SummaryWriter(log_dir='tensorboard') + OmegaConf.resolve(cfg) + cfg_dict = OmegaConf.to_object(cfg) + + if cfg.logging.method == "mlflow": + init_mlflow(cfg_dict, dist) + if dist.world_size > 1: + torch.distributed.barrier() + elif cfg.logging.method is not None: + raise ValueError("The only available logging method is mlflow. To disable logging set the method to null.") + logger = PythonLogger("main") # general logger logger0 = RankZeroLoggingWrapper(logger, dist) # rank 0 logger - OmegaConf.resolve(cfg) dataset_cfg = OmegaConf.to_container(cfg.dataset) if hasattr(cfg.dataset, "validation_path"): train_test_split = True @@ -96,14 +106,22 @@ def main(cfg: DictConfig) -> None: if dist.rank==0 and not os.path.exists(visualization_dir): os.makedirs(visualization_dir) # added creating checkpoint dir visualize_checkpoints = True + if cfg.training.hp.batch_size_per_gpu == "auto" and \ + cfg.training.hp.total_batch_size == "auto": + raise ValueError("batch_size_per_gpu and total_batch_size can't be both set to 'auto'.") if cfg.training.hp.batch_size_per_gpu == "auto": cfg.training.hp.batch_size_per_gpu = ( cfg.training.hp.total_batch_size // dist.world_size ) + elif cfg.training.hp.total_batch_size == "auto": + cfg.training.hp.total_batch_size = ( + cfg.training.hp.batch_size_per_gpu * dist.world_size + ) + set_seed(dist.rank) configure_cuda_for_consistent_precision() - + # Instantiate the dataset data_loader_kwargs = { "pin_memory": True, @@ -143,7 +161,7 @@ def main(cfg: DictConfig) -> None: if cfg.generation.times_range and cfg.generation.times: raise ValueError("Either times_range or times must be provided, but not both") if cfg.generation.times_range: - times = get_time_from_range(cfg.generation.times_range, time_format="%Y%m%d-%H%M") #TODO check what time formats we are using and adapt + times = get_time_from_range(cfg.generation.times_range, time_format="%Y%m%d-%H%M") else: times = cfg.generation.times viz_dataset_cfg = OmegaConf.to_container(cfg.generation.dataset) @@ -291,7 +309,6 @@ def main(cfg: DictConfig) -> None: raise FileNotFoundError( f"Expected this regression checkpoint but not found: {regression_checkpoint_path}" ) - #regression_net = torch.nn.Module() #TODO Module.from_checkpoint(regression_checkpoint_path) figure out how to save and load models, also, some basic functions like num_params, device #TODO make regression model loading more robust (model type is both in rergession_checkpoint_path and regression_name) #TODO add the option to choose epoch to load from / regression_checkpoint_path is now a folder regression_model_args_path = os.path.join(regression_checkpoint_path, 'model_args.json') @@ -567,9 +584,9 @@ def main(cfg: DictConfig) -> None: ) / n_average_loss_running_mean n_average_loss_running_mean += 1 - if dist.rank == 0: - writer.add_scalar("training_loss", average_loss, cur_nimg) - writer.add_scalar( + if dist.rank == 0 and cfg.logging.method == "mlflow": + mlflow.log_metric("training_loss", average_loss, cur_nimg) + mlflow.log_metric( "training_loss_running_mean", average_loss_running_mean, cur_nimg, @@ -598,8 +615,8 @@ def main(cfg: DictConfig) -> None: if cur_nimg >= lr_rampup: g["lr"] *= cfg.training.hp.lr_decay ** ((cur_nimg - lr_rampup) // cfg.training.hp.lr_decay_rate) current_lr = g["lr"] - if dist.rank == 0: - writer.add_scalar("learning_rate", current_lr, cur_nimg) + if dist.rank == 0 and cfg.logging.method == "mlflow": + mlflow.log_metric("learning_rate", current_lr, cur_nimg) handle_and_clip_gradients( model, grad_clip_threshold=cfg.training.hp.grad_clip_threshold ) @@ -737,9 +754,9 @@ def main(cfg: DictConfig) -> None: valid_loss_sum, op=torch.distributed.ReduceOp.SUM ) average_valid_loss = valid_loss_sum / dist.world_size - if dist.rank == 0: - writer.add_scalar( - "validation_loss", average_valid_loss, cur_nimg + if dist.rank == 0 and cfg.logging.method == "mlflow": + mlflow.log_metric( + "validation_loss", average_valid_loss, cur_nimg ) @@ -770,83 +787,84 @@ def main(cfg: DictConfig) -> None: done, cfg.training.hp.total_batch_size, dist.rank, - ): - if visualize_checkpoints: - with nvtx.annotate("validation", color="red"): + ) and visualize_checkpoints: + with nvtx.annotate("visualization", color="red"): + if dist.rank == 0: + writer_executor = ThreadPoolExecutor( + max_workers=cfg.generation.perf.num_writer_workers + ) + writer_threads = [] + + times = visualization_dataset.time() + time_index = -1 + for index, (img_clean_viz, img_lr_viz, *lead_time_label_viz) in enumerate( + iter(visualization_data_loader) + ): + time_index += 1 + logger0.info(f"starting index: {time_index}") + + # continue + if lead_time_label_viz: + lead_time_label_viz = lead_time_label_viz[0].to(dist.device).contiguous() + else: + lead_time_label_viz = None + + if use_apex_gn: + img_clean_viz = img_clean_viz.to( + dist.device, + dtype=input_dtype, + non_blocking=True, + ).to(memory_format=torch.channels_last) + img_lr_viz = img_lr_viz.to( + dist.device, + dtype=input_dtype, + non_blocking=True, + ).to(memory_format=torch.channels_last) + else: + img_clean_viz = ( + img_clean_viz.to(dist.device) + .to(input_dtype) + .contiguous() + ) + img_lr_viz = ( + img_lr_viz.to(dist.device) + .to(input_dtype) + .contiguous() + ) + with torch.autocast( + "cuda", dtype=amp_dtype, enabled=enable_amp + ): + image_pred_viz, image_reg_viz = generator.generate(img_lr_viz, lead_time_label_viz) if dist.rank == 0: - writer_executor = ThreadPoolExecutor( - max_workers=cfg.generation.perf.num_writer_workers + # write out data in a seperate thread so we don't hold up inferencing + output_path = os.path.join(visualization_dir, f"{cur_nimg}_{times[visualization_sampler[time_index]]}") + if dist.rank==0 and not os.path.exists(output_path): + os.makedirs(output_path) + writer_threads.append( + writer_executor.submit( + save_images, + output_path, + times[visualization_sampler[time_index]], + visualization_dataset, + image_pred_viz.cpu().numpy(), + img_clean_viz.cpu().numpy(), + img_lr_viz.cpu().numpy(), + image_reg_viz.cpu().numpy() if image_reg_viz is not None else None, + ) ) - writer_threads = [] - - times = visualization_dataset.time() - time_index = -1 - for index, (img_clean_viz, img_lr_viz, *lead_time_label_viz) in enumerate( - iter(visualization_data_loader) - ): - time_index += 1 - logger0.info(f"starting index: {time_index}") - - # continue - if lead_time_label_viz: - lead_time_label_viz = lead_time_label_viz[0].to(dist.device).contiguous() - else: - lead_time_label_viz = None + # make sure all the workers are done writing + if dist.rank == 0: + for thread in list(writer_threads): + thread.result() + writer_threads.remove(thread) + writer_executor.shutdown() - if use_apex_gn: - img_clean_viz = img_clean_viz.to( - dist.device, - dtype=input_dtype, - non_blocking=True, - ).to(memory_format=torch.channels_last) - img_lr_viz = img_lr_viz.to( - dist.device, - dtype=input_dtype, - non_blocking=True, - ).to(memory_format=torch.channels_last) - else: - img_clean_viz = ( - img_clean_viz.to(dist.device) - .to(input_dtype) - .contiguous() - ) - img_lr_viz = ( - img_lr_viz.to(dist.device) - .to(input_dtype) - .contiguous() - ) - with torch.autocast( - "cuda", dtype=amp_dtype, enabled=enable_amp - ): - image_pred_viz, image_reg_viz = generator.generate(img_lr_viz, lead_time_label_viz) - if dist.rank == 0: - # write out data in a seperate thread so we don't hold up inferencing - output_path = os.path.join(visualization_dir, f"{cur_nimg}_{times[visualization_sampler[time_index]]}") - if dist.rank==0 and not os.path.exists(output_path): - os.makedirs(output_path) - writer_threads.append( - writer_executor.submit( - save_images, - output_path, - times[visualization_sampler[time_index]], - visualization_dataset, - image_pred_viz.cpu().numpy(), - img_clean_viz.cpu().numpy(), - img_lr_viz.cpu().numpy(), - image_reg_viz.cpu().numpy() if image_reg_viz is not None else None, - ) - ) - # make sure all the workers are done writing - if dist.rank == 0: - for thread in list(writer_threads): - thread.result() - writer_threads.remove(thread) - writer_executor.shutdown() + if dist.world_size > 1: + torch.distributed.barrier() # Done. logger0.info("Training Completed.") - if __name__ == "__main__": main() \ No newline at end of file diff --git a/src/hirad/utils/env_info.py b/src/hirad/utils/env_info.py new file mode 100644 index 00000000..f1cfe642 --- /dev/null +++ b/src/hirad/utils/env_info.py @@ -0,0 +1,166 @@ +""" +Environment Introspection Module +================================ + +This module provides functionality to introspect the Python environment, listing all non-standard +library modules along with their versions and Git information if they are part of a Git repository. +It helps in understanding the environment setup by detailing the modules in use, their versions, and +relevant Git metadata. +""" + +import platform +import os +import sys +from types import ModuleType +from typing import Any, Optional +from git import Repo, InvalidGitRepositoryError + + +def get_module_version(module: ModuleType) -> Optional[str]: + """ + Retrieve the version of a module if available. + + This function attempts to get the version of a module by accessing its ``__version__`` + attribute. It checks if the version is a string to ensure correctness. + + :param module: The module whose version is to be retrieved. + :type module: ModuleType + :return: The version string if available and valid, otherwise None. + :rtype: Optional[str] + """ + version = getattr(module, "__version__", None) + if isinstance(version, str): + return version + return None + + +def get_git_info(path: str) -> Optional[dict[str, Any]]: + """ + Collect basic Git metadata for a given repository path. + + This function checks if the given path is part of a Git repository and collects metadata such as + the commit SHA, modified files, untracked files, and remote URLs. + + :param path: The path to check for Git repository metadata. + :type path: str + :return: A dictionary containing Git metadata if the path is a Git repository, otherwise None. + :rtype: Optional[Dict[str, Any]] + """ + try: + repo = Repo(path, search_parent_directories=True) + diff = "" + for diff_item in repo.index.diff(None, create_patch=True): + a_path = diff_item.a_blob.abspath if diff_item.a_blob else "" + b_path = diff_item.b_blob.abspath if diff_item.b_blob else "" + diff_content = diff_item.diff + if isinstance(diff_content, bytes): + diff_content = diff_content.decode("utf-8") + elif diff_content is None: + diff_content = "" + diff += f"--- a{a_path}\n+++ b{b_path}\n{diff_content}\n\n" + git_info = { + "sha1": repo.head.commit.hexsha, + "diff": diff, + "untracked_files": sorted(repo.untracked_files), + "remotes": [r.url for r in repo.remotes], + } + return git_info + except InvalidGitRepositoryError: + return None + + +def get_module_git_info(module: ModuleType) -> Optional[dict[str, Any]]: + """ + Get Git information for a module if its directory is a Git repository. + + This function determines the directory of the module and checks if it is part of a Git + repository. If it is, it collects and returns the Git metadata. + + :param module: The module to check for Git information. + :type module: ModuleType + :return: A dictionary containing Git metadata if the module is in a Git repository, otherwise + None. + :rtype: Optional[Dict[str, Any]] + """ + module_path = getattr(module, "__file__", None) + if module_path is None or not os.path.isabs(module_path): + return None + module_dir = os.path.dirname(module_path) + return get_git_info(module_dir) + + +def get_env_info(flatten: bool = True, exclude_prefixes: list[str] = None) -> tuple[dict[str, dict[str, Any]], str]: + """ + List all non-standard library modules with their versions and Git information if available. + + This function iterates over all loaded modules in the Python environment, filtering out + built-in modules. It collects version and Git information for each remaining module. + + :return: A tuple containing two elements: + - A dictionary mapping module names to their metadata + - A string containing concatenated Git diffs from all modules + :rtype: tuple[dict[str, dict[str, Any]], str] + :rtype: Dict[str, Dict[str, Any]] + """ + env_info = {} + diffs: list[str] = [] + + exclude_prefixes = exclude_prefixes or [] + + for name, module in sys.modules.copy().items(): + if name in sys.builtin_module_names or name.endswith(('.version','._version')): + continue + + if any(name == prefix or name.startswith(prefix + ".") for prefix in exclude_prefixes): + continue + + version = get_module_version(module) + git_info = get_module_git_info(module) + if version is None and git_info is None: + continue + + module_info: dict[str, Any] = {"version": version} + if git_info is not None: + module_diff = git_info.pop("diff") + if module_diff and module_diff not in diffs: + diffs.append(module_diff) + module_info["git"] = git_info + + env_info[name] = module_info + + env_info["python"] = {"version": platform.python_version()} + + diffs_str = "\n".join(diffs) + + if flatten: + return flatten_dict(env_info), diffs_str + + return env_info, diffs_str + + +def flatten_dict( + d: dict[str, Any], parent_key: str = "", sep: str = "." +) -> dict[str, Any]: + """ + Flatten a nested dictionary. + + This function recursively traverses a nested dictionary and flattens it into a single-level + dictionary with keys formed by concatenating the nested keys using a separator. + + :param d: The dictionary to flatten. + :type d: Dict[str, Any] + :param parent_key: The base key to use for concatenation. + :type parent_key: str + :param sep: The separator to use for concatenating keys. + :type sep: str + :return: A flattened dictionary. + :rtype: Dict[str, Any] + """ + items = {} + for k, v in d.items(): + new_key = f"{parent_key}{sep}{k}" if parent_key else k + if isinstance(v, dict): + items.update(flatten_dict(v, new_key, sep=sep)) + else: + items[new_key] = v + return items diff --git a/src/hirad/utils/train_helpers.py b/src/hirad/utils/train_helpers.py index dc1a5b9f..39a8127e 100644 --- a/src/hirad/utils/train_helpers.py +++ b/src/hirad/utils/train_helpers.py @@ -17,7 +17,10 @@ import torch import numpy as np import warnings +import mlflow +from hirad.distributed import DistributedManager +from hirad.utils.env_info import get_env_info, flatten_dict def set_patch_shape(img_shape, patch_shape): img_shape_y, img_shape_x = img_shape @@ -109,3 +112,39 @@ def is_time_for_periodic_task( return True else: return cur_nimg % freq < batch_size + + +def init_mlflow(cfg: dict, dist: DistributedManager) -> None: + if dist.rank==0: + if dist.world_size>4: + mlflow.set_experiment(experiment_name=cfg.logging.experiment_name) + mlflow.start_run(run_name=cfg.logging.run_name) #, log_system_metrics=True) + else: + mlflow.system_metrics.set_system_metrics_node_id("node-0") + # mlflow.set_system_metrics_sampling_interval(1) + mlflow.set_experiment(experiment_name=cfg.logging.experiment_name) + mlflow.start_run(run_name=cfg.logging.run_name, log_system_metrics=True) + run = mlflow.active_run() + with open("run_id.txt", 'w') as f: + f.write(run.info.run_id) + # log environment info + mlflow.log_params(flatten_dict(cfg)) + mlflow.log_dict(cfg, "config.json") + python_environment, git_diff = get_env_info(exclude_prefixes=['hirad', '__mp_main__']) + mlflow.log_dict(python_environment, "environment.json") + if git_diff: + mlflow.log_text(git_diff, "git_diff.txt") + + if dist.world_size > 4: + torch.distributed.barrier() + + if (dist.rank!=0 and dist._local_rank==0) or (dist.rank==1 and dist.world_size>4): + mlflow.system_metrics.set_system_metrics_node_id(f"node-{(dist.rank//4)}" + if dist.rank!=1 + else "node-0") + # mlflow.set_system_metrics_sampling_interval(1) + # mlflow.set_system_metrics_samples_before_logging(10) + mlflow.set_experiment(experiment_name=cfg.logging.experiment_name) + with open("run_id.txt", 'r') as f: + run_id = f.read() + mlflow.start_run(run_id=run_id, log_system_metrics=True) \ No newline at end of file From 0f3b0c6749a2fee0b5cca8e557af6da966d3eec0 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 25 Jul 2025 13:54:41 +0200 Subject: [PATCH 093/302] mlflow init bug fix --- src/hirad/training/train.py | 3 +-- src/hirad/utils/train_helpers.py | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index a60844c1..13c4865b 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -70,10 +70,9 @@ def main(cfg: DictConfig) -> None: dist = DistributedManager() OmegaConf.resolve(cfg) - cfg_dict = OmegaConf.to_object(cfg) if cfg.logging.method == "mlflow": - init_mlflow(cfg_dict, dist) + init_mlflow(cfg, dist) if dist.world_size > 1: torch.distributed.barrier() elif cfg.logging.method is not None: diff --git a/src/hirad/utils/train_helpers.py b/src/hirad/utils/train_helpers.py index 39a8127e..8adee63a 100644 --- a/src/hirad/utils/train_helpers.py +++ b/src/hirad/utils/train_helpers.py @@ -18,6 +18,7 @@ import numpy as np import warnings import mlflow +from omegaconf import DictConfig, OmegaConf from hirad.distributed import DistributedManager from hirad.utils.env_info import get_env_info, flatten_dict @@ -114,7 +115,7 @@ def is_time_for_periodic_task( return cur_nimg % freq < batch_size -def init_mlflow(cfg: dict, dist: DistributedManager) -> None: +def init_mlflow(cfg: DictConfig, dist: DistributedManager) -> None: if dist.rank==0: if dist.world_size>4: mlflow.set_experiment(experiment_name=cfg.logging.experiment_name) @@ -128,7 +129,7 @@ def init_mlflow(cfg: dict, dist: DistributedManager) -> None: with open("run_id.txt", 'w') as f: f.write(run.info.run_id) # log environment info - mlflow.log_params(flatten_dict(cfg)) + mlflow.log_params(flatten_dict(OmegaConf.to_object(cfg))) mlflow.log_dict(cfg, "config.json") python_environment, git_diff = get_env_info(exclude_prefixes=['hirad', '__mp_main__']) mlflow.log_dict(python_environment, "environment.json") From 3bc4caed92f4831a1c6b9131041f4628fee86e29 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 25 Jul 2025 15:10:11 +0200 Subject: [PATCH 094/302] add option to continue same run mlflow --- src/hirad/training/train.py | 2 +- src/hirad/utils/train_helpers.py | 40 +++++++++++++++++--------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 13c4865b..12131fc4 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -782,7 +782,7 @@ def main(cfg: DictConfig) -> None: torch.distributed.barrier() if is_time_for_periodic_task( cur_nimg, - cfg.training.io.save_checkpoint_freq, + cfg.training.io.visualization_freq, done, cfg.training.hp.total_batch_size, dist.rank, diff --git a/src/hirad/utils/train_helpers.py b/src/hirad/utils/train_helpers.py index 8adee63a..65b4b3c6 100644 --- a/src/hirad/utils/train_helpers.py +++ b/src/hirad/utils/train_helpers.py @@ -19,6 +19,7 @@ import warnings import mlflow from omegaconf import DictConfig, OmegaConf +import os from hirad.distributed import DistributedManager from hirad.utils.env_info import get_env_info, flatten_dict @@ -117,24 +118,27 @@ def is_time_for_periodic_task( def init_mlflow(cfg: DictConfig, dist: DistributedManager) -> None: if dist.rank==0: - if dist.world_size>4: - mlflow.set_experiment(experiment_name=cfg.logging.experiment_name) - mlflow.start_run(run_name=cfg.logging.run_name) #, log_system_metrics=True) - else: + run_id = None + if os.path.isfile('run_id.txt'): + with open('run_id.txt','r') as f: + run_id = f.read() + if dist.world_size<=4: mlflow.system_metrics.set_system_metrics_node_id("node-0") - # mlflow.set_system_metrics_sampling_interval(1) - mlflow.set_experiment(experiment_name=cfg.logging.experiment_name) - mlflow.start_run(run_name=cfg.logging.run_name, log_system_metrics=True) - run = mlflow.active_run() - with open("run_id.txt", 'w') as f: - f.write(run.info.run_id) - # log environment info - mlflow.log_params(flatten_dict(OmegaConf.to_object(cfg))) - mlflow.log_dict(cfg, "config.json") - python_environment, git_diff = get_env_info(exclude_prefixes=['hirad', '__mp_main__']) - mlflow.log_dict(python_environment, "environment.json") - if git_diff: - mlflow.log_text(git_diff, "git_diff.txt") + if run_id: + mlflow.start_run(run_id=run_id, log_system_metrics=False if dist.world_size>4 else True) + else: + mlflow.start_run(run_name=cfg.logging.run_name, log_system_metrics=False if dist.world_size>4 else True) + if run_id is None: + run = mlflow.active_run() + with open("run_id.txt", 'w') as f: + f.write(run.info.run_id) + # log environment info if run is not continuing from previous checkpoint + mlflow.log_params(flatten_dict(OmegaConf.to_object(cfg))) + mlflow.log_dict(cfg, "config.json") + python_environment, git_diff = get_env_info(exclude_prefixes=['hirad', '__mp_main__']) + mlflow.log_dict(python_environment, "environment.json") + if git_diff: + mlflow.log_text(git_diff, "git_diff.txt") if dist.world_size > 4: torch.distributed.barrier() @@ -143,8 +147,6 @@ def init_mlflow(cfg: DictConfig, dist: DistributedManager) -> None: mlflow.system_metrics.set_system_metrics_node_id(f"node-{(dist.rank//4)}" if dist.rank!=1 else "node-0") - # mlflow.set_system_metrics_sampling_interval(1) - # mlflow.set_system_metrics_samples_before_logging(10) mlflow.set_experiment(experiment_name=cfg.logging.experiment_name) with open("run_id.txt", 'r') as f: run_id = f.read() From 6ff06da774e8aa10ef31551118d19bd619e9009c Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 25 Jul 2025 15:11:05 +0200 Subject: [PATCH 095/302] add separate visualization frequency setting --- src/hirad/conf/training/era_cosmo_diffusion.yaml | 14 ++++++++------ src/hirad/conf/training/era_cosmo_regression.yaml | 14 ++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/hirad/conf/training/era_cosmo_diffusion.yaml b/src/hirad/conf/training/era_cosmo_diffusion.yaml index 5a14a6a9..40e37f34 100644 --- a/src/hirad/conf/training/era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training/era_cosmo_diffusion.yaml @@ -1,10 +1,10 @@ # Hyperparameters hp: - training_duration: 1024 + training_duration: 20000 # Training duration based on the number of processed samples - total_batch_size: 64 + total_batch_size: "auto" # Total batch size - batch_size_per_gpu: "auto" + batch_size_per_gpu: 22 # Batch size per GPU lr: 0.0002 # Learning rate @@ -31,11 +31,13 @@ perf: io: regression_checkpoint_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_era5_cosmo/checkpoints_regression # Where to load the regression checkpoint - print_progress_freq: 128 + print_progress_freq: 500 # How often to print progress - save_checkpoint_freq: 1024 + save_checkpoint_freq: 10000 # How often to save the checkpoints, measured in number of processed samples - validation_freq: 256 + visualization_freq: 200000 + # how often to visualize network outputs + validation_freq: 2000 # how often to record the validation loss, measured in number of processed samples validation_steps: 2 # how many loss evaluations are used to compute the validation loss per checkpoint diff --git a/src/hirad/conf/training/era_cosmo_regression.yaml b/src/hirad/conf/training/era_cosmo_regression.yaml index b45b206c..5cd5d0d6 100644 --- a/src/hirad/conf/training/era_cosmo_regression.yaml +++ b/src/hirad/conf/training/era_cosmo_regression.yaml @@ -1,10 +1,10 @@ # Hyperparameters hp: - training_duration: 1024 + training_duration: 40000 # Training duration based on the number of processed samples - total_batch_size: 64 + total_batch_size: "auto" # Total batch size -- based 8 per GPU -- 2 nodes is 2x8x4 -- see sbatch vars for how many gpus. diffusion need to point to the rgression. - batch_size_per_gpu: "auto" + batch_size_per_gpu: 22 # Batch size per GPU lr: 0.0002 # Learning rate @@ -31,11 +31,13 @@ perf: # I/O io: - print_progress_freq: 128 + print_progress_freq: 500 # How often to print progress - save_checkpoint_freq: 512 + save_checkpoint_freq: 100000 # How often to save the checkpoints, measured in number of processed samples - validation_freq: 256 + visualization_freq: 200000 + # how often to visualize network output + validation_freq: 2000 # how often to record the validation loss, measured in number of processed samples validation_steps: 2 # how many loss evaluations are used to compute the validation loss per checkpoint From 06c185f1a9b9f7189ba89f428eff807cfd817b69 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Fri, 25 Jul 2025 15:56:53 +0200 Subject: [PATCH 096/302] add plots of temperature and windspeed --- src/hirad/diurnal_cycle.sh | 1 + src/hirad/eval/diurnal_cycle_precip.py | 9 +- src/hirad/eval/diurnal_cycle_temp_wind.py | 142 ++++++++++++++++++++++ 3 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 src/hirad/eval/diurnal_cycle_temp_wind.py diff --git a/src/hirad/diurnal_cycle.sh b/src/hirad/diurnal_cycle.sh index 4a021c06..1a62fcf7 100644 --- a/src/hirad/diurnal_cycle.sh +++ b/src/hirad/diurnal_cycle.sh @@ -46,4 +46,5 @@ export OMP_NUM_THREADS=72 srun --environment=./ci/edf/modulus_env.toml bash -c " pip install -e . --no-dependencies python src/hirad/eval/diurnal_cycle_precip.py --config-name=generate_era_cosmo.yaml + python src/hirad/eval/diurnal_cycle_temp_wind.py --config-name=generate_era_cosmo.yaml " \ No newline at end of file diff --git a/src/hirad/eval/diurnal_cycle_precip.py b/src/hirad/eval/diurnal_cycle_precip.py index 231a5b33..77af57b0 100644 --- a/src/hirad/eval/diurnal_cycle_precip.py +++ b/src/hirad/eval/diurnal_cycle_precip.py @@ -16,7 +16,7 @@ # Constants CONV_FACTOR = 100 # Convert meters to mm/h WET_THRESHOLD = 0.1 # Threshold for wet-hour in mm/h -LOG_INTERVAL = 1 # Log progress every N timesteps +LOG_INTERVAL = 24 # Log progress every N timesteps def hour_of(dt: str, fmt: str = "%Y%m%d-%H%M") -> int: @@ -109,7 +109,7 @@ def main(cfg: DictConfig): for mode in ['target','baseline'] } wet_cycle = { - mode: [np.mean(wet_stats[mode][h]) for h in sorted(wet_stats[mode])] + mode: [np.mean(wet_stats[mode][h]) * 100. for h in sorted(wet_stats[mode])] for mode in ['target','baseline'] } logger.info("Computed hourly mean and wet-cycle statistics") @@ -117,6 +117,9 @@ def main(cfg: DictConfig): # Ensemble cycles (mean ± std) hrs, pred_mean, pred_std = compute_ensemble(stats['prediction']) _, wet_mean, wet_std = compute_ensemble(wet_stats['prediction']) + # Multiply ensemble wet-hour statistics by 100 for percentage + wet_mean = [v * 100. for v in wet_mean] + wet_std = [v * 100. for v in wet_std] logger.info("Computed ensemble statistics") # Prepare cyclic series @@ -137,7 +140,7 @@ def main(cfg: DictConfig): plot_paths.append(fn1) fn2 = out_root/'diurnal_cycle_precip_wethours.png' - save_plot(hrs_c, wet_lines * 100., ['COSMO-2','ERA5','Pred Mean ± Std'], 'Wet-Hour Fraction [%]', + save_plot(hrs_c, wet_lines, ['COSMO-2','ERA5','Pred Mean ± Std'], 'Wet-Hour Fraction [%]', 'Diurnal Cycle of Wet-Hours (>0.1 mm/h)', fn2) plot_paths.append(fn2) diff --git a/src/hirad/eval/diurnal_cycle_temp_wind.py b/src/hirad/eval/diurnal_cycle_temp_wind.py new file mode 100644 index 00000000..0f769ad6 --- /dev/null +++ b/src/hirad/eval/diurnal_cycle_temp_wind.py @@ -0,0 +1,142 @@ +import logging +from datetime import datetime +from pathlib import Path +from collections import defaultdict + +import hydra +import numpy as np +torch = __import__('torch') +from omegaconf import DictConfig, OmegaConf +import matplotlib.pyplot as plt + +from hirad.datasets import get_dataset_and_sampler_inference +from hirad.distributed import DistributedManager +from hirad.utils.function_utils import get_time_from_range + +LOG_INTERVAL = 24 + +def hour_of(dt: str, fmt: str = "%Y%m%d-%H%M") -> int: + return datetime.strptime(dt, fmt).hour + +def compute_ensemble(hourly_values): + hours = sorted(hourly_values) + means = [np.mean(hourly_values[h]) for h in hours] + stds = [np.std(hourly_values[h]) for h in hours] + return hours, means, stds + +def save_plot(hours, lines, labels, ylabel, title, out_path): + Path(out_path).parent.mkdir(parents=True, exist_ok=True) + plt.figure(figsize=(8,4)) + for data, label in zip(lines, labels): + if isinstance(data, tuple): # (mean, std) + mean, std = data + plt.plot(hours, mean, label=label) + plt.fill_between(hours, + np.array(mean)-std, + np.array(mean)+std, + alpha=0.3) + else: + plt.plot(hours, data, label=label) + plt.xlabel('Hour (UTC)') + plt.xticks(range(0,25,3)) + plt.xlim(0,24) + plt.ylabel(ylabel) + plt.title(title) + plt.grid(True) + plt.legend() + plt.tight_layout() + plt.savefig(out_path) + plt.close() + +@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") +def main(cfg: DictConfig): + DistributedManager.initialize() + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + + logger.info("Starting diurnal cycle computation for 2m temperature and windspeed") + times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + logger.info(f"Loaded {len(times)} timesteps to process") + + ds_cfg = OmegaConf.to_container(cfg.dataset) + dataset, _ = get_dataset_and_sampler_inference( + ds_cfg, times, cfg.generation.get('has_lead_time', False) + ) + logger.info("Dataset and sampler initialized") + + out_root = Path(cfg.generation.io.output_path or './outputs') + load = lambda ts, fn: torch.load(out_root/ts/fn, weights_only=False) + + # Find channel indices + out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} + in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} + t2m_out = out_ch.get('2t', out_ch.get('t2m')) + t2m_in = in_ch.get('2t', in_ch.get('t2m', t2m_out)) + u_out = out_ch.get('10u') + v_out = out_ch.get('10v') + u_in = in_ch.get('10u', u_out) + v_in = in_ch.get('10v', v_out) + logger.info(f"2T channel indices - output: {t2m_out}, input: {t2m_in}") + logger.info(f"10U/10V channel indices - output: {u_out}/{v_out}, input: {u_in}/{v_in}") + + stats_temp = {mode: defaultdict(list) for mode in ['target','baseline','prediction']} + stats_wind = {mode: defaultdict(list) for mode in ['target','baseline','prediction']} + + for idx, ts in enumerate(times, 1): + hr = hour_of(ts) + target = load(ts, f"{ts}-target") + baseline = load(ts, f"{ts}-baseline") + # 2m temperature + stats_temp['target'][hr].append(target[t2m_out].mean()) + stats_temp['baseline'][hr].append(baseline[t2m_in].mean()) + # windspeed using np.hypot + stats_wind['target'][hr].append(np.hypot(target[u_out], target[v_out]).mean()) + stats_wind['baseline'][hr].append(np.hypot(baseline[u_in], baseline[v_in]).mean()) + + preds = load(ts, f"{ts}-predictions") + for member in preds: + stats_temp['prediction'][hr].append(member[t2m_out].mean()) + stats_wind['prediction'][hr].append(np.hypot(member[u_out], member[v_out]).mean()) + + if idx % LOG_INTERVAL == 0 or idx == len(times): + logger.info(f"Processed {idx}/{len(times)} timesteps ({ts})") + + # Compute hourly means + mean_cycle_temp = { + mode: [np.mean(stats_temp[mode][h]) for h in sorted(stats_temp[mode])] + for mode in ['target','baseline'] + } + mean_cycle_wind = { + mode: [np.mean(stats_wind[mode][h]) for h in sorted(stats_wind[mode])] + for mode in ['target','baseline'] + } + logger.info("Computed hourly mean statistics") + + # Ensemble cycles (mean ± std) + hrs, pred_mean_temp, pred_std_temp = compute_ensemble(stats_temp['prediction']) + hrs_w, pred_mean_wind, pred_std_wind = compute_ensemble(stats_wind['prediction']) + logger.info("Computed ensemble statistics") + + # Prepare cyclic series + cycle = lambda x: x + [x[0]] + hrs_c = hrs + [24] + hrs_w_c = hrs_w + [24] + temp_lines = [cycle(mean_cycle_temp['target']), cycle(mean_cycle_temp['baseline']), (cycle(pred_mean_temp), cycle(pred_std_temp))] + wind_lines = [cycle(mean_cycle_wind['target']), cycle(mean_cycle_wind['baseline']), (cycle(pred_mean_wind), cycle(pred_std_wind))] + + # Plot + plot_paths = [] + fn1 = out_root/'diurnal_cycle_2t.png' + save_plot(hrs_c, temp_lines, ['COSMO-2','ERA5','CorrDiff ± Std(Members)'], '2m Temperature [K]', + 'Diurnal Cycle of 2m Temperature', fn1) + plot_paths.append(fn1) + + fn2 = out_root/'diurnal_cycle_windspeed.png' + save_plot(hrs_w_c, wind_lines, ['COSMO-2','ERA5','CorrDiff ± Std(Members)'], 'Windspeed [m/s]', + 'Diurnal Cycle of Windspeed', fn2) + plot_paths.append(fn2) + + logger.info(f"Plots saved: {', '.join(str(p) for p in plot_paths)}") + +if __name__ == '__main__': + main() From 02ab40ab8f32ce8f1a5925ae5ca9187fb4b5f742 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Fri, 25 Jul 2025 17:34:39 +0200 Subject: [PATCH 097/302] Add script to plot the 99th all-hour percentile --- src/hirad/diurnal_cycle.sh | 1 + src/hirad/eval/diurnal_cycle_precip.py | 11 +- src/hirad/eval/diurnal_cycle_temp_wind.py | 7 +- src/hirad/eval/percentile99_cycle_precip.py | 153 ++++++++++++++++++++ 4 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 src/hirad/eval/percentile99_cycle_precip.py diff --git a/src/hirad/diurnal_cycle.sh b/src/hirad/diurnal_cycle.sh index 1a62fcf7..66437574 100644 --- a/src/hirad/diurnal_cycle.sh +++ b/src/hirad/diurnal_cycle.sh @@ -46,5 +46,6 @@ export OMP_NUM_THREADS=72 srun --environment=./ci/edf/modulus_env.toml bash -c " pip install -e . --no-dependencies python src/hirad/eval/diurnal_cycle_precip.py --config-name=generate_era_cosmo.yaml + python src/hirad/eval/percentile99_cycle_precip.py --config-name=generate_era_cosmo.yaml python src/hirad/eval/diurnal_cycle_temp_wind.py --config-name=generate_era_cosmo.yaml " \ No newline at end of file diff --git a/src/hirad/eval/diurnal_cycle_precip.py b/src/hirad/eval/diurnal_cycle_precip.py index 77af57b0..8a38078f 100644 --- a/src/hirad/eval/diurnal_cycle_precip.py +++ b/src/hirad/eval/diurnal_cycle_precip.py @@ -5,7 +5,7 @@ import hydra import numpy as np -torch = __import__('torch') +import torch from omegaconf import DictConfig, OmegaConf import matplotlib.pyplot as plt @@ -34,13 +34,10 @@ def save_plot(hours, lines, labels, ylabel, title, out_path): Path(out_path).parent.mkdir(parents=True, exist_ok=True) plt.figure(figsize=(8,4)) for data, label in zip(lines, labels): - if isinstance(data, tuple): # (mean, std) + if isinstance(data, tuple): mean, std = data - plt.plot(hours, mean, label=label) - plt.fill_between(hours, - np.array(mean)-std, - np.array(mean)+std, - alpha=0.3) + line, = plt.plot(hours, mean, label=label) + plt.fill_between(hours, np.maximum(np.array(mean)-std, 0), np.array(mean)+std, alpha=0.3, color=line.get_color()) else: plt.plot(hours, data, label=label) plt.xlabel('Hour (UTC)') diff --git a/src/hirad/eval/diurnal_cycle_temp_wind.py b/src/hirad/eval/diurnal_cycle_temp_wind.py index 0f769ad6..ed35042c 100644 --- a/src/hirad/eval/diurnal_cycle_temp_wind.py +++ b/src/hirad/eval/diurnal_cycle_temp_wind.py @@ -5,7 +5,7 @@ import hydra import numpy as np -torch = __import__('torch') +import torch from omegaconf import DictConfig, OmegaConf import matplotlib.pyplot as plt @@ -30,11 +30,12 @@ def save_plot(hours, lines, labels, ylabel, title, out_path): for data, label in zip(lines, labels): if isinstance(data, tuple): # (mean, std) mean, std = data - plt.plot(hours, mean, label=label) + line, = plt.plot(hours, mean, label=label) plt.fill_between(hours, np.array(mean)-std, np.array(mean)+std, - alpha=0.3) + alpha=0.3, + color=line.get_color()) else: plt.plot(hours, data, label=label) plt.xlabel('Hour (UTC)') diff --git a/src/hirad/eval/percentile99_cycle_precip.py b/src/hirad/eval/percentile99_cycle_precip.py new file mode 100644 index 00000000..f657b21f --- /dev/null +++ b/src/hirad/eval/percentile99_cycle_precip.py @@ -0,0 +1,153 @@ +""" +Plots the diurnal cycle of the all-hour 99th percentile of +precipitation, a somewhat reliable measure of the precipitation intensity. + +Each hour, member and type is treaded separately, to conserve memory... but if the +period is long, this can still be a lot of data and thus an OOM error can occur. +""" +import logging +from datetime import datetime +from pathlib import Path + +import hydra +import matplotlib.pyplot as plt +import numpy as np +import torch +from omegaconf import DictConfig, OmegaConf + +from hirad.datasets import get_dataset_and_sampler_inference +from hirad.distributed import DistributedManager +from hirad.utils.function_utils import get_time_from_range + +# Constants +CONV_FACTOR = 100 # Convert meters to mm/h +LOG_INTERVAL = 24 # Log progress every N timesteps + + +def hour_of(dt: str, fmt: str = "%Y%m%d-%H%M") -> int: + return datetime.strptime(dt, fmt).hour + + +def save_plot(hours, lines, labels, ylabel, title, out_path): + Path(out_path).parent.mkdir(parents=True, exist_ok=True) + plt.figure(figsize=(8,4)) + for data, label in zip(lines, labels): + if isinstance(data, tuple): # (mean, std) + mean, std = data + lower = np.maximum(np.array(mean) - std, 0) + upper = np.array(mean) + std + line, = plt.plot(hours, mean, label=label) + plt.fill_between(hours, lower, upper, alpha=0.3, color=line.get_color()) + else: + plt.plot(hours, data, label=label) + plt.xlabel('Hour (UTC)') + plt.xticks(range(0,25,3)) + plt.xlim(0,24) + plt.ylabel(ylabel) + plt.title(title) + plt.grid(True) + plt.legend() + plt.tight_layout() + plt.savefig(out_path) + plt.close() + + +@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") +def main(cfg: DictConfig): + # Setup logging + DistributedManager.initialize() + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + + logger.info("Starting 99th-percentile diurnal cycle computation") + times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + logger.info(f"Loaded {len(times)} timesteps to process") + + # Initialize dataset + ds_cfg = OmegaConf.to_container(cfg.dataset) + dataset, _ = get_dataset_and_sampler_inference( + ds_cfg, times, cfg.generation.get('has_lead_time', False) + ) + logger.info("Dataset and sampler initialized") + + # Output root and loader + out_root = Path(cfg.generation.io.output_path or './outputs') + load = lambda ts, fn: torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR + + # Find channel indices + out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} + in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} + tp_out = out_ch['tp']; tp_in = in_ch.get('tp', tp_out) + logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") + + # Storage for diurnal cycles + pct99_mean = {'target': [], 'baseline': [], 'prediction': []} + pct99_std = {'target': [], 'baseline': [], 'prediction': []} + + # -- Target and Baseline: compute per hour -- + for mode in ['target', 'baseline']: + logger.info(f"Processing mode: {mode}") + for h in list(range(24)): + arrs = [ + load(ts, f"{ts}-{mode}")[tp_out if mode == 'target' else tp_in] + for ts in times if hour_of(ts) == h + ] + stack = np.stack(arrs, axis=0) + f99 = np.percentile(stack, 99, axis=0) + pct99_mean[mode].append(f99.mean()) + pct99_std[mode].append(np.std(f99, axis=None)) + del arrs, stack, f99 + + # -- Predictions: compute per hour per member, then mean+std across members -- + # Determine number of ensemble members + sample = load(times[0], f"{times[0]}-predictions") # [n_members, n_channels, lat, lon] + data_sample = sample[:, tp_out] + n_members = data_sample.shape[0] + + for h in list(range(24)): + logger.info(f"Processing predictions for hour {h}") + mem_f99 = [] + # for each ensemble member, gather its hourly fields + for m in range(n_members): + arrs = [] + for ts in times: + if hour_of(ts) != h: + continue + preds = load(ts, f"{ts}-predictions") # [n_members, n_channels, ...] + arrs.append(preds[m, tp_out]) # one field + # stack over time and compute 99th percentile at each grid point + stack_m = np.stack(arrs, axis=0) + f99_m = np.percentile(stack_m, 99, axis=0) + mem_f99.append(f99_m.mean()) + # ensemble-level mean and std over member-wise percentiles + pct99_mean['prediction'].append(np.mean(mem_f99)) + pct99_std['prediction'].append(np.std(mem_f99, axis=None)) + # clean up per-hour buffers + del mem_f99, stack_m, f99_m + + # Prepare cyclic series + cycle_fn = lambda x: x + [x[0]] + hrs_c = list(range(24)) + [list(range(24))[0] + 24] + pct99_lines = [ + cycle_fn(pct99_mean['target']), + cycle_fn(pct99_mean['baseline']), + ( + cycle_fn(pct99_mean['prediction']), + cycle_fn(pct99_std['prediction']) + ) + ] + + # Plot combined diurnal 99th-percentile cycle + fn = out_root/'diurnal_cycle_precip_99th_percentile.png' + save_plot( + hrs_c, + pct99_lines, + ['COSMO-2','ERA5','CorrDiff 99th Pct ± Std'], + 'Rain Rate (mm/h)', + 'Diurnal Cycle of 99th-Percentile Precipitation', + fn + ) + logger.info(f"Combined plot saved: {fn}") + +if __name__ == '__main__': + main() From c8a3f778d7a69b6a79da837a084c5bf70a6a5c5a Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 25 Jul 2025 17:39:37 +0200 Subject: [PATCH 098/302] add visualization logging option for mlflow --- src/hirad/conf/logging/era_cosmo_diffusion.yaml | 3 ++- src/hirad/conf/logging/era_cosmo_regression.yaml | 3 ++- src/hirad/training/train.py | 9 ++++++++- src/hirad/utils/train_helpers.py | 11 ++++++----- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/hirad/conf/logging/era_cosmo_diffusion.yaml b/src/hirad/conf/logging/era_cosmo_diffusion.yaml index df7f4812..c57560c3 100644 --- a/src/hirad/conf/logging/era_cosmo_diffusion.yaml +++ b/src/hirad/conf/logging/era_cosmo_diffusion.yaml @@ -2,4 +2,5 @@ method: null experiment_name: hirad-corrdiff-diffusion run_name: era-cosmo-1h -uri: null \ No newline at end of file +uri: null +log_images: false \ No newline at end of file diff --git a/src/hirad/conf/logging/era_cosmo_regression.yaml b/src/hirad/conf/logging/era_cosmo_regression.yaml index ba4095b2..ee426e50 100644 --- a/src/hirad/conf/logging/era_cosmo_regression.yaml +++ b/src/hirad/conf/logging/era_cosmo_regression.yaml @@ -2,4 +2,5 @@ method: null experiment_name: hirad-corrdiff-regression run_name: era-cosmo-1h -uri: null \ No newline at end of file +uri: null +log_images: false \ No newline at end of file diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 12131fc4..a6aff470 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -796,6 +796,7 @@ def main(cfg: DictConfig) -> None: times = visualization_dataset.time() time_index = -1 + output_paths_list = [] for index, (img_clean_viz, img_lr_viz, *lead_time_label_viz) in enumerate( iter(visualization_data_loader) ): @@ -837,6 +838,7 @@ def main(cfg: DictConfig) -> None: if dist.rank == 0: # write out data in a seperate thread so we don't hold up inferencing output_path = os.path.join(visualization_dir, f"{cur_nimg}_{times[visualization_sampler[time_index]]}") + output_paths_list.append(output_path) if dist.rank==0 and not os.path.exists(output_path): os.makedirs(output_path) writer_threads.append( @@ -857,7 +859,12 @@ def main(cfg: DictConfig) -> None: thread.result() writer_threads.remove(thread) writer_executor.shutdown() - + if cfg.logging.method == "mlflow" and cfg.logging.log_images: + for output_path in output_paths_list: + mlflow.log_artifacts(output_path, + os.path.join( + 'visualization', + os.path.split(output_path)[-1])) if dist.world_size > 1: diff --git a/src/hirad/utils/train_helpers.py b/src/hirad/utils/train_helpers.py index 65b4b3c6..9b53fc32 100644 --- a/src/hirad/utils/train_helpers.py +++ b/src/hirad/utils/train_helpers.py @@ -118,6 +118,7 @@ def is_time_for_periodic_task( def init_mlflow(cfg: DictConfig, dist: DistributedManager) -> None: if dist.rank==0: + mlflow.set_experiment(experiment_name=cfg.logging.experiment_name) run_id = None if os.path.isfile('run_id.txt'): with open('run_id.txt','r') as f: @@ -134,11 +135,11 @@ def init_mlflow(cfg: DictConfig, dist: DistributedManager) -> None: f.write(run.info.run_id) # log environment info if run is not continuing from previous checkpoint mlflow.log_params(flatten_dict(OmegaConf.to_object(cfg))) - mlflow.log_dict(cfg, "config.json") - python_environment, git_diff = get_env_info(exclude_prefixes=['hirad', '__mp_main__']) - mlflow.log_dict(python_environment, "environment.json") - if git_diff: - mlflow.log_text(git_diff, "git_diff.txt") + python_environment, git_diff = get_env_info(exclude_prefixes=['hirad', '__mp_main__']) + mlflow.log_dict(python_environment, "environment.json") + if git_diff: + mlflow.log_text(git_diff, "git_diff.txt") + mlflow.log_dict(cfg, "config.json") if dist.world_size > 4: torch.distributed.barrier() From 3d1347aca1f177bd467e9f62ba7999597367b697 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 28 Jul 2025 12:41:21 +0200 Subject: [PATCH 099/302] enable mlflow logging to remote server --- .gitignore | 2 ++ src/hirad/conf/logging/era_cosmo_diffusion.yaml | 4 +++- src/hirad/conf/logging/era_cosmo_regression.yaml | 4 +++- src/hirad/utils/train_helpers.py | 6 ++++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 17c4ea5d..74173161 100644 --- a/.gitignore +++ b/.gitignore @@ -195,3 +195,5 @@ temp.zarr.sync* src/hirad/eval/__pycache__/* interpolate_basic.log interpolated.torch +mlruns/ +.secrets.env diff --git a/src/hirad/conf/logging/era_cosmo_diffusion.yaml b/src/hirad/conf/logging/era_cosmo_diffusion.yaml index c57560c3..86ec7fe4 100644 --- a/src/hirad/conf/logging/era_cosmo_diffusion.yaml +++ b/src/hirad/conf/logging/era_cosmo_diffusion.yaml @@ -1,6 +1,8 @@ # set method to mlflow to log with mlflow -method: null +method: mlflow experiment_name: hirad-corrdiff-diffusion run_name: era-cosmo-1h +# change uri to remote mlflow server; if null, it is stored locally +# if uri is remote make sure to have credentials set in ~/.mlflow/credentials uri: null log_images: false \ No newline at end of file diff --git a/src/hirad/conf/logging/era_cosmo_regression.yaml b/src/hirad/conf/logging/era_cosmo_regression.yaml index ee426e50..e7a62873 100644 --- a/src/hirad/conf/logging/era_cosmo_regression.yaml +++ b/src/hirad/conf/logging/era_cosmo_regression.yaml @@ -1,6 +1,8 @@ # set method to mlflow to log with mlflow -method: null +method: mlflow experiment_name: hirad-corrdiff-regression run_name: era-cosmo-1h +# change uri to remote mlflow server; if null, it is stored locally +# if uri is remote make sure to have credentials set in ~/.mlflow/credentials uri: null log_images: false \ No newline at end of file diff --git a/src/hirad/utils/train_helpers.py b/src/hirad/utils/train_helpers.py index 9b53fc32..f379aa4b 100644 --- a/src/hirad/utils/train_helpers.py +++ b/src/hirad/utils/train_helpers.py @@ -118,6 +118,9 @@ def is_time_for_periodic_task( def init_mlflow(cfg: DictConfig, dist: DistributedManager) -> None: if dist.rank==0: + print("Started activating initial mlflow run") + if cfg.logging.uri is not None: + mlflow.set_tracking_uri(cfg.logging.uri) mlflow.set_experiment(experiment_name=cfg.logging.experiment_name) run_id = None if os.path.isfile('run_id.txt'): @@ -145,6 +148,9 @@ def init_mlflow(cfg: DictConfig, dist: DistributedManager) -> None: torch.distributed.barrier() if (dist.rank!=0 and dist._local_rank==0) or (dist.rank==1 and dist.world_size>4): + print("Started actvating sub mlflow run.") + if cfg.logging.uri is not None: + mlflow.set_tracking_uri(cfg.logging.uri) mlflow.system_metrics.set_system_metrics_node_id(f"node-{(dist.rank//4)}" if dist.rank!=1 else "node-0") From 678fbe97924651033ec46c5afd5ace35b025554a Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 28 Jul 2025 15:08:15 +0200 Subject: [PATCH 100/302] fix logging bug for average loss --- src/hirad/training/train.py | 98 +++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 54 deletions(-) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index a6aff470..05739296 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -577,32 +577,11 @@ def main(cfg: DictConfig) -> None: ) average_loss = (loss_sum / dist.world_size).cpu().item() - # update running mean of average loss since last periodic task - average_loss_running_mean += ( - average_loss - average_loss_running_mean - ) / n_average_loss_running_mean - n_average_loss_running_mean += 1 - - if dist.rank == 0 and cfg.logging.method == "mlflow": - mlflow.log_metric("training_loss", average_loss, cur_nimg) - mlflow.log_metric( - "training_loss_running_mean", - average_loss_running_mean, - cur_nimg, - ) - - ptt = is_time_for_periodic_task( - cur_nimg, - cfg.training.io.print_progress_freq, - done, - cfg.training.hp.total_batch_size, - dist.rank, - rank_0_only=True, - ) - if ptt: - # reset running mean of average loss - average_loss_running_mean = 0 - n_average_loss_running_mean = 1 + # update running mean of average loss since last periodic task + average_loss_running_mean += ( + average_loss - average_loss_running_mean + ) / n_average_loss_running_mean + n_average_loss_running_mean += 1 # Update weights. with nvtx.annotate("update weights", color="blue"): @@ -625,38 +604,49 @@ def main(cfg: DictConfig) -> None: cur_nimg += cfg.training.hp.total_batch_size done = cur_nimg >= cfg.training.hp.training_duration - if is_time_for_periodic_task( - cur_nimg, - cfg.training.io.print_progress_freq, - done, - cfg.training.hp.total_batch_size, - dist.rank, - rank_0_only=True, - ): - # Print stats if we crossed the printing threshold with this batch - tick_end_time = time.time() - fields = [] - fields += [f"samples {cur_nimg:<9.1f}"] - fields += [f"training_loss {average_loss:<7.2f}"] - fields += [f"training_loss_running_mean {average_loss_running_mean:<7.2f}"] - fields += [f"learning_rate {current_lr:<7.8f}"] - fields += [f"total_sec {(tick_end_time - start_time):<7.1f}"] - fields += [f"sec_per_tick {(tick_end_time - tick_start_time):<7.1f}"] - fields += [ - f"sec_per_sample {((tick_end_time - tick_start_time) / (cur_nimg - tick_start_nimg)):<7.2f}" - ] - fields += [ - f"cpu_mem_gb {(psutil.Process(os.getpid()).memory_info().rss / 2**30):<6.2f}" - ] - if torch.cuda.is_available(): + if is_time_for_periodic_task( + cur_nimg, + cfg.training.io.print_progress_freq, + done, + cfg.training.hp.total_batch_size, + dist.rank, + rank_0_only=True, + ): + # Print stats if we crossed the printing threshold with this batch + tick_end_time = time.time() + fields = [] + fields += [f"samples {cur_nimg:<9.1f}"] + fields += [f"training_loss {average_loss:<7.2f}"] + fields += [f"training_loss_running_mean {average_loss_running_mean:<7.2f}"] + fields += [f"learning_rate {current_lr:<7.8f}"] + fields += [f"total_sec {(tick_end_time - start_time):<7.1f}"] + fields += [f"sec_per_tick {(tick_end_time - tick_start_time):<7.1f}"] fields += [ - f"peak_gpu_mem_gb {(torch.cuda.max_memory_allocated(dist.device) / 2**30):<6.2f}" + f"sec_per_sample {((tick_end_time - tick_start_time) / (cur_nimg - tick_start_nimg)):<7.2f}" ] fields += [ - f"peak_gpu_mem_reserved_gb {(torch.cuda.max_memory_reserved(dist.device) / 2**30):<6.2f}" + f"cpu_mem_gb {(psutil.Process(os.getpid()).memory_info().rss / 2**30):<6.2f}" ] - torch.cuda.reset_peak_memory_stats() - logger0.info(" ".join(fields)) + if torch.cuda.is_available(): + fields += [ + f"peak_gpu_mem_gb {(torch.cuda.max_memory_allocated(dist.device) / 2**30):<6.2f}" + ] + fields += [ + f"peak_gpu_mem_reserved_gb {(torch.cuda.max_memory_reserved(dist.device) / 2**30):<6.2f}" + ] + torch.cuda.reset_peak_memory_stats() + logger0.info(" ".join(fields)) + + if cfg.logging.method == "mlflow": + mlflow.log_metric("training_loss", average_loss, cur_nimg) + mlflow.log_metric( + "training_loss_running_mean", + average_loss_running_mean, + cur_nimg, + ) + # reset running mean of average loss + average_loss_running_mean = 0 + n_average_loss_running_mean = 1 with nvtx.annotate("validation", color="red"): # Validation From e7d6c6bc900bad33ca53df02b7e42db659864932 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 28 Jul 2025 16:17:31 +0200 Subject: [PATCH 101/302] update readme --- README.md | 108 ++++++++++++++++++++++++------------------------------ 1 file changed, 47 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index b0dbd2eb..3e6e5c5a 100644 --- a/README.md +++ b/README.md @@ -2,37 +2,20 @@ HiRAD-Gen is short for high-resolution atmospheric downscaling using generative models. This repository contains the code and configuration required to train and use the model. -## Installation (Alps) +[Setup clariden/santis](#setup-claridensantis) +[Regression training - clariden/santis](#run-regression-model-training-alps) +[Diffusion training - clariden/santis](#run-diffusion-model-training-alps) +[Inference - clariden/santis](#running-inference-on-alps) +[Installation - uenv/venv - deprecated](#installation-alps-uenvvenv---deprecated) -To set up the environment for **HiRAD-Gen** on Alps supercomputer, follow these steps: - -1. **Start the PyTorch user environment**: - ```bash - uenv start pytorch/v2.6.0:v1 --view=default - ``` - -2. **Create a Python virtual environment** (replace `{env_name}` with your desired environment name): - ```bash - python -m venv ./{env_name} - ``` - -3. **Activate the virtual environment**: - ```bash - source ./{env_name}/bin/activate - ``` - -4. **Install project dependencies**: - ```bash - pip install -e . - ``` - -This will set up the necessary environment to run HiRAD-Gen within the Alps infrastructure. +## Setup clariden/santis +Container environment setup needed to run training and inference experiments on clariden/santis is contained in this repository under `ci/edf/modulus_env.toml`. Image squash is on clariden/alps under `/capstor/scratch/cscs/pstamenk/hirad.sqsh`. All the jobs can be run using this environment without additional installations and setup. ## Training ### Run regression model training (Alps) -1. Script for running the training of regression model is in `src/hirad/train_regression.sh`. +1. Script for running the training of regression model is in `src/hirad/train_regression.sh`. Here, you can change the sbatch settings. Inside this script set the following: ```bash ### OUTPUT ### @@ -42,12 +25,6 @@ Inside this script set the following: ```bash #SBATCH -A your_compute_group ``` -```bash -srun bash -c " - . ./{your_env_name}/bin/activate - python src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml -" -``` 2. Set up the following config files in `src/hirad/conf`: @@ -55,14 +32,9 @@ srun bash -c " ``` hydra: run: - dir: your_path_to_save_training_output -``` -- In `training/era_cosmo_regression.yaml` set: -``` -hp: - training_duration: number of samples to train for (set to 4 for debugging, 512 fits into 30 minutes on 1 gpu with total_batch_size: 4) + dir: your_path_to_save_training_outputs ``` -- In `dataset/era_cosmo.yaml` set the `dataset_path` if different from default. +- All other parameters for training regression can be changed in the main config file `training_era_cosmo_regression.yaml` and config files the main config is referencing (default values are working for debugging purposes). 3. Submit the job with: ```bash @@ -72,8 +44,7 @@ sbatch src/hirad/train_regression.sh ### Run diffusion model training (Alps) Before training diffusion model, checkpoint for regression model has to exist. -1. Script for running the training of diffusion model is in `src/hirad/train_diffusion.sh`. -Inside this script set the following: +1. Script for running the training of diffusion model is in `src/hirad/train_diffusion.sh`. Here, you can change the sbatch settings. Inside this script set the following: ```bash ### OUTPUT ### #SBATCH --output=your_path_to_output_log @@ -82,12 +53,6 @@ Inside this script set the following: ```bash #SBATCH -A your_compute_group ``` -```bash -srun bash -c " - . ./{your_env_name}/bin/activate - python src/hirad/training/train.py --config-name=training_era_cosmo_diffusion.yaml -" -``` 2. Set up the following config files in `src/hirad/conf`: @@ -97,14 +62,12 @@ hydra: run: dir: your_path_to_save_training_output ``` -- In `training/era_cosmo_regression.yaml` set: +- In `training/era_cosmo_diffusion.yaml` set: ``` -hp: - training_duration: number of samples to train for (set to 4 for debugging, 512 fits into 30 minutes on 1 gpu with total_batch_size: 4) io: regression_checkpoint_path: path_to_directory_containing_regression_training_model_checkpoints ``` -- In `dataset/era_cosmo.yaml` set the `dataset_path` if different from default. +- All other parameters for training regression can be changed in the main config file `training_era_cosmo_diffusion.yaml` and config files the main config is referencing (default values are working for debugging purposes). 3. Submit the job with: ```bash @@ -125,12 +88,6 @@ Inside this script set the following: ```bash #SBATCH -A your_compute_group ``` -```bash -srun bash -c " - . ./{your_env_name}/bin/activate - python src/hirad/inference/generate.py --config-name=generate_era_cosmo.yaml -" -``` 2. Set up the following config files in `src/hirad/conf`: @@ -155,13 +112,42 @@ Finally, from the dataset, subset of time steps can be chosen to do inference fo One way is to list steps under `times:` in format `%Y%m%d-%H%M` for era5_cosmo dataset. -The other way is to specify `times_range:` with three items: first time step (`%Y%m%d-%H%M`), last time step (`%Y%m%d-%H%M`), hour shift (int). Hour shift specifies distance in hours between closest time steps for specific dataset (6 for era_cosmo). +The other way is to specify `times_range:` with three items: first time step (`%Y%m%d-%H%M`), last time step (`%Y%m%d-%H%M`), hour shift (int). Hour shift specifies distance in hours between closest time steps for specific dataset. -By default, inference is done for one time step `20160101-0000` - -- In `dataset/era_cosmo.yaml` set the `dataset_path` if different from default. +- In `dataset/era_cosmo_inference.yaml` set the `dataset_path` if different from default. Make sure that specified times or times_range is contained in dataset_path. 3. Submit the job with: ```bash sbatch src/hirad/generate.sh -``` \ No newline at end of file +``` + +## MLflow logging + +During training MLflow can be used to log metrics. +Logging config files for regression and diffusion are located in `src/hirad/conf/logging/`. Set `method` to `mlflow` and specify `uri` if you want to log on remote server, otherwise run will be logged locally in output directory. Other options can also be modified here. + +## Installation (Alps uenv/venv) - deprecated + +To set up the environment for **HiRAD-Gen** on Alps supercomputer, follow these steps: + +1. **Start the PyTorch user environment**: + ```bash + uenv start pytorch/v2.6.0:v1 --view=default + ``` + +2. **Create a Python virtual environment** (replace `{env_name}` with your desired environment name): + ```bash + python -m venv ./{env_name} + ``` + +3. **Activate the virtual environment**: + ```bash + source ./{env_name}/bin/activate + ``` + +4. **Install project dependencies**: + ```bash + pip install -e . + ``` + +This will set up the necessary environment to run HiRAD-Gen within the Alps infrastructure. \ No newline at end of file From 6a63bcd5d85ceb07cbbf30cbd3e0dc9cef83238e Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 28 Jul 2025 16:18:46 +0200 Subject: [PATCH 102/302] fix indents in readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3e6e5c5a..0244ba8d 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ HiRAD-Gen is short for high-resolution atmospheric downscaling using generative models. This repository contains the code and configuration required to train and use the model. -[Setup clariden/santis](#setup-claridensantis) -[Regression training - clariden/santis](#run-regression-model-training-alps) -[Diffusion training - clariden/santis](#run-diffusion-model-training-alps) -[Inference - clariden/santis](#running-inference-on-alps) +[Setup clariden/santis](#setup-claridensantis) +[Regression training - clariden/santis](#run-regression-model-training-alps) +[Diffusion training - clariden/santis](#run-diffusion-model-training-alps) +[Inference - clariden/santis](#running-inference-on-alps) [Installation - uenv/venv - deprecated](#installation-alps-uenvvenv---deprecated) ## Setup clariden/santis From f9b8c87780b4ffc721abe98deec6af90ba14f5ed Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 28 Jul 2025 16:19:50 +0200 Subject: [PATCH 103/302] simplify cscs.yml to try to get ci/cd to run --- ci/cscs.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ci/cscs.yml b/ci/cscs.yml index 1668b814..3e50b378 100644 --- a/ci/cscs.yml +++ b/ci/cscs.yml @@ -19,8 +19,11 @@ test_job: extends: .container-runner-clariden-gh200 image: $PERSIST_IMAGE_NAME script: - - pip install -e . --no-dependencies - - python src/hirad/training/train.py --config-name=training_era_cosmo_regression_test.yaml + - echo 'hello world' + # - pip install -e . --no-dependencies + # - python src/hirad/training/train.py --config-name=training_era_cosmo_regression_test.yaml variables: SLURM_JOB_NUM_NODES: 2 SLURM_NTASKS: 2 + SLURM_ACCOUNT: a161 + SBATCH_ACCOUNT: a161 From 79e31126963a448d5fdebcb4abd219d292749ded Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Tue, 29 Jul 2025 18:37:42 +0200 Subject: [PATCH 104/302] logs --- src/hirad/eval/diurnal_cycle_precip.py | 2 +- src/hirad/eval/diurnal_cycle_temp_wind.py | 2 +- src/hirad/eval/percentile99_cycle_precip.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hirad/eval/diurnal_cycle_precip.py b/src/hirad/eval/diurnal_cycle_precip.py index 8a38078f..f6e944ae 100644 --- a/src/hirad/eval/diurnal_cycle_precip.py +++ b/src/hirad/eval/diurnal_cycle_precip.py @@ -59,7 +59,7 @@ def main(cfg: DictConfig): logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) - logger.info("Starting diurnal cycle computation") + logger.info("Starting computations for diurnal cycle of precipitation amount and wet-hours") times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") logger.info(f"Loaded {len(times)} timesteps to process") diff --git a/src/hirad/eval/diurnal_cycle_temp_wind.py b/src/hirad/eval/diurnal_cycle_temp_wind.py index ed35042c..e5e5c0fc 100644 --- a/src/hirad/eval/diurnal_cycle_temp_wind.py +++ b/src/hirad/eval/diurnal_cycle_temp_wind.py @@ -55,7 +55,7 @@ def main(cfg: DictConfig): logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) - logger.info("Starting diurnal cycle computation for 2m temperature and windspeed") + logger.info("Starting computation for diurnal cyles of 2m temperature and windspeed") times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") logger.info(f"Loaded {len(times)} timesteps to process") diff --git a/src/hirad/eval/percentile99_cycle_precip.py b/src/hirad/eval/percentile99_cycle_precip.py index f657b21f..823a2d47 100644 --- a/src/hirad/eval/percentile99_cycle_precip.py +++ b/src/hirad/eval/percentile99_cycle_precip.py @@ -59,7 +59,7 @@ def main(cfg: DictConfig): logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) - logger.info("Starting 99th-percentile diurnal cycle computation") + logger.info("Starting computation for diurnal cycle of 99th-percentile of precipitation") times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") logger.info(f"Loaded {len(times)} timesteps to process") From 7a117853b6a2573bb7da4677da521c9206644b2c Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Wed, 30 Jul 2025 13:00:48 +0200 Subject: [PATCH 105/302] mask out sea points --- src/hirad/eval/diurnal_cycle_precip.py | 17 ++++++++++++++--- src/hirad/eval/diurnal_cycle_temp_wind.py | 9 +++++++++ src/hirad/eval/percentile99_cycle_precip.py | 9 +++++++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/hirad/eval/diurnal_cycle_precip.py b/src/hirad/eval/diurnal_cycle_precip.py index f6e944ae..31a22edb 100644 --- a/src/hirad/eval/diurnal_cycle_precip.py +++ b/src/hirad/eval/diurnal_cycle_precip.py @@ -18,7 +18,6 @@ WET_THRESHOLD = 0.1 # Threshold for wet-hour in mm/h LOG_INTERVAL = 24 # Log progress every N timesteps - def hour_of(dt: str, fmt: str = "%Y%m%d-%H%M") -> int: return datetime.strptime(dt, fmt).hour @@ -81,18 +80,30 @@ def main(cfg: DictConfig): # Prepare data structures stats = {mode: defaultdict(list) for mode in ['target','baseline','prediction']} wet_stats = {mode: defaultdict(list) for mode in stats} + + # Load land mask + lsm_dat = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy') + lsm=np.flip(lsm_dat.reshape(352,544),0) + # Collect data for idx, ts in enumerate(times, 1): hr = hour_of(ts) target = load(ts, f"{ts}-target")[tp_out] baseline = load(ts, f"{ts}-baseline")[tp_in] + + # Mask target, baseline, and preds where lsm < 0.5 + land_mask = lsm >= 0.5 + target = target * land_mask + baseline = baseline * land_mask + stats['target'][hr].append(target) stats['baseline'][hr].append(baseline) wet_stats['target'][hr].append((target > WET_THRESHOLD).mean()) wet_stats['baseline'][hr].append((baseline > WET_THRESHOLD).mean()) preds = load(ts, f"{ts}-predictions")[:, tp_out] + preds = preds * land_mask for member in preds: stats['prediction'][hr].append(member.mean()) wet_stats['prediction'][hr].append((member > WET_THRESHOLD).mean()) @@ -126,8 +137,8 @@ def main(cfg: DictConfig): wet_lines = [cycle(wet_cycle['target']), cycle(wet_cycle['baseline']), (cycle(wet_mean), cycle(wet_std))] # Log the lines to be plotted (debug) - logger.info(f"amount_lines: {amount_lines}") - logger.info(f"wet_lines: {wet_lines}") + # logger.info(f"amount_lines: {amount_lines}") + # logger.info(f"wet_lines: {wet_lines}") # Plot plot_paths = [] diff --git a/src/hirad/eval/diurnal_cycle_temp_wind.py b/src/hirad/eval/diurnal_cycle_temp_wind.py index e5e5c0fc..5a333b10 100644 --- a/src/hirad/eval/diurnal_cycle_temp_wind.py +++ b/src/hirad/eval/diurnal_cycle_temp_wind.py @@ -83,10 +83,18 @@ def main(cfg: DictConfig): stats_temp = {mode: defaultdict(list) for mode in ['target','baseline','prediction']} stats_wind = {mode: defaultdict(list) for mode in ['target','baseline','prediction']} + # Load land-sea mask + lsm_dat = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy') + lsm = np.flip(lsm_dat.reshape(352,544), 0) + land_mask = lsm >= 0.5 + for idx, ts in enumerate(times, 1): hr = hour_of(ts) target = load(ts, f"{ts}-target") baseline = load(ts, f"{ts}-baseline") + # Apply land mask + target = target * land_mask + baseline = baseline * land_mask # 2m temperature stats_temp['target'][hr].append(target[t2m_out].mean()) stats_temp['baseline'][hr].append(baseline[t2m_in].mean()) @@ -95,6 +103,7 @@ def main(cfg: DictConfig): stats_wind['baseline'][hr].append(np.hypot(baseline[u_in], baseline[v_in]).mean()) preds = load(ts, f"{ts}-predictions") + preds = preds * land_mask for member in preds: stats_temp['prediction'][hr].append(member[t2m_out].mean()) stats_wind['prediction'][hr].append(np.hypot(member[u_out], member[v_out]).mean()) diff --git a/src/hirad/eval/percentile99_cycle_precip.py b/src/hirad/eval/percentile99_cycle_precip.py index 823a2d47..5013b911 100644 --- a/src/hirad/eval/percentile99_cycle_precip.py +++ b/src/hirad/eval/percentile99_cycle_precip.py @@ -80,6 +80,11 @@ def main(cfg: DictConfig): tp_out = out_ch['tp']; tp_in = in_ch.get('tp', tp_out) logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") + # Load land-sea mask + lsm_dat = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy') + lsm = np.flip(lsm_dat.reshape(352,544), 0) + land_mask = lsm >= 0.5 + # Storage for diurnal cycles pct99_mean = {'target': [], 'baseline': [], 'prediction': []} pct99_std = {'target': [], 'baseline': [], 'prediction': []} @@ -89,7 +94,7 @@ def main(cfg: DictConfig): logger.info(f"Processing mode: {mode}") for h in list(range(24)): arrs = [ - load(ts, f"{ts}-{mode}")[tp_out if mode == 'target' else tp_in] + load(ts, f"{ts}-{mode}")[tp_out if mode == 'target' else tp_in] * land_mask for ts in times if hour_of(ts) == h ] stack = np.stack(arrs, axis=0) @@ -114,7 +119,7 @@ def main(cfg: DictConfig): if hour_of(ts) != h: continue preds = load(ts, f"{ts}-predictions") # [n_members, n_channels, ...] - arrs.append(preds[m, tp_out]) # one field + arrs.append(preds[m, tp_out] * land_mask) # apply mask # stack over time and compute 99th percentile at each grid point stack_m = np.stack(arrs, axis=0) f99_m = np.percentile(stack_m, 99, axis=0) From e23452856954c4549fadfab03e2dfad137994819 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Wed, 30 Jul 2025 18:48:58 +0200 Subject: [PATCH 106/302] Convert to xarray, still buggy --- src/hirad/eval/diurnal_cycle_precip.py | 203 ++++++++--------- src/hirad/eval/diurnal_cycle_temp_wind.py | 234 +++++++++++--------- src/hirad/eval/percentile99_cycle_precip.py | 22 +- 3 files changed, 237 insertions(+), 222 deletions(-) diff --git a/src/hirad/eval/diurnal_cycle_precip.py b/src/hirad/eval/diurnal_cycle_precip.py index 31a22edb..989ddc53 100644 --- a/src/hirad/eval/diurnal_cycle_precip.py +++ b/src/hirad/eval/diurnal_cycle_precip.py @@ -1,56 +1,23 @@ import logging from datetime import datetime from pathlib import Path -from collections import defaultdict import hydra +import matplotlib.pyplot as plt import numpy as np import torch +import xarray as xr from omegaconf import DictConfig, OmegaConf -import matplotlib.pyplot as plt from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range # Constants -CONV_FACTOR = 100 # Convert meters to mm/h +CONV_FACTOR = 100*24 # Convert meters to mm/day WET_THRESHOLD = 0.1 # Threshold for wet-hour in mm/h LOG_INTERVAL = 24 # Log progress every N timesteps -def hour_of(dt: str, fmt: str = "%Y%m%d-%H%M") -> int: - return datetime.strptime(dt, fmt).hour - - -def compute_ensemble(hourly_values): - hours = sorted(hourly_values) - means = [np.mean(hourly_values[h]) for h in hours] - stds = [np.std(hourly_values[h]) for h in hours] - return hours, means, stds - - -def save_plot(hours, lines, labels, ylabel, title, out_path): - Path(out_path).parent.mkdir(parents=True, exist_ok=True) - plt.figure(figsize=(8,4)) - for data, label in zip(lines, labels): - if isinstance(data, tuple): - mean, std = data - line, = plt.plot(hours, mean, label=label) - plt.fill_between(hours, np.maximum(np.array(mean)-std, 0), np.array(mean)+std, alpha=0.3, color=line.get_color()) - else: - plt.plot(hours, data, label=label) - plt.xlabel('Hour (UTC)') - plt.xticks(range(0,25,3)) - plt.xlim(0,24) - plt.ylabel(ylabel) - plt.title(title) - plt.grid(True) - plt.legend() - plt.tight_layout() - plt.savefig(out_path) - plt.close() - - @hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") def main(cfg: DictConfig): # Setup logging @@ -60,6 +27,7 @@ def main(cfg: DictConfig): logger.info("Starting computations for diurnal cycle of precipitation amount and wet-hours") times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + datetimes = [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times] logger.info(f"Loaded {len(times)} timesteps to process") ds_cfg = OmegaConf.to_container(cfg.dataset) @@ -69,90 +37,113 @@ def main(cfg: DictConfig): logger.info("Dataset and sampler initialized") out_root = Path(cfg.generation.io.output_path or './outputs') - load = lambda ts, fn: torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR + def load(ts, fn): + return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR # Find channel indices out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} - tp_out = out_ch['tp']; tp_in = in_ch.get('tp', tp_out) + tp_out = out_ch['tp'] + tp_in = in_ch.get('tp', tp_out) logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") - # Prepare data structures - stats = {mode: defaultdict(list) for mode in ['target','baseline','prediction']} - wet_stats = {mode: defaultdict(list) for mode in stats} - - # Load land mask - lsm_dat = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy') - lsm=np.flip(lsm_dat.reshape(352,544),0) + # Land-sea mask + lsm_data = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy').reshape(352,544) + land_mask = np.where(lsm_data >= 0.5, 1.0, np.nan) + coords = {"lat": np.arange(land_mask.shape[0]), "lon": np.arange(land_mask.shape[1])} + # Prepare lists to collect DataArrays + target_precip, baseline_precip, pred_precip = [], [], [] + target_wet, baseline_wet, pred_wet = [], [], [] # Collect data for idx, ts in enumerate(times, 1): - hr = hour_of(ts) - target = load(ts, f"{ts}-target")[tp_out] - baseline = load(ts, f"{ts}-baseline")[tp_in] - - # Mask target, baseline, and preds where lsm < 0.5 - land_mask = lsm >= 0.5 - target = target * land_mask - baseline = baseline * land_mask - - stats['target'][hr].append(target) - stats['baseline'][hr].append(baseline) - wet_stats['target'][hr].append((target > WET_THRESHOLD).mean()) - wet_stats['baseline'][hr].append((baseline > WET_THRESHOLD).mean()) - - preds = load(ts, f"{ts}-predictions")[:, tp_out] - preds = preds * land_mask - for member in preds: - stats['prediction'][hr].append(member.mean()) - wet_stats['prediction'][hr].append((member > WET_THRESHOLD).mean()) + dt = datetimes[idx-1] + target = load(ts, f"{ts}-target")[tp_out] * land_mask + baseline = load(ts, f"{ts}-baseline")[tp_in] * land_mask / 6 # 6 becasue 1h -> 6h bug in dataset? + preds = load(ts, f"{ts}-predictions")[:, tp_out, :, :] * land_mask + + # DataArrays for spatial mean + da_target = xr.DataArray(target, dims=("lat","lon"), coords=coords) + da_baseline = xr.DataArray(baseline, dims=("lat","lon"), coords=coords) + da_preds = xr.DataArray(preds, dims=("member","lat","lon"), coords={"member": np.arange(preds.shape[0]), **coords}) + + # Spatial mean + target_precip.append(da_target.mean(dim=("lat","lon")).assign_coords(time=dt)) + baseline_precip.append(da_baseline.mean(dim=("lat","lon")).assign_coords(time=dt)) + pred_precip.append(da_preds.mean(dim=("lat","lon")).assign_coords(time=dt)) + + # Wet-hour fraction (percentage) + target_wet.append(((da_target / 24 > WET_THRESHOLD).mean().assign_coords(time=dt))) + baseline_wet.append(((da_baseline / 24 > WET_THRESHOLD).mean().assign_coords(time=dt))) + pred_wet.append(((da_preds / 24> WET_THRESHOLD).mean(dim=("lat","lon")).assign_coords(time=dt))) if idx % LOG_INTERVAL == 0 or idx == len(times): logger.info(f"Processed {idx}/{len(times)} timesteps ({ts})") - # Compute hourly means - mean_cycle = { - mode: [np.mean(stats[mode][h]) for h in sorted(stats[mode])] - for mode in ['target','baseline'] - } - wet_cycle = { - mode: [np.mean(wet_stats[mode][h]) * 100. for h in sorted(wet_stats[mode])] - for mode in ['target','baseline'] - } - logger.info("Computed hourly mean and wet-cycle statistics") - - # Ensemble cycles (mean ± std) - hrs, pred_mean, pred_std = compute_ensemble(stats['prediction']) - _, wet_mean, wet_std = compute_ensemble(wet_stats['prediction']) - # Multiply ensemble wet-hour statistics by 100 for percentage - wet_mean = [v * 100. for v in wet_mean] - wet_std = [v * 100. for v in wet_std] - logger.info("Computed ensemble statistics") - - # Prepare cyclic series - cycle = lambda x: x + [x[0]] - hrs_c = hrs + [24] - amount_lines = [cycle(mean_cycle['target']), cycle(mean_cycle['baseline']), (cycle(pred_mean), cycle(pred_std))] - wet_lines = [cycle(wet_cycle['target']), cycle(wet_cycle['baseline']), (cycle(wet_mean), cycle(wet_std))] - - # Log the lines to be plotted (debug) - # logger.info(f"amount_lines: {amount_lines}") - # logger.info(f"wet_lines: {wet_lines}") - - # Plot - plot_paths = [] - fn1 = out_root/'diurnal_cycle_precip_amount.png' - save_plot(hrs_c, amount_lines, ['COSMO-2','ERA5','CorrDiff ± Std(Members)'], 'Rain Rate (mm/h)', - 'Diurnal Cycle of Precip Amount', fn1) - plot_paths.append(fn1) - - fn2 = out_root/'diurnal_cycle_precip_wethours.png' - save_plot(hrs_c, wet_lines, ['COSMO-2','ERA5','Pred Mean ± Std'], 'Wet-Hour Fraction [%]', - 'Diurnal Cycle of Wet-Hours (>0.1 mm/h)', fn2) - plot_paths.append(fn2) - - logger.info(f"Plots saved: {', '.join(str(p) for p in plot_paths)}") + # Helper to concat and compute diurnal stats + def concat_and_group(list_of_da, is_member=False, scale=1.0): + da = xr.concat(list_of_da, dim="time").groupby("time.hour") + if is_member: + mean = da.mean(dim=[d for d in da.dims if d in ['time', 'member']]) * scale + std = da.std(dim=[d for d in da.dims if d in ['time', 'member']]) * scale + else: + mean = da.mean(dim="time") * scale + std = None + return mean, std + + # Compute diurnal means and stds + amount_target_mean, _ = concat_and_group(target_precip) + amount_baseline_mean, _ = concat_and_group(baseline_precip) + amount_pred_mean, amount_pred_std = concat_and_group(pred_precip, is_member=True) + + wet_target_mean, _ = concat_and_group(target_wet, scale=100.0) + wet_baseline_mean, _ = concat_and_group(baseline_wet, scale=100.0) + wet_pred_mean, wet_pred_std = concat_and_group(pred_wet, is_member=True, scale=100.0) + + # Plot helper + def save_plot(hour, means, stds, labels, ylabel, title, out_path): + hrs = np.concatenate([hour.values, [24]]) + plt.figure(figsize=(8,4)) + for mean, std, label in zip(means, stds, labels): + vals = np.append(mean.values, mean.values[0]) + line, = plt.plot(hrs, vals, label=label) + if std is not None: + stdv = np.append(std.values, std.values[0]) + plt.fill_between(hrs, np.maximum(vals - stdv, 0), vals + stdv, color=line.get_color(), alpha=0.3) + plt.xlabel('Hour (UTC)') + plt.xticks(range(0,25,3)) + plt.xlim(0,24) + plt.ylabel(ylabel) + plt.title(title) + plt.grid(True) + plt.legend() + plt.tight_layout() + Path(out_path).parent.mkdir(parents=True, exist_ok=True) + plt.savefig(out_path) + plt.close() + + # Generate plots + save_plot( + amount_target_mean.hour, + [amount_target_mean, amount_baseline_mean, amount_pred_mean], + [None, None, amount_pred_std], + ['COSMO-2','ERA5','CorrDiff ± Std(Members)'], + 'Precipitation (mm/day)', + 'Diurnal Cycle of Precip Amount', + out_root / 'diurnal_cycle_precip_amount.png' + ) + save_plot( + wet_target_mean.hour, + [wet_target_mean, wet_baseline_mean, wet_pred_mean], + [None, None, wet_pred_std], + ['COSMO-2','ERA5','CorrDiff ± Std(Members)'], + 'Wet-Hour Fraction [%]', + 'Diurnal Cycle of Wet-Hours (>0.1 mm/h)', + out_root / 'diurnal_cycle_precip_wethours.png' + ) + + logger.info("Plots saved.") if __name__ == '__main__': main() diff --git a/src/hirad/eval/diurnal_cycle_temp_wind.py b/src/hirad/eval/diurnal_cycle_temp_wind.py index 5a333b10..d03e62c2 100644 --- a/src/hirad/eval/diurnal_cycle_temp_wind.py +++ b/src/hirad/eval/diurnal_cycle_temp_wind.py @@ -1,13 +1,13 @@ import logging from datetime import datetime from pathlib import Path -from collections import defaultdict import hydra +import matplotlib.pyplot as plt import numpy as np import torch +import xarray as xr from omegaconf import DictConfig, OmegaConf -import matplotlib.pyplot as plt from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager @@ -15,60 +15,26 @@ LOG_INTERVAL = 24 -def hour_of(dt: str, fmt: str = "%Y%m%d-%H%M") -> int: - return datetime.strptime(dt, fmt).hour - -def compute_ensemble(hourly_values): - hours = sorted(hourly_values) - means = [np.mean(hourly_values[h]) for h in hours] - stds = [np.std(hourly_values[h]) for h in hours] - return hours, means, stds - -def save_plot(hours, lines, labels, ylabel, title, out_path): - Path(out_path).parent.mkdir(parents=True, exist_ok=True) - plt.figure(figsize=(8,4)) - for data, label in zip(lines, labels): - if isinstance(data, tuple): # (mean, std) - mean, std = data - line, = plt.plot(hours, mean, label=label) - plt.fill_between(hours, - np.array(mean)-std, - np.array(mean)+std, - alpha=0.3, - color=line.get_color()) - else: - plt.plot(hours, data, label=label) - plt.xlabel('Hour (UTC)') - plt.xticks(range(0,25,3)) - plt.xlim(0,24) - plt.ylabel(ylabel) - plt.title(title) - plt.grid(True) - plt.legend() - plt.tight_layout() - plt.savefig(out_path) - plt.close() - @hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") def main(cfg: DictConfig): + # Initialize DistributedManager.initialize() logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) - logger.info("Starting computation for diurnal cyles of 2m temperature and windspeed") + # Load times + logger.info("Starting computation for diurnal cycles of 2m temperature and windspeed") times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + datetimes = [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times] logger.info(f"Loaded {len(times)} timesteps to process") + # Dataset ds_cfg = OmegaConf.to_container(cfg.dataset) dataset, _ = get_dataset_and_sampler_inference( ds_cfg, times, cfg.generation.get('has_lead_time', False) ) - logger.info("Dataset and sampler initialized") - out_root = Path(cfg.generation.io.output_path or './outputs') - load = lambda ts, fn: torch.load(out_root/ts/fn, weights_only=False) - - # Find channel indices + # Indices for channels out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} t2m_out = out_ch.get('2t', out_ch.get('t2m')) @@ -77,76 +43,136 @@ def main(cfg: DictConfig): v_out = out_ch.get('10v') u_in = in_ch.get('10u', u_out) v_in = in_ch.get('10v', v_out) - logger.info(f"2T channel indices - output: {t2m_out}, input: {t2m_in}") - logger.info(f"10U/10V channel indices - output: {u_out}/{v_out}, input: {u_in}/{v_in}") - stats_temp = {mode: defaultdict(list) for mode in ['target','baseline','prediction']} - stats_wind = {mode: defaultdict(list) for mode in ['target','baseline','prediction']} + # Output path + out_root = Path(cfg.generation.io.output_path or './outputs') + load = lambda ts, fn: torch.load(out_root/ts/fn, weights_only=False) + + # Land-sea mask + lsm_data = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy').reshape(352,544) + land_mask = np.where(lsm_data >= 0.5, 1.0, np.nan) + coords = {"lat": np.arange(land_mask.shape[0]), "lon": np.arange(land_mask.shape[1])} - # Load land-sea mask - lsm_dat = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy') - lsm = np.flip(lsm_dat.reshape(352,544), 0) - land_mask = lsm >= 0.5 + # Prepare lists to collect DataArrays + target_temp, baseline_temp, pred_temp = [], [], [] + target_wind, baseline_wind, pred_wind = [], [], [] + # Loop over timestamps for idx, ts in enumerate(times, 1): - hr = hour_of(ts) - target = load(ts, f"{ts}-target") - baseline = load(ts, f"{ts}-baseline") - # Apply land mask - target = target * land_mask - baseline = baseline * land_mask - # 2m temperature - stats_temp['target'][hr].append(target[t2m_out].mean()) - stats_temp['baseline'][hr].append(baseline[t2m_in].mean()) - # windspeed using np.hypot - stats_wind['target'][hr].append(np.hypot(target[u_out], target[v_out]).mean()) - stats_wind['baseline'][hr].append(np.hypot(baseline[u_in], baseline[v_in]).mean()) - - preds = load(ts, f"{ts}-predictions") - preds = preds * land_mask - for member in preds: - stats_temp['prediction'][hr].append(member[t2m_out].mean()) - stats_wind['prediction'][hr].append(np.hypot(member[u_out], member[v_out]).mean()) + dt = datetimes[idx-1] + + # Load and apply land mask + target = load(ts, f"{ts}-target") * land_mask + baseline = load(ts, f"{ts}-baseline") * land_mask + predictions = load(ts, f"{ts}-predictions") * land_mask + + # Wrap into DataArrays (convert temperature to Celsius inline) + da_tgt_temp = xr.DataArray( + target[t2m_out] - 273.15, dims=("lat","lon"), coords=coords + ) + da_bsl_temp = xr.DataArray( + baseline[t2m_in] - 273.15, dims=("lat","lon"), coords=coords + ) + tgt_wind = np.hypot(target[u_out], target[v_out]) + bsl_wind = np.hypot(baseline[u_in], baseline[v_in]) + da_tgt_wind = xr.DataArray(tgt_wind, dims=("lat","lon"), coords=coords) + da_bsl_wind = xr.DataArray(bsl_wind, dims=("lat","lon"), coords=coords) + + da_pred_members_temp = xr.DataArray( + predictions[:, t2m_out, :, :] - 273.15, dims=("member","lat","lon"), + coords={"member": np.arange(predictions.shape[0]), **coords} + ) + da_pred_members_wind = xr.DataArray( + np.hypot(predictions[:, u_out, :, :], predictions[:, v_out, :, :]), + dims=("member","lat","lon"), coords={"member": np.arange(predictions.shape[0]), **coords} + ) + + # Compute spatial mean and assign time coordinate + target_temp.append( + da_tgt_temp.mean(dim=("lat","lon")).assign_coords(time=dt) + ) + baseline_temp.append( + da_bsl_temp.mean(dim=("lat","lon")).assign_coords(time=dt) + ) + pred_temp.append( + da_pred_members_temp.mean(dim=("lat","lon")).assign_coords(time=dt) + ) + target_wind.append( + da_tgt_wind.mean(dim=("lat","lon")).assign_coords(time=dt) + ) + baseline_wind.append( + da_bsl_wind.mean(dim=("lat","lon")).assign_coords(time=dt) + ) + pred_wind.append( + da_pred_members_wind.mean(dim=("lat","lon")).assign_coords(time=dt) + ) if idx % LOG_INTERVAL == 0 or idx == len(times): logger.info(f"Processed {idx}/{len(times)} timesteps ({ts})") - # Compute hourly means - mean_cycle_temp = { - mode: [np.mean(stats_temp[mode][h]) for h in sorted(stats_temp[mode])] - for mode in ['target','baseline'] - } - mean_cycle_wind = { - mode: [np.mean(stats_wind[mode][h]) for h in sorted(stats_wind[mode])] - for mode in ['target','baseline'] - } - logger.info("Computed hourly mean statistics") - - # Ensemble cycles (mean ± std) - hrs, pred_mean_temp, pred_std_temp = compute_ensemble(stats_temp['prediction']) - hrs_w, pred_mean_wind, pred_std_wind = compute_ensemble(stats_wind['prediction']) - logger.info("Computed ensemble statistics") - - # Prepare cyclic series - cycle = lambda x: x + [x[0]] - hrs_c = hrs + [24] - hrs_w_c = hrs_w + [24] - temp_lines = [cycle(mean_cycle_temp['target']), cycle(mean_cycle_temp['baseline']), (cycle(pred_mean_temp), cycle(pred_std_temp))] - wind_lines = [cycle(mean_cycle_wind['target']), cycle(mean_cycle_wind['baseline']), (cycle(pred_mean_wind), cycle(pred_std_wind))] - - # Plot - plot_paths = [] - fn1 = out_root/'diurnal_cycle_2t.png' - save_plot(hrs_c, temp_lines, ['COSMO-2','ERA5','CorrDiff ± Std(Members)'], '2m Temperature [K]', - 'Diurnal Cycle of 2m Temperature', fn1) - plot_paths.append(fn1) - - fn2 = out_root/'diurnal_cycle_windspeed.png' - save_plot(hrs_w_c, wind_lines, ['COSMO-2','ERA5','CorrDiff ± Std(Members)'], 'Windspeed [m/s]', - 'Diurnal Cycle of Windspeed', fn2) - plot_paths.append(fn2) - - logger.info(f"Plots saved: {', '.join(str(p) for p in plot_paths)}") + # Helper to concat and compute diurnal stats + def concat_and_group(list_of_da): + da = xr.concat(list_of_da, dim="time").groupby("time.hour") + mean = da.mean(dim=[d for d in da.dims if d in ['time', 'member']]) + std = da.std(dim=[d for d in da.dims if d in ['time', 'member']]) + return mean, std + + # Compute diurnal means and stds + temp_target_mean, _ = concat_and_group(target_temp) + temp_baseline_mean, _ = concat_and_group(baseline_temp) + temp_pred_mean, temp_pred_std = concat_and_group(pred_temp) + + wind_target_mean, _ = concat_and_group(target_wind) + wind_baseline_mean, _ = concat_and_group(baseline_wind) + wind_pred_mean, wind_pred_std = concat_and_group(pred_wind) + + # Plot helper + def save_plot(hour, means, stds, labels, ylabel, title, out_path): + hrs = np.concatenate([hour.values, [24]]) + plt.figure(figsize=(8,4)) + for mean, std, label in zip(means, stds, labels): + vals = np.append(mean.values, mean.values[0]) + line, = plt.plot(hrs, vals, label=label) + if std is not None: + stdv = np.append(std.values, std.values[0]) + plt.fill_between(hrs, vals - stdv, vals + stdv, color=line.get_color(), alpha=0.3) + plt.xlabel('Hour (UTC)') + plt.xticks(range(0,25,3)) + plt.xlim(0,24) + plt.ylabel(ylabel) + plt.title(title) + plt.grid(True) + plt.legend() + plt.tight_layout() + Path(out_path).parent.mkdir(parents=True, exist_ok=True) + plt.savefig(out_path) + plt.close() + + # Generate plots + save_plot( + temp_target_mean.hour, + [temp_target_mean, temp_baseline_mean, temp_pred_mean], + [None, None, temp_pred_std], + ['COSMO-2', 'ERA5', 'CorrDiff ± Std(Members)'], + '2m Temperature [°C]', + 'Diurnal Cycle of 2m Temperature', + out_root / 'diurnal_cycle_2t.png' + ) + save_plot( + wind_target_mean.hour, + [wind_target_mean, wind_baseline_mean, wind_pred_mean], + [None, None, wind_pred_std], + ['COSMO-2', 'ERA5', 'CorrDiff ± Std(Members)'], + 'Windspeed [m/s]', + 'Diurnal Cycle of Windspeed', + out_root / 'diurnal_cycle_windspeed.png' + ) + + logger.info("Plots saved.") + + plt.imshow(target[t2m_out], cmap='viridis') + plt.colorbar(label='2m Temperature [°C]') + plt.savefig('lsm.png') if __name__ == '__main__': main() diff --git a/src/hirad/eval/percentile99_cycle_precip.py b/src/hirad/eval/percentile99_cycle_precip.py index 5013b911..1f4e4791 100644 --- a/src/hirad/eval/percentile99_cycle_precip.py +++ b/src/hirad/eval/percentile99_cycle_precip.py @@ -20,7 +20,7 @@ from hirad.utils.function_utils import get_time_from_range # Constants -CONV_FACTOR = 100 # Convert meters to mm/h +CONV_FACTOR = 100 * 24 # Convert meters to mm/day LOG_INTERVAL = 24 # Log progress every N timesteps @@ -80,10 +80,9 @@ def main(cfg: DictConfig): tp_out = out_ch['tp']; tp_in = in_ch.get('tp', tp_out) logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") - # Load land-sea mask - lsm_dat = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy') - lsm = np.flip(lsm_dat.reshape(352,544), 0) - land_mask = lsm >= 0.5 + # Land-sea mask + lsm_data = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy').reshape(352,544) + land_mask = np.where(lsm_data >= 0.5, 1.0, np.nan) # Storage for diurnal cycles pct99_mean = {'target': [], 'baseline': [], 'prediction': []} @@ -99,8 +98,7 @@ def main(cfg: DictConfig): ] stack = np.stack(arrs, axis=0) f99 = np.percentile(stack, 99, axis=0) - pct99_mean[mode].append(f99.mean()) - pct99_std[mode].append(np.std(f99, axis=None)) + pct99_mean[mode].append(np.nanmean(f99) if mode == 'target' else np.nanmean(f99) / 6.0) # / 6 because bug in dataset? del arrs, stack, f99 # -- Predictions: compute per hour per member, then mean+std across members -- @@ -123,10 +121,10 @@ def main(cfg: DictConfig): # stack over time and compute 99th percentile at each grid point stack_m = np.stack(arrs, axis=0) f99_m = np.percentile(stack_m, 99, axis=0) - mem_f99.append(f99_m.mean()) + mem_f99.append(np.nanmean(f99_m)) # mean over grid points # ensemble-level mean and std over member-wise percentiles - pct99_mean['prediction'].append(np.mean(mem_f99)) - pct99_std['prediction'].append(np.std(mem_f99, axis=None)) + pct99_mean['prediction'].append(np.nanmean(mem_f99)) + pct99_std['prediction'].append(np.std(pct99_mean['prediction'])) # clean up per-hour buffers del mem_f99, stack_m, f99_m @@ -135,7 +133,7 @@ def main(cfg: DictConfig): hrs_c = list(range(24)) + [list(range(24))[0] + 24] pct99_lines = [ cycle_fn(pct99_mean['target']), - cycle_fn(pct99_mean['baseline']), + cycle_fn(pct99_mean['baseline']), # 6 becasue bug in dataset? ( cycle_fn(pct99_mean['prediction']), cycle_fn(pct99_std['prediction']) @@ -148,7 +146,7 @@ def main(cfg: DictConfig): hrs_c, pct99_lines, ['COSMO-2','ERA5','CorrDiff 99th Pct ± Std'], - 'Rain Rate (mm/h)', + 'Precipitation (mm/day)', 'Diurnal Cycle of 99th-Percentile Precipitation', fn ) From 12702b67ec24cae514b9ca586c9505efa9f6ddee Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Thu, 31 Jul 2025 11:55:52 +0200 Subject: [PATCH 107/302] comment --- src/hirad/eval/diurnal_cycle_precip.py | 5 ++--- src/hirad/eval/percentile99_cycle_precip.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/hirad/eval/diurnal_cycle_precip.py b/src/hirad/eval/diurnal_cycle_precip.py index 989ddc53..e5c95602 100644 --- a/src/hirad/eval/diurnal_cycle_precip.py +++ b/src/hirad/eval/diurnal_cycle_precip.py @@ -14,7 +14,7 @@ from hirad.utils.function_utils import get_time_from_range # Constants -CONV_FACTOR = 100*24 # Convert meters to mm/day +CONV_FACTOR = 100*24 # Convert meters to mm/day WET_THRESHOLD = 0.1 # Threshold for wet-hour in mm/h LOG_INTERVAL = 24 # Log progress every N timesteps @@ -34,7 +34,6 @@ def main(cfg: DictConfig): dataset, _ = get_dataset_and_sampler_inference( ds_cfg, times, cfg.generation.get('has_lead_time', False) ) - logger.info("Dataset and sampler initialized") out_root = Path(cfg.generation.io.output_path or './outputs') def load(ts, fn): @@ -60,7 +59,7 @@ def load(ts, fn): for idx, ts in enumerate(times, 1): dt = datetimes[idx-1] target = load(ts, f"{ts}-target")[tp_out] * land_mask - baseline = load(ts, f"{ts}-baseline")[tp_in] * land_mask / 6 # 6 becasue 1h -> 6h bug in dataset? + baseline = load(ts, f"{ts}-baseline")[tp_in] * land_mask / 6. # 6 because 1h -> accumulation period is 6h in hourly ERA5 dataset preds = load(ts, f"{ts}-predictions")[:, tp_out, :, :] * land_mask # DataArrays for spatial mean diff --git a/src/hirad/eval/percentile99_cycle_precip.py b/src/hirad/eval/percentile99_cycle_precip.py index 1f4e4791..51fc28b0 100644 --- a/src/hirad/eval/percentile99_cycle_precip.py +++ b/src/hirad/eval/percentile99_cycle_precip.py @@ -98,7 +98,7 @@ def main(cfg: DictConfig): ] stack = np.stack(arrs, axis=0) f99 = np.percentile(stack, 99, axis=0) - pct99_mean[mode].append(np.nanmean(f99) if mode == 'target' else np.nanmean(f99) / 6.0) # / 6 because bug in dataset? + pct99_mean[mode].append(np.nanmean(f99) if mode == 'target' else np.nanmean(f99) / 6.0) # 6 because 1h -> accumulation period is 6h in hourly ERA5 dataset del arrs, stack, f99 # -- Predictions: compute per hour per member, then mean+std across members -- From b537630c09969e38209f18eca331abd8e42c108d Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Thu, 31 Jul 2025 14:01:48 +0200 Subject: [PATCH 108/302] fix bug in std --- src/hirad/eval/diurnal_cycle_precip.py | 13 +++++++------ src/hirad/eval/diurnal_cycle_temp_wind.py | 11 ++++++++--- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/hirad/eval/diurnal_cycle_precip.py b/src/hirad/eval/diurnal_cycle_precip.py index e5c95602..2bc31c07 100644 --- a/src/hirad/eval/diurnal_cycle_precip.py +++ b/src/hirad/eval/diurnal_cycle_precip.py @@ -62,7 +62,7 @@ def load(ts, fn): baseline = load(ts, f"{ts}-baseline")[tp_in] * land_mask / 6. # 6 because 1h -> accumulation period is 6h in hourly ERA5 dataset preds = load(ts, f"{ts}-predictions")[:, tp_out, :, :] * land_mask - # DataArrays for spatial mean + # DataArrays for spatial means at each timestep da_target = xr.DataArray(target, dims=("lat","lon"), coords=coords) da_baseline = xr.DataArray(baseline, dims=("lat","lon"), coords=coords) da_preds = xr.DataArray(preds, dims=("member","lat","lon"), coords={"member": np.arange(preds.shape[0]), **coords}) @@ -72,7 +72,7 @@ def load(ts, fn): baseline_precip.append(da_baseline.mean(dim=("lat","lon")).assign_coords(time=dt)) pred_precip.append(da_preds.mean(dim=("lat","lon")).assign_coords(time=dt)) - # Wet-hour fraction (percentage) + # Wet-hour fraction, i.e., freq(precip) > WET_THRESHOLD target_wet.append(((da_target / 24 > WET_THRESHOLD).mean().assign_coords(time=dt))) baseline_wet.append(((da_baseline / 24 > WET_THRESHOLD).mean().assign_coords(time=dt))) pred_wet.append(((da_preds / 24> WET_THRESHOLD).mean(dim=("lat","lon")).assign_coords(time=dt))) @@ -84,10 +84,11 @@ def load(ts, fn): def concat_and_group(list_of_da, is_member=False, scale=1.0): da = xr.concat(list_of_da, dim="time").groupby("time.hour") if is_member: - mean = da.mean(dim=[d for d in da.dims if d in ['time', 'member']]) * scale - std = da.std(dim=[d for d in da.dims if d in ['time', 'member']]) * scale + timmean = da.mean(dim='time') * scale + mean = timmean.mean(dim='member') + std = timmean.std(dim='member') else: - mean = da.mean(dim="time") * scale + mean = da.mean(dim='time') * scale std = None return mean, std @@ -96,7 +97,7 @@ def concat_and_group(list_of_da, is_member=False, scale=1.0): amount_baseline_mean, _ = concat_and_group(baseline_precip) amount_pred_mean, amount_pred_std = concat_and_group(pred_precip, is_member=True) - wet_target_mean, _ = concat_and_group(target_wet, scale=100.0) + wet_target_mean, _ = concat_and_group(target_wet, scale=100.0) # scale to percentage wet_baseline_mean, _ = concat_and_group(baseline_wet, scale=100.0) wet_pred_mean, wet_pred_std = concat_and_group(pred_wet, is_member=True, scale=100.0) diff --git a/src/hirad/eval/diurnal_cycle_temp_wind.py b/src/hirad/eval/diurnal_cycle_temp_wind.py index d03e62c2..43a33b89 100644 --- a/src/hirad/eval/diurnal_cycle_temp_wind.py +++ b/src/hirad/eval/diurnal_cycle_temp_wind.py @@ -111,10 +111,15 @@ def main(cfg: DictConfig): logger.info(f"Processed {idx}/{len(times)} timesteps ({ts})") # Helper to concat and compute diurnal stats - def concat_and_group(list_of_da): + def concat_and_group(list_of_da, is_member=False, scale=1.0): da = xr.concat(list_of_da, dim="time").groupby("time.hour") - mean = da.mean(dim=[d for d in da.dims if d in ['time', 'member']]) - std = da.std(dim=[d for d in da.dims if d in ['time', 'member']]) + if is_member: + timmean = da.mean(dim='time') * scale + mean = timmean.mean(dim='member') + std = timmean.std(dim='member') + else: + mean = da.mean(dim='time') * scale + std = None return mean, std # Compute diurnal means and stds From 1873b5833fde39d677c53918d73eb2404144dfff Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Thu, 31 Jul 2025 16:43:03 +0200 Subject: [PATCH 109/302] add patched diffusion configs --- .../era_cosmo_training_patched.yaml | 24 ++++ .../model/era_cosmo_diffusion_patched.yaml | 13 ++ .../training/era_cosmo_diffusion_patched.yaml | 53 +++++++ .../training_era_cosmo_diffusion_patched.yaml | 27 ++++ src/hirad/inference/generate.py | 2 +- src/hirad/inference/stochastic_sampler.py | 3 +- src/hirad/training/train.py | 130 +++++++++--------- 7 files changed, 182 insertions(+), 70 deletions(-) create mode 100644 src/hirad/conf/generation/era_cosmo_training_patched.yaml create mode 100644 src/hirad/conf/model/era_cosmo_diffusion_patched.yaml create mode 100644 src/hirad/conf/training/era_cosmo_diffusion_patched.yaml create mode 100644 src/hirad/conf/training_era_cosmo_diffusion_patched.yaml diff --git a/src/hirad/conf/generation/era_cosmo_training_patched.yaml b/src/hirad/conf/generation/era_cosmo_training_patched.yaml new file mode 100644 index 00000000..e6e2d7c1 --- /dev/null +++ b/src/hirad/conf/generation/era_cosmo_training_patched.yaml @@ -0,0 +1,24 @@ +defaults: + - ../sampler@sampler: stochastic + - ../dataset@dataset: era_cosmo_inference + +num_ensembles: 16 + # Number of ensembles to generate per input + +patching: True +# Use patch-based sampling +overlap_pix: 4 +# Number of overlapping pixels between adjacent patches +boundary_pix: 2 +# Number of boundary pixels to be cropped out. 2 is recommended to address the boundary +# artifact. +patch_shape_x: 128 +patch_shape_y: 128 + +times_range: null +times: + - 20200926-1800 + # - 20200927-0000 + +perf: + num_writer_workers: 10 \ No newline at end of file diff --git a/src/hirad/conf/model/era_cosmo_diffusion_patched.yaml b/src/hirad/conf/model/era_cosmo_diffusion_patched.yaml new file mode 100644 index 00000000..9362932b --- /dev/null +++ b/src/hirad/conf/model/era_cosmo_diffusion_patched.yaml @@ -0,0 +1,13 @@ +name: patched_diffusion + # Name of the preconditioner +hr_mean_conditioning: True + # High-res mean (regression's output) as additional condition + +# Standard model parameters. +# Standard model parameters. +model_args: + gridtype: "learnable" + # Type of positional grid to use: 'sinusoidal', 'learnable', 'linear'. + # Controls how positional information is encoded. + N_grid_channels: 100 + # Number of channels for positional grid embeddings \ No newline at end of file diff --git a/src/hirad/conf/training/era_cosmo_diffusion_patched.yaml b/src/hirad/conf/training/era_cosmo_diffusion_patched.yaml new file mode 100644 index 00000000..679afd65 --- /dev/null +++ b/src/hirad/conf/training/era_cosmo_diffusion_patched.yaml @@ -0,0 +1,53 @@ +# Hyperparameters +hp: + training_duration: 1000000 + # Training duration based on the number of processed samples + total_batch_size: "auto" + # Total batch size + batch_size_per_gpu: 10 + # Batch size per GPU + lr: 0.0002 + # Learning rate + grad_clip_threshold: 1e6 + # no gradient clipping for defualt non-patch-based training + lr_decay: 0.7 + # LR decay rate + lr_rampup: 1000000 + # Rampup for learning rate, in number of samples + lr_decay_rate: 5e5 + # Learning rate decay threshold in number of samples, applied every lr_decay_rate samples. + patch_shape_x: 128 + patch_shape_y: 128 + # Patch size. Patch training is used if these dimensions differ from + # img_shape_x and img_shape_y. + patch_num: 7 + # Number of patches from a single sample. Total number of patches is + # patch_num * batch_size_global. + max_patch_per_gpu: 100 + # Maximum number of pataches a gpu can hold + +# Performance +perf: + fp_optimizations: amp-bf16 + # Floating point mode, one of ["fp32", "fp16", "amp-fp16", "amp-bf16"] + # "amp-{fp16,bf16}" activates Automatic Mixed Precision (AMP) with {float16,bfloat16} + dataloader_workers: 10 + # DataLoader worker processes + songunet_checkpoint_level: 0 # 0 means no checkpointing + # Gradient checkpointing level, value is number of layers to checkpoint + +# I/O +io: + regression_checkpoint_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_era5_cosmo/checkpoints_regression + # Where to load the regression checkpoint + print_progress_freq: 1000 + # How often to print progress + save_checkpoint_freq: 500000 + # How often to save the checkpoints, measured in number of processed samples + visualization_freq: 250000 + # how often to visualize network outputs + validation_freq: 50000 + # how often to record the validation loss, measured in number of processed samples + validation_steps: 100 + # how many loss evaluations are used to compute the validation loss per checkpoint + checkpoint_dir: . \ No newline at end of file diff --git a/src/hirad/conf/training_era_cosmo_diffusion_patched.yaml b/src/hirad/conf/training_era_cosmo_diffusion_patched.yaml new file mode 100644 index 00000000..38baba08 --- /dev/null +++ b/src/hirad/conf/training_era_cosmo_diffusion_patched.yaml @@ -0,0 +1,27 @@ +hydra: + job: + chdir: true + name: diffusion_era5_cosmo_patched + run: + dir: /capstor/scratch/cscs/pstamenk/outputs/training/${hydra:job.name} + +# Get defaults +defaults: + - _self_ + + # Dataset + - dataset/era_cosmo + + # Model + - model/era_cosmo_diffusion_patched + + - model_size/normal + + # Training + - training/era_cosmo_diffusion_patched + + # Inference visualization + - generation/era_cosmo_training_patched + + # Logging + - logging/era_cosmo_diffusion \ No newline at end of file diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index 23206568..a553af88 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -131,7 +131,7 @@ def main(cfg: DictConfig) -> None: else: net_reg = None - # Reset since we are using a different mode. + # Reset since we are using a different mode. if cfg.generation.perf.use_torch_compile: torch._dynamo.reset() # Only compile residual network diff --git a/src/hirad/inference/stochastic_sampler.py b/src/hirad/inference/stochastic_sampler.py index 606c911b..24c5f7a9 100644 --- a/src/hirad/inference/stochastic_sampler.py +++ b/src/hirad/inference/stochastic_sampler.py @@ -180,10 +180,11 @@ def stochastic_sampler( # input and position padding + patching if patching: + # print(f"Input for generator beofre patching {x_lr.shape}") # Patched conditioning [x_lr, mean_hr] # (batch_size * patch_num, C_in + C_out, patch_shape_y, patch_shape_x) x_lr = patching.apply(input=x_lr, additional_input=img_lr) - + # print(f"Input for generator after patching {x_lr.shape}") # Function to select the correct positional embedding for each patch def patch_embedding_selector(emb): # emb: (N_pe, image_shape_y, image_shape_x) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 05739296..519845b4 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -82,7 +82,7 @@ def main(cfg: DictConfig) -> None: logger0 = RankZeroLoggingWrapper(logger, dist) # rank 0 logger dataset_cfg = OmegaConf.to_container(cfg.dataset) - if hasattr(cfg.dataset, "validation_path"): + if hasattr(cfg.dataset, "validation_path") and cfg.dataset.validation_path is not None: train_test_split = True else: train_test_split = False @@ -222,19 +222,12 @@ def main(cfg: DictConfig) -> None: if hasattr(cfg.model, "model_args"): # override defaults from config file model_args.update(OmegaConf.to_container(cfg.model.model_args)) - use_torch_compile = False - use_apex_gn = False - profile_mode = False + use_torch_compile = getattr(cfg.training.perf, "torch_compile", False) + use_apex_gn = getattr(cfg.training.perf, "use_apex_gn", False) + profile_mode = getattr(cfg.training.perf, "profile_mode", False) - if hasattr(cfg.training.perf, "torch_compile"): - use_torch_compile = cfg.training.perf.torch_compile - if hasattr(cfg.training.perf, "use_apex_gn"): - use_apex_gn = cfg.training.perf.use_apex_gn - model_args["use_apex_gn"] = use_apex_gn - - if hasattr(cfg.training.perf, "profile_mode"): - profile_mode = cfg.training.perf.profile_mode - model_args["profile_mode"] = profile_mode + model_args["use_apex_gn"] = use_apex_gn + model_args["profile_mode"] = profile_mode if enable_amp: model_args["amp_mode"] = enable_amp @@ -622,7 +615,7 @@ def main(cfg: DictConfig) -> None: fields += [f"total_sec {(tick_end_time - start_time):<7.1f}"] fields += [f"sec_per_tick {(tick_end_time - tick_start_time):<7.1f}"] fields += [ - f"sec_per_sample {((tick_end_time - tick_start_time) / (cur_nimg - tick_start_nimg)):<7.2f}" + f"sec_per_sample {((tick_end_time - tick_start_time) / (cur_nimg - tick_start_nimg)):<7.4f}" ] fields += [ f"cpu_mem_gb {(psutil.Process(os.getpid()).memory_info().rss / 2**30):<6.2f}" @@ -787,62 +780,63 @@ def main(cfg: DictConfig) -> None: times = visualization_dataset.time() time_index = -1 output_paths_list = [] - for index, (img_clean_viz, img_lr_viz, *lead_time_label_viz) in enumerate( - iter(visualization_data_loader) - ): - time_index += 1 - logger0.info(f"starting index: {time_index}") + with torch.no_grad(): + for index, (img_clean_viz, img_lr_viz, *lead_time_label_viz) in enumerate( + iter(visualization_data_loader) + ): + time_index += 1 + logger0.info(f"starting index: {time_index}") + + # continue + if lead_time_label_viz: + lead_time_label_viz = lead_time_label_viz[0].to(dist.device).contiguous() + else: + lead_time_label_viz = None - # continue - if lead_time_label_viz: - lead_time_label_viz = lead_time_label_viz[0].to(dist.device).contiguous() - else: - lead_time_label_viz = None - - if use_apex_gn: - img_clean_viz = img_clean_viz.to( - dist.device, - dtype=input_dtype, - non_blocking=True, - ).to(memory_format=torch.channels_last) - img_lr_viz = img_lr_viz.to( - dist.device, - dtype=input_dtype, - non_blocking=True, - ).to(memory_format=torch.channels_last) - else: - img_clean_viz = ( - img_clean_viz.to(dist.device) - .to(input_dtype) - .contiguous() - ) - img_lr_viz = ( - img_lr_viz.to(dist.device) - .to(input_dtype) - .contiguous() - ) - with torch.autocast( - "cuda", dtype=amp_dtype, enabled=enable_amp - ): - image_pred_viz, image_reg_viz = generator.generate(img_lr_viz, lead_time_label_viz) - if dist.rank == 0: - # write out data in a seperate thread so we don't hold up inferencing - output_path = os.path.join(visualization_dir, f"{cur_nimg}_{times[visualization_sampler[time_index]]}") - output_paths_list.append(output_path) - if dist.rank==0 and not os.path.exists(output_path): - os.makedirs(output_path) - writer_threads.append( - writer_executor.submit( - save_images, - output_path, - times[visualization_sampler[time_index]], - visualization_dataset, - image_pred_viz.cpu().numpy(), - img_clean_viz.cpu().numpy(), - img_lr_viz.cpu().numpy(), - image_reg_viz.cpu().numpy() if image_reg_viz is not None else None, + if use_apex_gn: + img_clean_viz = img_clean_viz.to( + dist.device, + dtype=input_dtype, + non_blocking=True, + ).to(memory_format=torch.channels_last) + img_lr_viz = img_lr_viz.to( + dist.device, + dtype=input_dtype, + non_blocking=True, + ).to(memory_format=torch.channels_last) + else: + img_clean_viz = ( + img_clean_viz.to(dist.device) + .to(input_dtype) + .contiguous() + ) + img_lr_viz = ( + img_lr_viz.to(dist.device) + .to(input_dtype) + .contiguous() + ) + with torch.autocast( + "cuda", dtype=amp_dtype, enabled=enable_amp + ): + image_pred_viz, image_reg_viz = generator.generate(img_lr_viz, lead_time_label_viz) + if dist.rank == 0: + # write out data in a seperate thread so we don't hold up inferencing + output_path = os.path.join(visualization_dir, f"{cur_nimg}_{times[visualization_sampler[time_index]]}") + output_paths_list.append(output_path) + if dist.rank==0 and not os.path.exists(output_path): + os.makedirs(output_path) + writer_threads.append( + writer_executor.submit( + save_images, + output_path, + times[visualization_sampler[time_index]], + visualization_dataset, + image_pred_viz.cpu().numpy(), + img_clean_viz.cpu().numpy(), + img_lr_viz.cpu().numpy(), + image_reg_viz.cpu().numpy() if image_reg_viz is not None else None, + ) ) - ) # make sure all the workers are done writing if dist.rank == 0: for thread in list(writer_threads): From 79c6f6c4777b105b229aa41afe3a03bdfdd545b4 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Thu, 31 Jul 2025 18:39:46 +0200 Subject: [PATCH 110/302] small fixes --- src/hirad/eval/diurnal_cycle_precip.py | 1 - src/hirad/eval/diurnal_cycle_temp_wind.py | 12 ++++-------- src/hirad/eval/percentile99_cycle_precip.py | 4 ++-- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/hirad/eval/diurnal_cycle_precip.py b/src/hirad/eval/diurnal_cycle_precip.py index 2bc31c07..50961e34 100644 --- a/src/hirad/eval/diurnal_cycle_precip.py +++ b/src/hirad/eval/diurnal_cycle_precip.py @@ -101,7 +101,6 @@ def concat_and_group(list_of_da, is_member=False, scale=1.0): wet_baseline_mean, _ = concat_and_group(baseline_wet, scale=100.0) wet_pred_mean, wet_pred_std = concat_and_group(pred_wet, is_member=True, scale=100.0) - # Plot helper def save_plot(hour, means, stds, labels, ylabel, title, out_path): hrs = np.concatenate([hour.values, [24]]) plt.figure(figsize=(8,4)) diff --git a/src/hirad/eval/diurnal_cycle_temp_wind.py b/src/hirad/eval/diurnal_cycle_temp_wind.py index 43a33b89..8e1ff309 100644 --- a/src/hirad/eval/diurnal_cycle_temp_wind.py +++ b/src/hirad/eval/diurnal_cycle_temp_wind.py @@ -125,13 +125,12 @@ def concat_and_group(list_of_da, is_member=False, scale=1.0): # Compute diurnal means and stds temp_target_mean, _ = concat_and_group(target_temp) temp_baseline_mean, _ = concat_and_group(baseline_temp) - temp_pred_mean, temp_pred_std = concat_and_group(pred_temp) + temp_pred_mean, temp_pred_std = concat_and_group(pred_temp, is_member=True) wind_target_mean, _ = concat_and_group(target_wind) wind_baseline_mean, _ = concat_and_group(baseline_wind) - wind_pred_mean, wind_pred_std = concat_and_group(pred_wind) + wind_pred_mean, wind_pred_std = concat_and_group(pred_wind, is_member=True) - # Plot helper def save_plot(hour, means, stds, labels, ylabel, title, out_path): hrs = np.concatenate([hour.values, [24]]) plt.figure(figsize=(8,4)) @@ -140,7 +139,7 @@ def save_plot(hour, means, stds, labels, ylabel, title, out_path): line, = plt.plot(hrs, vals, label=label) if std is not None: stdv = np.append(std.values, std.values[0]) - plt.fill_between(hrs, vals - stdv, vals + stdv, color=line.get_color(), alpha=0.3) + plt.fill_between(hrs, np.maximum(vals - stdv, 0), vals + stdv, color=line.get_color(), alpha=0.3) plt.xlabel('Hour (UTC)') plt.xticks(range(0,25,3)) plt.xlim(0,24) @@ -163,6 +162,7 @@ def save_plot(hour, means, stds, labels, ylabel, title, out_path): 'Diurnal Cycle of 2m Temperature', out_root / 'diurnal_cycle_2t.png' ) + save_plot( wind_target_mean.hour, [wind_target_mean, wind_baseline_mean, wind_pred_mean], @@ -175,9 +175,5 @@ def save_plot(hour, means, stds, labels, ylabel, title, out_path): logger.info("Plots saved.") - plt.imshow(target[t2m_out], cmap='viridis') - plt.colorbar(label='2m Temperature [°C]') - plt.savefig('lsm.png') - if __name__ == '__main__': main() diff --git a/src/hirad/eval/percentile99_cycle_precip.py b/src/hirad/eval/percentile99_cycle_precip.py index 51fc28b0..bd6991db 100644 --- a/src/hirad/eval/percentile99_cycle_precip.py +++ b/src/hirad/eval/percentile99_cycle_precip.py @@ -124,7 +124,7 @@ def main(cfg: DictConfig): mem_f99.append(np.nanmean(f99_m)) # mean over grid points # ensemble-level mean and std over member-wise percentiles pct99_mean['prediction'].append(np.nanmean(mem_f99)) - pct99_std['prediction'].append(np.std(pct99_mean['prediction'])) + pct99_std['prediction'].append(np.nanstd(mem_f99)) # clean up per-hour buffers del mem_f99, stack_m, f99_m @@ -133,7 +133,7 @@ def main(cfg: DictConfig): hrs_c = list(range(24)) + [list(range(24))[0] + 24] pct99_lines = [ cycle_fn(pct99_mean['target']), - cycle_fn(pct99_mean['baseline']), # 6 becasue bug in dataset? + cycle_fn(pct99_mean['baseline']), ( cycle_fn(pct99_mean['prediction']), cycle_fn(pct99_std['prediction']) From 001d5e2b8e2f7ce5eabe48a43cccf7e4b461ce66 Mon Sep 17 00:00:00 2001 From: David Leutwyler <14977216+leuty@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:46:14 +0200 Subject: [PATCH 111/302] New script to plot maps (#18) --- src/hirad/eval/__init__.py | 2 +- src/hirad/eval/plot_maps.py | 265 ++++++++++++++++++++++++++++++++ src/hirad/eval/plotting.py | 96 +++++++++++- src/hirad/inference/generate.py | 15 +- src/hirad/maps.sh | 49 ++++++ 5 files changed, 407 insertions(+), 20 deletions(-) create mode 100644 src/hirad/eval/plot_maps.py create mode 100644 src/hirad/maps.sh diff --git a/src/hirad/eval/__init__.py b/src/hirad/eval/__init__.py index 82e9073a..31b49dd2 100644 --- a/src/hirad/eval/__init__.py +++ b/src/hirad/eval/__init__.py @@ -1,2 +1,2 @@ from .metrics import absolute_error, compute_mae, average_power_spectrum, crps -from .plotting import plot_error_projection, plot_power_spectra, plot_scores_vs_t \ No newline at end of file +from .plotting import plot_map, plot_error_projection, plot_power_spectra, plot_scores_vs_t \ No newline at end of file diff --git a/src/hirad/eval/plot_maps.py b/src/hirad/eval/plot_maps.py new file mode 100644 index 00000000..e68f956e --- /dev/null +++ b/src/hirad/eval/plot_maps.py @@ -0,0 +1,265 @@ +"""Generates maps of precipitation, temperature, and wind components/speed/direction.""" +import logging +from dataclasses import dataclass, field, replace +from datetime import datetime +from pathlib import Path + +import hydra +import numpy as np +import torch +from omegaconf import DictConfig, OmegaConf + +from hirad.datasets import get_dataset_and_sampler_inference +from hirad.distributed import DistributedManager +from hirad.eval import compute_mae, plot_map +from hirad.eval.plotting import plot_map_precipitation, wind_direction +from hirad.utils.function_utils import get_time_from_range +from hirad.utils.inference_utils import calculate_bounds + +@dataclass +class ChannelMeta: + """Metadata for a channel.""" + name: str + cmap: str = "viridis" + me_cmap: str | None = None + unit: str = "" + norm: any = None + err_vmin: float = None + err_vmax: float = None + vmin: float = None + vmax: float = None + extend: str = "both" + precip_kwargs: dict = field(default_factory=lambda: {"threshold": 0.1, "rfac": 100.0}) + + @classmethod + def get(cls, ch_or_name: "ChannelMeta | str | None", *, vmin=None, vmax=None) -> "ChannelMeta": + name = getattr(ch_or_name, "name", ch_or_name or "") + base = CHANNELS.get(name) or cls(name=name) + if vmin is not None or vmax is not None: + return replace(base, vmin=vmin, vmax=vmax) + return base + +CHANNELS = { + "tp": ChannelMeta(name="tp", cmap=None, unit="mm/h", extend="max", precip_kwargs={"threshold": 0.1, "rfac": 100.0}), + "2t": ChannelMeta(name="2t", cmap="RdYlBu_r", me_cmap="RdBu", unit="K", err_vmin=-4.5, err_vmax=4.5), + "10u": ChannelMeta(name="10u", cmap="BrBG", me_cmap="BrBG", unit="m/s", err_vmin=-10, err_vmax=10, vmin=-10, vmax=10), + "10v": ChannelMeta(name="10v", cmap="BrBG", me_cmap="BrBG", unit="m/s", err_vmin=-10, err_vmax=10, vmin=-10, vmax=10), +} + +def format_time_str(dt_str, input_fmt="%Y%m%d-%H%M", output_fmt="%d-%m-%Y %H:%M"): + """Convert time string from input_fmt to output_fmt.""" + dt = datetime.strptime(dt_str, input_fmt) + return dt.strftime(output_fmt) + +class FileRepository: + def __init__(self, root_path): + self.root = Path(root_path) + + def load(self, time, filename): + return torch.load(self.root / time / filename, weights_only=False) + + def _ensure_dir(self, *subdirs): + """Make (and return) root_path/subdir1/subdir2/….""" + d = self.root.joinpath(*subdirs) + d.mkdir(parents=True, exist_ok=True) + return d + + def _make_fname(self, curr_time, prefix, suffix, member_idx): + """Build a filename like '20250724-1230-prefix-suffix[_member]'.""" + base = f"{curr_time}-{prefix}-{suffix}" + if member_idx is not None: + base += f"_{member_idx}" + return base + + def output_file(self, channel, curr_time, suffix, member_idx=None): + # decide on the folder name: e.g. 'tp_100m' or just 'tp' + folder = f"{channel.name}_{channel.level}" if getattr(channel, "level", None) else channel.name + fname = self._make_fname(curr_time, channel.name, suffix, member_idx) + return self._ensure_dir(folder) / fname + + def wind_file(self, wind_type, curr_time, suffix, member_idx=None): + # e.g. wind_type = "FF10m" or "DD10m" + fname = self._make_fname(curr_time, wind_type, suffix, member_idx) + return self._ensure_dir(wind_type) / fname + +def map_output_to_input_channels(output_channels, input_channels): + """ + Maps output channels to input channels based on their names. + """ + return { + j: next((k for k, input_channel in enumerate(input_channels) if input_channel.name == output_channel.name), -1) + for j, output_channel in enumerate(output_channels) + } + +def save_field(name, data, meta, files, channel, t, member=None, kind=None, cmap=None, vmin=None, vmax=None, custom_path=None, plot_func=None, title=None, **plot_kwargs): + """Save a field by plotting it with the appropriate function and parameters. Handles precipitation specially and supports custom paths.""" + # Determine output path + suffix = f"{name}-{kind}" if kind else f"{name}" + out_path = custom_path or files.output_file(channel, t, suffix, member) + + # Choose plotting function + plot = plot_func or plot_map + + # Case dependent plot parameters + title = title or meta.name + extend = 'max' if kind == 'mae' else meta.extend + common = {'title': title, 'norm': meta.norm, 'extend': extend, **plot_kwargs} + + # Precipitation case + if plot.__name__ == 'plot_map_precipitation': + precip_args = {**meta.precip_kwargs, **{k: plot_kwargs[k] for k in meta.precip_kwargs if k in plot_kwargs}} + plot(data, out_path, title=title, **precip_args) + else: + plot(data, out_path, vmin=vmin if vmin is not None else meta.vmin, vmax=vmax if vmax is not None else meta.vmax, cmap=cmap or meta.cmap, label=meta.unit, **common) + + +@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") +def main(cfg: DictConfig) -> None: + + # Initialize distributed manager + DistributedManager.initialize() + + # Initialize logger + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger("plot_maps") + + if cfg.generation.times_range: + times = get_time_from_range(cfg.generation.times_range, time_format="%Y%m%d-%H%M") + + dataset_cfg = OmegaConf.to_container(cfg.dataset) + has_lead_time = cfg.generation.get("has_lead_time", False) + dataset, sampler = get_dataset_and_sampler_inference( + dataset_cfg=dataset_cfg, times=times, has_lead_time=has_lead_time + ) + + output_path = getattr(cfg.generation.io, "output_path", "./outputs") + files = FileRepository(output_path) + + for curr_time in times: + prediction = files.load(curr_time, f'{curr_time}-predictions') + baseline = files.load(curr_time, f'{curr_time}-baseline') + target = files.load(curr_time, f'{curr_time}-target') + + input_channels = dataset.input_channels() + output_channels = dataset.output_channels() + output_to_input_channel_map = map_output_to_input_channels(output_channels, input_channels) + + for idx, channel in enumerate(output_channels): + input_channel_idx = output_to_input_channel_map[idx] + plot_title = f"{format_time_str(curr_time)}: {getattr(channel, 'title', channel.name)}" + vmin, vmax = calculate_bounds( + target[idx,:,:], + prediction[:,idx,:,:], + baseline[input_channel_idx,:,:] + ) + metadata = ChannelMeta.get(channel, vmin=vmin, vmax=vmax) + + if channel.name == "tp": + save_field( + "target", target[idx, :, :], metadata, files, channel, curr_time, + plot_func=plot_map_precipitation, title=plot_title + ) + save_field( + "baseline", baseline[input_channel_idx, :, :], metadata, files, channel, curr_time, + plot_func=plot_map_precipitation, title=plot_title + ) + if prediction.shape[0] > 1: + for member_idx in range(prediction.shape[0]): + save_field( + "prediction", prediction[member_idx, idx, :, :], metadata, files, channel, curr_time, + member=member_idx, plot_func=plot_map_precipitation, title=plot_title + ) + else: + save_field( + "prediction", prediction[0, idx, :, :], metadata, files, channel, curr_time, + plot_func=plot_map_precipitation, title=plot_title + ) + continue + + # Plot target and baseline + save_field("target", target[idx, :, :], metadata, files, channel, curr_time, title=plot_title) + save_field("baseline", baseline[input_channel_idx, :, :], metadata, files, channel, curr_time, title=plot_title) + + # Baseline MAE and ME + _, baseline_mae = compute_mae(baseline[input_channel_idx, :, :], target[idx, :, :]) + baseline_me = (baseline[input_channel_idx, :, :] - target[idx, :, :]) + save_field("baseline", baseline_mae.reshape(baseline[input_channel_idx, :, :].shape), metadata, files, channel, curr_time, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title) + save_field("baseline", baseline_me, metadata, files, channel, curr_time, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title) + + # Ensemble predictions + for member_idx in range(prediction.shape[0]): + member = prediction[member_idx, idx, :, :] + save_field("prediction", member, metadata, files, channel, curr_time, member=member_idx, title=plot_title) + _, prediction_mae = compute_mae(member, target[idx, :, :]) + save_field("prediction", prediction_mae.reshape(member.shape), metadata, files, channel, curr_time, member=member_idx, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title) + prediction_me = (member - target[idx, :, :]) + save_field("prediction", prediction_me, metadata, files, channel, curr_time, member=member_idx, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title) + + # Plot Windspeed and direction + wind_channels = {ch.name: idx for idx, ch in enumerate(output_channels) if ch.name in ("10u", "10v")} + if "10u" in wind_channels and "10v" in wind_channels: + idx_10u = wind_channels["10u"] + idx_10v = wind_channels["10v"] + input_idx_10u = output_to_input_channel_map[idx_10u] + input_idx_10v = output_to_input_channel_map[idx_10v] + + # Compute windspeed and direction for target, baseline, prediction + target_wind_speed = np.hypot(target[idx_10u, :, :], target[idx_10v, :, :]) + target_wind_dir = wind_direction(target[idx_10u, :, :], target[idx_10v, :, :]) + baseline_wind_speed = np.hypot(baseline[input_idx_10u, :, :], baseline[input_idx_10v, :, :]) + baseline_wind_dir = wind_direction(baseline[input_idx_10u, :, :], baseline[input_idx_10v, :, :]) + prediction_wind_speed = np.hypot(prediction[:, idx_10u, :, :], prediction[:, idx_10v, :, :]) + prediction_wind_dir = wind_direction(prediction[:, idx_10u, :, :], prediction[:, idx_10v, :, :]) + + plot_title_speed = f"{format_time_str(curr_time)}: FF10m" + plot_title_dir = f"{format_time_str(curr_time)}: DD10m" + + wind_meta = ChannelMeta.get("10u", vmin=0, vmax=10) + dir_meta = ChannelMeta.get("10u", vmin=0, vmax=360) + + # Save windspeed plots + save_field( + "FF10m-target", target_wind_speed, wind_meta, files, None, curr_time, + cmap="viridis", vmin=0, vmax=10, extend='max', + custom_path=files.wind_file("FF10m", curr_time, "FF10m-target"), + plot_func=plot_map, title=plot_title_speed + ) + save_field( + "FF10m-baseline", baseline_wind_speed, wind_meta, files, None, curr_time, + cmap="viridis", vmin=0, vmax=10, extend='max', + custom_path=files.wind_file("FF10m", curr_time, "FF10m-baseline"), + plot_func=plot_map, title=plot_title_speed + ) + for member_idx in range(prediction.shape[0]): + save_field( + "FF10m-prediction", prediction_wind_speed[member_idx], wind_meta, files, None, curr_time, + member=member_idx, cmap="viridis", vmin=0, vmax=10, extend='max', + custom_path=files.wind_file("FF10m", curr_time, "FF10m-prediction", member_idx), + plot_func=plot_map, title=plot_title_speed + ) + + # Save wind direction plots + save_field( + "DD10m-target", target_wind_dir, dir_meta, files, None, curr_time, + cmap="twilight", vmin=0, vmax=360, + custom_path=files.wind_file("DD10m", curr_time, "DD10m-target"), + plot_func=plot_map, title=plot_title_dir + ) + save_field( + "DD10m-baseline", baseline_wind_dir, dir_meta, files, None, curr_time, + cmap="twilight", vmin=0, vmax=360, + custom_path=files.wind_file("DD10m", curr_time, "DD10m-baseline"), + plot_func=plot_map, title=plot_title_dir + ) + for member_idx in range(prediction.shape[0]): + save_field( + "DD10m-prediction", prediction_wind_dir[member_idx], dir_meta, files, None, curr_time, + member=member_idx, cmap="twilight", vmin=0, vmax=360, + custom_path=files.wind_file("DD10m", curr_time, "DD10m-prediction", member_idx), + plot_func=plot_map, title=plot_title_dir + ) + + logger.info("Image loading and plotting completed.") + +if __name__ == "__main__": + main() diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index a7603b79..2eb1c62d 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -1,13 +1,99 @@ import logging -import os - -from hirad.eval import crps, absolute_error import cartopy.crs as ccrs +import cartopy.feature as cfeature import matplotlib.pyplot as plt import numpy as np -import torch +from matplotlib.colors import BoundaryNorm, ListedColormap + + +# COSMO‑2 GRID: TODO: Add to dataset config +LAT = np.arange(-4.42, 3.36 + 0.02, 0.02) +LON = np.arange(-6.82, 4.80 + 0.02, 0.02) +RELAX_ZONE = 19 # Number of points dropped on each side (relaxation zone) + +def plot_map(values: np.array, + filename: str, + label='', + title='', + vmin=None, + vmax=None, + cmap=None, + extend='neither', + norm=None, + ticks=None): + """Plot observed or interpolated data in a scatter plot.""" + logging.info(f'Creating map: {filename}') + latitudes = LAT[RELAX_ZONE : RELAX_ZONE + 352] + longitudes = LON[RELAX_ZONE : RELAX_ZONE + 544] + lon2d, lat2d = np.meshgrid(longitudes, latitudes) + + fig, ax = plt.subplots( + figsize=(8, 6), + subplot_kw={"projection": ccrs.RotatedPole(pole_longitude=-170.0, + pole_latitude= 43.0)} + ) + contour = ax.pcolormesh( + lon2d, lat2d, values, + cmap=cmap, shading="auto", + norm=norm if norm else None, + vmin=None if norm else vmin, + vmax=None if norm else vmax, + ) + ax.coastlines() + ax.add_feature(cfeature.BORDERS, linewidth=1) + ax.gridlines(visible=False) + ax.set_xticks([]) + ax.set_yticks([]) + + plt.title(title) + cbar = plt.colorbar( + contour, + label=label, + orientation="horizontal", + extend=extend, + shrink=0.75, + pad=0.02 + ) + if ticks is not None: + cbar.set_ticks(ticks) + + plt.tight_layout() + fig.savefig(f"{filename}.png", dpi=300, bbox_inches="tight") + plt.close(fig) + +def plot_map_precipitation(values, filename, title='', threshold=0.1, rfac=100.0): + """Plot precipitation data with specific colormap and thresholds.""" + # Scale and mask values below threshold + values = rfac * values # m/h --> mm/h + values = np.ma.masked_where(values <= threshold, values) + + # Predefined colors and bounds specific for precipitation + colors = ['none', 'powderblue', 'dodgerblue', 'mediumblue', + 'forestgreen', 'limegreen', 'lawngreen', + 'yellow', 'gold', 'darkorange', 'red', + 'darkviolet', 'violet', 'thistle'] + bounds = [0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 30, 50, 70, 100, 150, 200] + + cmap = ListedColormap(colors) + norm = BoundaryNorm(bounds, ncolors=len(colors), clip=False) + + plot_map( + values, filename, + cmap=cmap, + norm=norm, + ticks=bounds, + title=title, + label='mm/h', + extend='max' + ) + +def wind_direction(u, v): + """Compute wind direction from u and v components.""" + return(np.arctan2(-u, -v) * 180 / np.pi) % 360 + +@DeprecationWarning def plot_error_projection(values: np.array, latitudes: np.array, longitudes: np.array, filename: str, label='', title='', vmin=None, vmax=None): """Plot observed or interpolated data in a scatter plot.""" fig = plt.figure() @@ -56,4 +142,4 @@ def plot_power_spectra(freqs: dict, spec: dict, channel_name, filename): #plt.psd(x) logging.info(f'plotting values to {filename}') plt.savefig(filename) - plt.close('all') + plt.close('all') \ No newline at end of file diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index a553af88..bd4d2e1b 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -13,7 +13,7 @@ from hirad.models import EDMPrecondSuperResolution, UNet from hirad.inference import Generator -from hirad.utils.inference_utils import save_images, save_results_as_torch +from hirad.utils.inference_utils import save_results_as_torch from hirad.utils.function_utils import get_time_from_range from hirad.utils.checkpoint import load_checkpoint @@ -250,19 +250,6 @@ def elapsed_time(self, _): batch_size = image_out.shape[0] # write out data in a seperate thread so we don't hold up inferencing - if not cfg.generation.times_range: - writer_threads.append( - writer_executor.submit( - save_images, - savedir, - times[sampler[time_index]], - dataset, - image_out.cpu().numpy(), - image_tar.cpu().numpy(), - image_lr.cpu().numpy(), - image_reg.cpu().numpy() if image_reg is not None else None, - ) - ) writer_threads.append( writer_executor.submit( save_results_as_torch, diff --git a/src/hirad/maps.sh b/src/hirad/maps.sh new file mode 100644 index 00000000..bcf40085 --- /dev/null +++ b/src/hirad/maps.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +#SBATCH --job-name="plot" + +### HARDWARE ### +#SBATCH --partition=debug +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=2 +#SBATCH --gpus-per-node=2 +#SBATCH --cpus-per-task=72 +#SBATCH --time=00:10:00 +#SBATCH --no-requeue +#SBATCH --exclusive + +### OUTPUT ### +#SBATCH --output=./logs/plot_maps.log + +### ENVIRONMENT #### +#SBATCH -A a161 + +# Choose method to initialize dist in pythorch +export DISTRIBUTED_INITIALIZATION_METHOD=SLURM + +MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" +echo "Master node : $MASTER_ADDR" +# Get IP for hostname. +MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" +echo "Master address : $MASTER_ADDR" +export MASTER_ADDR +export MASTER_PORT=29500 +echo "Master port: $MASTER_PORT" + +# Get number of physical cores using Python +# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") +# # Use SLURM_NTASKS (number of processes to be launched by torchrun) +# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} +# # Compute threads per process +# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) +# export OMP_NUM_THREADS=$OMP_THREADS +export OMP_NUM_THREADS=72 +# echo "Physical cores: $PHYSICAL_CORES" +# echo "Local processes: $LOCAL_PROCS" +# echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" + +# python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml +srun --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . --no-dependencies + python src/hirad/eval/plot_maps.py --config-name=generate_era_cosmo.yaml +" \ No newline at end of file From 8efcedd1076b8c03b3c2a16151ab8f015657e513 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Mon, 4 Aug 2025 15:34:16 +0200 Subject: [PATCH 112/302] use more xarray --- src/hirad/eval/percentile99_cycle_precip.py | 99 ++++++++++++--------- 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/src/hirad/eval/percentile99_cycle_precip.py b/src/hirad/eval/percentile99_cycle_precip.py index bd6991db..abfa5487 100644 --- a/src/hirad/eval/percentile99_cycle_precip.py +++ b/src/hirad/eval/percentile99_cycle_precip.py @@ -14,6 +14,7 @@ import numpy as np import torch from omegaconf import DictConfig, OmegaConf +import xarray as xr from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager @@ -82,55 +83,67 @@ def main(cfg: DictConfig): # Land-sea mask lsm_data = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy').reshape(352,544) - land_mask = np.where(lsm_data >= 0.5, 1.0, np.nan) + land_mask = xr.DataArray( + np.where(lsm_data >= 0.5, 1.0, np.nan), + dims=['lat', 'lon'] + ) # Storage for diurnal cycles - pct99_mean = {'target': [], 'baseline': [], 'prediction': []} - pct99_std = {'target': [], 'baseline': [], 'prediction': []} - - # -- Target and Baseline: compute per hour -- + pct99_mean = {} + pct99_std = {} + + # -- Process target and baseline -- for mode in ['target', 'baseline']: logger.info(f"Processing mode: {mode}") - for h in list(range(24)): - arrs = [ - load(ts, f"{ts}-{mode}")[tp_out if mode == 'target' else tp_in] * land_mask - for ts in times if hour_of(ts) == h - ] - stack = np.stack(arrs, axis=0) - f99 = np.percentile(stack, 99, axis=0) - pct99_mean[mode].append(np.nanmean(f99) if mode == 'target' else np.nanmean(f99) / 6.0) # 6 because 1h -> accumulation period is 6h in hourly ERA5 dataset - del arrs, stack, f99 + + data_list = [] + for ts in times: + data = load(ts, f"{ts}-{mode}")[tp_out if mode == 'target' else tp_in] * land_mask + data_list.append(data) + + da = xr.DataArray( + np.stack(data_list, axis=0), + dims=['time', 'lat', 'lon'], + coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} + ) + + # Group by hour and compute 99th percentile + hourly_p99 = da.groupby('time.hour').quantile(0.99, dim='time') + + # Apply scaling factor for baseline + if mode == 'baseline': + hourly_p99 = hourly_p99 / 6.0 + + pct99_mean[mode] = hourly_p99.mean(dim=['lat', 'lon']) # -- Predictions: compute per hour per member, then mean+std across members -- - # Determine number of ensemble members - sample = load(times[0], f"{times[0]}-predictions") # [n_members, n_channels, lat, lon] - data_sample = sample[:, tp_out] - n_members = data_sample.shape[0] - - for h in list(range(24)): - logger.info(f"Processing predictions for hour {h}") - mem_f99 = [] - # for each ensemble member, gather its hourly fields - for m in range(n_members): - arrs = [] - for ts in times: - if hour_of(ts) != h: - continue - preds = load(ts, f"{ts}-predictions") # [n_members, n_channels, ...] - arrs.append(preds[m, tp_out] * land_mask) # apply mask - # stack over time and compute 99th percentile at each grid point - stack_m = np.stack(arrs, axis=0) - f99_m = np.percentile(stack_m, 99, axis=0) - mem_f99.append(np.nanmean(f99_m)) # mean over grid points - # ensemble-level mean and std over member-wise percentiles - pct99_mean['prediction'].append(np.nanmean(mem_f99)) - pct99_std['prediction'].append(np.nanstd(mem_f99)) - # clean up per-hour buffers - del mem_f99, stack_m, f99_m - - # Prepare cyclic series - cycle_fn = lambda x: x + [x[0]] - hrs_c = list(range(24)) + [list(range(24))[0] + 24] + logger.info("Processing predictions") + + # Load all prediction data at once into xarray + pred_data_list = [] + for ts in times: + preds = load(ts, f"{ts}-predictions") # [n_members, n_channels, lat, lon] + pred_data_list.append(preds[:, tp_out] * land_mask) # apply mask + + pred_da = xr.DataArray( + np.stack(pred_data_list, axis=1), # [n_members, time, lat, lon] + dims=['member', 'time', 'lat', 'lon'], + coords={ + 'member': range(len(pred_data_list[0])), + 'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times] + } + ) + + # Group by hour, compute 99th percentile across time, then spatial mean + hourly_p99_by_member = pred_da.groupby('time.hour').quantile(0.99, dim='time').mean(dim=['lat', 'lon']) + + # Store ensemble statistics as xarray DataArrays + pct99_mean['prediction'] = hourly_p99_by_member.mean(dim='member') + pct99_std['prediction'] = hourly_p99_by_member.std(dim='member') + + # Prepare cyclic lists for plotting + cycle_fn = lambda x: x.values.tolist() + [x.values.tolist()[0]] + hrs_c = list(range(24)) + [0 + 24] pct99_lines = [ cycle_fn(pct99_mean['target']), cycle_fn(pct99_mean['baseline']), From 99a066116af67f0cb1c77ccb9278c8b76125e15b Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Mon, 4 Aug 2025 19:15:27 +0200 Subject: [PATCH 113/302] add histograms --- src/hirad/eval/hist.py | 279 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 src/hirad/eval/hist.py diff --git a/src/hirad/eval/hist.py b/src/hirad/eval/hist.py new file mode 100644 index 00000000..804096dc --- /dev/null +++ b/src/hirad/eval/hist.py @@ -0,0 +1,279 @@ +""" +Plots the domain-mean precipitation distribution over land. + +This script computes and visualizes the distribution of precipitation values +across the land domain for different data sources (target, baseline, predictions). +""" +import logging +from pathlib import Path + +import hydra +import matplotlib.pyplot as plt +import numpy as np +import torch +from omegaconf import DictConfig, OmegaConf +import xarray as xr + +from hirad.datasets import get_dataset_and_sampler_inference +from hirad.distributed import DistributedManager +from hirad.utils.function_utils import get_time_from_range + +# Constants +CONV_FACTOR = 100 # Convert meters to mm/h +LOG_INTERVAL = 24 # Log progress every N timesteps + + +def save_distribution_plot(hist_data_dict, bin_edges, labels, colors, title, ylabel, out_path, percentiles_data=None): + """Save distribution plot with pre-computed histograms.""" + Path(out_path).parent.mkdir(parents=True, exist_ok=True) + + plt.figure(figsize=(10, 6)) + + # Plot histograms from pre-computed bin counts + for (key, hist_data), label, color in zip(hist_data_dict.items(), labels, colors): + if isinstance(hist_data, tuple): # Handle ensemble data + # Plot individual members with transparency + for i, member_hist in enumerate(hist_data): + alpha = 0.5 if i > 0 else 0.7 + label_member = label if i == 0 else None + # Plot histogram from bin counts + bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2 + plt.plot(bin_centers, member_hist, alpha=alpha, color=color, + label=label_member, drawstyle='steps-mid') + else: + # Plot histogram from bin counts + bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2 + plt.plot(bin_centers, hist_data, alpha=0.7, color=color, + label=label, linewidth=2, drawstyle='steps-mid') + + plt.xscale('log') + plt.yscale('log') + plt.xlabel(ylabel) + plt.ylabel('Probability Density') + plt.title(title) + plt.grid(True, alpha=0.3) + + # Add percentile lines if provided + if percentiles_data: + # Define colors for different datasets + percentile_colors = {'target': 'blue', 'baseline': 'orange', 'predictions': 'green'} + # Track if we've added legend labels for line styles + legend_added = {'99': False, '99.9': False, '99.99': False} + + for dataset_name, percentiles in percentiles_data.items(): + if dataset_name in ['target', 'baseline']: + # Plot percentiles for target and baseline + color = percentile_colors[dataset_name] + for percentile, value in percentiles.items(): + if percentile == 99: + linestyle = '--' + legend_label = '99th percentiles' if not legend_added['99'] else None + legend_added['99'] = True + elif percentile == 99.9: + linestyle = ':' + legend_label = '99.9th percentiles' if not legend_added['99.9'] else None + legend_added['99.9'] = True + elif percentile == 99.99: + linestyle = '-.' + legend_label = '99.99th percentiles' if not legend_added['99.99'] else None + legend_added['99.99'] = True + + plt.vlines(x=value, colors=color, + linestyles=linestyle, alpha=0.8, label=legend_label) + + elif dataset_name == 'predictions': + # Plot percentiles for ensemble members + color = percentile_colors[dataset_name] + for member_name, member_percentiles in percentiles.items(): + for percentile, value in member_percentiles.items(): + if percentile == 99: + linestyle = '--' + legend_label = '99th percentiles' if not legend_added['99'] else None + legend_added['99'] = True + elif percentile == 99.9: + linestyle = ':' + legend_label = '99.9th percentiles' if not legend_added['99.9'] else None + legend_added['99.9'] = True + elif percentile == 99.99: + linestyle = '-.' + legend_label = '99.99th percentiles' if not legend_added['99.99'] else None + legend_added['99.99'] = True + + plt.vlines(x=value, colors=color, + linestyles=linestyle, alpha=0.6, label=legend_label) + + plt.legend() + plt.tight_layout() + plt.savefig(out_path, dpi=300, bbox_inches='tight') + plt.close() + + +@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") +def main(cfg: DictConfig): + # Setup logging + DistributedManager.initialize() + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + + logger.info("Starting computation for domain-mean precipitation distribution over land") + times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + logger.info(f"Loaded {len(times)} timesteps to process") + + # Initialize dataset + ds_cfg = OmegaConf.to_container(cfg.dataset) + dataset, _ = get_dataset_and_sampler_inference( + ds_cfg, times, cfg.generation.get('has_lead_time', False) + ) + logger.info("Dataset and sampler initialized") + + # Output root and loader + out_root = Path(cfg.generation.io.output_path or './outputs') + + def load(ts, fn): + return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR + + # Find channel indices + out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} + in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} + tp_out = out_ch['tp'] + tp_in = in_ch.get('tp', tp_out) + logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") + + # Land-sea mask + lsm_data = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy').reshape(352,544) + land_mask = xr.DataArray( + np.where(lsm_data >= 0.5, 1.0, np.nan), + dims=['lat', 'lon'] + ) + + # Define histogram bins + bins = np.logspace(-1, 1, 50) # Log-spaced bins for precipitation + + # Storage for histogram data + hist_data = {} + # Store all land values for percentile calculation + all_land_values = {} + + # -- Process target and baseline -- + for mode in ['target', 'baseline']: + logger.info(f"Processing mode: {mode}") + + # Initialize histogram accumulator and collect all values + hist_counts = np.zeros(len(bins) - 1) + total_samples = 0 + all_values = [] + + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Processing timestep {i+1}/{len(times)}") + + data = load(ts, f"{ts}-{mode}")[tp_out if mode == 'target' else tp_in] * land_mask + + # Apply scaling factor for baseline + if mode == 'baseline': + data = data / 6.0 + + # Extract land values (remove NaN values) + land_values = data.values[~np.isnan(data.values)] + all_values.extend(land_values) + + # Accumulate histogram counts + counts, _ = np.histogram(land_values, bins=bins) + hist_counts += counts + total_samples += len(land_values) + + # Normalize to probability density + bin_widths = np.diff(bins) + hist_data[mode] = hist_counts / (total_samples * bin_widths) + all_land_values[mode] = np.array(all_values) + logger.info(f"Processed {total_samples} land values for {mode}") + + # -- Process predictions: compute histogram for each ensemble member -- + logger.info("Processing predictions") + + n_members = None + member_hist_data = [] + all_member_values = [] # Store all values for each member + + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Processing timestep {i+1}/{len(times)}") + + preds = load(ts, f"{ts}-predictions") # [n_members, n_channels, lat, lon] + + if n_members is None: + n_members = preds.shape[0] + # Initialize histogram accumulators for each member + member_hist_data = [np.zeros(len(bins) - 1) for _ in range(n_members)] + member_sample_counts = [0 for _ in range(n_members)] + all_member_values = [[] for _ in range(n_members)] # Initialize value storage + + for member_idx in range(n_members): + data = preds[member_idx, tp_out] * land_mask + # Extract land values (remove NaN values) + land_values = data.values[~np.isnan(data.values)] + all_member_values[member_idx].extend(land_values) # Store values for percentiles + + # Accumulate histogram counts for this member + counts, _ = np.histogram(land_values, bins=bins) + member_hist_data[member_idx] += counts + member_sample_counts[member_idx] += len(land_values) + + # Normalize member histograms to probability density + bin_widths = np.diff(bins) + normalized_member_hists = [] + for member_idx in range(n_members): + normalized_hist = member_hist_data[member_idx] / (member_sample_counts[member_idx] * bin_widths) + normalized_member_hists.append(normalized_hist) + + hist_data['predictions'] = tuple(normalized_member_hists) + + logger.info(f"Collected {n_members} ensemble members for predictions") + + # Compute percentiles for all datasets + percentiles_data = {} + + # Target percentiles + target_data_array = xr.DataArray(all_land_values['target']) + target_p99 = target_data_array.quantile(0.99).item() + target_p999 = target_data_array.quantile(0.999).item() + target_p9999 = target_data_array.quantile(0.9999).item() + percentiles_data['target'] = {99: target_p99, 99.9: target_p999, 99.99: target_p9999} + + # Baseline percentiles + baseline_data_array = xr.DataArray(all_land_values['baseline']) + baseline_p99 = baseline_data_array.quantile(0.99).item() + baseline_p999 = baseline_data_array.quantile(0.999).item() + baseline_p9999 = baseline_data_array.quantile(0.9999).item() + percentiles_data['baseline'] = {99: baseline_p99, 99.9: baseline_p999, 99.99: baseline_p9999} + + # Ensemble member percentiles + percentiles_data['predictions'] = {} + for member_idx in range(n_members): + member_data_array = xr.DataArray(all_member_values[member_idx]) + member_p99 = member_data_array.quantile(0.99).item() + member_p999 = member_data_array.quantile(0.999).item() + member_p9999 = member_data_array.quantile(0.9999).item() + percentiles_data['predictions'][f'member_{member_idx}'] = {99: member_p99, 99.9: member_p999, 99.99: member_p9999} + + + # Create distribution plots + labels = ['COSMO-2', 'ERA5', 'CorrDiff Ensemble'] + colors = ['blue', 'orange', 'green'] + + fn = out_root / 'precipitation_distribution_over_land.png' + save_distribution_plot( + hist_data, + bins, + labels, + colors, + 'Domain-Mean Precip. Over Land (Pooled Data)', + 'Precipitation (mm/h)', + fn, + percentiles_data + ) + logger.info(f"Distribution plot saved: {fn}") + + +if __name__ == '__main__': + main() \ No newline at end of file From 477140b5e59395b5386d827c26f92fb6d0cb0979 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Tue, 5 Aug 2025 10:08:09 +0200 Subject: [PATCH 114/302] add percentile lines --- src/hirad/eval/hist.py | 81 ++++++++++----------- src/hirad/eval/percentile99_cycle_precip.py | 29 +++++--- 2 files changed, 55 insertions(+), 55 deletions(-) diff --git a/src/hirad/eval/hist.py b/src/hirad/eval/hist.py index 804096dc..59ee2ed9 100644 --- a/src/hirad/eval/hist.py +++ b/src/hirad/eval/hist.py @@ -2,7 +2,7 @@ Plots the domain-mean precipitation distribution over land. This script computes and visualizes the distribution of precipitation values -across the land domain for different data sources (target, baseline, predictions). +over land. """ import logging from pathlib import Path @@ -50,57 +50,52 @@ def save_distribution_plot(hist_data_dict, bin_edges, labels, colors, title, yla plt.yscale('log') plt.xlabel(ylabel) plt.ylabel('Probability Density') + plt.ylim(1e-8, 1) + plt.xlim(bin_edges[1], bin_edges[-1]) plt.title(title) plt.grid(True, alpha=0.3) # Add percentile lines if provided if percentiles_data: - # Define colors for different datasets - percentile_colors = {'target': 'blue', 'baseline': 'orange', 'predictions': 'green'} - # Track if we've added legend labels for line styles - legend_added = {'99': False, '99.9': False, '99.99': False} + # Calculate y-range for percentile lines (lowest 10% of log scale) + y_bottom, y_top = plt.ylim() + log_bottom, log_top = np.log10(y_bottom), np.log10(y_top) + vline_ymax = 10**(log_bottom + 0.1 * (log_top - log_bottom)) + vline_ymin = y_bottom - for dataset_name, percentiles in percentiles_data.items(): + # Define line styles for percentiles + percentile_styles = {99: '--', 99.9: ':', 99.99: '-.'} + percentile_labels = {99: '99th all-hour percentiles', 99.9: '99.9th all-hour percentiles', 99.99: '99.99th all-hour percentiles'} + colors = {'target': 'blue', 'baseline': 'orange', 'predictions': 'green'} + legend_added = set() + + # Plot all percentile lines + for dataset_name, data in percentiles_data.items(): + color = colors[dataset_name] + if dataset_name in ['target', 'baseline']: - # Plot percentiles for target and baseline - color = percentile_colors[dataset_name] - for percentile, value in percentiles.items(): - if percentile == 99: - linestyle = '--' - legend_label = '99th percentiles' if not legend_added['99'] else None - legend_added['99'] = True - elif percentile == 99.9: - linestyle = ':' - legend_label = '99.9th percentiles' if not legend_added['99.9'] else None - legend_added['99.9'] = True - elif percentile == 99.99: - linestyle = '-.' - legend_label = '99.99th percentiles' if not legend_added['99.99'] else None - legend_added['99.99'] = True + # Single dataset + for percentile, value in data.items(): + linestyle = percentile_styles[percentile] + legend_added.add(percentile) # Track percentiles for black legend entries - plt.vlines(x=value, colors=color, - linestyles=linestyle, alpha=0.8, label=legend_label) - - elif dataset_name == 'predictions': - # Plot percentiles for ensemble members - color = percentile_colors[dataset_name] - for member_name, member_percentiles in percentiles.items(): - for percentile, value in member_percentiles.items(): - if percentile == 99: - linestyle = '--' - legend_label = '99th percentiles' if not legend_added['99'] else None - legend_added['99'] = True - elif percentile == 99.9: - linestyle = ':' - legend_label = '99.9th percentiles' if not legend_added['99.9'] else None - legend_added['99.9'] = True - elif percentile == 99.99: - linestyle = '-.' - legend_label = '99.99th percentiles' if not legend_added['99.99'] else None - legend_added['99.99'] = True + plt.vlines(x=value, colors=color, ymin=vline_ymin, ymax=vline_ymax, + linestyles=linestyle, alpha=0.8) # No label here + else: + # Ensemble members + for member_data in data.values(): + for percentile, value in member_data.items(): + linestyle = percentile_styles[percentile] + legend_added.add(percentile) # Track percentiles for black legend entries - plt.vlines(x=value, colors=color, - linestyles=linestyle, alpha=0.6, label=legend_label) + plt.vlines(x=value, colors=color, ymin=vline_ymin, ymax=vline_ymax, + linestyles=linestyle, alpha=0.6) # No label here + + # Add black legend entries for percentiles (override the colored ones) + for percentile in [99, 99.9, 99.99]: + if percentile in legend_added: + plt.plot([], [], color='black', linestyle=percentile_styles[percentile], + label=percentile_labels[percentile]) plt.legend() plt.tight_layout() diff --git a/src/hirad/eval/percentile99_cycle_precip.py b/src/hirad/eval/percentile99_cycle_precip.py index abfa5487..7f97e211 100644 --- a/src/hirad/eval/percentile99_cycle_precip.py +++ b/src/hirad/eval/percentile99_cycle_precip.py @@ -73,12 +73,14 @@ def main(cfg: DictConfig): # Output root and loader out_root = Path(cfg.generation.io.output_path or './outputs') - load = lambda ts, fn: torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR + def load(ts, fn): + return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR # Find channel indices out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} - tp_out = out_ch['tp']; tp_in = in_ch.get('tp', tp_out) + tp_out = out_ch['tp'] + tp_in = in_ch.get('tp', tp_out) logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") # Land-sea mask @@ -123,16 +125,17 @@ def main(cfg: DictConfig): pred_data_list = [] for ts in times: preds = load(ts, f"{ts}-predictions") # [n_members, n_channels, lat, lon] - pred_data_list.append(preds[:, tp_out] * land_mask) # apply mask + # Extract precipitation channel and convert to xarray for proper broadcasting + tp_data = preds[:, tp_out] # [n_members, lat, lon] + tp_da = xr.DataArray(tp_data, dims=['member', 'lat', 'lon']) + pred_data_list.append(tp_da * land_mask) # apply mask - pred_da = xr.DataArray( - np.stack(pred_data_list, axis=1), # [n_members, time, lat, lon] - dims=['member', 'time', 'lat', 'lon'], - coords={ - 'member': range(len(pred_data_list[0])), - 'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times] - } - ) + pred_da = xr.concat(pred_data_list, dim='time') # [n_members, time, lat, lon] + pred_da = pred_da.assign_coords({ + 'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times] + }) + # Transpose to get the expected dimension order: [member, time, lat, lon] + pred_da = pred_da.transpose('member', 'time', 'lat', 'lon') # Group by hour, compute 99th percentile across time, then spatial mean hourly_p99_by_member = pred_da.groupby('time.hour').quantile(0.99, dim='time').mean(dim=['lat', 'lon']) @@ -142,7 +145,9 @@ def main(cfg: DictConfig): pct99_std['prediction'] = hourly_p99_by_member.std(dim='member') # Prepare cyclic lists for plotting - cycle_fn = lambda x: x.values.tolist() + [x.values.tolist()[0]] + def cycle_fn(x): + return x.values.tolist() + [x.values.tolist()[0]] + hrs_c = list(range(24)) + [0 + 24] pct99_lines = [ cycle_fn(pct99_mean['target']), From f79f95f3707d6c167b3ee983ad041c1d1c944be1 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Tue, 5 Aug 2025 13:46:39 +0200 Subject: [PATCH 115/302] add plot for probability of exceedance --- src/hirad/eval/probability_of_exceedance.py | 262 ++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 src/hirad/eval/probability_of_exceedance.py diff --git a/src/hirad/eval/probability_of_exceedance.py b/src/hirad/eval/probability_of_exceedance.py new file mode 100644 index 00000000..e5da6b9a --- /dev/null +++ b/src/hirad/eval/probability_of_exceedance.py @@ -0,0 +1,262 @@ +""" +Plots the probability of exceedance for precipitation over land. + +This script computes and visualizes the complementary cumulative distribution +(probability of exceeding x mm/h) over land). +""" +import logging +from pathlib import Path + +import hydra +import matplotlib.pyplot as plt +import numpy as np +import torch +from omegaconf import DictConfig, OmegaConf +import xarray as xr + +from hirad.datasets import get_dataset_and_sampler_inference +from hirad.distributed import DistributedManager +from hirad.utils.function_utils import get_time_from_range + +# Constants +CONV_FACTOR = 100 # Convert meters to mm/h +LOG_INTERVAL = 24 # Log progress every N timesteps + + +def save_exceedance_plot(exceedance_data_dict, thresholds, labels, colors, title, ylabel, out_path, percentiles_data=None): + """Save probability of exceedance plot with pre-computed data.""" + Path(out_path).parent.mkdir(parents=True, exist_ok=True) + + plt.figure(figsize=(10, 6)) + + # Plot exceedance curves + for (key, exceedance_data), label, color in zip(exceedance_data_dict.items(), labels, colors): + if isinstance(exceedance_data, tuple): # Handle ensemble data + # Plot individual members with transparency + for i, member_exceedance in enumerate(exceedance_data): + alpha = 0.5 if i > 0 else 0.7 + label_member = label if i == 0 else None + plt.plot(thresholds, member_exceedance, alpha=alpha, color=color, + label=label_member, linewidth=1) + else: + # Plot single dataset + plt.plot(thresholds, exceedance_data, alpha=0.7, color=color, + label=label, linewidth=2) + + plt.xscale('log') + plt.yscale('log') + plt.xlabel(ylabel) + plt.ylabel('Probability of Exceedance') + plt.ylim(1e-8, 1) + plt.xlim(thresholds[1], thresholds[-1]) + plt.title(title) + plt.grid(True, alpha=0.3) + + # Add percentile lines if provided + if percentiles_data: + # Calculate y-range for percentile lines (lowest 10% of log scale) + y_bottom, y_top = plt.ylim() + log_bottom, log_top = np.log10(y_bottom), np.log10(y_top) + vline_ymax = 10**(log_bottom + 0.1 * (log_top - log_bottom)) + vline_ymin = y_bottom + + # Define line styles for percentiles + percentile_styles = {99: '--', 99.9: ':', 99.99: '-.'} + percentile_labels = {99: '99th all-hour percentiles', 99.9: '99.9th all-hour percentiles', 99.99: '99.99th all-hour percentiles'} + colors_perc = {'target': 'blue', 'baseline': 'orange', 'predictions': 'green'} + legend_added = set() + + # Plot all percentile lines + for dataset_name, data in percentiles_data.items(): + color = colors_perc[dataset_name] + + if dataset_name in ['target', 'baseline']: + # Single dataset + for percentile, value in data.items(): + linestyle = percentile_styles[percentile] + legend_added.add(percentile) # Track percentiles for black legend entries + + plt.vlines(x=value, colors=color, ymin=vline_ymin, ymax=vline_ymax, + linestyles=linestyle, alpha=0.8) # No label here + else: + # Ensemble members + for member_data in data.values(): + for percentile, value in member_data.items(): + linestyle = percentile_styles[percentile] + legend_added.add(percentile) # Track percentiles for black legend entries + + plt.vlines(x=value, colors=color, ymin=vline_ymin, ymax=vline_ymax, + linestyles=linestyle, alpha=0.6) # No label here + + # Add black legend entries for percentiles (override the colored ones) + for percentile in [99, 99.9, 99.99]: + if percentile in legend_added: + plt.plot([], [], color='black', linestyle=percentile_styles[percentile], + label=percentile_labels[percentile]) + + plt.legend() + plt.tight_layout() + plt.savefig(out_path, dpi=300, bbox_inches='tight') + plt.close() + + +@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") +def main(cfg: DictConfig): + # Setup logging + DistributedManager.initialize() + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + + logger.info("Starting computation for probability of exceedance over land") + times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + logger.info(f"Loaded {len(times)} timesteps to process") + + # Initialize dataset + ds_cfg = OmegaConf.to_container(cfg.dataset) + dataset, _ = get_dataset_and_sampler_inference( + ds_cfg, times, cfg.generation.get('has_lead_time', False) + ) + logger.info("Dataset and sampler initialized") + + # Output root and loader + out_root = Path(cfg.generation.io.output_path or './outputs') + + def load(ts, fn): + return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR + + # Find channel indices + out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} + in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} + tp_out = out_ch['tp'] + tp_in = in_ch.get('tp', tp_out) + logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") + + # Land-sea mask + lsm_data = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy').reshape(352,544) + land_mask = xr.DataArray( + np.where(lsm_data >= 0.5, 1.0, np.nan), + dims=['lat', 'lon'] + ) + + # Define thresholds for exceedance calculation + thresholds = np.logspace(-2, 2, 200) # From 0.01 to 100 mm/h + + # Storage for exceedance data + exceedance_data = {} + # Store all land values for percentile calculation + all_land_values = {} + + # -- Process target and baseline -- + for mode in ['target', 'baseline']: + logger.info(f"Processing mode: {mode}") + + all_values = [] + + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Processing timestep {i+1}/{len(times)}") + + data = load(ts, f"{ts}-{mode}")[tp_out if mode == 'target' else tp_in] * land_mask + + # Apply scaling factor for baseline + if mode == 'baseline': + data = data / 6.0 + + # Extract land values (remove NaN values) + land_values = data.values[~np.isnan(data.values)] + all_values.extend(land_values) + + # Compute exceedance probabilities + all_values = np.array(all_values) + exceedance_probs = [] + for threshold in thresholds: + prob_exceed = np.mean(all_values > threshold) + exceedance_probs.append(prob_exceed) + + exceedance_data[mode] = np.array(exceedance_probs) + all_land_values[mode] = all_values + logger.info(f"Processed {len(all_values)} land values for {mode}") + + # -- Process predictions: compute exceedance for each ensemble member -- + logger.info("Processing predictions") + + n_members = None + all_member_values = [] # Store all values for each member + + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Processing timestep {i+1}/{len(times)}") + + preds = load(ts, f"{ts}-predictions") # [n_members, n_channels, lat, lon] + + if n_members is None: + n_members = preds.shape[0] + all_member_values = [[] for _ in range(n_members)] # Initialize value storage + + for member_idx in range(n_members): + data = preds[member_idx, tp_out] * land_mask + # Extract land values (remove NaN values) + land_values = data.values[~np.isnan(data.values)] + all_member_values[member_idx].extend(land_values) + + # Compute exceedance probabilities for each ensemble member + member_exceedance_data = [] + for member_idx in range(n_members): + member_values = np.array(all_member_values[member_idx]) + member_exceedance = [] + for threshold in thresholds: + prob_exceed = np.mean(member_values > threshold) + member_exceedance.append(prob_exceed) + member_exceedance_data.append(np.array(member_exceedance)) + + exceedance_data['predictions'] = tuple(member_exceedance_data) + + logger.info(f"Collected {n_members} ensemble members for predictions") + + # Compute percentiles for all datasets + percentiles_data = {} + + # Target percentiles + target_data_array = xr.DataArray(all_land_values['target']) + target_p99 = target_data_array.quantile(0.99).item() + target_p999 = target_data_array.quantile(0.999).item() + target_p9999 = target_data_array.quantile(0.9999).item() + percentiles_data['target'] = {99: target_p99, 99.9: target_p999, 99.99: target_p9999} + + # Baseline percentiles + baseline_data_array = xr.DataArray(all_land_values['baseline']) + baseline_p99 = baseline_data_array.quantile(0.99).item() + baseline_p999 = baseline_data_array.quantile(0.999).item() + baseline_p9999 = baseline_data_array.quantile(0.9999).item() + percentiles_data['baseline'] = {99: baseline_p99, 99.9: baseline_p999, 99.99: baseline_p9999} + + # Ensemble member percentiles + percentiles_data['predictions'] = {} + for member_idx in range(n_members): + member_data_array = xr.DataArray(all_member_values[member_idx]) + member_p99 = member_data_array.quantile(0.99).item() + member_p999 = member_data_array.quantile(0.999).item() + member_p9999 = member_data_array.quantile(0.9999).item() + percentiles_data['predictions'][f'member_{member_idx}'] = {99: member_p99, 99.9: member_p999, 99.99: member_p9999} + + + # Create exceedance plots + labels = ['COSMO-2 Analysis', 'ERA5', 'CorrDiff Ensemble'] + colors = ['blue', 'orange', 'green'] + + fn = out_root / 'precipitation_exceedance_over_land.png' + save_exceedance_plot( + exceedance_data, + thresholds, + labels, + colors, + 'Probability of Exceedance', + 'All-hour Precipitation Over Land [mm/h] (Pooled Data)', + fn, + percentiles_data + ) + logger.info(f"Exceedance plot saved: {fn}") + + +if __name__ == '__main__': + main() From e631235252813a192d56dc4ac0b74b1c011be44a Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Tue, 5 Aug 2025 13:46:54 +0200 Subject: [PATCH 116/302] label --- src/hirad/eval/diurnal_cycle_precip.py | 4 ++-- src/hirad/eval/diurnal_cycle_temp_wind.py | 4 ++-- src/hirad/eval/hist.py | 2 +- src/hirad/eval/percentile99_cycle_precip.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hirad/eval/diurnal_cycle_precip.py b/src/hirad/eval/diurnal_cycle_precip.py index 50961e34..ef57155f 100644 --- a/src/hirad/eval/diurnal_cycle_precip.py +++ b/src/hirad/eval/diurnal_cycle_precip.py @@ -127,7 +127,7 @@ def save_plot(hour, means, stds, labels, ylabel, title, out_path): amount_target_mean.hour, [amount_target_mean, amount_baseline_mean, amount_pred_mean], [None, None, amount_pred_std], - ['COSMO-2','ERA5','CorrDiff ± Std(Members)'], + ['COSMO-2 Analysis','ERA5','CorrDiff ± Std(Members)'], 'Precipitation (mm/day)', 'Diurnal Cycle of Precip Amount', out_root / 'diurnal_cycle_precip_amount.png' @@ -136,7 +136,7 @@ def save_plot(hour, means, stds, labels, ylabel, title, out_path): wet_target_mean.hour, [wet_target_mean, wet_baseline_mean, wet_pred_mean], [None, None, wet_pred_std], - ['COSMO-2','ERA5','CorrDiff ± Std(Members)'], + ['COSMO-2 Analysis','ERA5','CorrDiff ± Std(Members)'], 'Wet-Hour Fraction [%]', 'Diurnal Cycle of Wet-Hours (>0.1 mm/h)', out_root / 'diurnal_cycle_precip_wethours.png' diff --git a/src/hirad/eval/diurnal_cycle_temp_wind.py b/src/hirad/eval/diurnal_cycle_temp_wind.py index 8e1ff309..90d178f6 100644 --- a/src/hirad/eval/diurnal_cycle_temp_wind.py +++ b/src/hirad/eval/diurnal_cycle_temp_wind.py @@ -157,7 +157,7 @@ def save_plot(hour, means, stds, labels, ylabel, title, out_path): temp_target_mean.hour, [temp_target_mean, temp_baseline_mean, temp_pred_mean], [None, None, temp_pred_std], - ['COSMO-2', 'ERA5', 'CorrDiff ± Std(Members)'], + ['COSMO-2 Analysis', 'ERA5', 'CorrDiff ± Std(Members)'], '2m Temperature [°C]', 'Diurnal Cycle of 2m Temperature', out_root / 'diurnal_cycle_2t.png' @@ -167,7 +167,7 @@ def save_plot(hour, means, stds, labels, ylabel, title, out_path): wind_target_mean.hour, [wind_target_mean, wind_baseline_mean, wind_pred_mean], [None, None, wind_pred_std], - ['COSMO-2', 'ERA5', 'CorrDiff ± Std(Members)'], + ['COSMO-2 Analysis', 'ERA5', 'CorrDiff ± Std(Members)'], 'Windspeed [m/s]', 'Diurnal Cycle of Windspeed', out_root / 'diurnal_cycle_windspeed.png' diff --git a/src/hirad/eval/hist.py b/src/hirad/eval/hist.py index 59ee2ed9..49b6bd23 100644 --- a/src/hirad/eval/hist.py +++ b/src/hirad/eval/hist.py @@ -253,7 +253,7 @@ def load(ts, fn): # Create distribution plots - labels = ['COSMO-2', 'ERA5', 'CorrDiff Ensemble'] + labels = ['COSMO-2 Analysis', 'ERA5', 'CorrDiff Ensemble'] colors = ['blue', 'orange', 'green'] fn = out_root / 'precipitation_distribution_over_land.png' diff --git a/src/hirad/eval/percentile99_cycle_precip.py b/src/hirad/eval/percentile99_cycle_precip.py index 7f97e211..3ee9d1c7 100644 --- a/src/hirad/eval/percentile99_cycle_precip.py +++ b/src/hirad/eval/percentile99_cycle_precip.py @@ -163,7 +163,7 @@ def cycle_fn(x): save_plot( hrs_c, pct99_lines, - ['COSMO-2','ERA5','CorrDiff 99th Pct ± Std'], + ['COSMO-2 Analysis','ERA5','CorrDiff 99th Pct ± Std'], 'Precipitation (mm/day)', 'Diurnal Cycle of 99th-Percentile Precipitation', fn From 4904a539e9075bda78057960f3c2fe934d42ceea Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Tue, 5 Aug 2025 14:45:37 +0200 Subject: [PATCH 117/302] rename --- src/hirad/eval/{plot_maps.py => snapshots.py} | 0 src/hirad/{diurnal_cycle.sh => eval_precip.sh} | 14 ++++++++++---- src/hirad/maps.sh | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) rename src/hirad/eval/{plot_maps.py => snapshots.py} (100%) rename src/hirad/{diurnal_cycle.sh => eval_precip.sh} (80%) diff --git a/src/hirad/eval/plot_maps.py b/src/hirad/eval/snapshots.py similarity index 100% rename from src/hirad/eval/plot_maps.py rename to src/hirad/eval/snapshots.py diff --git a/src/hirad/diurnal_cycle.sh b/src/hirad/eval_precip.sh similarity index 80% rename from src/hirad/diurnal_cycle.sh rename to src/hirad/eval_precip.sh index 66437574..bb4b810e 100644 --- a/src/hirad/diurnal_cycle.sh +++ b/src/hirad/eval_precip.sh @@ -1,19 +1,19 @@ #!/bin/bash -#SBATCH --job-name="plot_diurnal_cycle" +#SBATCH --job-name="eval_precip" ### HARDWARE ### -#SBATCH --partition=debug +#SBATCH --partition=normal #SBATCH --nodes=1 #SBATCH --ntasks-per-node=2 #SBATCH --gpus-per-node=2 #SBATCH --cpus-per-task=72 -##SBATCH --time=00:10:00 +#SBATCH --time=05:00:00 #SBATCH --no-requeue #SBATCH --exclusive ### OUTPUT ### -#SBATCH --output=./logs/plot_diurnal_cycle.log +#SBATCH --output=./logs/plots_precipe.log ### ENVIRONMENT #### #SBATCH -A a161 @@ -48,4 +48,10 @@ srun --environment=./ci/edf/modulus_env.toml bash -c " python src/hirad/eval/diurnal_cycle_precip.py --config-name=generate_era_cosmo.yaml python src/hirad/eval/percentile99_cycle_precip.py --config-name=generate_era_cosmo.yaml python src/hirad/eval/diurnal_cycle_temp_wind.py --config-name=generate_era_cosmo.yaml +" + +srun --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . --no-dependencies + python src/hirad/eval/hist.py --config-name=generate_era_cosmo.yaml + python src/hirad/eval/probability_of_exceedance.py --config-name=generate_era_cosmo.yaml " \ No newline at end of file diff --git a/src/hirad/maps.sh b/src/hirad/maps.sh index bcf40085..db6722b8 100644 --- a/src/hirad/maps.sh +++ b/src/hirad/maps.sh @@ -45,5 +45,5 @@ export OMP_NUM_THREADS=72 # python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml srun --environment=./ci/edf/modulus_env.toml bash -c " pip install -e . --no-dependencies - python src/hirad/eval/plot_maps.py --config-name=generate_era_cosmo.yaml + python src/hirad/eval/snapshots.py --config-name=generate_era_cosmo.yaml " \ No newline at end of file From 58c6607ccdb3c5f77188eca906645ffdd54f120c Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Tue, 5 Aug 2025 15:15:31 +0200 Subject: [PATCH 118/302] plot map of the 99th all-hour percentile --- src/hirad/eval/map_99pctl.py | 173 +++++++++++++++++++++++++++++++++++ src/hirad/maps.sh | 1 + 2 files changed, 174 insertions(+) create mode 100644 src/hirad/eval/map_99pctl.py diff --git a/src/hirad/eval/map_99pctl.py b/src/hirad/eval/map_99pctl.py new file mode 100644 index 00000000..547a81e0 --- /dev/null +++ b/src/hirad/eval/map_99pctl.py @@ -0,0 +1,173 @@ +""" +Plots maps of the 99th percentile of precipitation over the entire time period. + +This script computes the all-time 99th percentile of precipitation for each grid point +and creates maps for target (COSMO-2), baseline (ERA5), and predictions (CorrDiff). +""" +import logging +from datetime import datetime +from pathlib import Path + +import hydra +import numpy as np +import torch +from omegaconf import DictConfig, OmegaConf +import xarray as xr + +from hirad.datasets import get_dataset_and_sampler_inference +from hirad.distributed import DistributedManager +from hirad.utils.function_utils import get_time_from_range +from hirad.eval.plotting import plot_map_precipitation + +# Constants +CONV_FACTOR = 100 * 24 # Convert meters to mm/day +LOG_INTERVAL = 24 # Log progress every N timesteps + + +@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") +def main(cfg: DictConfig): + # Setup logging + DistributedManager.initialize() + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + + logger.info("Starting computation for 99th percentile precipitation maps") + times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + logger.info(f"Loaded {len(times)} timesteps to process") + + # Initialize dataset + ds_cfg = OmegaConf.to_container(cfg.dataset) + dataset, _ = get_dataset_and_sampler_inference( + ds_cfg, times, cfg.generation.get('has_lead_time', False) + ) + logger.info("Dataset and sampler initialized") + + # Output root and loader + out_root = Path(cfg.generation.io.output_path or './outputs') + def load(ts, fn): + return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR + + # Find channel indices + out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} + in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} + tp_out = out_ch['tp'] + tp_in = in_ch.get('tp', tp_out) + logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") + + # -- Process target -- + logger.info("Processing target (COSMO-2)") + target_data_list = [] + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Processing target timestep {i+1}/{len(times)}: {ts}") + data = load(ts, f"{ts}-target")[tp_out] + target_data_list.append(data) + + target_da = xr.DataArray( + np.stack(target_data_list, axis=0), + dims=['time', 'lat', 'lon'], + coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} + ) + + # Compute 99th percentile across all time steps + target_p99 = target_da.quantile(0.99, dim='time') + logger.info("Target 99th percentile computed") + + # -- Process baseline -- + logger.info("Processing baseline (ERA5)") + baseline_data_list = [] + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Processing baseline timestep {i+1}/{len(times)}: {ts}") + data = load(ts, f"{ts}-baseline")[tp_in] + baseline_data_list.append(data) + + baseline_da = xr.DataArray( + np.stack(baseline_data_list, axis=0), + dims=['time', 'lat', 'lon'], + coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} + ) + + # Apply scaling factor for baseline and compute 99th percentile + baseline_p99 = (baseline_da / 6.0).quantile(0.99, dim='time') + logger.info("Baseline 99th percentile computed") + + # -- Process predictions -- + logger.info("Processing predictions (CorrDiff)") + pred_data_list = [] + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Processing predictions timestep {i+1}/{len(times)}: {ts}") + preds = load(ts, f"{ts}-predictions") # [n_members, n_channels, lat, lon] + # Extract precipitation channel + tp_data = preds[:, tp_out] # [n_members, lat, lon] + tp_da = xr.DataArray(tp_data, dims=['member', 'lat', 'lon']) + pred_data_list.append(tp_da) + + pred_da = xr.concat(pred_data_list, dim='time') # [member, time, lat, lon] + pred_da = pred_da.assign_coords({ + 'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times] + }) + pred_da = pred_da.transpose('member', 'time', 'lat', 'lon') + + # Compute 99th percentile across time for each member, then ensemble mean + pred_p99_by_member = pred_da.quantile(0.99, dim='time') + pred_p99_mean = pred_p99_by_member.mean(dim='member') + logger.info("Predictions 99th percentile computed") + + # Create output directory + map_output_dir = out_root / 'maps_99th_percentile' + map_output_dir.mkdir(parents=True, exist_ok=True) + + # Plot maps using the precipitation-specific plotting function + logger.info("Creating precipitation maps") + + # Target map + plot_map_precipitation( + target_p99.values, + str(map_output_dir / 'target_99th_percentile'), + title='COSMO-2 Analysis: 99th Percentile Precipitation', + threshold=0.1, + rfac=1.0 # Already converted to mm/day + ) + logger.info("Target map saved") + + # Baseline map + plot_map_precipitation( + baseline_p99.values, + str(map_output_dir / 'baseline_99th_percentile'), + title='ERA5: 99th Percentile Precipitation', + threshold=0.1, + rfac=1.0 # Already converted to mm/day + ) + logger.info("Baseline map saved") + + # Prediction ensemble mean map + plot_map_precipitation( + pred_p99_mean.values, + str(map_output_dir / 'prediction_ensmean_99th_percentile'), + title='CorrDiff Ensemble Mean: 99th Percentile Precipitation', + threshold=0.1, + rfac=1.0 # Already converted to mm/day + ) + logger.info("Prediction ensemble mean map saved") + + # Individual ensemble member maps + logger.info("Creating individual ensemble member maps") + n_members = pred_p99_by_member.shape[0] + for member_idx in range(n_members): + plot_map_precipitation( + pred_p99_by_member[member_idx].values, + str(map_output_dir / f'prediction_member_{member_idx:02d}_99th_percentile'), + title=f'CorrDiff Member {member_idx+1}: 99th Percentile Precipitation', + threshold=0.1, + rfac=1.0 # Already converted to mm/day + ) + logger.info(f"Individual ensemble member maps saved ({n_members} members)") + + logger.info(f"All maps saved to {map_output_dir}") + logger.info("99th percentile precipitation mapping completed successfully") + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/hirad/maps.sh b/src/hirad/maps.sh index db6722b8..aa1bcbd7 100644 --- a/src/hirad/maps.sh +++ b/src/hirad/maps.sh @@ -46,4 +46,5 @@ export OMP_NUM_THREADS=72 srun --environment=./ci/edf/modulus_env.toml bash -c " pip install -e . --no-dependencies python src/hirad/eval/snapshots.py --config-name=generate_era_cosmo.yaml + python src/hirad/eval/ap_99pctl.py --config-name=generate_era_cosmo.yaml " \ No newline at end of file From 588681a197754deebe4ca96e2cf8eef24e5c6cd1 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Tue, 5 Aug 2025 15:57:16 +0200 Subject: [PATCH 119/302] plot mean --- src/hirad/eval/map_mean.py | 173 +++++++++++++++++++++++++++++++++++++ src/hirad/maps.sh | 7 +- 2 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 src/hirad/eval/map_mean.py diff --git a/src/hirad/eval/map_mean.py b/src/hirad/eval/map_mean.py new file mode 100644 index 00000000..2eae774f --- /dev/null +++ b/src/hirad/eval/map_mean.py @@ -0,0 +1,173 @@ +""" +Plots maps of the mean precipitation over the entire time period. + +This script computes the temporal mean of precipitation for each grid point +and creates maps for target (COSMO-2), baseline (ERA5), and predictions (CorrDiff). +""" +import logging +from datetime import datetime +from pathlib import Path + +import hydra +import numpy as np +import torch +from omegaconf import DictConfig, OmegaConf +import xarray as xr + +from hirad.datasets import get_dataset_and_sampler_inference +from hirad.distributed import DistributedManager +from hirad.utils.function_utils import get_time_from_range +from hirad.eval.plotting import plot_map_precipitation + +# Constants +CONV_FACTOR = 100 * 24 # Convert meters to mm/day +LOG_INTERVAL = 24 # Log progress every N timesteps + + +@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") +def main(cfg: DictConfig): + # Setup logging + DistributedManager.initialize() + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + + logger.info("Starting computation for mean precipitation maps") + times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + logger.info(f"Loaded {len(times)} timesteps to process") + + # Initialize dataset + ds_cfg = OmegaConf.to_container(cfg.dataset) + dataset, _ = get_dataset_and_sampler_inference( + ds_cfg, times, cfg.generation.get('has_lead_time', False) + ) + logger.info("Dataset and sampler initialized") + + # Output root and loader + out_root = Path(cfg.generation.io.output_path or './outputs') + def load(ts, fn): + return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR + + # Find channel indices + out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} + in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} + tp_out = out_ch['tp'] + tp_in = in_ch.get('tp', tp_out) + logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") + + # -- Process target -- + logger.info("Processing target (COSMO-2)") + target_data_list = [] + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Processing target timestep {i+1}/{len(times)}: {ts}") + data = load(ts, f"{ts}-target")[tp_out] + target_data_list.append(data) + + target_da = xr.DataArray( + np.stack(target_data_list, axis=0), + dims=['time', 'lat', 'lon'], + coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} + ) + + # Compute mean across all time steps + target_mean = target_da.mean(dim='time') + logger.info("Target mean computed") + + # -- Process baseline -- + logger.info("Processing baseline (ERA5)") + baseline_data_list = [] + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Processing baseline timestep {i+1}/{len(times)}: {ts}") + data = load(ts, f"{ts}-baseline")[tp_in] + baseline_data_list.append(data) + + baseline_da = xr.DataArray( + np.stack(baseline_data_list, axis=0), + dims=['time', 'lat', 'lon'], + coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} + ) + + # Apply scaling factor for baseline and compute mean + baseline_mean = (baseline_da / 6.0).mean(dim='time') + logger.info("Baseline mean computed") + + # -- Process predictions -- + logger.info("Processing predictions (CorrDiff)") + pred_data_list = [] + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Processing predictions timestep {i+1}/{len(times)}: {ts}") + preds = load(ts, f"{ts}-predictions") # [n_members, n_channels, lat, lon] + # Extract precipitation channel + tp_data = preds[:, tp_out] # [n_members, lat, lon] + tp_da = xr.DataArray(tp_data, dims=['member', 'lat', 'lon']) + pred_data_list.append(tp_da) + + pred_da = xr.concat(pred_data_list, dim='time') # [member, time, lat, lon] + pred_da = pred_da.assign_coords({ + 'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times] + }) + pred_da = pred_da.transpose('member', 'time', 'lat', 'lon') + + # Compute mean across time for each member, then ensemble mean + pred_mean_by_member = pred_da.mean(dim='time') + pred_mean_mean = pred_mean_by_member.mean(dim='member') + logger.info("Predictions mean computed") + + # Create output directory + map_output_dir = out_root / 'maps_mean' + map_output_dir.mkdir(parents=True, exist_ok=True) + + # Plot maps using the precipitation-specific plotting function + logger.info("Creating precipitation maps") + + # Target map + plot_map_precipitation( + target_mean.values, + str(map_output_dir / 'target_mean'), + title='COSMO-2 Analysis: Mean Precipitation', + threshold=0.01, # Lower threshold for mean precipitation + rfac=1.0 # Already converted to mm/day + ) + logger.info("Target map saved") + + # Baseline map + plot_map_precipitation( + baseline_mean.values, + str(map_output_dir / 'baseline_mean'), + title='ERA5: Mean Precipitation', + threshold=0.01, # Lower threshold for mean precipitation + rfac=1.0 # Already converted to mm/day + ) + logger.info("Baseline map saved") + + # Prediction ensemble mean map + plot_map_precipitation( + pred_mean_mean.values, + str(map_output_dir / 'prediction_ensmean_mean'), + title='CorrDiff Ensemble Mean: Mean Precipitation', + threshold=0.01, # Lower threshold for mean precipitation + rfac=1.0 # Already converted to mm/day + ) + logger.info("Prediction ensemble mean map saved") + + # Individual ensemble member maps + logger.info("Creating individual ensemble member maps") + n_members = pred_mean_by_member.shape[0] + for member_idx in range(n_members): + plot_map_precipitation( + pred_mean_by_member[member_idx].values, + str(map_output_dir / f'prediction_member_{member_idx:02d}_mean'), + title=f'CorrDiff Member {member_idx+1}: Mean Precipitation', + threshold=0.01, # Lower threshold for mean precipitation + rfac=1.0 # Already converted to mm/day + ) + logger.info(f"Individual ensemble member maps saved ({n_members} members)") + + logger.info(f"All maps saved to {map_output_dir}") + logger.info("Mean precipitation mapping completed successfully") + + +if __name__ == '__main__': + main() diff --git a/src/hirad/maps.sh b/src/hirad/maps.sh index aa1bcbd7..543de5a8 100644 --- a/src/hirad/maps.sh +++ b/src/hirad/maps.sh @@ -3,12 +3,12 @@ #SBATCH --job-name="plot" ### HARDWARE ### -#SBATCH --partition=debug +#SBATCH --partition=normal #SBATCH --nodes=1 #SBATCH --ntasks-per-node=2 #SBATCH --gpus-per-node=2 #SBATCH --cpus-per-task=72 -#SBATCH --time=00:10:00 +#SBATCH --time=01:00:00 #SBATCH --no-requeue #SBATCH --exclusive @@ -46,5 +46,6 @@ export OMP_NUM_THREADS=72 srun --environment=./ci/edf/modulus_env.toml bash -c " pip install -e . --no-dependencies python src/hirad/eval/snapshots.py --config-name=generate_era_cosmo.yaml - python src/hirad/eval/ap_99pctl.py --config-name=generate_era_cosmo.yaml + python src/hirad/eval/map_99pctl.py --config-name=generate_era_cosmo.yaml + python src/hirad/eval/map_mean.py --config-name=generate_era_cosmo.yaml " \ No newline at end of file From 3ca921c8d8f206e4ef5f97d248835465318ad487 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 6 Aug 2025 14:11:18 +0200 Subject: [PATCH 120/302] Update model compilation - Adjusted model compilation logic to compile models before DistributedDataParallel is called. This reduces overhead of DDP that causes torch compile to issue warnings - Added device_id parameter to DistributedManager initialization. - Removed 'reduce-overhead' mode from torch.compile to fix CUDA graph compilation issues during inference. - Changed logging from error to warning when model file is not found. --- src/hirad/distributed/manager.py | 1 + src/hirad/inference/generate.py | 4 ++-- src/hirad/training/train.py | 5 ++++- src/hirad/utils/checkpoint.py | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/hirad/distributed/manager.py b/src/hirad/distributed/manager.py index eca46c63..96958185 100644 --- a/src/hirad/distributed/manager.py +++ b/src/hirad/distributed/manager.py @@ -551,6 +551,7 @@ def setup( backend, rank=manager.rank, world_size=manager.world_size, + device_id=manager.device, ) # rank=manager.rank, # world_size=manager.world_size, diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index a553af88..4ef2970e 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -137,8 +137,8 @@ def main(cfg: DictConfig) -> None: # Only compile residual network # Overhead of compiling regression network outweights any benefits if net_res: - net_res = torch.compile(net_res, mode="reduce-overhead") - + net_res = torch.compile(net_res) #, mode="reduce-overhead") + # removed reduce-overhead because it was breaking cuda graph compilation generator = Generator( net_reg=net_reg, net_res=net_res, diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 519845b4..ba1b975f 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -282,6 +282,8 @@ def main(cfg: DictConfig) -> None: # Enable distributed data parallel if applicable if dist.world_size > 1: + if use_torch_compile: + model = torch.compile(model) model = DistributedDataParallel( model, device_ids=[dist.local_rank], @@ -332,7 +334,8 @@ def main(cfg: DictConfig) -> None: # Compile the model and regression net if applicable if use_torch_compile: - model = torch.compile(model) + if dist.world_size==1: + model = torch.compile(model) if regression_net: regression_net = torch.compile(regression_net) diff --git a/src/hirad/utils/checkpoint.py b/src/hirad/utils/checkpoint.py index a346b16f..070f8c34 100644 --- a/src/hirad/utils/checkpoint.py +++ b/src/hirad/utils/checkpoint.py @@ -279,7 +279,7 @@ def load_checkpoint( path, name, index=epoch, ) if not Path(file_name).exists(): - checkpoint_logging.error( + checkpoint_logging.warning( f"Could not find valid model file {file_name}, skipping load" ) else: From f4453421e51d8b89de388443563081e1659fd74d Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 6 Aug 2025 14:26:03 +0200 Subject: [PATCH 121/302] turn on optimizations --- src/hirad/conf/training/era_cosmo_diffusion.yaml | 13 ++++++++----- src/hirad/conf/training/era_cosmo_regression.yaml | 13 +++++++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/hirad/conf/training/era_cosmo_diffusion.yaml b/src/hirad/conf/training/era_cosmo_diffusion.yaml index 40e37f34..2a2fd335 100644 --- a/src/hirad/conf/training/era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training/era_cosmo_diffusion.yaml @@ -1,10 +1,10 @@ # Hyperparameters hp: - training_duration: 20000 + training_duration: 10000 # Training duration based on the number of processed samples total_batch_size: "auto" # Total batch size - batch_size_per_gpu: 22 + batch_size_per_gpu: 20 # Batch size per GPU lr: 0.0002 # Learning rate @@ -26,16 +26,19 @@ perf: # DataLoader worker processes songunet_checkpoint_level: 0 # 0 means no checkpointing # Gradient checkpointing level, value is number of layers to checkpoint - + use_apex_gn: True + torch_compile: True + profile_mode: False # I/O io: - regression_checkpoint_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_era5_cosmo/checkpoints_regression + # regression_checkpoint_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_era5_cosmo/checkpoints_regression + regression_checkpoint_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_era5_cosmo_perf_apex_gn/checkpoints_regression # Where to load the regression checkpoint print_progress_freq: 500 # How often to print progress save_checkpoint_freq: 10000 # How often to save the checkpoints, measured in number of processed samples - visualization_freq: 200000 + visualization_freq: 10000 # how often to visualize network outputs validation_freq: 2000 # how often to record the validation loss, measured in number of processed samples diff --git a/src/hirad/conf/training/era_cosmo_regression.yaml b/src/hirad/conf/training/era_cosmo_regression.yaml index 5cd5d0d6..40e90055 100644 --- a/src/hirad/conf/training/era_cosmo_regression.yaml +++ b/src/hirad/conf/training/era_cosmo_regression.yaml @@ -1,10 +1,10 @@ # Hyperparameters hp: - training_duration: 40000 + training_duration: 10000 # Training duration based on the number of processed samples total_batch_size: "auto" # Total batch size -- based 8 per GPU -- 2 nodes is 2x8x4 -- see sbatch vars for how many gpus. diffusion need to point to the rgression. - batch_size_per_gpu: 22 + batch_size_per_gpu: 20 # Batch size per GPU lr: 0.0002 # Learning rate @@ -26,16 +26,17 @@ perf: # DataLoader worker processes songunet_checkpoint_level: 0 # 0 means no checkpointing # Gradient checkpointing level, value is number of layers to checkpoint - # torch_compile: True - # use_apex_gn: True + use_apex_gn: True + torch_compile: True + profile_mode: False # I/O io: print_progress_freq: 500 # How often to print progress - save_checkpoint_freq: 100000 + save_checkpoint_freq: 10000 # How often to save the checkpoints, measured in number of processed samples - visualization_freq: 200000 + visualization_freq: 10000 # how often to visualize network output validation_freq: 2000 # how often to record the validation loss, measured in number of processed samples From 062c8d8de2fbfcb0c09099f4e9e942885bb1cbeb Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Wed, 6 Aug 2025 15:58:26 +0200 Subject: [PATCH 122/302] add some common untility functions --- src/hirad/eval/plotting.py | 68 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index 2eb1c62d..39b32084 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -4,7 +4,11 @@ import cartopy.feature as cfeature import matplotlib.pyplot as plt import numpy as np +import torch +import xarray as xr from matplotlib.colors import BoundaryNorm, ListedColormap +from pathlib import Path +from datetime import datetime # COSMO‑2 GRID: TODO: Add to dataset config @@ -12,6 +16,68 @@ LON = np.arange(-6.82, 4.80 + 0.02, 0.02) RELAX_ZONE = 19 # Number of points dropped on each side (relaxation zone) +# Constants for data processing +CONV_FACTOR_HOURLY = 100 # Convert precip meters to mm/h +CONV_FACTOR = CONV_FACTOR_HOURLY * 24 # Convert precip from meters to mm/day +WET_THRESHOLD = 0.1 # Threshold for wet-hour in mm/h + +def get_channel_indices(dataset, channels=None): + """ + Get channel indices for input and output channels from dataset. + + Args: + dataset: Dataset object with input_channels() and output_channels() methods + channels: Optional list of channel names to look up. If None, returns all channel mappings. + + Returns: + dict: Dictionary with 'input' and 'output' keys, each containing channel name -> index mapping + + Example: + indices = get_channel_indices(dataset, ['tp', '2t', '10u', '10v']) + tp_out = indices['output']['tp'] + tp_in = indices['input'].get('tp', tp_out) # Fallback to output index if not in input + """ + out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} + in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} + + if channels is None: + return {'input': in_ch, 'output': out_ch} + + # Filter to requested channels only + filtered_out = {ch: out_ch[ch] for ch in channels if ch in out_ch} + filtered_in = {ch: in_ch[ch] for ch in channels if ch in in_ch} + + return {'input': filtered_in, 'output': filtered_out} + +def load_land_sea_mask(path='/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy'): + """Load and retrun a land-sea mask as xarray DataArray.""" + lsm_data = np.load(path).reshape(352, 544) + return xr.DataArray( + np.where(lsm_data >= 0.5, 1.0, np.nan), + dims=['lat', 'lon'], + coords={"lat": np.arange(352), "lon": np.arange(544)} + ) + +def load_prediction_data_torch(out_root, times, filename_pattern, conv_factor=CONV_FACTOR): + """Generic loader for prediction data with conversion factor.""" + def load(ts, fn): + return torch.load(out_root/ts/fn, weights_only=False) * conv_factor + + return lambda ts, fn: load(ts, filename_pattern.format(ts=ts, fn=fn)) + +def concat_and_group_diurnal(list_of_da, is_member=False, scale=1.0): + """Helper to concatenate DataArrays and compute diurnal statistics.""" + da = xr.concat(list_of_da, dim="time").groupby("time.hour") + if is_member: + timmean = da.mean(dim='time') * scale + mean = timmean.mean(dim='member') + std = timmean.std(dim='member') + else: + mean = da.mean(dim='time') * scale + std = None + return mean, std + + def plot_map(values: np.array, filename: str, label='', @@ -108,7 +174,6 @@ def plot_error_projection(values: np.array, latitudes: np.array, longitudes: np. def plot_scores_vs_t(scores: dict[str,np.ndarray], times: np.array, filename: str, xlabel='', ylabel='', title=''): - fig = plt.figure() ax = plt.subplot() colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'] # TODO, add more i=0 @@ -131,7 +196,6 @@ def plot_scores_vs_t(scores: dict[str,np.ndarray], times: np.array, filename: st plt.close('all') def plot_power_spectra(freqs: dict, spec: dict, channel_name, filename): - fig = plt.figure() for k in freqs.keys(): plt.loglog(freqs[k], spec[k], label=k) plt.title(channel_name) From 392637dd0bfe9e14a2f6d001588456d36a95a0a9 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Wed, 6 Aug 2025 15:59:36 +0200 Subject: [PATCH 123/302] use get_channel_indices function --- src/hirad/eval/diurnal_cycle_precip.py | 8 ++++---- src/hirad/eval/diurnal_cycle_temp_wind.py | 17 ++++++++++++----- src/hirad/eval/hist.py | 8 ++++---- src/hirad/eval/map_99pctl.py | 9 ++++----- src/hirad/eval/map_mean.py | 9 ++++----- src/hirad/eval/percentile99_cycle_precip.py | 8 ++++---- src/hirad/eval/probability_of_exceedance.py | 8 ++++---- src/hirad/eval_precip.sh | 2 +- 8 files changed, 37 insertions(+), 32 deletions(-) diff --git a/src/hirad/eval/diurnal_cycle_precip.py b/src/hirad/eval/diurnal_cycle_precip.py index ef57155f..56e19a5e 100644 --- a/src/hirad/eval/diurnal_cycle_precip.py +++ b/src/hirad/eval/diurnal_cycle_precip.py @@ -12,6 +12,7 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range +from hirad.eval.plotting import get_channel_indices # Constants CONV_FACTOR = 100*24 # Convert meters to mm/day @@ -40,10 +41,9 @@ def load(ts, fn): return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR # Find channel indices - out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} - in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} - tp_out = out_ch['tp'] - tp_in = in_ch.get('tp', tp_out) + indices = get_channel_indices(dataset) + tp_out = indices['output']['tp'] + tp_in = indices['input'].get('tp', tp_out) logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") # Land-sea mask diff --git a/src/hirad/eval/diurnal_cycle_temp_wind.py b/src/hirad/eval/diurnal_cycle_temp_wind.py index 90d178f6..1859ad77 100644 --- a/src/hirad/eval/diurnal_cycle_temp_wind.py +++ b/src/hirad/eval/diurnal_cycle_temp_wind.py @@ -12,6 +12,7 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range +from hirad.eval.plotting import get_channel_indices LOG_INTERVAL = 24 @@ -35,18 +36,24 @@ def main(cfg: DictConfig): ) # Indices for channels - out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} - in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} + indices = get_channel_indices(dataset) + out_ch = indices['output'] + in_ch = indices['input'] + + # Temperature channel (try '2t' first, fallback to 't2m') t2m_out = out_ch.get('2t', out_ch.get('t2m')) t2m_in = in_ch.get('2t', in_ch.get('t2m', t2m_out)) - u_out = out_ch.get('10u') - v_out = out_ch.get('10v') + + # Wind channels + u_out = out_ch['10u'] u_in = in_ch.get('10u', u_out) + v_out = out_ch['10v'] v_in = in_ch.get('10v', v_out) # Output path out_root = Path(cfg.generation.io.output_path or './outputs') - load = lambda ts, fn: torch.load(out_root/ts/fn, weights_only=False) + def load(ts, fn): + return torch.load(out_root/ts/fn, weights_only=False) # Land-sea mask lsm_data = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy').reshape(352,544) diff --git a/src/hirad/eval/hist.py b/src/hirad/eval/hist.py index 49b6bd23..2a9f8a74 100644 --- a/src/hirad/eval/hist.py +++ b/src/hirad/eval/hist.py @@ -17,6 +17,7 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range +from hirad.eval.plotting import get_channel_indices # Constants CONV_FACTOR = 100 # Convert meters to mm/h @@ -128,10 +129,9 @@ def load(ts, fn): return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR # Find channel indices - out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} - in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} - tp_out = out_ch['tp'] - tp_in = in_ch.get('tp', tp_out) + indices = get_channel_indices(dataset) + tp_out = indices['output']['tp'] + tp_in = indices['input'].get('tp', tp_out) logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") # Land-sea mask diff --git a/src/hirad/eval/map_99pctl.py b/src/hirad/eval/map_99pctl.py index 547a81e0..4e7395ac 100644 --- a/src/hirad/eval/map_99pctl.py +++ b/src/hirad/eval/map_99pctl.py @@ -17,7 +17,7 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import plot_map_precipitation +from hirad.eval.plotting import plot_map_precipitation, get_channel_indices # Constants CONV_FACTOR = 100 * 24 # Convert meters to mm/day @@ -48,10 +48,9 @@ def load(ts, fn): return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR # Find channel indices - out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} - in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} - tp_out = out_ch['tp'] - tp_in = in_ch.get('tp', tp_out) + indices = get_channel_indices(dataset) + tp_out = indices['output']['tp'] + tp_in = indices['input'].get('tp', tp_out) logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") # -- Process target -- diff --git a/src/hirad/eval/map_mean.py b/src/hirad/eval/map_mean.py index 2eae774f..40f80c92 100644 --- a/src/hirad/eval/map_mean.py +++ b/src/hirad/eval/map_mean.py @@ -17,7 +17,7 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import plot_map_precipitation +from hirad.eval.plotting import plot_map_precipitation, get_channel_indices # Constants CONV_FACTOR = 100 * 24 # Convert meters to mm/day @@ -48,10 +48,9 @@ def load(ts, fn): return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR # Find channel indices - out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} - in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} - tp_out = out_ch['tp'] - tp_in = in_ch.get('tp', tp_out) + indices = get_channel_indices(dataset) + tp_out = indices['output']['tp'] + tp_in = indices['input'].get('tp', tp_out) logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") # -- Process target -- diff --git a/src/hirad/eval/percentile99_cycle_precip.py b/src/hirad/eval/percentile99_cycle_precip.py index 3ee9d1c7..15aec33b 100644 --- a/src/hirad/eval/percentile99_cycle_precip.py +++ b/src/hirad/eval/percentile99_cycle_precip.py @@ -19,6 +19,7 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range +from hirad.eval.plotting import get_channel_indices # Constants CONV_FACTOR = 100 * 24 # Convert meters to mm/day @@ -77,10 +78,9 @@ def load(ts, fn): return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR # Find channel indices - out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} - in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} - tp_out = out_ch['tp'] - tp_in = in_ch.get('tp', tp_out) + indices = get_channel_indices(dataset) + tp_out = indices['output']['tp'] + tp_in = indices['input'].get('tp', tp_out) logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") # Land-sea mask diff --git a/src/hirad/eval/probability_of_exceedance.py b/src/hirad/eval/probability_of_exceedance.py index e5da6b9a..52452c13 100644 --- a/src/hirad/eval/probability_of_exceedance.py +++ b/src/hirad/eval/probability_of_exceedance.py @@ -17,6 +17,7 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range +from hirad.eval.plotting import get_channel_indices # Constants CONV_FACTOR = 100 # Convert meters to mm/h @@ -125,10 +126,9 @@ def load(ts, fn): return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR # Find channel indices - out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} - in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} - tp_out = out_ch['tp'] - tp_in = in_ch.get('tp', tp_out) + indices = get_channel_indices(dataset) + tp_out = indices['output']['tp'] + tp_in = indices['input'].get('tp', tp_out) logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") # Land-sea mask diff --git a/src/hirad/eval_precip.sh b/src/hirad/eval_precip.sh index bb4b810e..44eb3bcc 100644 --- a/src/hirad/eval_precip.sh +++ b/src/hirad/eval_precip.sh @@ -13,7 +13,7 @@ #SBATCH --exclusive ### OUTPUT ### -#SBATCH --output=./logs/plots_precipe.log +#SBATCH --output=./logs/plots_precip.log ### ENVIRONMENT #### #SBATCH -A a161 From 4dbaae3fbfff9c6d1d69d9afdc7e99ca9b37e390 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 6 Aug 2025 16:55:33 +0200 Subject: [PATCH 124/302] remove slurm account variables --- ci/cscs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/cscs.yml b/ci/cscs.yml index 3e50b378..c3ac15c6 100644 --- a/ci/cscs.yml +++ b/ci/cscs.yml @@ -25,5 +25,5 @@ test_job: variables: SLURM_JOB_NUM_NODES: 2 SLURM_NTASKS: 2 - SLURM_ACCOUNT: a161 - SBATCH_ACCOUNT: a161 + #SLURM_ACCOUNT: a161 + #SBATCH_ACCOUNT: a161 From 63bb3cbce268407d9d52013af84bef34d9682125 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Wed, 6 Aug 2025 17:04:34 +0200 Subject: [PATCH 125/302] put more constants in plotting, use land-mask from plotting --- src/hirad/eval/diurnal_cycle_precip.py | 11 +++-------- src/hirad/eval/diurnal_cycle_temp_wind.py | 8 +++----- src/hirad/eval/hist.py | 14 +++----------- src/hirad/eval/map_99pctl.py | 6 +----- src/hirad/eval/map_mean.py | 6 +----- src/hirad/eval/percentile99_cycle_precip.py | 12 ++---------- src/hirad/eval/plotting.py | 5 +++-- src/hirad/eval/probability_of_exceedance.py | 14 +++----------- 8 files changed, 19 insertions(+), 57 deletions(-) diff --git a/src/hirad/eval/diurnal_cycle_precip.py b/src/hirad/eval/diurnal_cycle_precip.py index 56e19a5e..d6f0eaf5 100644 --- a/src/hirad/eval/diurnal_cycle_precip.py +++ b/src/hirad/eval/diurnal_cycle_precip.py @@ -12,12 +12,7 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import get_channel_indices - -# Constants -CONV_FACTOR = 100*24 # Convert meters to mm/day -WET_THRESHOLD = 0.1 # Threshold for wet-hour in mm/h -LOG_INTERVAL = 24 # Log progress every N timesteps +from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, CONV_FACTOR, WET_THRESHOLD, LOG_INTERVAL @hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") def main(cfg: DictConfig): @@ -47,8 +42,8 @@ def load(ts, fn): logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") # Land-sea mask - lsm_data = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy').reshape(352,544) - land_mask = np.where(lsm_data >= 0.5, 1.0, np.nan) + land_mask_da = load_land_sea_mask() + land_mask = land_mask_da.values coords = {"lat": np.arange(land_mask.shape[0]), "lon": np.arange(land_mask.shape[1])} # Prepare lists to collect DataArrays diff --git a/src/hirad/eval/diurnal_cycle_temp_wind.py b/src/hirad/eval/diurnal_cycle_temp_wind.py index 1859ad77..85ba9cf1 100644 --- a/src/hirad/eval/diurnal_cycle_temp_wind.py +++ b/src/hirad/eval/diurnal_cycle_temp_wind.py @@ -12,9 +12,7 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import get_channel_indices - -LOG_INTERVAL = 24 +from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, LOG_INTERVAL @hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") def main(cfg: DictConfig): @@ -56,8 +54,8 @@ def load(ts, fn): return torch.load(out_root/ts/fn, weights_only=False) # Land-sea mask - lsm_data = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy').reshape(352,544) - land_mask = np.where(lsm_data >= 0.5, 1.0, np.nan) + land_mask_da = load_land_sea_mask() + land_mask = land_mask_da.values coords = {"lat": np.arange(land_mask.shape[0]), "lon": np.arange(land_mask.shape[1])} # Prepare lists to collect DataArrays diff --git a/src/hirad/eval/hist.py b/src/hirad/eval/hist.py index 2a9f8a74..8f2da491 100644 --- a/src/hirad/eval/hist.py +++ b/src/hirad/eval/hist.py @@ -17,11 +17,7 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import get_channel_indices - -# Constants -CONV_FACTOR = 100 # Convert meters to mm/h -LOG_INTERVAL = 24 # Log progress every N timesteps +from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, CONV_FACTOR_HOURLY, LOG_INTERVAL def save_distribution_plot(hist_data_dict, bin_edges, labels, colors, title, ylabel, out_path, percentiles_data=None): @@ -126,7 +122,7 @@ def main(cfg: DictConfig): out_root = Path(cfg.generation.io.output_path or './outputs') def load(ts, fn): - return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR + return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR_HOURLY # Find channel indices indices = get_channel_indices(dataset) @@ -135,11 +131,7 @@ def load(ts, fn): logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") # Land-sea mask - lsm_data = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy').reshape(352,544) - land_mask = xr.DataArray( - np.where(lsm_data >= 0.5, 1.0, np.nan), - dims=['lat', 'lon'] - ) + land_mask = load_land_sea_mask() # Define histogram bins bins = np.logspace(-1, 1, 50) # Log-spaced bins for precipitation diff --git a/src/hirad/eval/map_99pctl.py b/src/hirad/eval/map_99pctl.py index 4e7395ac..a399a82c 100644 --- a/src/hirad/eval/map_99pctl.py +++ b/src/hirad/eval/map_99pctl.py @@ -17,11 +17,7 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import plot_map_precipitation, get_channel_indices - -# Constants -CONV_FACTOR = 100 * 24 # Convert meters to mm/day -LOG_INTERVAL = 24 # Log progress every N timesteps +from hirad.eval.plotting import plot_map_precipitation, get_channel_indices, CONV_FACTOR, LOG_INTERVAL @hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") diff --git a/src/hirad/eval/map_mean.py b/src/hirad/eval/map_mean.py index 40f80c92..e7c33ae1 100644 --- a/src/hirad/eval/map_mean.py +++ b/src/hirad/eval/map_mean.py @@ -17,11 +17,7 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import plot_map_precipitation, get_channel_indices - -# Constants -CONV_FACTOR = 100 * 24 # Convert meters to mm/day -LOG_INTERVAL = 24 # Log progress every N timesteps +from hirad.eval.plotting import plot_map_precipitation, get_channel_indices, CONV_FACTOR, LOG_INTERVAL @hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") diff --git a/src/hirad/eval/percentile99_cycle_precip.py b/src/hirad/eval/percentile99_cycle_precip.py index 15aec33b..3ed38a03 100644 --- a/src/hirad/eval/percentile99_cycle_precip.py +++ b/src/hirad/eval/percentile99_cycle_precip.py @@ -19,11 +19,7 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import get_channel_indices - -# Constants -CONV_FACTOR = 100 * 24 # Convert meters to mm/day -LOG_INTERVAL = 24 # Log progress every N timesteps +from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, CONV_FACTOR def hour_of(dt: str, fmt: str = "%Y%m%d-%H%M") -> int: @@ -84,11 +80,7 @@ def load(ts, fn): logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") # Land-sea mask - lsm_data = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy').reshape(352,544) - land_mask = xr.DataArray( - np.where(lsm_data >= 0.5, 1.0, np.nan), - dims=['lat', 'lon'] - ) + land_mask = load_land_sea_mask() # Storage for diurnal cycles pct99_mean = {} diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index 39b32084..9c94e307 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -17,9 +17,10 @@ RELAX_ZONE = 19 # Number of points dropped on each side (relaxation zone) # Constants for data processing -CONV_FACTOR_HOURLY = 100 # Convert precip meters to mm/h -CONV_FACTOR = CONV_FACTOR_HOURLY * 24 # Convert precip from meters to mm/day +CONV_FACTOR_HOURLY = 100 # Convert precip of ERA5 from meters to mm/h +CONV_FACTOR = CONV_FACTOR_HOURLY * 24 # Convert precip of ERA5 from from meters to mm/day WET_THRESHOLD = 0.1 # Threshold for wet-hour in mm/h +LOG_INTERVAL = 24 # Log progress every N timesteps def get_channel_indices(dataset, channels=None): """ diff --git a/src/hirad/eval/probability_of_exceedance.py b/src/hirad/eval/probability_of_exceedance.py index 52452c13..3ab645a4 100644 --- a/src/hirad/eval/probability_of_exceedance.py +++ b/src/hirad/eval/probability_of_exceedance.py @@ -17,11 +17,7 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import get_channel_indices - -# Constants -CONV_FACTOR = 100 # Convert meters to mm/h -LOG_INTERVAL = 24 # Log progress every N timesteps +from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, CONV_FACTOR_HOURLY, LOG_INTERVAL def save_exceedance_plot(exceedance_data_dict, thresholds, labels, colors, title, ylabel, out_path, percentiles_data=None): @@ -123,7 +119,7 @@ def main(cfg: DictConfig): out_root = Path(cfg.generation.io.output_path or './outputs') def load(ts, fn): - return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR + return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR_HOURLY # Find channel indices indices = get_channel_indices(dataset) @@ -132,11 +128,7 @@ def load(ts, fn): logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") # Land-sea mask - lsm_data = np.load('/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy').reshape(352,544) - land_mask = xr.DataArray( - np.where(lsm_data >= 0.5, 1.0, np.nan), - dims=['lat', 'lon'] - ) + land_mask = load_land_sea_mask() # Define thresholds for exceedance calculation thresholds = np.logspace(-2, 2, 200) # From 0.01 to 100 mm/h From 6236e7e594ce49042e78741e7a3a3d5cd43e55f9 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Wed, 6 Aug 2025 17:09:23 +0200 Subject: [PATCH 126/302] re-use concat_and_group_diurnal --- src/hirad/eval/diurnal_cycle_precip.py | 26 ++++++----------------- src/hirad/eval/diurnal_cycle_temp_wind.py | 26 ++++++----------------- 2 files changed, 14 insertions(+), 38 deletions(-) diff --git a/src/hirad/eval/diurnal_cycle_precip.py b/src/hirad/eval/diurnal_cycle_precip.py index d6f0eaf5..2270db93 100644 --- a/src/hirad/eval/diurnal_cycle_precip.py +++ b/src/hirad/eval/diurnal_cycle_precip.py @@ -12,7 +12,7 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, CONV_FACTOR, WET_THRESHOLD, LOG_INTERVAL +from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, CONV_FACTOR, WET_THRESHOLD, LOG_INTERVAL, concat_and_group_diurnal @hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") def main(cfg: DictConfig): @@ -75,26 +75,14 @@ def load(ts, fn): if idx % LOG_INTERVAL == 0 or idx == len(times): logger.info(f"Processed {idx}/{len(times)} timesteps ({ts})") - # Helper to concat and compute diurnal stats - def concat_and_group(list_of_da, is_member=False, scale=1.0): - da = xr.concat(list_of_da, dim="time").groupby("time.hour") - if is_member: - timmean = da.mean(dim='time') * scale - mean = timmean.mean(dim='member') - std = timmean.std(dim='member') - else: - mean = da.mean(dim='time') * scale - std = None - return mean, std - # Compute diurnal means and stds - amount_target_mean, _ = concat_and_group(target_precip) - amount_baseline_mean, _ = concat_and_group(baseline_precip) - amount_pred_mean, amount_pred_std = concat_and_group(pred_precip, is_member=True) + amount_target_mean, _ = concat_and_group_diurnal(target_precip) + amount_baseline_mean, _ = concat_and_group_diurnal(baseline_precip) + amount_pred_mean, amount_pred_std = concat_and_group_diurnal(pred_precip, is_member=True) - wet_target_mean, _ = concat_and_group(target_wet, scale=100.0) # scale to percentage - wet_baseline_mean, _ = concat_and_group(baseline_wet, scale=100.0) - wet_pred_mean, wet_pred_std = concat_and_group(pred_wet, is_member=True, scale=100.0) + wet_target_mean, _ = concat_and_group_diurnal(target_wet, scale=100.0) # scale to percentage + wet_baseline_mean, _ = concat_and_group_diurnal(baseline_wet, scale=100.0) + wet_pred_mean, wet_pred_std = concat_and_group_diurnal(pred_wet, is_member=True, scale=100.0) def save_plot(hour, means, stds, labels, ylabel, title, out_path): hrs = np.concatenate([hour.values, [24]]) diff --git a/src/hirad/eval/diurnal_cycle_temp_wind.py b/src/hirad/eval/diurnal_cycle_temp_wind.py index 85ba9cf1..e624641d 100644 --- a/src/hirad/eval/diurnal_cycle_temp_wind.py +++ b/src/hirad/eval/diurnal_cycle_temp_wind.py @@ -12,7 +12,7 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, LOG_INTERVAL +from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, LOG_INTERVAL, concat_and_group_diurnal @hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") def main(cfg: DictConfig): @@ -115,26 +115,14 @@ def load(ts, fn): if idx % LOG_INTERVAL == 0 or idx == len(times): logger.info(f"Processed {idx}/{len(times)} timesteps ({ts})") - # Helper to concat and compute diurnal stats - def concat_and_group(list_of_da, is_member=False, scale=1.0): - da = xr.concat(list_of_da, dim="time").groupby("time.hour") - if is_member: - timmean = da.mean(dim='time') * scale - mean = timmean.mean(dim='member') - std = timmean.std(dim='member') - else: - mean = da.mean(dim='time') * scale - std = None - return mean, std - # Compute diurnal means and stds - temp_target_mean, _ = concat_and_group(target_temp) - temp_baseline_mean, _ = concat_and_group(baseline_temp) - temp_pred_mean, temp_pred_std = concat_and_group(pred_temp, is_member=True) + temp_target_mean, _ = concat_and_group_diurnal(target_temp) + temp_baseline_mean, _ = concat_and_group_diurnal(baseline_temp) + temp_pred_mean, temp_pred_std = concat_and_group_diurnal(pred_temp, is_member=True) - wind_target_mean, _ = concat_and_group(target_wind) - wind_baseline_mean, _ = concat_and_group(baseline_wind) - wind_pred_mean, wind_pred_std = concat_and_group(pred_wind, is_member=True) + wind_target_mean, _ = concat_and_group_diurnal(target_wind) + wind_baseline_mean, _ = concat_and_group_diurnal(baseline_wind) + wind_pred_mean, wind_pred_std = concat_and_group_diurnal(pred_wind, is_member=True) def save_plot(hour, means, stds, labels, ylabel, title, out_path): hrs = np.concatenate([hour.values, [24]]) From 489d0a6e340c9ca4ae67ec2b55198c6bd08162f7 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Wed, 6 Aug 2025 17:23:11 +0200 Subject: [PATCH 127/302] remove unneeded function --- src/hirad/eval/plotting.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index 9c94e307..edbcbc7f 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -59,13 +59,6 @@ def load_land_sea_mask(path='/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy'): coords={"lat": np.arange(352), "lon": np.arange(544)} ) -def load_prediction_data_torch(out_root, times, filename_pattern, conv_factor=CONV_FACTOR): - """Generic loader for prediction data with conversion factor.""" - def load(ts, fn): - return torch.load(out_root/ts/fn, weights_only=False) * conv_factor - - return lambda ts, fn: load(ts, filename_pattern.format(ts=ts, fn=fn)) - def concat_and_group_diurnal(list_of_da, is_member=False, scale=1.0): """Helper to concatenate DataArrays and compute diurnal statistics.""" da = xr.concat(list_of_da, dim="time").groupby("time.hour") From e2b1aecc0c2ef609095bbfd265a85933caa6e285 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Wed, 6 Aug 2025 17:41:24 +0200 Subject: [PATCH 128/302] cleanup dcycles --- ... => diurnal_cycle_precip_mean_wet-hour.py} | 68 ++++++++--------- ..._precip.py => diurnal_cycle_precip_p99.py} | 12 +-- src/hirad/eval/diurnal_cycle_temp_wind.py | 73 +++++++------------ src/hirad/eval_precip.sh | 4 +- 4 files changed, 66 insertions(+), 91 deletions(-) rename src/hirad/eval/{diurnal_cycle_precip.py => diurnal_cycle_precip_mean_wet-hour.py} (72%) rename src/hirad/eval/{percentile99_cycle_precip.py => diurnal_cycle_precip_p99.py} (93%) diff --git a/src/hirad/eval/diurnal_cycle_precip.py b/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py similarity index 72% rename from src/hirad/eval/diurnal_cycle_precip.py rename to src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py index 2270db93..f2715880 100644 --- a/src/hirad/eval/diurnal_cycle_precip.py +++ b/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py @@ -14,6 +14,27 @@ from hirad.utils.function_utils import get_time_from_range from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, CONV_FACTOR, WET_THRESHOLD, LOG_INTERVAL, concat_and_group_diurnal +def save_plot(hour, means, stds, labels, ylabel, title, out_path): + hrs = np.concatenate([hour.values, [24]]) + plt.figure(figsize=(8,4)) + for mean, std, label in zip(means, stds, labels): + vals = np.append(mean.values, mean.values[0]) + line, = plt.plot(hrs, vals, label=label) + if std is not None: + stdv = np.append(std.values, std.values[0]) + plt.fill_between(hrs, np.maximum(vals - stdv, 0), vals + stdv, color=line.get_color(), alpha=0.3) + plt.xlabel('Hour (UTC)') + plt.xticks(range(0,25,3)) + plt.xlim(0,24) + plt.ylabel(ylabel) + plt.title(title) + plt.grid(True) + plt.legend() + plt.tight_layout() + Path(out_path).parent.mkdir(parents=True, exist_ok=True) + plt.savefig(out_path) + plt.close() + @hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") def main(cfg: DictConfig): # Setup logging @@ -31,9 +52,8 @@ def main(cfg: DictConfig): ds_cfg, times, cfg.generation.get('has_lead_time', False) ) + # Location of the output from inference out_root = Path(cfg.generation.io.output_path or './outputs') - def load(ts, fn): - return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR # Find channel indices indices = get_channel_indices(dataset) @@ -42,9 +62,7 @@ def load(ts, fn): logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") # Land-sea mask - land_mask_da = load_land_sea_mask() - land_mask = land_mask_da.values - coords = {"lat": np.arange(land_mask.shape[0]), "lon": np.arange(land_mask.shape[1])} + land_mask = load_land_sea_mask() # Prepare lists to collect DataArrays target_precip, baseline_precip, pred_precip = [], [], [] @@ -53,14 +71,19 @@ def load(ts, fn): # Collect data for idx, ts in enumerate(times, 1): dt = datetimes[idx-1] - target = load(ts, f"{ts}-target")[tp_out] * land_mask - baseline = load(ts, f"{ts}-baseline")[tp_in] * land_mask / 6. # 6 because 1h -> accumulation period is 6h in hourly ERA5 dataset - preds = load(ts, f"{ts}-predictions")[:, tp_out, :, :] * land_mask + target = torch.load(out_root/ts/f"{ts}-target", weights_only=False)[tp_out] * CONV_FACTOR + baseline = torch.load(out_root/ts/f"{ts}-baseline", weights_only=False)[tp_in] * CONV_FACTOR / 6. # 6 because 1h -> accumulation period is 6h in hourly ERA5 dataset + preds = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False)[:, tp_out, :, :] * CONV_FACTOR # DataArrays for spatial means at each timestep - da_target = xr.DataArray(target, dims=("lat","lon"), coords=coords) - da_baseline = xr.DataArray(baseline, dims=("lat","lon"), coords=coords) - da_preds = xr.DataArray(preds, dims=("member","lat","lon"), coords={"member": np.arange(preds.shape[0]), **coords}) + da_target = xr.DataArray(target, dims=("lat","lon"), coords=land_mask.coords) + da_baseline = xr.DataArray(baseline, dims=("lat","lon"), coords=land_mask.coords) + da_preds = xr.DataArray(preds, dims=("member","lat","lon"), coords={"member": np.arange(preds.shape[0]), **land_mask.coords}) + + # Apply land mask after conversion to xarray + da_target = da_target * land_mask + da_baseline = da_baseline * land_mask + da_preds = da_preds * land_mask # Spatial mean target_precip.append(da_target.mean(dim=("lat","lon")).assign_coords(time=dt)) @@ -80,31 +103,10 @@ def load(ts, fn): amount_baseline_mean, _ = concat_and_group_diurnal(baseline_precip) amount_pred_mean, amount_pred_std = concat_and_group_diurnal(pred_precip, is_member=True) - wet_target_mean, _ = concat_and_group_diurnal(target_wet, scale=100.0) # scale to percentage + wet_target_mean, _ = concat_and_group_diurnal(target_wet, scale=100.0) # scale to obtain percentages wet_baseline_mean, _ = concat_and_group_diurnal(baseline_wet, scale=100.0) wet_pred_mean, wet_pred_std = concat_and_group_diurnal(pred_wet, is_member=True, scale=100.0) - def save_plot(hour, means, stds, labels, ylabel, title, out_path): - hrs = np.concatenate([hour.values, [24]]) - plt.figure(figsize=(8,4)) - for mean, std, label in zip(means, stds, labels): - vals = np.append(mean.values, mean.values[0]) - line, = plt.plot(hrs, vals, label=label) - if std is not None: - stdv = np.append(std.values, std.values[0]) - plt.fill_between(hrs, np.maximum(vals - stdv, 0), vals + stdv, color=line.get_color(), alpha=0.3) - plt.xlabel('Hour (UTC)') - plt.xticks(range(0,25,3)) - plt.xlim(0,24) - plt.ylabel(ylabel) - plt.title(title) - plt.grid(True) - plt.legend() - plt.tight_layout() - Path(out_path).parent.mkdir(parents=True, exist_ok=True) - plt.savefig(out_path) - plt.close() - # Generate plots save_plot( amount_target_mean.hour, diff --git a/src/hirad/eval/percentile99_cycle_precip.py b/src/hirad/eval/diurnal_cycle_precip_p99.py similarity index 93% rename from src/hirad/eval/percentile99_cycle_precip.py rename to src/hirad/eval/diurnal_cycle_precip_p99.py index 3ed38a03..bb641532 100644 --- a/src/hirad/eval/percentile99_cycle_precip.py +++ b/src/hirad/eval/diurnal_cycle_precip_p99.py @@ -22,10 +22,6 @@ from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, CONV_FACTOR -def hour_of(dt: str, fmt: str = "%Y%m%d-%H%M") -> int: - return datetime.strptime(dt, fmt).hour - - def save_plot(hours, lines, labels, ylabel, title, out_path): Path(out_path).parent.mkdir(parents=True, exist_ok=True) plt.figure(figsize=(8,4)) @@ -68,10 +64,8 @@ def main(cfg: DictConfig): ) logger.info("Dataset and sampler initialized") - # Output root and loader + # Output root out_root = Path(cfg.generation.io.output_path or './outputs') - def load(ts, fn): - return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR # Find channel indices indices = get_channel_indices(dataset) @@ -92,7 +86,7 @@ def load(ts, fn): data_list = [] for ts in times: - data = load(ts, f"{ts}-{mode}")[tp_out if mode == 'target' else tp_in] * land_mask + data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode == 'target' else tp_in] * CONV_FACTOR * land_mask data_list.append(data) da = xr.DataArray( @@ -116,7 +110,7 @@ def load(ts, fn): # Load all prediction data at once into xarray pred_data_list = [] for ts in times: - preds = load(ts, f"{ts}-predictions") # [n_members, n_channels, lat, lon] + preds = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) * CONV_FACTOR # [n_members, n_channels, lat, lon] # Extract precipitation channel and convert to xarray for proper broadcasting tp_data = preds[:, tp_out] # [n_members, lat, lon] tp_da = xr.DataArray(tp_data, dims=['member', 'lat', 'lon']) diff --git a/src/hirad/eval/diurnal_cycle_temp_wind.py b/src/hirad/eval/diurnal_cycle_temp_wind.py index e624641d..c0c97fee 100644 --- a/src/hirad/eval/diurnal_cycle_temp_wind.py +++ b/src/hirad/eval/diurnal_cycle_temp_wind.py @@ -54,63 +54,42 @@ def load(ts, fn): return torch.load(out_root/ts/fn, weights_only=False) # Land-sea mask - land_mask_da = load_land_sea_mask() - land_mask = land_mask_da.values - coords = {"lat": np.arange(land_mask.shape[0]), "lon": np.arange(land_mask.shape[1])} + land_mask = load_land_sea_mask() # Prepare lists to collect DataArrays target_temp, baseline_temp, pred_temp = [], [], [] target_wind, baseline_wind, pred_wind = [], [], [] + def mean_over_land(data, dims, coords, time_coord): + da = xr.DataArray(data, dims=dims, coords=coords) * land_mask + return da.mean(dim=("lat","lon")).assign_coords(time=time_coord) + # Loop over timestamps for idx, ts in enumerate(times, 1): dt = datetimes[idx-1] - # Load and apply land mask - target = load(ts, f"{ts}-target") * land_mask - baseline = load(ts, f"{ts}-baseline") * land_mask - predictions = load(ts, f"{ts}-predictions") * land_mask - - # Wrap into DataArrays (convert temperature to Celsius inline) - da_tgt_temp = xr.DataArray( - target[t2m_out] - 273.15, dims=("lat","lon"), coords=coords - ) - da_bsl_temp = xr.DataArray( - baseline[t2m_in] - 273.15, dims=("lat","lon"), coords=coords - ) - tgt_wind = np.hypot(target[u_out], target[v_out]) - bsl_wind = np.hypot(baseline[u_in], baseline[v_in]) - da_tgt_wind = xr.DataArray(tgt_wind, dims=("lat","lon"), coords=coords) - da_bsl_wind = xr.DataArray(bsl_wind, dims=("lat","lon"), coords=coords) - - da_pred_members_temp = xr.DataArray( - predictions[:, t2m_out, :, :] - 273.15, dims=("member","lat","lon"), - coords={"member": np.arange(predictions.shape[0]), **coords} - ) - da_pred_members_wind = xr.DataArray( + # Load data + target = load(ts, f"{ts}-target") + baseline = load(ts, f"{ts}-baseline") + predictions = load(ts, f"{ts}-predictions") + + # Process temperature (convert to Celsius) + target_temp.append(mean_over_land( + target[t2m_out] - 273.15, ("lat","lon"), land_mask.coords, dt)) + baseline_temp.append(mean_over_land( + baseline[t2m_in] - 273.15, ("lat","lon"), land_mask.coords, dt)) + pred_temp.append(mean_over_land( + predictions[:, t2m_out, :, :] - 273.15, ("member","lat","lon"), + {"member": np.arange(predictions.shape[0]), **land_mask.coords}, dt)) + + # Process wind speed + target_wind.append(mean_over_land( + np.hypot(target[u_out], target[v_out]), ("lat","lon"), land_mask.coords, dt)) + baseline_wind.append(mean_over_land( + np.hypot(baseline[u_in], baseline[v_in]), ("lat","lon"), land_mask.coords, dt)) + pred_wind.append(mean_over_land( np.hypot(predictions[:, u_out, :, :], predictions[:, v_out, :, :]), - dims=("member","lat","lon"), coords={"member": np.arange(predictions.shape[0]), **coords} - ) - - # Compute spatial mean and assign time coordinate - target_temp.append( - da_tgt_temp.mean(dim=("lat","lon")).assign_coords(time=dt) - ) - baseline_temp.append( - da_bsl_temp.mean(dim=("lat","lon")).assign_coords(time=dt) - ) - pred_temp.append( - da_pred_members_temp.mean(dim=("lat","lon")).assign_coords(time=dt) - ) - target_wind.append( - da_tgt_wind.mean(dim=("lat","lon")).assign_coords(time=dt) - ) - baseline_wind.append( - da_bsl_wind.mean(dim=("lat","lon")).assign_coords(time=dt) - ) - pred_wind.append( - da_pred_members_wind.mean(dim=("lat","lon")).assign_coords(time=dt) - ) + ("member","lat","lon"), {"member": np.arange(predictions.shape[0]), **land_mask.coords}, dt)) if idx % LOG_INTERVAL == 0 or idx == len(times): logger.info(f"Processed {idx}/{len(times)} timesteps ({ts})") diff --git a/src/hirad/eval_precip.sh b/src/hirad/eval_precip.sh index 44eb3bcc..52232c46 100644 --- a/src/hirad/eval_precip.sh +++ b/src/hirad/eval_precip.sh @@ -45,8 +45,8 @@ export OMP_NUM_THREADS=72 # python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml srun --environment=./ci/edf/modulus_env.toml bash -c " pip install -e . --no-dependencies - python src/hirad/eval/diurnal_cycle_precip.py --config-name=generate_era_cosmo.yaml - python src/hirad/eval/percentile99_cycle_precip.py --config-name=generate_era_cosmo.yaml + python src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py --config-name=generate_era_cosmo.yaml + python src/hirad/eval/diurnal_cycle_precip_p99.py --config-name=generate_era_cosmo.yaml python src/hirad/eval/diurnal_cycle_temp_wind.py --config-name=generate_era_cosmo.yaml " From 3928d6e9ea0e1e921f698445df56645495884c99 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Wed, 6 Aug 2025 18:10:25 +0200 Subject: [PATCH 129/302] cleanup --- src/hirad/eval/hist.py | 54 ++++++++------------- src/hirad/eval/probability_of_exceedance.py | 48 +++++++----------- 2 files changed, 37 insertions(+), 65 deletions(-) diff --git a/src/hirad/eval/hist.py b/src/hirad/eval/hist.py index 8f2da491..bdd241bf 100644 --- a/src/hirad/eval/hist.py +++ b/src/hirad/eval/hist.py @@ -118,11 +118,8 @@ def main(cfg: DictConfig): ) logger.info("Dataset and sampler initialized") - # Output root and loader + # Output root out_root = Path(cfg.generation.io.output_path or './outputs') - - def load(ts, fn): - return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR_HOURLY # Find channel indices indices = get_channel_indices(dataset) @@ -136,16 +133,14 @@ def load(ts, fn): # Define histogram bins bins = np.logspace(-1, 1, 50) # Log-spaced bins for precipitation - # Storage for histogram data + # Storage for histogram data and land values hist_data = {} - # Store all land values for percentile calculation all_land_values = {} # -- Process target and baseline -- for mode in ['target', 'baseline']: logger.info(f"Processing mode: {mode}") - # Initialize histogram accumulator and collect all values hist_counts = np.zeros(len(bins) - 1) total_samples = 0 all_values = [] @@ -154,17 +149,15 @@ def load(ts, fn): if i % LOG_INTERVAL == 0: logger.info(f"Processing timestep {i+1}/{len(times)}") - data = load(ts, f"{ts}-{mode}")[tp_out if mode == 'target' else tp_in] * land_mask + data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode == 'target' else tp_in] * CONV_FACTOR_HOURLY * land_mask # Apply scaling factor for baseline if mode == 'baseline': data = data / 6.0 - # Extract land values (remove NaN values) land_values = data.values[~np.isnan(data.values)] all_values.extend(land_values) - # Accumulate histogram counts counts, _ = np.histogram(land_values, bins=bins) hist_counts += counts total_samples += len(land_values) @@ -180,28 +173,25 @@ def load(ts, fn): n_members = None member_hist_data = [] - all_member_values = [] # Store all values for each member + all_member_values = [] for i, ts in enumerate(times): if i % LOG_INTERVAL == 0: logger.info(f"Processing timestep {i+1}/{len(times)}") - preds = load(ts, f"{ts}-predictions") # [n_members, n_channels, lat, lon] + preds = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) * CONV_FACTOR_HOURLY # [n_members, n_channels, lat, lon] if n_members is None: n_members = preds.shape[0] - # Initialize histogram accumulators for each member member_hist_data = [np.zeros(len(bins) - 1) for _ in range(n_members)] member_sample_counts = [0 for _ in range(n_members)] - all_member_values = [[] for _ in range(n_members)] # Initialize value storage + all_member_values = [[] for _ in range(n_members)] for member_idx in range(n_members): data = preds[member_idx, tp_out] * land_mask - # Extract land values (remove NaN values) land_values = data.values[~np.isnan(data.values)] - all_member_values[member_idx].extend(land_values) # Store values for percentiles + all_member_values[member_idx].extend(land_values) - # Accumulate histogram counts for this member counts, _ = np.histogram(land_values, bins=bins) member_hist_data[member_idx] += counts member_sample_counts[member_idx] += len(land_values) @@ -219,30 +209,24 @@ def load(ts, fn): # Compute percentiles for all datasets percentiles_data = {} + percentiles = {99: 0.99, 99.9: 0.999, 99.99: 0.9999} - # Target percentiles - target_data_array = xr.DataArray(all_land_values['target']) - target_p99 = target_data_array.quantile(0.99).item() - target_p999 = target_data_array.quantile(0.999).item() - target_p9999 = target_data_array.quantile(0.9999).item() - percentiles_data['target'] = {99: target_p99, 99.9: target_p999, 99.99: target_p9999} - - # Baseline percentiles - baseline_data_array = xr.DataArray(all_land_values['baseline']) - baseline_p99 = baseline_data_array.quantile(0.99).item() - baseline_p999 = baseline_data_array.quantile(0.999).item() - baseline_p9999 = baseline_data_array.quantile(0.9999).item() - percentiles_data['baseline'] = {99: baseline_p99, 99.9: baseline_p999, 99.99: baseline_p9999} + # Target and baseline percentiles + for mode in ['target', 'baseline']: + data_array = xr.DataArray(all_land_values[mode]) + percentiles_data[mode] = { + key: data_array.quantile(p).item() + for key, p in percentiles.items() + } # Ensemble member percentiles percentiles_data['predictions'] = {} for member_idx in range(n_members): member_data_array = xr.DataArray(all_member_values[member_idx]) - member_p99 = member_data_array.quantile(0.99).item() - member_p999 = member_data_array.quantile(0.999).item() - member_p9999 = member_data_array.quantile(0.9999).item() - percentiles_data['predictions'][f'member_{member_idx}'] = {99: member_p99, 99.9: member_p999, 99.99: member_p9999} - + percentiles_data['predictions'][f'member_{member_idx}'] = { + key: member_data_array.quantile(p).item() + for key, p in percentiles.items() + } # Create distribution plots labels = ['COSMO-2 Analysis', 'ERA5', 'CorrDiff Ensemble'] diff --git a/src/hirad/eval/probability_of_exceedance.py b/src/hirad/eval/probability_of_exceedance.py index 3ab645a4..febda1f8 100644 --- a/src/hirad/eval/probability_of_exceedance.py +++ b/src/hirad/eval/probability_of_exceedance.py @@ -115,11 +115,8 @@ def main(cfg: DictConfig): ) logger.info("Dataset and sampler initialized") - # Output root and loader + # Output root out_root = Path(cfg.generation.io.output_path or './outputs') - - def load(ts, fn): - return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR_HOURLY # Find channel indices indices = get_channel_indices(dataset) @@ -133,9 +130,8 @@ def load(ts, fn): # Define thresholds for exceedance calculation thresholds = np.logspace(-2, 2, 200) # From 0.01 to 100 mm/h - # Storage for exceedance data + # Storage for exceedance data and land values exceedance_data = {} - # Store all land values for percentile calculation all_land_values = {} # -- Process target and baseline -- @@ -148,13 +144,12 @@ def load(ts, fn): if i % LOG_INTERVAL == 0: logger.info(f"Processing timestep {i+1}/{len(times)}") - data = load(ts, f"{ts}-{mode}")[tp_out if mode == 'target' else tp_in] * land_mask + data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode == 'target' else tp_in] * CONV_FACTOR_HOURLY * land_mask # Apply scaling factor for baseline if mode == 'baseline': data = data / 6.0 - # Extract land values (remove NaN values) land_values = data.values[~np.isnan(data.values)] all_values.extend(land_values) @@ -173,21 +168,20 @@ def load(ts, fn): logger.info("Processing predictions") n_members = None - all_member_values = [] # Store all values for each member + all_member_values = [] for i, ts in enumerate(times): if i % LOG_INTERVAL == 0: logger.info(f"Processing timestep {i+1}/{len(times)}") - preds = load(ts, f"{ts}-predictions") # [n_members, n_channels, lat, lon] + preds = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) * CONV_FACTOR_HOURLY # [n_members, n_channels, lat, lon] if n_members is None: n_members = preds.shape[0] - all_member_values = [[] for _ in range(n_members)] # Initialize value storage + all_member_values = [[] for _ in range(n_members)] for member_idx in range(n_members): data = preds[member_idx, tp_out] * land_mask - # Extract land values (remove NaN values) land_values = data.values[~np.isnan(data.values)] all_member_values[member_idx].extend(land_values) @@ -207,30 +201,24 @@ def load(ts, fn): # Compute percentiles for all datasets percentiles_data = {} + percentiles = {99: 0.99, 99.9: 0.999, 99.99: 0.9999} - # Target percentiles - target_data_array = xr.DataArray(all_land_values['target']) - target_p99 = target_data_array.quantile(0.99).item() - target_p999 = target_data_array.quantile(0.999).item() - target_p9999 = target_data_array.quantile(0.9999).item() - percentiles_data['target'] = {99: target_p99, 99.9: target_p999, 99.99: target_p9999} - - # Baseline percentiles - baseline_data_array = xr.DataArray(all_land_values['baseline']) - baseline_p99 = baseline_data_array.quantile(0.99).item() - baseline_p999 = baseline_data_array.quantile(0.999).item() - baseline_p9999 = baseline_data_array.quantile(0.9999).item() - percentiles_data['baseline'] = {99: baseline_p99, 99.9: baseline_p999, 99.99: baseline_p9999} + # Target and baseline percentiles + for mode in ['target', 'baseline']: + data_array = xr.DataArray(all_land_values[mode]) + percentiles_data[mode] = { + key: data_array.quantile(p).item() + for key, p in percentiles.items() + } # Ensemble member percentiles percentiles_data['predictions'] = {} for member_idx in range(n_members): member_data_array = xr.DataArray(all_member_values[member_idx]) - member_p99 = member_data_array.quantile(0.99).item() - member_p999 = member_data_array.quantile(0.999).item() - member_p9999 = member_data_array.quantile(0.9999).item() - percentiles_data['predictions'][f'member_{member_idx}'] = {99: member_p99, 99.9: member_p999, 99.99: member_p9999} - + percentiles_data['predictions'][f'member_{member_idx}'] = { + key: member_data_array.quantile(p).item() + for key, p in percentiles.items() + } # Create exceedance plots labels = ['COSMO-2 Analysis', 'ERA5', 'CorrDiff Ensemble'] From 0738df23afc7d211b5463da4757ad150019ce416 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Thu, 7 Aug 2025 11:13:05 +0200 Subject: [PATCH 130/302] rename --- src/hirad/eval/{map_99pctl.py => map_precip_99pctl.py} | 0 src/hirad/eval/{map_mean.py => map_precip_mean.py} | 0 src/hirad/maps.sh | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/hirad/eval/{map_99pctl.py => map_precip_99pctl.py} (100%) rename src/hirad/eval/{map_mean.py => map_precip_mean.py} (100%) diff --git a/src/hirad/eval/map_99pctl.py b/src/hirad/eval/map_precip_99pctl.py similarity index 100% rename from src/hirad/eval/map_99pctl.py rename to src/hirad/eval/map_precip_99pctl.py diff --git a/src/hirad/eval/map_mean.py b/src/hirad/eval/map_precip_mean.py similarity index 100% rename from src/hirad/eval/map_mean.py rename to src/hirad/eval/map_precip_mean.py diff --git a/src/hirad/maps.sh b/src/hirad/maps.sh index 543de5a8..770074f5 100644 --- a/src/hirad/maps.sh +++ b/src/hirad/maps.sh @@ -46,6 +46,6 @@ export OMP_NUM_THREADS=72 srun --environment=./ci/edf/modulus_env.toml bash -c " pip install -e . --no-dependencies python src/hirad/eval/snapshots.py --config-name=generate_era_cosmo.yaml - python src/hirad/eval/map_99pctl.py --config-name=generate_era_cosmo.yaml - python src/hirad/eval/map_mean.py --config-name=generate_era_cosmo.yaml + python src/hirad/eval/map_precip_99pctl.py --config-name=generate_era_cosmo.yaml + python src/hirad/eval/map_precip_mean.py --config-name=generate_era_cosmo.yaml " \ No newline at end of file From ecb686fb7473f0e029c79a094504b46c72262e4f Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Thu, 7 Aug 2025 12:01:27 +0200 Subject: [PATCH 131/302] unify maps in one script and more stats --- src/hirad/eval/map_precip_99pctl.py | 168 ---------------------- src/hirad/eval/map_precip_mean.py | 168 ---------------------- src/hirad/eval/map_precip_stats.py | 207 ++++++++++++++++++++++++++++ src/hirad/eval_precip.sh | 14 +- src/hirad/maps.sh | 51 ------- 5 files changed, 216 insertions(+), 392 deletions(-) delete mode 100644 src/hirad/eval/map_precip_99pctl.py delete mode 100644 src/hirad/eval/map_precip_mean.py create mode 100644 src/hirad/eval/map_precip_stats.py delete mode 100644 src/hirad/maps.sh diff --git a/src/hirad/eval/map_precip_99pctl.py b/src/hirad/eval/map_precip_99pctl.py deleted file mode 100644 index a399a82c..00000000 --- a/src/hirad/eval/map_precip_99pctl.py +++ /dev/null @@ -1,168 +0,0 @@ -""" -Plots maps of the 99th percentile of precipitation over the entire time period. - -This script computes the all-time 99th percentile of precipitation for each grid point -and creates maps for target (COSMO-2), baseline (ERA5), and predictions (CorrDiff). -""" -import logging -from datetime import datetime -from pathlib import Path - -import hydra -import numpy as np -import torch -from omegaconf import DictConfig, OmegaConf -import xarray as xr - -from hirad.datasets import get_dataset_and_sampler_inference -from hirad.distributed import DistributedManager -from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import plot_map_precipitation, get_channel_indices, CONV_FACTOR, LOG_INTERVAL - - -@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") -def main(cfg: DictConfig): - # Setup logging - DistributedManager.initialize() - logging.basicConfig(level=logging.INFO) - logger = logging.getLogger(__name__) - - logger.info("Starting computation for 99th percentile precipitation maps") - times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") - logger.info(f"Loaded {len(times)} timesteps to process") - - # Initialize dataset - ds_cfg = OmegaConf.to_container(cfg.dataset) - dataset, _ = get_dataset_and_sampler_inference( - ds_cfg, times, cfg.generation.get('has_lead_time', False) - ) - logger.info("Dataset and sampler initialized") - - # Output root and loader - out_root = Path(cfg.generation.io.output_path or './outputs') - def load(ts, fn): - return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR - - # Find channel indices - indices = get_channel_indices(dataset) - tp_out = indices['output']['tp'] - tp_in = indices['input'].get('tp', tp_out) - logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") - - # -- Process target -- - logger.info("Processing target (COSMO-2)") - target_data_list = [] - for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: - logger.info(f"Processing target timestep {i+1}/{len(times)}: {ts}") - data = load(ts, f"{ts}-target")[tp_out] - target_data_list.append(data) - - target_da = xr.DataArray( - np.stack(target_data_list, axis=0), - dims=['time', 'lat', 'lon'], - coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} - ) - - # Compute 99th percentile across all time steps - target_p99 = target_da.quantile(0.99, dim='time') - logger.info("Target 99th percentile computed") - - # -- Process baseline -- - logger.info("Processing baseline (ERA5)") - baseline_data_list = [] - for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: - logger.info(f"Processing baseline timestep {i+1}/{len(times)}: {ts}") - data = load(ts, f"{ts}-baseline")[tp_in] - baseline_data_list.append(data) - - baseline_da = xr.DataArray( - np.stack(baseline_data_list, axis=0), - dims=['time', 'lat', 'lon'], - coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} - ) - - # Apply scaling factor for baseline and compute 99th percentile - baseline_p99 = (baseline_da / 6.0).quantile(0.99, dim='time') - logger.info("Baseline 99th percentile computed") - - # -- Process predictions -- - logger.info("Processing predictions (CorrDiff)") - pred_data_list = [] - for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: - logger.info(f"Processing predictions timestep {i+1}/{len(times)}: {ts}") - preds = load(ts, f"{ts}-predictions") # [n_members, n_channels, lat, lon] - # Extract precipitation channel - tp_data = preds[:, tp_out] # [n_members, lat, lon] - tp_da = xr.DataArray(tp_data, dims=['member', 'lat', 'lon']) - pred_data_list.append(tp_da) - - pred_da = xr.concat(pred_data_list, dim='time') # [member, time, lat, lon] - pred_da = pred_da.assign_coords({ - 'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times] - }) - pred_da = pred_da.transpose('member', 'time', 'lat', 'lon') - - # Compute 99th percentile across time for each member, then ensemble mean - pred_p99_by_member = pred_da.quantile(0.99, dim='time') - pred_p99_mean = pred_p99_by_member.mean(dim='member') - logger.info("Predictions 99th percentile computed") - - # Create output directory - map_output_dir = out_root / 'maps_99th_percentile' - map_output_dir.mkdir(parents=True, exist_ok=True) - - # Plot maps using the precipitation-specific plotting function - logger.info("Creating precipitation maps") - - # Target map - plot_map_precipitation( - target_p99.values, - str(map_output_dir / 'target_99th_percentile'), - title='COSMO-2 Analysis: 99th Percentile Precipitation', - threshold=0.1, - rfac=1.0 # Already converted to mm/day - ) - logger.info("Target map saved") - - # Baseline map - plot_map_precipitation( - baseline_p99.values, - str(map_output_dir / 'baseline_99th_percentile'), - title='ERA5: 99th Percentile Precipitation', - threshold=0.1, - rfac=1.0 # Already converted to mm/day - ) - logger.info("Baseline map saved") - - # Prediction ensemble mean map - plot_map_precipitation( - pred_p99_mean.values, - str(map_output_dir / 'prediction_ensmean_99th_percentile'), - title='CorrDiff Ensemble Mean: 99th Percentile Precipitation', - threshold=0.1, - rfac=1.0 # Already converted to mm/day - ) - logger.info("Prediction ensemble mean map saved") - - # Individual ensemble member maps - logger.info("Creating individual ensemble member maps") - n_members = pred_p99_by_member.shape[0] - for member_idx in range(n_members): - plot_map_precipitation( - pred_p99_by_member[member_idx].values, - str(map_output_dir / f'prediction_member_{member_idx:02d}_99th_percentile'), - title=f'CorrDiff Member {member_idx+1}: 99th Percentile Precipitation', - threshold=0.1, - rfac=1.0 # Already converted to mm/day - ) - logger.info(f"Individual ensemble member maps saved ({n_members} members)") - - logger.info(f"All maps saved to {map_output_dir}") - logger.info("99th percentile precipitation mapping completed successfully") - - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/src/hirad/eval/map_precip_mean.py b/src/hirad/eval/map_precip_mean.py deleted file mode 100644 index e7c33ae1..00000000 --- a/src/hirad/eval/map_precip_mean.py +++ /dev/null @@ -1,168 +0,0 @@ -""" -Plots maps of the mean precipitation over the entire time period. - -This script computes the temporal mean of precipitation for each grid point -and creates maps for target (COSMO-2), baseline (ERA5), and predictions (CorrDiff). -""" -import logging -from datetime import datetime -from pathlib import Path - -import hydra -import numpy as np -import torch -from omegaconf import DictConfig, OmegaConf -import xarray as xr - -from hirad.datasets import get_dataset_and_sampler_inference -from hirad.distributed import DistributedManager -from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import plot_map_precipitation, get_channel_indices, CONV_FACTOR, LOG_INTERVAL - - -@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") -def main(cfg: DictConfig): - # Setup logging - DistributedManager.initialize() - logging.basicConfig(level=logging.INFO) - logger = logging.getLogger(__name__) - - logger.info("Starting computation for mean precipitation maps") - times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") - logger.info(f"Loaded {len(times)} timesteps to process") - - # Initialize dataset - ds_cfg = OmegaConf.to_container(cfg.dataset) - dataset, _ = get_dataset_and_sampler_inference( - ds_cfg, times, cfg.generation.get('has_lead_time', False) - ) - logger.info("Dataset and sampler initialized") - - # Output root and loader - out_root = Path(cfg.generation.io.output_path or './outputs') - def load(ts, fn): - return torch.load(out_root/ts/fn, weights_only=False) * CONV_FACTOR - - # Find channel indices - indices = get_channel_indices(dataset) - tp_out = indices['output']['tp'] - tp_in = indices['input'].get('tp', tp_out) - logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") - - # -- Process target -- - logger.info("Processing target (COSMO-2)") - target_data_list = [] - for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: - logger.info(f"Processing target timestep {i+1}/{len(times)}: {ts}") - data = load(ts, f"{ts}-target")[tp_out] - target_data_list.append(data) - - target_da = xr.DataArray( - np.stack(target_data_list, axis=0), - dims=['time', 'lat', 'lon'], - coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} - ) - - # Compute mean across all time steps - target_mean = target_da.mean(dim='time') - logger.info("Target mean computed") - - # -- Process baseline -- - logger.info("Processing baseline (ERA5)") - baseline_data_list = [] - for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: - logger.info(f"Processing baseline timestep {i+1}/{len(times)}: {ts}") - data = load(ts, f"{ts}-baseline")[tp_in] - baseline_data_list.append(data) - - baseline_da = xr.DataArray( - np.stack(baseline_data_list, axis=0), - dims=['time', 'lat', 'lon'], - coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} - ) - - # Apply scaling factor for baseline and compute mean - baseline_mean = (baseline_da / 6.0).mean(dim='time') - logger.info("Baseline mean computed") - - # -- Process predictions -- - logger.info("Processing predictions (CorrDiff)") - pred_data_list = [] - for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: - logger.info(f"Processing predictions timestep {i+1}/{len(times)}: {ts}") - preds = load(ts, f"{ts}-predictions") # [n_members, n_channels, lat, lon] - # Extract precipitation channel - tp_data = preds[:, tp_out] # [n_members, lat, lon] - tp_da = xr.DataArray(tp_data, dims=['member', 'lat', 'lon']) - pred_data_list.append(tp_da) - - pred_da = xr.concat(pred_data_list, dim='time') # [member, time, lat, lon] - pred_da = pred_da.assign_coords({ - 'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times] - }) - pred_da = pred_da.transpose('member', 'time', 'lat', 'lon') - - # Compute mean across time for each member, then ensemble mean - pred_mean_by_member = pred_da.mean(dim='time') - pred_mean_mean = pred_mean_by_member.mean(dim='member') - logger.info("Predictions mean computed") - - # Create output directory - map_output_dir = out_root / 'maps_mean' - map_output_dir.mkdir(parents=True, exist_ok=True) - - # Plot maps using the precipitation-specific plotting function - logger.info("Creating precipitation maps") - - # Target map - plot_map_precipitation( - target_mean.values, - str(map_output_dir / 'target_mean'), - title='COSMO-2 Analysis: Mean Precipitation', - threshold=0.01, # Lower threshold for mean precipitation - rfac=1.0 # Already converted to mm/day - ) - logger.info("Target map saved") - - # Baseline map - plot_map_precipitation( - baseline_mean.values, - str(map_output_dir / 'baseline_mean'), - title='ERA5: Mean Precipitation', - threshold=0.01, # Lower threshold for mean precipitation - rfac=1.0 # Already converted to mm/day - ) - logger.info("Baseline map saved") - - # Prediction ensemble mean map - plot_map_precipitation( - pred_mean_mean.values, - str(map_output_dir / 'prediction_ensmean_mean'), - title='CorrDiff Ensemble Mean: Mean Precipitation', - threshold=0.01, # Lower threshold for mean precipitation - rfac=1.0 # Already converted to mm/day - ) - logger.info("Prediction ensemble mean map saved") - - # Individual ensemble member maps - logger.info("Creating individual ensemble member maps") - n_members = pred_mean_by_member.shape[0] - for member_idx in range(n_members): - plot_map_precipitation( - pred_mean_by_member[member_idx].values, - str(map_output_dir / f'prediction_member_{member_idx:02d}_mean'), - title=f'CorrDiff Member {member_idx+1}: Mean Precipitation', - threshold=0.01, # Lower threshold for mean precipitation - rfac=1.0 # Already converted to mm/day - ) - logger.info(f"Individual ensemble member maps saved ({n_members} members)") - - logger.info(f"All maps saved to {map_output_dir}") - logger.info("Mean precipitation mapping completed successfully") - - -if __name__ == '__main__': - main() diff --git a/src/hirad/eval/map_precip_stats.py b/src/hirad/eval/map_precip_stats.py new file mode 100644 index 00000000..62c574dd --- /dev/null +++ b/src/hirad/eval/map_precip_stats.py @@ -0,0 +1,207 @@ +""" +Generates: + - outputs/maps_mean/ + - outputs/maps_p99/ + - outputs/maps_p99.9/ + - outputs/maps_p99.99/ + - outputs/maps_Rx1hr/ + - outputs/maps_Rx1day/ + - outputs/maps_Rx5day/ + - outputs/maps_cdd/ + - outputs/maps_cwd/ + - outputs/maps_weth_freq/ +""" +import logging +from datetime import datetime +from pathlib import Path + +import hydra +import numpy as np +import torch +from omegaconf import DictConfig, OmegaConf +import xarray as xr + +from hirad.datasets import get_dataset_and_sampler_inference +from hirad.distributed import DistributedManager +from hirad.utils.function_utils import get_time_from_range +from hirad.eval.plotting import plot_map_precipitation, plot_map, get_channel_indices, CONV_FACTOR, LOG_INTERVAL, WET_THRESHOLD + + +def process_data_for_stat(times, out_root, tp_channel, mode, stat_type, stat_param, logger): + """Process data for a given mode and compute the specified statistic.""" + def consecutive_spell(condition): + """Calculate longest consecutive spell where condition is True.""" + def _spell_length(x): + x = np.asarray(x, dtype=bool) + if len(x) == 0: + return 0 + runs = np.diff(np.concatenate(([False], x, [False])).astype(int)) + starts = np.where(runs == 1)[0] + ends = np.where(runs == -1)[0] + return int(np.max(ends - starts)) if len(starts) > 0 else 0 + return xr.apply_ufunc(_spell_length, condition, input_core_dims=[['time']], vectorize=True) + + def apply_statistic(data, stat_type, stat_param): + if stat_type == 'mean': + return data.mean(dim='time') + elif stat_type == 'quantile': + return data.quantile(stat_param, dim='time') + elif stat_type == 'Rx1hr': + return data.max(dim='time') + elif stat_type == 'Rx1day': + # Maximum daily precipitation total + daily = data.resample(time="1D").sum("time") + return daily.max(dim='time') + elif stat_type == 'Rx5day': + # Maximum 5-consecutive-day precipitation total + daily = data.resample(time="1D").sum("time") + return daily.rolling(time=5, center=False).sum().max(dim='time') + elif stat_type == 'cdd': # Consecutive Dry Days (< 1 mm) + daily = data.resample(time="1D").sum("time") + return consecutive_spell(daily < 1.0) + elif stat_type == 'cwd': # Consecutive Wet Days (≥ 1 mm) + daily = data.resample(time="1D").sum("time") + return consecutive_spell(daily >= 1.0) + elif stat_type == 'weth_freq': + return (data / 24 > WET_THRESHOLD).mean(dim='time') * 100 + else: + raise ValueError(f"Unsupported statistic type: {stat_type}") + + # Load data + data_list = [] + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Processing {mode} timestep {i+1}/{len(times)}: {ts}") + + data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False) * CONV_FACTOR + if mode == 'predictions': + tp_da = xr.DataArray(data[:, tp_channel], dims=['member', 'lat', 'lon']) + data_list.append(tp_da) + else: + data_list.append(data[tp_channel]) + + # Process data based on mode + if mode == 'predictions': + pred_da = xr.concat(data_list, dim='time').assign_coords({ + 'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times] + }).transpose('member', 'time', 'lat', 'lon') + + result_by_member = apply_statistic(pred_da, stat_type, stat_param) + return result_by_member.mean(dim='member'), result_by_member, pred_da.shape[0] + + else: + da = xr.DataArray( + np.stack(data_list, axis=0), + dims=['time', 'lat', 'lon'], + coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} + ) + if mode == 'baseline': + da = da / 6.0 + + return apply_statistic(da, stat_type, stat_param), None, None + + +# Statistics configuration +STATISTICS_CONFIG = { + 'mean': {'type': 'mean', 'threshold': 0.01, 'title': 'Mean'}, + 'p99': {'type': 'quantile', 'param': 0.99, 'threshold': 0.1, 'title': '99th Percentile'}, + 'p99.9': {'type': 'quantile', 'param': 0.999, 'threshold': 0.1, 'title': '99.9th Percentile'}, + 'p99.99': {'type': 'quantile', 'param': 0.9999, 'threshold': 0.1, 'title': '99.99th Percentile'}, + 'Rx1hr': {'type': 'Rx1hr', 'threshold': 0.1, 'title': 'Maximum (Rx1hr)'}, + 'Rx1day': {'type': 'Rx1day', 'threshold': 0.1, 'title': 'Maximum 1-day Amount (Rx1day)'}, + 'Rx5day': {'type': 'Rx5day', 'threshold': 0.1, 'title': 'Maximum 5-day Total (Rx5day)'}, + 'cdd': {'type': 'cdd', 'threshold': 0.1, 'title': 'Consecutive Dry Days (CDD)'}, + 'cwd': {'type': 'cwd', 'threshold': 0.1, 'title': 'Consecutive Wet Days (CWD)'}, + 'weth_freq': {'type': 'weth_freq', 'threshold': 0.01, 'title': 'Wet-Hour Frequency'} +} + +def get_all_stat_configs(): + """Get all statistic configurations to generate.""" + return [ + { + 'stat_name': name, + 'title_stat': config['title'], + 'param': config.get('param'), # Use get() to handle missing params + **config + } + for name, config in STATISTICS_CONFIG.items() + ] + + +def plot_stat_map(data, filename, stat_config, label): + """Plot a single statistic map with appropriate styling.""" + if stat_config['type'] == 'weth_freq': + plot_map(data, filename, title=f'{label}: {stat_config["title_stat"]} (%)', + label='Wet-Hour Frequency [%]', vmin=0, vmax=100, cmap='Blues') + elif stat_config['type'] in ['cdd', 'cwd']: + plot_map(data, filename, title=f'{label}: {stat_config["title_stat"]}', + label='Days', vmin=0, vmax=None, cmap='viridis') + else: + plot_map_precipitation(data, filename, title=f'{label}: {stat_config["title_stat"]} Precipitation', + threshold=stat_config['threshold'], rfac=1.0) + + +@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") +def main(cfg: DictConfig): + # Setup logging + DistributedManager.initialize() + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + + logger.info("Starting precipitation statistics generation") + times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + logger.info(f"Processing {len(times)} timesteps") + + # Initialize dataset + ds_cfg = OmegaConf.to_container(cfg.dataset) + dataset, _ = get_dataset_and_sampler_inference( + ds_cfg, times, cfg.generation.get('has_lead_time', False) + ) + + # Output root and channel indices + out_root = Path(cfg.generation.io.output_path or './outputs') + indices = get_channel_indices(dataset) + tp_out, tp_in = indices['output']['tp'], indices['input'].get('tp', indices['output']['tp']) + + # Mode configuration + modes = { + 'target': (tp_out, 'COSMO-2 Analysis'), + 'baseline': (tp_in, 'ERA5'), + 'predictions': (tp_out, 'CorrDiff Ensemble Mean') + } + + all_stat_configs = get_all_stat_configs() + logger.info(f"Generating {len(all_stat_configs)} statistics for {len(modes)} modes") + + # Process each statistic + for stat_config in all_stat_configs: + logger.info(f"Processing {stat_config['title_stat']}...") + + # Process all modes for this statistic + results = {} + for mode, (tp_channel, label) in modes.items(): + result_mean, result_by_member, n_members = process_data_for_stat( + times, out_root, tp_channel, mode, stat_config['type'], stat_config['param'], logger + ) + results[mode] = (result_mean, result_by_member, n_members, label) + + # Create maps + map_output_dir = out_root / f"maps_{stat_config['stat_name']}" + map_output_dir.mkdir(parents=True, exist_ok=True) + + for mode, (result_mean, result_by_member, n_members, label) in results.items(): + # Main ensemble mean map + plot_stat_map(result_mean.values, str(map_output_dir / f'{mode}_{stat_config["stat_name"]}'), stat_config, label) + + # Individual ensemble member maps for predictions + if mode == 'predictions' and result_by_member is not None: + for member_idx in range(n_members): + member_filename = str(map_output_dir / f'prediction_member_{member_idx:02d}_{stat_config["stat_name"]}') + member_label = f'CorrDiff Member {member_idx+1}' + plot_stat_map(result_by_member[member_idx].values, member_filename, stat_config, member_label) + + logger.info("All precipitation statistics maps generated successfully") + + +if __name__ == '__main__': + main() diff --git a/src/hirad/eval_precip.sh b/src/hirad/eval_precip.sh index 52232c46..db9b190e 100644 --- a/src/hirad/eval_precip.sh +++ b/src/hirad/eval_precip.sh @@ -42,16 +42,20 @@ export OMP_NUM_THREADS=72 # echo "Local processes: $LOCAL_PROCS" # echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" -# python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml + srun --environment=./ci/edf/modulus_env.toml bash -c " pip install -e . --no-dependencies + + # Diurnal cycle python src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py --config-name=generate_era_cosmo.yaml python src/hirad/eval/diurnal_cycle_precip_p99.py --config-name=generate_era_cosmo.yaml - python src/hirad/eval/diurnal_cycle_temp_wind.py --config-name=generate_era_cosmo.yaml -" + python src/hirad/eval/diurnal_cycle_temp_wind.py --config-name=generate_era_cosmo.yaml # TODO: Transfer to relevant script. -srun --environment=./ci/edf/modulus_env.toml bash -c " - pip install -e . --no-dependencies + # Histograms python src/hirad/eval/hist.py --config-name=generate_era_cosmo.yaml python src/hirad/eval/probability_of_exceedance.py --config-name=generate_era_cosmo.yaml + + # Maps + python src/hirad/eval/map_precip_stats.py --config-name=generate_era_cosmo.yaml + # python src/hirad/eval/snapshots.py --config-name=generate_era_cosmo.yaml " \ No newline at end of file diff --git a/src/hirad/maps.sh b/src/hirad/maps.sh deleted file mode 100644 index 770074f5..00000000 --- a/src/hirad/maps.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -#SBATCH --job-name="plot" - -### HARDWARE ### -#SBATCH --partition=normal -#SBATCH --nodes=1 -#SBATCH --ntasks-per-node=2 -#SBATCH --gpus-per-node=2 -#SBATCH --cpus-per-task=72 -#SBATCH --time=01:00:00 -#SBATCH --no-requeue -#SBATCH --exclusive - -### OUTPUT ### -#SBATCH --output=./logs/plot_maps.log - -### ENVIRONMENT #### -#SBATCH -A a161 - -# Choose method to initialize dist in pythorch -export DISTRIBUTED_INITIALIZATION_METHOD=SLURM - -MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" -echo "Master node : $MASTER_ADDR" -# Get IP for hostname. -MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" -echo "Master address : $MASTER_ADDR" -export MASTER_ADDR -export MASTER_PORT=29500 -echo "Master port: $MASTER_PORT" - -# Get number of physical cores using Python -# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") -# # Use SLURM_NTASKS (number of processes to be launched by torchrun) -# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} -# # Compute threads per process -# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) -# export OMP_NUM_THREADS=$OMP_THREADS -export OMP_NUM_THREADS=72 -# echo "Physical cores: $PHYSICAL_CORES" -# echo "Local processes: $LOCAL_PROCS" -# echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" - -# python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml -srun --environment=./ci/edf/modulus_env.toml bash -c " - pip install -e . --no-dependencies - python src/hirad/eval/snapshots.py --config-name=generate_era_cosmo.yaml - python src/hirad/eval/map_precip_99pctl.py --config-name=generate_era_cosmo.yaml - python src/hirad/eval/map_precip_mean.py --config-name=generate_era_cosmo.yaml -" \ No newline at end of file From 622a36ccb958fd3a0da7bb2b14d10131c2e3f806 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Thu, 7 Aug 2025 14:32:17 +0200 Subject: [PATCH 132/302] point to store --- src/hirad/eval/plotting.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index edbcbc7f..13849f13 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -22,6 +22,8 @@ WET_THRESHOLD = 0.1 # Threshold for wet-hour in mm/h LOG_INTERVAL = 24 # Log progress every N timesteps +LAND_SEA_MASK_PATH = '/capstor/store/mch/msopr/hirad-gen/eval/lsm.npy' + def get_channel_indices(dataset, channels=None): """ Get channel indices for input and output channels from dataset. @@ -50,7 +52,7 @@ def get_channel_indices(dataset, channels=None): return {'input': filtered_in, 'output': filtered_out} -def load_land_sea_mask(path='/iopsstor/scratch/cscs/davidle/HiRAD-Gen/lsm.npy'): +def load_land_sea_mask(path=LAND_SEA_MASK_PATH): """Load and retrun a land-sea mask as xarray DataArray.""" lsm_data = np.load(path).reshape(352, 544) return xr.DataArray( From 42250be8a262cb078136c53414fd8e56342d97cc Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Thu, 7 Aug 2025 16:48:04 +0200 Subject: [PATCH 133/302] conserve memory and cleanup --- src/hirad/eval/map_precip_stats.py | 308 ++++++++++++++--------------- src/hirad/eval/plotting.py | 3 +- 2 files changed, 151 insertions(+), 160 deletions(-) diff --git a/src/hirad/eval/map_precip_stats.py b/src/hirad/eval/map_precip_stats.py index 62c574dd..26740c1d 100644 --- a/src/hirad/eval/map_precip_stats.py +++ b/src/hirad/eval/map_precip_stats.py @@ -1,16 +1,3 @@ -""" -Generates: - - outputs/maps_mean/ - - outputs/maps_p99/ - - outputs/maps_p99.9/ - - outputs/maps_p99.99/ - - outputs/maps_Rx1hr/ - - outputs/maps_Rx1day/ - - outputs/maps_Rx5day/ - - outputs/maps_cdd/ - - outputs/maps_cwd/ - - outputs/maps_weth_freq/ -""" import logging from datetime import datetime from pathlib import Path @@ -24,126 +11,81 @@ from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import plot_map_precipitation, plot_map, get_channel_indices, CONV_FACTOR, LOG_INTERVAL, WET_THRESHOLD - - -def process_data_for_stat(times, out_root, tp_channel, mode, stat_type, stat_param, logger): - """Process data for a given mode and compute the specified statistic.""" - def consecutive_spell(condition): - """Calculate longest consecutive spell where condition is True.""" - def _spell_length(x): - x = np.asarray(x, dtype=bool) - if len(x) == 0: - return 0 - runs = np.diff(np.concatenate(([False], x, [False])).astype(int)) - starts = np.where(runs == 1)[0] - ends = np.where(runs == -1)[0] - return int(np.max(ends - starts)) if len(starts) > 0 else 0 - return xr.apply_ufunc(_spell_length, condition, input_core_dims=[['time']], vectorize=True) - - def apply_statistic(data, stat_type, stat_param): - if stat_type == 'mean': - return data.mean(dim='time') - elif stat_type == 'quantile': - return data.quantile(stat_param, dim='time') - elif stat_type == 'Rx1hr': - return data.max(dim='time') - elif stat_type == 'Rx1day': - # Maximum daily precipitation total - daily = data.resample(time="1D").sum("time") - return daily.max(dim='time') - elif stat_type == 'Rx5day': - # Maximum 5-consecutive-day precipitation total - daily = data.resample(time="1D").sum("time") - return daily.rolling(time=5, center=False).sum().max(dim='time') - elif stat_type == 'cdd': # Consecutive Dry Days (< 1 mm) - daily = data.resample(time="1D").sum("time") - return consecutive_spell(daily < 1.0) - elif stat_type == 'cwd': # Consecutive Wet Days (≥ 1 mm) - daily = data.resample(time="1D").sum("time") - return consecutive_spell(daily >= 1.0) - elif stat_type == 'weth_freq': - return (data / 24 > WET_THRESHOLD).mean(dim='time') * 100 - else: - raise ValueError(f"Unsupported statistic type: {stat_type}") - - # Load data - data_list = [] - for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: - logger.info(f"Processing {mode} timestep {i+1}/{len(times)}: {ts}") - - data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False) * CONV_FACTOR - if mode == 'predictions': - tp_da = xr.DataArray(data[:, tp_channel], dims=['member', 'lat', 'lon']) - data_list.append(tp_da) - else: - data_list.append(data[tp_channel]) - - # Process data based on mode - if mode == 'predictions': - pred_da = xr.concat(data_list, dim='time').assign_coords({ - 'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times] - }).transpose('member', 'time', 'lat', 'lon') - - result_by_member = apply_statistic(pred_da, stat_type, stat_param) - return result_by_member.mean(dim='member'), result_by_member, pred_da.shape[0] - - else: - da = xr.DataArray( - np.stack(data_list, axis=0), - dims=['time', 'lat', 'lon'], - coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} - ) - if mode == 'baseline': - da = da / 6.0 - - return apply_statistic(da, stat_type, stat_param), None, None - - -# Statistics configuration -STATISTICS_CONFIG = { - 'mean': {'type': 'mean', 'threshold': 0.01, 'title': 'Mean'}, - 'p99': {'type': 'quantile', 'param': 0.99, 'threshold': 0.1, 'title': '99th Percentile'}, - 'p99.9': {'type': 'quantile', 'param': 0.999, 'threshold': 0.1, 'title': '99.9th Percentile'}, - 'p99.99': {'type': 'quantile', 'param': 0.9999, 'threshold': 0.1, 'title': '99.99th Percentile'}, - 'Rx1hr': {'type': 'Rx1hr', 'threshold': 0.1, 'title': 'Maximum (Rx1hr)'}, - 'Rx1day': {'type': 'Rx1day', 'threshold': 0.1, 'title': 'Maximum 1-day Amount (Rx1day)'}, - 'Rx5day': {'type': 'Rx5day', 'threshold': 0.1, 'title': 'Maximum 5-day Total (Rx5day)'}, - 'cdd': {'type': 'cdd', 'threshold': 0.1, 'title': 'Consecutive Dry Days (CDD)'}, - 'cwd': {'type': 'cwd', 'threshold': 0.1, 'title': 'Consecutive Wet Days (CWD)'}, - 'weth_freq': {'type': 'weth_freq', 'threshold': 0.01, 'title': 'Wet-Hour Frequency'} -} - -def get_all_stat_configs(): - """Get all statistic configurations to generate.""" - return [ - { - 'stat_name': name, - 'title_stat': config['title'], - 'param': config.get('param'), # Use get() to handle missing params - **config - } - for name, config in STATISTICS_CONFIG.items() - ] +from hirad.eval.plotting import ( + plot_map_precipitation, plot_map, get_channel_indices, + CONV_FACTOR, LOG_INTERVAL, WET_THRESHOLD +) + + +def consecutive_spell(condition): + """Return longest consecutive spell where condition is True (per gridpoint).""" + def _spell_length(x): + x = np.asarray(x, dtype=bool) + if len(x) == 0: + return 0 + runs = np.diff(np.concatenate(([False], x, [False])).astype(int)) + starts = np.where(runs == 1)[0] + ends = np.where(runs == -1)[0] + return int(np.max(ends - starts)) if len(starts) > 0 else 0 + return xr.apply_ufunc(_spell_length, condition, input_core_dims=[['time']], vectorize=True) + + +def apply_statistic(data, stat_type, stat_param): + """Apply a statistic to the data along the time dimension.""" + if stat_type == 'mean': + return data.mean(dim='time') + if stat_type == 'quantile': + return data.quantile(stat_param, dim='time') + if stat_type == 'Rx1hr': + return data.max(dim='time') + if stat_type == 'Rx1day': + daily = data.resample(time="1D").sum("time") + return daily.max(dim='time') + if stat_type == 'Rx5day': + daily = data.resample(time="1D").sum("time") + return daily.rolling(time=5, center=False).sum().max(dim='time') + if stat_type == 'cdd': + daily = data.resample(time="1D").sum("time") + return consecutive_spell(daily < 1.0) + if stat_type == 'cwd': + daily = data.resample(time="1D").sum("time") + return consecutive_spell(daily >= 1.0) + if stat_type == 'weth_freq': + return (data / 24 > WET_THRESHOLD).mean(dim='time') * 100 + raise ValueError(f"Unsupported statistic type: {stat_type}") def plot_stat_map(data, filename, stat_config, label): """Plot a single statistic map with appropriate styling.""" if stat_config['type'] == 'weth_freq': - plot_map(data, filename, title=f'{label}: {stat_config["title_stat"]} (%)', - label='Wet-Hour Frequency [%]', vmin=0, vmax=100, cmap='Blues') - elif stat_config['type'] in ['cdd', 'cwd']: - plot_map(data, filename, title=f'{label}: {stat_config["title_stat"]}', - label='Days', vmin=0, vmax=None, cmap='viridis') + plot_map( + data, filename, + title=f'{label}: {stat_config["title_stat"]} (%)', + label='Wet-Hour Frequency [%]', vmin=0, vmax=10, cmap='PuBu', extend='max' + ) + elif stat_config['type'] == 'cdd': + plot_map( + data, filename, + title=f'{label}: {stat_config["title_stat"]}', + label='Days', vmin=0, vmax=60, cmap='viridis', extend='max' + ) + elif stat_config['type'] == 'cwd': + plot_map( + data, filename, + title=f'{label}: {stat_config["title_stat"]}', + label='Days', vmin=0, vmax=20, cmap='viridis', extend='max' + ) else: - plot_map_precipitation(data, filename, title=f'{label}: {stat_config["title_stat"]} Precipitation', - threshold=stat_config['threshold'], rfac=1.0) + plot_map_precipitation( + data, filename, + title=f'{label}: {stat_config["title_stat"]} Precipitation', + threshold=stat_config['threshold'], rfac=1.0 + ) @hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") def main(cfg: DictConfig): - # Setup logging + # Setup and config DistributedManager.initialize() logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -152,54 +94,102 @@ def main(cfg: DictConfig): times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") logger.info(f"Processing {len(times)} timesteps") - # Initialize dataset ds_cfg = OmegaConf.to_container(cfg.dataset) dataset, _ = get_dataset_and_sampler_inference( ds_cfg, times, cfg.generation.get('has_lead_time', False) ) - - # Output root and channel indices out_root = Path(cfg.generation.io.output_path or './outputs') indices = get_channel_indices(dataset) - tp_out, tp_in = indices['output']['tp'], indices['input'].get('tp', indices['output']['tp']) + tp_out = indices['output']['tp'] + tp_in = indices['input'].get('tp', tp_out) + + # Statistic configuration + STATISTICS_CONFIG = { + 'mean': {'type': 'mean', 'threshold': 0.01, 'title': 'Mean'}, + 'p99': {'type': 'quantile', 'param': 0.99, 'threshold': 0.1, 'title': '99th Percentile'}, + 'p99.9': {'type': 'quantile', 'param': 0.999, 'threshold': 0.1, 'title': '99.9th Percentile'}, + 'p99.99': {'type': 'quantile', 'param': 0.9999, 'threshold': 0.1, 'title': '99.99th Percentile'}, + 'Rx1hr': {'type': 'Rx1hr', 'threshold': 0.1, 'title': 'Maximum (Rx1hr)'}, + 'Rx1day': {'type': 'Rx1day', 'threshold': 0.1, 'title': 'Maximum 1-day Amount (Rx1day)'}, + 'Rx5day': {'type': 'Rx5day', 'threshold': 0.1, 'title': 'Maximum 5-day Total (Rx5day)'}, + 'cdd': {'type': 'cdd', 'threshold': 0.1, 'title': 'Consecutive Dry Days (CDD)'}, + 'cwd': {'type': 'cwd', 'threshold': 0.1, 'title': 'Consecutive Wet Days (CWD)'}, + 'weth_freq': {'type': 'weth_freq', 'threshold': 0.01, 'title': 'Wet-Hour Frequency'} + } + stat_configs = [ + { + 'stat_name': name, + 'title_stat': config['title'], + 'param': config.get('param'), + **config + } + for name, config in STATISTICS_CONFIG.items() + ] - # Mode configuration - modes = { + # Target and baseline modes + basic_modes = { 'target': (tp_out, 'COSMO-2 Analysis'), - 'baseline': (tp_in, 'ERA5'), - 'predictions': (tp_out, 'CorrDiff Ensemble Mean') + 'baseline': (tp_in, 'ERA5') } + logger.info(f"Generating {len(stat_configs)} statistics for {len(basic_modes)} basic modes + predictions") + + for mode, (tp_channel, label) in basic_modes.items(): + logger.info(f"Processing mode: {mode}") + # Load all timesteps for this mode + data_list = [] + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Loading {mode} timestep {i+1}/{len(times)}: {ts}") + data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False) * CONV_FACTOR + data_list.append(data[tp_channel]) + mode_data = xr.DataArray( + np.stack(data_list, axis=0), + dims=['time', 'lat', 'lon'], + coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} + ) + if mode == 'baseline': + mode_data = mode_data / 6.0 + # Compute and plot all statistics for this mode + for stat_config in stat_configs: + logger.info(f"Computing {stat_config['title_stat']} for {mode}...") + result = apply_statistic(mode_data, stat_config['type'], stat_config['param']) + map_output_dir = out_root / f"maps_{stat_config['stat_name']}" + map_output_dir.mkdir(parents=True, exist_ok=True) + plot_stat_map(result.values, str(map_output_dir / f'{mode}_{stat_config["stat_name"]}'), stat_config, label) + + # Predictions mode: process each member separately to save memory + logger.info("Processing predictions mode...") + data = torch.load(out_root/times[0]/f"{times[0]}-predictions", weights_only=False) + n_members = data.shape[0] + logger.info(f"Found {n_members} ensemble members") - all_stat_configs = get_all_stat_configs() - logger.info(f"Generating {len(all_stat_configs)} statistics for {len(modes)} modes") - - # Process each statistic - for stat_config in all_stat_configs: - logger.info(f"Processing {stat_config['title_stat']}...") - - # Process all modes for this statistic - results = {} - for mode, (tp_channel, label) in modes.items(): - result_mean, result_by_member, n_members = process_data_for_stat( - times, out_root, tp_channel, mode, stat_config['type'], stat_config['param'], logger - ) - results[mode] = (result_mean, result_by_member, n_members, label) - - # Create maps - map_output_dir = out_root / f"maps_{stat_config['stat_name']}" - map_output_dir.mkdir(parents=True, exist_ok=True) + for member_idx in range(n_members): + logger.info(f"Processing prediction member {member_idx+1}/{n_members}") + # Load all timesteps for this member + data_list = [] + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Loading prediction member {member_idx} timestep {i+1}/{len(times)}: {ts}") + pred_data = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) * CONV_FACTOR + data_list.append(pred_data[member_idx, tp_out]) + member_data = xr.DataArray( + np.stack(data_list, axis=0), + dims=['time', 'lat', 'lon'], + coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} + ) - for mode, (result_mean, result_by_member, n_members, label) in results.items(): - # Main ensemble mean map - plot_stat_map(result_mean.values, str(map_output_dir / f'{mode}_{stat_config["stat_name"]}'), stat_config, label) + # Compute and plot all statistics for this member + for stat_config in stat_configs: + logger.info(f"Computing {stat_config['title_stat']} for member {member_idx+1}...") + member_result = apply_statistic(member_data, stat_config['type'], stat_config['param']) - # Individual ensemble member maps for predictions - if mode == 'predictions' and result_by_member is not None: - for member_idx in range(n_members): - member_filename = str(map_output_dir / f'prediction_member_{member_idx:02d}_{stat_config["stat_name"]}') - member_label = f'CorrDiff Member {member_idx+1}' - plot_stat_map(result_by_member[member_idx].values, member_filename, stat_config, member_label) - + # Create map + map_output_dir = out_root / f"maps_{stat_config['stat_name']}" + map_output_dir.mkdir(parents=True, exist_ok=True) + member_filename = str(map_output_dir / f'prediction_member_{member_idx:02d}_{stat_config["stat_name"]}') + member_label = f'CorrDiff Member {member_idx+1}' + plot_stat_map(member_result.values, member_filename, stat_config, member_label) + logger.info("All precipitation statistics maps generated successfully") diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index 13849f13..558b415c 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -120,6 +120,7 @@ def plot_map(values: np.array, ) if ticks is not None: cbar.set_ticks(ticks) + cbar.set_ticklabels([f'{tick:g}' for tick in ticks]) plt.tight_layout() fig.savefig(f"{filename}.png", dpi=300, bbox_inches="tight") @@ -136,7 +137,7 @@ def plot_map_precipitation(values, filename, title='', threshold=0.1, rfac=100.0 'forestgreen', 'limegreen', 'lawngreen', 'yellow', 'gold', 'darkorange', 'red', 'darkviolet', 'violet', 'thistle'] - bounds = [0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 30, 50, 70, 100, 150, 200] + bounds = [0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000] cmap = ListedColormap(colors) norm = BoundaryNorm(bounds, ncolors=len(colors), clip=False) From 27296546de71b4ad5db71fe76762767d1a040bb2 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 25 Aug 2025 15:48:58 +0200 Subject: [PATCH 134/302] remove dependencies from pyproject --- pyproject.toml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1477899a..7ffe3f37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,19 +10,10 @@ authors = [ { name="Petar Stamenkovic", email="petar.stamenkovic@meteoswiss.ch" } ] readme = "README.md" -requires-python = ">=3.12" +#requires-python = ">=3.12" license = {file = "LICENSE"} dependencies = [ - "cartopy>=0.24.1", - "cftime>=1.6.4", - "hydra-core>=1.3.2", - "matplotlib>=3.10.1", - "omegaconf>=2.3.0", - "tensorboard>=2.19.0", - "termcolor>=3.1.0", - "torchinfo>=1.8.0", - "treelib>=1.7.1" ] [tool.setuptools] From 8b53777eddda39f3d36a3955d9337f36ea9f7c11 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 25 Aug 2025 15:49:23 +0200 Subject: [PATCH 135/302] change conversion factor to get mm instead of cm --- src/hirad/eval/plotting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index 558b415c..e8630531 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -17,7 +17,7 @@ RELAX_ZONE = 19 # Number of points dropped on each side (relaxation zone) # Constants for data processing -CONV_FACTOR_HOURLY = 100 # Convert precip of ERA5 from meters to mm/h +CONV_FACTOR_HOURLY = 1000 # Convert precip of ERA5 from meters to mm/h CONV_FACTOR = CONV_FACTOR_HOURLY * 24 # Convert precip of ERA5 from from meters to mm/day WET_THRESHOLD = 0.1 # Threshold for wet-hour in mm/h LOG_INTERVAL = 24 # Log progress every N timesteps @@ -126,7 +126,7 @@ def plot_map(values: np.array, fig.savefig(f"{filename}.png", dpi=300, bbox_inches="tight") plt.close(fig) -def plot_map_precipitation(values, filename, title='', threshold=0.1, rfac=100.0): +def plot_map_precipitation(values, filename, title='', threshold=0.1, rfac=1000.0): """Plot precipitation data with specific colormap and thresholds.""" # Scale and mask values below threshold values = rfac * values # m/h --> mm/h From 9fe0b69b25219d8c02527e71de18204e0b08b5dc Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 25 Aug 2025 15:49:38 +0200 Subject: [PATCH 136/302] add __init__ package for input_data --- src/hirad/input_data/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/hirad/input_data/__init__.py diff --git a/src/hirad/input_data/__init__.py b/src/hirad/input_data/__init__.py new file mode 100644 index 00000000..e69de29b From 630086a539a890170977f5d2689c21f6ebe085c0 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 25 Aug 2025 15:50:19 +0200 Subject: [PATCH 137/302] Clean up copernicus processing --- src/hirad/input_data/read_tp.py | 206 ++++++++++++++------------------ 1 file changed, 91 insertions(+), 115 deletions(-) diff --git a/src/hirad/input_data/read_tp.py b/src/hirad/input_data/read_tp.py index a13fed31..37767a0e 100644 --- a/src/hirad/input_data/read_tp.py +++ b/src/hirad/input_data/read_tp.py @@ -1,5 +1,6 @@ import logging import netCDF4 +import xarray from anemoi.datasets import open_dataset import numpy as np import yaml @@ -9,6 +10,10 @@ import cartopy.feature as cfeature from matplotlib.colors import BoundaryNorm, ListedColormap +from hirad.eval.plotting import plot_map_precipitation, plot_scores_vs_t +from hirad.eval.metrics import compute_mae, absolute_error + + import interpolate_basic import sys @@ -24,18 +29,20 @@ COSMO_6H_FILENAME = "/scratch/mch/fzanetta/data/anemoi/datasets/mch-co2-an-archive-0p02-2015-2020-6h-v3-pl13.zarr" COSMO_1H_FILENAME = "/scratch/mch/fzanetta/data/anemoi/datasets/mch-co2-an-archive-0p02-2015-2020-1h-v3-pl13.zarr" COSMO_CONFIG_FILE="src/input_data/cosmo.yaml" -CDF_FILENAME = "8e49f064d738154bed136666ff72ae1c.nc" +#CDF_FILENAME = "/store_new/mch/msopr/hirad-gen/copernicus-datasets/tp-janfeb2020.nc" +CDF_FILENAME = "/store_new/mch/msopr/hirad-gen/copernicus-datasets/surface-janfeb2020.zip" +GRIB_FILENAME = "/store_new/mch/msopr/hirad-gen/copernicus-datasets/surface-janfeb2020.grib" LAT = np.arange(-4.42, 3.36 + 0.02, 0.02) LON = np.arange(-6.82, 4.80 + 0.02, 0.02) RELAX_ZONE = 19 # Number of points dropped on each side (relaxation zone) - -def extract_values(netcdf_data): +def extract_netcdf_values(netcdf_data): netcdf_lat = netcdf_data['latitude'][:] netcdf_lon = netcdf_data['longitude'][:] netcdf_tp = netcdf_data['tp'][:,:] + logging.info(f'num nonzeros in tp is {np.count_nonzero(netcdf_tp)}') values = np.zeros((netcdf_tp.shape[0], netcdf_tp.shape[1]*netcdf_tp.shape[2])) latitudes = np.zeros(values.shape[1]) longitudes = np.zeros(values.shape[1]) @@ -44,111 +51,94 @@ def extract_values(netcdf_data): if i % 10 == 0: print(i) for j in range(len(netcdf_lon)): - grid_index = i * len(netcdf_lon) + j - values[:,grid_index] = netcdf_tp[:,i,j] - latitudes[grid_index] = netcdf_lat[i] - longitudes[grid_index] = netcdf_lon[j] - return values, latitudes, longitudes - -def plot_map(values: np.array, - filename: str, - label='', - title='', - vmin=None, - vmax=None, - cmap=None, - extend='neither', - norm=None, - ticks=None): - """Plot observed or interpolated data in a scatter plot.""" - logging.info(f'Creating map: {filename}') - - latitudes = LAT[RELAX_ZONE : RELAX_ZONE + 352] - longitudes = LON[RELAX_ZONE : RELAX_ZONE + 544] - lon2d, lat2d = np.meshgrid(longitudes, latitudes) - - fig, ax = plt.subplots( - figsize=(8, 6), - subplot_kw={"projection": ccrs.RotatedPole(pole_longitude=-170.0, - pole_latitude= 43.0)} - ) - values = values.reshape((len(latitudes), len(longitudes))) - contour = ax.pcolormesh( - lon2d, lat2d, values, - cmap=cmap, shading="auto", - norm=norm if norm else None, - vmin=None if norm else vmin, - vmax=None if norm else vmax, - ) - ax.coastlines() - ax.add_feature(cfeature.BORDERS, linewidth=1) - ax.gridlines(visible=False) - ax.set_xticks([]) - ax.set_yticks([]) - - plt.title(title) - cbar = plt.colorbar( - contour, - label=label, - orientation="horizontal", - extend=extend, - shrink=0.75, - pad=0.02 - ) - if ticks is not None: - cbar.set_ticks(ticks) - cbar.set_ticklabels([f'{tick:g}' for tick in ticks]) - - plt.tight_layout() - fig.savefig(f"{filename}.png", dpi=300, bbox_inches="tight") - plt.close(fig) - -def plot_map_precipitation(values, filename, title='', threshold=0.1, rfac=1000.0): - """Plot precipitation data with specific colormap and thresholds.""" - # Scale and mask values below threshold - values = rfac * values # m/h --> mm/h - values = np.ma.masked_where(values <= threshold, values) - - # Predefined colors and bounds specific for precipitation - colors = ['none', 'powderblue', 'dodgerblue', 'mediumblue', - 'forestgreen', 'limegreen', 'lawngreen', - 'yellow', 'gold', 'darkorange', 'red', - 'darkviolet', 'violet', 'thistle'] - bounds = [0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 30, 50, 70, 100, 150, 200] - bounds = [0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000] - - cmap = ListedColormap(colors) - norm = BoundaryNorm(bounds, ncolors=len(colors), clip=False) - - plot_map( - values, filename, - cmap=cmap, - norm=norm, - ticks=bounds, - title=title, - label='mm/h', - extend='max' - ) + grid_index = i * len(netcdf_lon) + j + values[:,grid_index] = netcdf_tp[:,i,j] + latitudes[grid_index] = netcdf_lat[i] + longitudes[grid_index] = netcdf_lon[j] + reshape_values = np.reshape(netcdf_tp, (netcdf_tp.shape[0], netcdf_tp.shape[1]*netcdf_tp.shape[2])) + logging.info(f'Array equal? {np.array_equal(values, reshape_values)}') + return reshape_values, latitudes, longitudes +def reshape_to_cosmo(vals): + return vals.reshape((len(LAT)-RELAX_ZONE*2, len(LON)-RELAX_ZONE*2)) -print(interpolate_basic.regrid) - -file_id = netCDF4.Dataset(CDF_FILENAME) -#anemoi1 = open_dataset(ANEMOI_1H_FILENAME) -#anemoi6 = open_dataset(ANEMOI_6H_FILENAME) -#with open(COSMO_CONFIG_FILE) as cosmo_file: -# cosmo_config = yaml.safe_load(cosmo_file) -#cosmo = open_dataset(cosmo_config) +root = logging.getLogger() +root.setLevel(logging.INFO) + +logging.info('loading data') +grib_data = xarray.load_dataset(GRIB_FILENAME) +netcdf_data = netCDF4.Dataset(CDF_FILENAME) cosmo1 = open_dataset(COSMO_1H_FILENAME, trim_edge=19, select=['tp'],start='2016-01-01',end='2016-02-29') cosmo6 = open_dataset(COSMO_6H_FILENAME, trim_edge=19, select=['tp'],start='2016-01-01',end='2016-02-29') - +era1 = open_dataset(ANEMOI_1H_FILENAME, select=['tp'],start='2016-01-01',end='2016-02-29') +era6 = open_dataset(ANEMOI_6H_FILENAME, select=['tp'],start='2016-01-01',end='2016-02-29') +logging.info('loading data complete') output_grid= np.column_stack((cosmo1.longitudes, cosmo1.latitudes)) -print(output_grid.shape) -print(cosmo1[0,0,0,:].shape) -plot_map_precipitation(values=cosmo1[0,:], filename="cosmo1.png") -plot_map_precipitation(values=cosmo6[0,:], filename="cosmo6.png") +logging.info('processing netcdf data') +netcdf_values, netcdf_latitudes, netcdf_longitudes = extract_netcdf_values(netcdf_data=netcdf_data) +netcdf_grid=np.column_stack((netcdf_longitudes, netcdf_latitudes)) + +make_plots = False + +prev_netcdf_regrid = [] + +netcdf_error = np.zeros(cosmo1.dates.shape) +era_norm_error = np.zeros(cosmo1.dates.shape) +netcdf_early_error = np.zeros(cosmo1.dates.shape) +netcdf_late_error = np.zeros(cosmo1.dates.shape) + +for t in range(len(cosmo1.dates)): + date = cosmo1.dates[t] + era_date = era1.dates[t] + if date != era_date: + logging.error('dates do not match: cosmo date: {date}, era date: {era_date}') + if date != netcdf_data['valid_time'][t]: + logging.error(f'dates do not match: cosmo date: {date}, netcdf: {netcdf_data["valid_time"][t]}') + + + # plot cosmo + if make_plots: + plot_map_precipitation(values=reshape_to_cosmo(cosmo1[t,:]), filename=f'plots/tp/{date}-cosmo1') + if t % 6 == 0: + if cosmo6.dates[t//6] != date: + logging.error(f'dates do not match: cosmo1: {date}, cosmo6: {cosmo6.dates[t//6]}') + plot_map_precipitation(values=reshape_to_cosmo(cosmo6[t//6,:]), filename=f'plots/tp/{date}-cosmo6') + + # plot netcdf + netcdf_vals = netcdf_values[t,:].reshape((1,1,netcdf_values.shape[1])) + netcdf_regrid=interpolate_basic.regrid(netcdf_vals, netcdf_grid, output_grid) + if make_plots: + plot_map_precipitation(reshape_to_cosmo(netcdf_regrid), f'plots/tp/{date}-netcdf') + + # plot era + era_grid = np.column_stack((era1.longitudes, era1.latitudes)) + era1_regrid = interpolate_basic.regrid(era1[t,:], era_grid, output_grid) + if make_plots: + plot_map_precipitation(reshape_to_cosmo(era1_regrid/6), f'plots/tp/{date}-era1-norm') + + #if t % 6 == 0: + # if era6.dates[t//6] != date: + # logging.error(f'dates do not match: era1: {date}, era6: {era6.dates[t//6]}') + # era6_regrid = interpolate_basic.regrid(era6[t//6,:], era_grid, output_grid) + # plot_map_precipitation(reshape_to_cosmo(era6_regrid), f'plots/tp/{date}-era6') + + era_norm_error[t] = np.mean(absolute_error(era1_regrid/6, cosmo1[t,:])) + netcdf_error[t] = np.mean(absolute_error(netcdf_regrid, cosmo1[t,:])) + logging.info(f'era norm error: {era_norm_error[t]} netcdf err: {netcdf_error[t]}') + if t>0: + netcdf_early_error[t] = np.mean(absolute_error(prev_netcdf_regrid, cosmo1[t,:])) + netcdf_late_error[t-1] = np.mean(absolute_error(netcdf_regrid, cosmo1[t-1,:])) + logging.info(f'netcdf early err: {netcdf_early_error[t]}, netcdf late err: {netcdf_late_error[t-1]}') + prev_netcdf_regrid = netcdf_regrid + +maes = {} +maes['era normalized'] = era_norm_error +maes['copernicus'] = netcdf_error +maes['copernicus-early'] = netcdf_early_error +maes['copernicus-late'] = netcdf_late_error +plot_scores_vs_t(maes, times=cosmo1.dates, filename='plots/errors.png') #fig = plt.figure() #fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) @@ -161,23 +151,9 @@ def plot_map_precipitation(values, filename, title='', threshold=0.1, rfac=1000. #fig.savefig('cosmo6.png') -values, latitudes, longitudes = extract_values(netcdf_data=file_id) -input_grid=np.column_stack((longitudes, latitudes)) -vals = values[0,:].reshape((1,1,values.shape[1])) -regrid=interpolate_basic.regrid(vals, input_grid, output_grid) -plot_map_precipitation(regrid, 'netcdf.png') - - -era1 = open_dataset(ANEMOI_1H_FILENAME, select=['tp'],start='2016-01-01',end='2016-02-29') -era6 = open_dataset(ANEMOI_6H_FILENAME, select=['tp'],start='2016-01-01',end='2016-02-29') -era_grid = np.column_stack((era1.longitudes, era1.latitudes)) -era1_regrid = interpolate_basic.regrid(era1[0,:], era_grid, output_grid) -plot_map_precipitation(era1_regrid, "era1.png") -era6_regrid = interpolate_basic.regrid(era6[0,:], era_grid, output_grid) -plot_map_precipitation(era1_regrid, "era6.png") From a7a541f3af32ba36ba111d74dc17ddcbfdd03c94 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 26 Aug 2025 08:54:59 +0200 Subject: [PATCH 138/302] Generalize copernicus script to work with grib/netcdf --- src/hirad/input_data/read_tp.py | 37 +++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/hirad/input_data/read_tp.py b/src/hirad/input_data/read_tp.py index 37767a0e..99530869 100644 --- a/src/hirad/input_data/read_tp.py +++ b/src/hirad/input_data/read_tp.py @@ -29,8 +29,7 @@ COSMO_6H_FILENAME = "/scratch/mch/fzanetta/data/anemoi/datasets/mch-co2-an-archive-0p02-2015-2020-6h-v3-pl13.zarr" COSMO_1H_FILENAME = "/scratch/mch/fzanetta/data/anemoi/datasets/mch-co2-an-archive-0p02-2015-2020-1h-v3-pl13.zarr" COSMO_CONFIG_FILE="src/input_data/cosmo.yaml" -#CDF_FILENAME = "/store_new/mch/msopr/hirad-gen/copernicus-datasets/tp-janfeb2020.nc" -CDF_FILENAME = "/store_new/mch/msopr/hirad-gen/copernicus-datasets/surface-janfeb2020.zip" +CDF_FILENAME = "/store_new/mch/msopr/hirad-gen/copernicus-datasets/tp-janfeb2020.nc" GRIB_FILENAME = "/store_new/mch/msopr/hirad-gen/copernicus-datasets/surface-janfeb2020.grib" @@ -38,6 +37,29 @@ LON = np.arange(-6.82, 4.80 + 0.02, 0.02) RELAX_ZONE = 19 # Number of points dropped on each side (relaxation zone) +def extract_grib_values(grib_data): + grib_lat = grib_data['latitude'][:] + grib_lon = grib_data['longitude'][:] + grib_t2m = grib_data['t2m'][:] + +def extract_lat_lon(data): + lat = data['latitude'][:] + lon = data['longitude'][:] + output_lat = np.zeros(len(lat)* len(lon)) + output_lon = np.zeros(len(lat) * len(lon)) + for i in range(len(lat)): + if i % 10 == 0: + print(i) + for j in range(len(lon)): + grid_index = i * len(lon) + j + output_lat[grid_index] = lat[i] + output_lon[grid_index] = lon[j] + return output_lat, output_lon + +def extract_values(data, variable): + values = data[variable][:] + return np.reshape(values, (values.shape[0], values.shape[1]*values.shape[2])) + def extract_netcdf_values(netcdf_data): netcdf_lat = netcdf_data['latitude'][:] netcdf_lon = netcdf_data['longitude'][:] @@ -77,10 +99,12 @@ def reshape_to_cosmo(vals): output_grid= np.column_stack((cosmo1.longitudes, cosmo1.latitudes)) logging.info('processing netcdf data') -netcdf_values, netcdf_latitudes, netcdf_longitudes = extract_netcdf_values(netcdf_data=netcdf_data) +netcdf_latitudes, netcdf_longitudes = extract_lat_lon(netcdf_data) +netcdf_values = extract_values(netcdf_data, 'tp') +#netcdf_values, netcdf_latitudes, netcdf_longitudes = extract_netcdf_values(netcdf_data=netcdf_data) netcdf_grid=np.column_stack((netcdf_longitudes, netcdf_latitudes)) -make_plots = False +make_plots = True prev_netcdf_regrid = [] @@ -89,7 +113,8 @@ def reshape_to_cosmo(vals): netcdf_early_error = np.zeros(cosmo1.dates.shape) netcdf_late_error = np.zeros(cosmo1.dates.shape) -for t in range(len(cosmo1.dates)): +for t in range(4): +#for t in range(len(cosmo1.dates)): date = cosmo1.dates[t] era_date = era1.dates[t] if date != era_date: @@ -110,7 +135,7 @@ def reshape_to_cosmo(vals): netcdf_vals = netcdf_values[t,:].reshape((1,1,netcdf_values.shape[1])) netcdf_regrid=interpolate_basic.regrid(netcdf_vals, netcdf_grid, output_grid) if make_plots: - plot_map_precipitation(reshape_to_cosmo(netcdf_regrid), f'plots/tp/{date}-netcdf') + plot_map_precipitation(reshape_to_cosmo(netcdf_regrid), f'plots/tp/{date}-netcdf-refactor') # plot era era_grid = np.column_stack((era1.longitudes, era1.latitudes)) From 4a963ee91936e092110fe82e109b30a43d6cbd32 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 26 Aug 2025 08:59:41 +0200 Subject: [PATCH 139/302] add copy anemoi script for copying from catalogue --- copyanemoi.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 copyanemoi.sh diff --git a/copyanemoi.sh b/copyanemoi.sh new file mode 100644 index 00000000..361a9fbb --- /dev/null +++ b/copyanemoi.sh @@ -0,0 +1,11 @@ +#!/bin/bash -l +# +#SBATCH --time=23:59:00 +#SBATCH --ntasks=1 +#SBATCH --partition=xfer + +echo -e "$SLURM_JOB_NAME started on $(date):\n $command $1 $2" +cp -rvn $1 $2 + +echo -e "$SLURM_JOB_NAME finished on $(date)\n" + From fcef8fc920979fef4b3b4b48176211c2038bdba7 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 26 Aug 2025 09:00:55 +0200 Subject: [PATCH 140/302] move anemoi script to input_data directory --- copyanemoi.sh => src/hirad/input_data/copyanemoi.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename copyanemoi.sh => src/hirad/input_data/copyanemoi.sh (100%) diff --git a/copyanemoi.sh b/src/hirad/input_data/copyanemoi.sh similarity index 100% rename from copyanemoi.sh rename to src/hirad/input_data/copyanemoi.sh From 41208b2c8d5a862c6a2eb26bf9937a2c46825923 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 26 Aug 2025 16:55:57 +0200 Subject: [PATCH 141/302] Set training parameters in configs; add input/output channel names selection to dataset config and update era5cosmo dataset class; replace image saving function with results saving during training; improve channel transformation logic. --- src/hirad/conf/dataset/era_cosmo.yaml | 5 +- .../conf/dataset/era_cosmo_inference.yaml | 4 +- .../conf/generation/era_cosmo_training.yaml | 4 +- src/hirad/conf/plot_maps.yaml | 21 ++++++ .../conf/training/era_cosmo_diffusion.yaml | 12 ++-- .../conf/training/era_cosmo_regression.yaml | 10 +-- .../conf/training_era_cosmo_diffusion.yaml | 4 +- .../conf/training_era_cosmo_regression.yaml | 4 +- src/hirad/datasets/era5_cosmo.py | 44 ++++++++----- src/hirad/distributed/manager.py | 30 ++++----- src/hirad/inference/generate.py | 5 -- src/hirad/training/train.py | 4 +- src/hirad/utils/inference_utils.py | 65 +++++++++++++------ 13 files changed, 133 insertions(+), 79 deletions(-) create mode 100644 src/hirad/conf/plot_maps.yaml diff --git a/src/hirad/conf/dataset/era_cosmo.yaml b/src/hirad/conf/dataset/era_cosmo.yaml index 5d32f4ed..37b3df2a 100644 --- a/src/hirad/conf/dataset/era_cosmo.yaml +++ b/src/hirad/conf/dataset/era_cosmo.yaml @@ -1,3 +1,6 @@ type: era5_cosmo dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/train -validation_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/validation \ No newline at end of file +# dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-all-channels +validation_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/validation +input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] +output_channel_names: [2t, 10u, 10v, tp] \ No newline at end of file diff --git a/src/hirad/conf/dataset/era_cosmo_inference.yaml b/src/hirad/conf/dataset/era_cosmo_inference.yaml index f819b18f..bca045dc 100644 --- a/src/hirad/conf/dataset/era_cosmo_inference.yaml +++ b/src/hirad/conf/dataset/era_cosmo_inference.yaml @@ -1,2 +1,4 @@ type: era5_cosmo -dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/validation \ No newline at end of file +dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/validation +input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] +output_channel_names: [2t, 10u, 10v, tp] \ No newline at end of file diff --git a/src/hirad/conf/generation/era_cosmo_training.yaml b/src/hirad/conf/generation/era_cosmo_training.yaml index 4374fc88..54dc8402 100644 --- a/src/hirad/conf/generation/era_cosmo_training.yaml +++ b/src/hirad/conf/generation/era_cosmo_training.yaml @@ -11,8 +11,8 @@ num_ensembles: 16 # artifact. times_range: null times: - - 20200926-1800 - - 20200927-0000 + - 20200721-1900 + - 20200722-1900 perf: num_writer_workers: 10 \ No newline at end of file diff --git a/src/hirad/conf/plot_maps.yaml b/src/hirad/conf/plot_maps.yaml new file mode 100644 index 00000000..07ecb854 --- /dev/null +++ b/src/hirad/conf/plot_maps.yaml @@ -0,0 +1,21 @@ +hydra: + job: + chdir: true + name: plot_maps + run: + dir: . + +# Get defaults +defaults: + - _self_ + # Dataset + - dataset/era_cosmo_inference + +results_dir: /capstor/scratch/cscs/pstamenk/outputs/generation/test_period_advanced_stats_2 + +time_steps: null # If null, will use all time steps in the results directory + +output_dir: ${results_dir}/plots_boxcox_masked_limits # Directory to save the plots + +plot_box_precipitation: False # Whether to plot box precipitation maps, otherwise plot with box-cox transform +tp_threshold: 0.0002 # Threshold in m/h for masking precipitation values \ No newline at end of file diff --git a/src/hirad/conf/training/era_cosmo_diffusion.yaml b/src/hirad/conf/training/era_cosmo_diffusion.yaml index 2a2fd335..f673e57d 100644 --- a/src/hirad/conf/training/era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training/era_cosmo_diffusion.yaml @@ -1,6 +1,6 @@ # Hyperparameters hp: - training_duration: 10000 + training_duration: 3500000 # Training duration based on the number of processed samples total_batch_size: "auto" # Total batch size @@ -34,14 +34,14 @@ io: # regression_checkpoint_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_era5_cosmo/checkpoints_regression regression_checkpoint_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_era5_cosmo_perf_apex_gn/checkpoints_regression # Where to load the regression checkpoint - print_progress_freq: 500 + print_progress_freq: 2000 # How often to print progress - save_checkpoint_freq: 10000 + save_checkpoint_freq: 250000 # How often to save the checkpoints, measured in number of processed samples - visualization_freq: 10000 + visualization_freq: 250000 # how often to visualize network outputs - validation_freq: 2000 + validation_freq: 50000 # how often to record the validation loss, measured in number of processed samples - validation_steps: 2 + validation_steps: 30 # how many loss evaluations are used to compute the validation loss per checkpoint checkpoint_dir: . \ No newline at end of file diff --git a/src/hirad/conf/training/era_cosmo_regression.yaml b/src/hirad/conf/training/era_cosmo_regression.yaml index 40e90055..7d35d3da 100644 --- a/src/hirad/conf/training/era_cosmo_regression.yaml +++ b/src/hirad/conf/training/era_cosmo_regression.yaml @@ -1,6 +1,6 @@ # Hyperparameters hp: - training_duration: 10000 + training_duration: 1000000 # Training duration based on the number of processed samples total_batch_size: "auto" # Total batch size -- based 8 per GPU -- 2 nodes is 2x8x4 -- see sbatch vars for how many gpus. diffusion need to point to the rgression. @@ -34,12 +34,12 @@ perf: io: print_progress_freq: 500 # How often to print progress - save_checkpoint_freq: 10000 + save_checkpoint_freq: 100000 # How often to save the checkpoints, measured in number of processed samples - visualization_freq: 10000 + visualization_freq: 50000 # how often to visualize network output - validation_freq: 2000 + validation_freq: 20000 # how often to record the validation loss, measured in number of processed samples - validation_steps: 2 + validation_steps: 30 # how many loss evaluations are used to compute the validation loss per checkpoint checkpoint_dir: . \ No newline at end of file diff --git a/src/hirad/conf/training_era_cosmo_diffusion.yaml b/src/hirad/conf/training_era_cosmo_diffusion.yaml index fee76274..467c52d3 100644 --- a/src/hirad/conf/training_era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training_era_cosmo_diffusion.yaml @@ -15,13 +15,13 @@ defaults: # Model - model/era_cosmo_diffusion - - model_size/mini + - model_size/normal # Training - training/era_cosmo_diffusion # Inference visualization - - generation/era_cosmo_training + # - generation/era_cosmo_training # Logging - logging/era_cosmo_diffusion \ No newline at end of file diff --git a/src/hirad/conf/training_era_cosmo_regression.yaml b/src/hirad/conf/training_era_cosmo_regression.yaml index ce04119b..8059f338 100644 --- a/src/hirad/conf/training_era_cosmo_regression.yaml +++ b/src/hirad/conf/training_era_cosmo_regression.yaml @@ -15,13 +15,13 @@ defaults: # Model - model/era_cosmo_regression - - model_size/mini + - model_size/normal # Training - training/era_cosmo_regression # Inference visualization - - generation/era_cosmo_training + # - generation/era_cosmo_training # Logging - logging/era_cosmo_regression \ No newline at end of file diff --git a/src/hirad/datasets/era5_cosmo.py b/src/hirad/datasets/era5_cosmo.py index f97dbc64..76d19827 100644 --- a/src/hirad/datasets/era5_cosmo.py +++ b/src/hirad/datasets/era5_cosmo.py @@ -7,7 +7,7 @@ import torch.nn.functional as F class ERA5_COSMO(DownscalingDataset): - def __init__(self, dataset_path: str): + def __init__(self, dataset_path: str, input_channel_names: List[str] = [], output_channel_names: List[str] = []): super().__init__() #TODO switch hanbdling paths to Path rather than pure strings @@ -22,51 +22,61 @@ def __init__(self, dataset_path: str): # Load cosmo info and channel names with open(os.path.join(self._info_path,'cosmo.yaml'), 'r') as file: self._cosmo_info = yaml.safe_load(file) - self._cosmo_channels = [ChannelMetadata(name) for name in self._cosmo_info['select']] + if output_channel_names: + self._cosmo_indeces = [self._cosmo_info['select'].index(name) for name in output_channel_names] + else: + self._cosmo_indeces = list(range(len(self._cosmo_info['select']))) + output_channel_names = self._cosmo_info['select'] + self._cosmo_channels = [ChannelMetadata(name) if len(name.split('_'))==1 + else ChannelMetadata(name.split('_')[0],name.split('_')[1]) + for name in self._cosmo_info['select'] if name in output_channel_names] # Load era5 info and channel names with open(os.path.join(self._info_path,'era.yaml'), 'r') as file: self._era_info = yaml.safe_load(file) + if input_channel_names: + self._era_indeces = [self._era_info['select'].index(name) for name in input_channel_names] + else: + self._era_indeces = list(range(len(self._era_info['select']))) + input_channel_names = self._era_info['select'] self._era_channels = [ChannelMetadata(name) if len(name.split('_'))==1 - else ChannelMetadata(name.split('_')[0],name.split('_')[1]) - for name in self._era_info['select']] + else ChannelMetadata(name.split('_')[0],name.split('_')[1]) + for name in self._era_info['select'] if name in input_channel_names] # Load stats for normalizing channels of input and output cosmo_stats = torch.load(os.path.join(self._info_path,'cosmo-stats'), weights_only=False) - self.output_mean = cosmo_stats['mean'] - self.output_std = cosmo_stats['stdev'] + self.output_mean = cosmo_stats['mean'][self._cosmo_indeces] + self.output_std = cosmo_stats['stdev'][self._cosmo_indeces] era_stats = torch.load(os.path.join(self._info_path,'era-stats'), weights_only=False) - self.input_mean = era_stats['mean'] - self.input_std = era_stats['stdev'] - + self.input_mean = era_stats['mean'][self._era_indeces] + self.input_std = era_stats['stdev'][self._era_indeces] def __getitem__(self, idx): """Get cosmo and era5 interpolated to cosmo grid""" - # get era5 data point + # get data point # squeeze the ensemble dimesnsion # reshape to image_shape # flip so that it starts in top-left corner (by default it is bottom left) # orig_shape = [350,542] #TODO currently padding to be divisible by 16 orig_shape = self.image_shape() - era5_data = np.flip(torch.load(os.path.join(self._era5_path,self._file_list[idx]), weights_only=False)\ + era5_data = torch.load(os.path.join(self._era5_path,self._file_list[idx]), weights_only=False)[self._era_indeces] + era5_data = np.flip(era5_data \ .squeeze() \ .reshape(-1,*orig_shape), 1) era5_data = self.normalize_input(era5_data) - # get cosmo data point - cosmo_data = np.flip(torch.load(os.path.join(self._cosmo_path,self._file_list[idx]), weights_only=False)\ + + cosmo_data = torch.load(os.path.join(self._cosmo_path,self._file_list[idx]), weights_only=False)[self._cosmo_indeces] + cosmo_data = np.flip(cosmo_data\ .squeeze() \ .reshape(-1,*orig_shape), 1) cosmo_data = self.normalize_output(cosmo_data) - # return samples + return torch.tensor(cosmo_data),\ torch.tensor(era5_data), - # return F.pad(torch.tensor(cosmo_data), pad=(1,1,1,1), mode='constant', value=0), \ - # F.pad(torch.tensor(era5_data), pad=(1,1,1,1), mode='constant', value=0), \ - # 0 def __len__(self): return len(self._file_list) diff --git a/src/hirad/distributed/manager.py b/src/hirad/distributed/manager.py index 96958185..75c0fe7d 100644 --- a/src/hirad/distributed/manager.py +++ b/src/hirad/distributed/manager.py @@ -529,7 +529,7 @@ def setup( DistributedManager._shared_state["_is_initialized"] = True manager = DistributedManager() - manager._distributed = torch.distributed.is_available() + manager._distributed = torch.distributed.is_available() and world_size > 1 if manager._distributed: # Update rank and world_size if using distributed manager._rank = rank @@ -546,23 +546,23 @@ def setup( #TODO device_id makes the init hang, couldn't figure out why if manager._distributed: # Setup distributed process group - # try: - dist.init_process_group( - backend, - rank=manager.rank, - world_size=manager.world_size, - device_id=manager.device, - ) + try: + dist.init_process_group( + backend, + rank=manager.rank, + world_size=manager.world_size, + device_id=manager.device, + ) # rank=manager.rank, # world_size=manager.world_size, # device_id=manager.device, - # except TypeError: - # # device_id only introduced in PyTorch 2.3 - # dist.init_process_group( - # backend, - # rank=manager.rank, - # world_size=manager.world_size, - # ) + except TypeError: + # device_id only introduced in PyTorch 2.3 + dist.init_process_group( + backend, + rank=manager.rank, + world_size=manager.world_size, + ) if torch.cuda.is_available(): # Set device for this process and empty cache to optimize memory usage diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index 49f82f15..3790820e 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -90,7 +90,6 @@ def main(cfg: DictConfig) -> None: device=dist.device ) - #TODO fix to use channels_last which is optimal for H100 net_res = net_res.eval().to(device).to(memory_format=torch.channels_last) if cfg.generation.perf.force_fp16: net_res.use_fp16 = True @@ -172,10 +171,6 @@ def main(cfg: DictConfig) -> None: logger0.info(f"Generating images, saving results to {output_path}...") batch_size = 1 warmup_steps = min(len(times) - 1, 2) - # Generates model predictions from the input data using the specified - # `generate_fn`, and save the predictions to the provided NetCDF file. It iterates - # through the dataset using a data loader, computes predictions, and saves them along - # with associated metadata. torch_cuda_profiler = ( torch.cuda.profiler.profile() diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index ba1b975f..e865bca4 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -25,7 +25,7 @@ from hirad.utils.checkpoint import load_checkpoint, save_checkpoint from hirad.utils.patching import RandomPatching2D from hirad.utils.function_utils import get_time_from_range -from hirad.utils.inference_utils import save_images +from hirad.utils.inference_utils import save_results_as_torch from hirad.utils.env_info import get_env_info, flatten_dict from hirad.models import UNet, EDMPrecondSuperResolution from hirad.losses import ResidualLoss, RegressionLoss, RegressionLossCE @@ -830,7 +830,7 @@ def main(cfg: DictConfig) -> None: os.makedirs(output_path) writer_threads.append( writer_executor.submit( - save_images, + save_results_as_torch, output_path, times[visualization_sampler[time_index]], visualization_dataset, diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index 14be4466..b2a11681 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -210,20 +210,17 @@ def diffusion_step( def save_results_as_torch(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): os.makedirs(output_path, exist_ok=True) - - target = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze()),1) #.reshape(len(output_channels),-1) - # prediction.shape = (num_channels, X, Y) - # prediction = np.flip(dataset.denormalize_output(image_pred[-1,::].squeeze()),1) #.reshape(len(output_channels),-1) - # prediction_ensemble.shape = (num_ensembles, num_channels, X, Y) + target = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze()),1) prediction_ensemble = np.flip(dataset.denormalize_output(image_pred.squeeze()),-2) - baseline = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze()),1)# .reshape(len(input_channels),-1) + baseline = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze()),1) if mean_pred is not None: - mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze()),1) #.reshape(len(output_channels),-1) + mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze()),1) + torch.save(mean_pred, os.path.join(output_path, f'{time_step}-regression-prediction')) torch.save(target, os.path.join(output_path, f'{time_step}-target')) torch.save(prediction_ensemble, os.path.join(output_path, f'{time_step}-predictions')) torch.save(baseline, os.path.join(output_path, f'{time_step}-baseline')) - +@DeprecationWarning def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): os.makedirs(output_path, exist_ok=True) @@ -250,11 +247,11 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, input_channel_idx = input_channels.index(channel) if channel.name=="tp": - target[idx,::] = _prepare_precipitation(target[idx,:,:]) - prediction[:,idx,::] = _prepare_precipitation(prediction[:,idx,:,:]) - baseline[input_channel_idx,:,:] = _prepare_precipitation(baseline[input_channel_idx,::]) + target[idx,::] = transform_channel(target[idx,:,:]) + prediction[:,idx,::] = transform_channel(prediction[:,idx,:,:]) + baseline[input_channel_idx,:,:] = transform_channel(baseline[input_channel_idx,::]) if mean_pred is not None: - mean_pred[idx,::] = _prepare_precipitation(mean_pred[idx,::]) + mean_pred[idx,::] = transform_channel(mean_pred[idx,::]) if mean_pred is not None: vmin, vmax = calculate_bounds(target[idx,:,:], @@ -314,17 +311,23 @@ def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, power['mean_prediction'] = mp_power plot_power_spectra(freqs, power, channel.name, os.path.join(output_path_channel, f'{time_step}-{channel.name}-spectra.jpg')) -def _prepare_precipitation(precip_array): - precip_array = np.clip(precip_array, 0, None) - precip_array = np.where(precip_array == 0, 1e-6, precip_array) +def transform_channel(channel_array, channel_name="tp"): + # precip_array = np.clip(precip_array, 0, None) + # precip_array = np.where(precip_array == 0, 1e-6, precip_array) # epsilon = 1e-2 # precip_array = precip_array + epsilon - precip_array = np.log10(precip_array) + # precip_array = np.log10(precip_array) # log_min, log_max = precip_array.min(), precip_array.max() # precip_array = (precip_array-log_min)/(log_max-log_min) - return precip_array - - + if channel_name == "tp": + channel_array = np.clip(channel_array, 0, None) + channel_array = (np.power(channel_array,0.25)-1)/0.25 + elif channel_name == "2t": + channel_array = channel_array - 273.15 + # precip_array = np.sqrt(precip_array) + return channel_array + +@DeprecationWarning def _plot_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, cmap=None, vmin = None, vmax = None): """Plot observed or interpolated data in a scatter plot.""" @@ -339,6 +342,26 @@ def _plot_projection(longitudes: np.array, latitudes: np.array, values: np.array plt.close('all') def calculate_bounds(*arrays: np.ndarray) -> tuple[float]: - vmin = min(*[np.min(array).item() for array in arrays]) - vmax = max(*[np.max(array).item() for array in arrays]) + """Calculate consistent bounds across all arrays""" + valid_arrays = [arr for arr in arrays if arr is not None] + if not valid_arrays: + return 0, 1 + + # hanndle if there are masked arrays with invalid values (e.g. NaNs) + all_values = [] + for arr in valid_arrays: + if hasattr(arr, 'compressed'): # Masked array + compressed = arr.compressed() + if len(compressed) > 0: + all_values.extend(compressed) + elif hasattr(arr, 'flatten'): # Regular numpy array + all_values.extend(arr.flatten()) + else: + all_values.append(arr) + + if not all_values: + return 0, 1 + + vmin = min(all_values) + vmax = max(all_values) return vmin, vmax \ No newline at end of file From d28a8becc3bdee7bb2f64511238d5258775bcfa5 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 26 Aug 2025 18:15:21 +0200 Subject: [PATCH 142/302] Enhance evaluation scripts to include regression predictions - Modified `diurnal_cycle_precip_mean_wet-hour.py`, `diurnal_cycle_precip_p99.py`, `diurnal_cycle_temp_wind.py`, `hist.py`, `probability_of_exceedance.py`, `snapshots.py`, and `map_precip_stats.py` to handle regression predictions. Should be refactored for better error exception handling. - Updated plotting functions to include regression predictions where applicable. --- src/hirad/conf/generation/era_cosmo.yaml | 7 +- .../diurnal_cycle_precip_mean_wet-hour.py | 32 +++- src/hirad/eval/diurnal_cycle_precip_p99.py | 19 +- src/hirad/eval/diurnal_cycle_temp_wind.py | 39 ++++- src/hirad/eval/hist.py | 60 ++++--- src/hirad/eval/map_precip_stats.py | 17 +- src/hirad/eval/plot_maps.py | 163 ++++++++++++++++++ src/hirad/eval/probability_of_exceedance.py | 53 +++--- src/hirad/eval/snapshots.py | 42 ++++- 9 files changed, 346 insertions(+), 86 deletions(-) create mode 100644 src/hirad/eval/plot_maps.py diff --git a/src/hirad/conf/generation/era_cosmo.yaml b/src/hirad/conf/generation/era_cosmo.yaml index a50bf12b..3396d43d 100644 --- a/src/hirad/conf/generation/era_cosmo.yaml +++ b/src/hirad/conf/generation/era_cosmo.yaml @@ -1,4 +1,4 @@ -num_ensembles: 8 +num_ensembles: 16 # Number of ensembles to generate per input seed_batch_size: 4 # Size of the batched inference @@ -37,9 +37,8 @@ perf: io: # res_ckpt_path: /capstor/scratch/cscs/pstamenk/outputs/diffusion_full/checkpoints_diffusion res_ckpt_path: /capstor/scratch/cscs/pstamenk/outputs/training/diffusion_era5_cosmo/checkpoints_diffusion - # res_ckpt_path: null + # res_ckpt_path: /capstor/scratch/cscs/pstamenk/outputs/training/diffusion_era5_cosmo_cmp_fix_2/checkpoints_diffusion # Checkpoint filename for the diffusion model reg_ckpt_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_era5_cosmo/checkpoints_regression - # reg_ckpt_path: /iopsstor/scratch/cscs/pstamenk/outputs/regression_test/checkpoints_regression # Checkpoint filename for the mean predictor model - output_path: ./outputs/evaluation + output_path: . \ No newline at end of file diff --git a/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py b/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py index f2715880..988024a0 100644 --- a/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py +++ b/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py @@ -65,8 +65,8 @@ def main(cfg: DictConfig): land_mask = load_land_sea_mask() # Prepare lists to collect DataArrays - target_precip, baseline_precip, pred_precip = [], [], [] - target_wet, baseline_wet, pred_wet = [], [], [] + target_precip, baseline_precip, pred_precip, mean_pred_precip = [], [], [], [] + target_wet, baseline_wet, pred_wet, mean_pred_wet = [], [], [], [] # Collect data for idx, ts in enumerate(times, 1): @@ -74,26 +74,38 @@ def main(cfg: DictConfig): target = torch.load(out_root/ts/f"{ts}-target", weights_only=False)[tp_out] * CONV_FACTOR baseline = torch.load(out_root/ts/f"{ts}-baseline", weights_only=False)[tp_in] * CONV_FACTOR / 6. # 6 because 1h -> accumulation period is 6h in hourly ERA5 dataset preds = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False)[:, tp_out, :, :] * CONV_FACTOR + try: + mean_pred = torch.load(out_root/ts/f"{ts}-regression-prediction", weights_only=False)[tp_out] * CONV_FACTOR + except: + mean_pred = None # DataArrays for spatial means at each timestep da_target = xr.DataArray(target, dims=("lat","lon"), coords=land_mask.coords) da_baseline = xr.DataArray(baseline, dims=("lat","lon"), coords=land_mask.coords) da_preds = xr.DataArray(preds, dims=("member","lat","lon"), coords={"member": np.arange(preds.shape[0]), **land_mask.coords}) + if mean_pred is not None: + da_mean_pred = xr.DataArray(mean_pred, dims=("lat","lon"), coords=land_mask.coords) # Apply land mask after conversion to xarray da_target = da_target * land_mask da_baseline = da_baseline * land_mask da_preds = da_preds * land_mask + if mean_pred is not None: + da_mean_pred = da_mean_pred * land_mask # Spatial mean target_precip.append(da_target.mean(dim=("lat","lon")).assign_coords(time=dt)) baseline_precip.append(da_baseline.mean(dim=("lat","lon")).assign_coords(time=dt)) pred_precip.append(da_preds.mean(dim=("lat","lon")).assign_coords(time=dt)) + if mean_pred is not None: + mean_pred_precip.append(da_mean_pred.mean(dim=("lat","lon")).assign_coords(time=dt)) # Wet-hour fraction, i.e., freq(precip) > WET_THRESHOLD target_wet.append(((da_target / 24 > WET_THRESHOLD).mean().assign_coords(time=dt))) baseline_wet.append(((da_baseline / 24 > WET_THRESHOLD).mean().assign_coords(time=dt))) pred_wet.append(((da_preds / 24> WET_THRESHOLD).mean(dim=("lat","lon")).assign_coords(time=dt))) + if mean_pred is not None: + mean_pred_wet.append(((da_mean_pred / 24 > WET_THRESHOLD).mean().assign_coords(time=dt))) if idx % LOG_INTERVAL == 0 or idx == len(times): logger.info(f"Processed {idx}/{len(times)} timesteps ({ts})") @@ -102,26 +114,30 @@ def main(cfg: DictConfig): amount_target_mean, _ = concat_and_group_diurnal(target_precip) amount_baseline_mean, _ = concat_and_group_diurnal(baseline_precip) amount_pred_mean, amount_pred_std = concat_and_group_diurnal(pred_precip, is_member=True) + if mean_pred_precip: + amount_mean_pred_mean, _ = concat_and_group_diurnal(mean_pred_precip) wet_target_mean, _ = concat_and_group_diurnal(target_wet, scale=100.0) # scale to obtain percentages wet_baseline_mean, _ = concat_and_group_diurnal(baseline_wet, scale=100.0) wet_pred_mean, wet_pred_std = concat_and_group_diurnal(pred_wet, is_member=True, scale=100.0) + if mean_pred_wet: + wet_mean_pred_mean, _ = concat_and_group_diurnal(mean_pred_wet, scale=100.0) # Generate plots save_plot( amount_target_mean.hour, - [amount_target_mean, amount_baseline_mean, amount_pred_mean], - [None, None, amount_pred_std], - ['COSMO-2 Analysis','ERA5','CorrDiff ± Std(Members)'], + [amount_target_mean, amount_baseline_mean, amount_pred_mean, amount_mean_pred_mean] if mean_pred_precip else [amount_target_mean, amount_baseline_mean, amount_pred_mean], + [None, None, amount_pred_std, None] if mean_pred_precip else [None, None, amount_pred_std], + ['COSMO-2 Analysis','ERA5','CorrDiff ± Std(Members)', 'Regression Prediction'] if mean_pred_precip else ['COSMO-2 Analysis','ERA5','CorrDiff ± Std(Members)'], 'Precipitation (mm/day)', 'Diurnal Cycle of Precip Amount', out_root / 'diurnal_cycle_precip_amount.png' ) save_plot( wet_target_mean.hour, - [wet_target_mean, wet_baseline_mean, wet_pred_mean], - [None, None, wet_pred_std], - ['COSMO-2 Analysis','ERA5','CorrDiff ± Std(Members)'], + [wet_target_mean, wet_baseline_mean, wet_pred_mean, wet_mean_pred_mean] if mean_pred_wet else [wet_target_mean, wet_baseline_mean, wet_pred_mean], + [None, None, wet_pred_std, None] if mean_pred_wet else [None, None, wet_pred_std], + ['COSMO-2 Analysis','ERA5','CorrDiff ± Std(Members)', 'Regression Prediction'] if mean_pred_wet else ['COSMO-2 Analysis','ERA5','CorrDiff ± Std(Members)'], 'Wet-Hour Fraction [%]', 'Diurnal Cycle of Wet-Hours (>0.1 mm/h)', out_root / 'diurnal_cycle_precip_wethours.png' diff --git a/src/hirad/eval/diurnal_cycle_precip_p99.py b/src/hirad/eval/diurnal_cycle_precip_p99.py index bb641532..316a387b 100644 --- a/src/hirad/eval/diurnal_cycle_precip_p99.py +++ b/src/hirad/eval/diurnal_cycle_precip_p99.py @@ -81,13 +81,17 @@ def main(cfg: DictConfig): pct99_std = {} # -- Process target and baseline -- - for mode in ['target', 'baseline']: + for mode in ['target', 'baseline', 'regression-prediction']: logger.info(f"Processing mode: {mode}") data_list = [] - for ts in times: - data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode == 'target' else tp_in] * CONV_FACTOR * land_mask - data_list.append(data) + try: + for ts in times: + data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode in ['target','regression-prediction'] else tp_in] * CONV_FACTOR * land_mask + data_list.append(data) + except: + logger.error(f"Error loading data for mode {mode}. Skipping.") + continue da = xr.DataArray( np.stack(data_list, axis=0), @@ -123,6 +127,7 @@ def main(cfg: DictConfig): # Transpose to get the expected dimension order: [member, time, lat, lon] pred_da = pred_da.transpose('member', 'time', 'lat', 'lon') + logger.info('Calculating 99th percentile for predictions') # Group by hour, compute 99th percentile across time, then spatial mean hourly_p99_by_member = pred_da.groupby('time.hour').quantile(0.99, dim='time').mean(dim=['lat', 'lon']) @@ -134,6 +139,7 @@ def main(cfg: DictConfig): def cycle_fn(x): return x.values.tolist() + [x.values.tolist()[0]] + logger.info("Preparing data for plotting") hrs_c = list(range(24)) + [0 + 24] pct99_lines = [ cycle_fn(pct99_mean['target']), @@ -143,13 +149,16 @@ def cycle_fn(x): cycle_fn(pct99_std['prediction']) ) ] + if 'regression-prediction' in pct99_mean: + pct99_lines.append(cycle_fn(pct99_mean['regression-prediction'])) # Plot combined diurnal 99th-percentile cycle + labels = ['COSMO-2 Analysis', 'ERA5', 'CorrDiff 99th Pct ± Std', 'Regression Prediction'] if 'regression-prediction' in pct99_mean else ['COSMO-2 Analysis', 'ERA5', 'CorrDiff 99th Pct ± Std'] fn = out_root/'diurnal_cycle_precip_99th_percentile.png' save_plot( hrs_c, pct99_lines, - ['COSMO-2 Analysis','ERA5','CorrDiff 99th Pct ± Std'], + labels, 'Precipitation (mm/day)', 'Diurnal Cycle of 99th-Percentile Precipitation', fn diff --git a/src/hirad/eval/diurnal_cycle_temp_wind.py b/src/hirad/eval/diurnal_cycle_temp_wind.py index c0c97fee..5b4c36f4 100644 --- a/src/hirad/eval/diurnal_cycle_temp_wind.py +++ b/src/hirad/eval/diurnal_cycle_temp_wind.py @@ -57,8 +57,8 @@ def load(ts, fn): land_mask = load_land_sea_mask() # Prepare lists to collect DataArrays - target_temp, baseline_temp, pred_temp = [], [], [] - target_wind, baseline_wind, pred_wind = [], [], [] + target_temp, baseline_temp, pred_temp, mean_pred_temp = [], [], [], [] + target_wind, baseline_wind, pred_wind, mean_pred_wind = [], [], [], [] def mean_over_land(data, dims, coords, time_coord): da = xr.DataArray(data, dims=dims, coords=coords) * land_mask @@ -72,6 +72,10 @@ def mean_over_land(data, dims, coords, time_coord): target = load(ts, f"{ts}-target") baseline = load(ts, f"{ts}-baseline") predictions = load(ts, f"{ts}-predictions") + try: + regression_pred = load(ts, f"{ts}-regression-prediction") + except: + regression_pred = None # Process temperature (convert to Celsius) target_temp.append(mean_over_land( @@ -81,6 +85,10 @@ def mean_over_land(data, dims, coords, time_coord): pred_temp.append(mean_over_land( predictions[:, t2m_out, :, :] - 273.15, ("member","lat","lon"), {"member": np.arange(predictions.shape[0]), **land_mask.coords}, dt)) + if regression_pred is not None: + mean_pred_temp.append(mean_over_land( + regression_pred[t2m_out] - 273.15, ("lat","lon"), land_mask.coords, dt)) + # Process wind speed target_wind.append(mean_over_land( @@ -90,6 +98,9 @@ def mean_over_land(data, dims, coords, time_coord): pred_wind.append(mean_over_land( np.hypot(predictions[:, u_out, :, :], predictions[:, v_out, :, :]), ("member","lat","lon"), {"member": np.arange(predictions.shape[0]), **land_mask.coords}, dt)) + if regression_pred is not None: + mean_pred_wind.append(mean_over_land( + np.hypot(regression_pred[u_out], regression_pred[v_out]), ("lat","lon"), land_mask.coords, dt)) if idx % LOG_INTERVAL == 0 or idx == len(times): logger.info(f"Processed {idx}/{len(times)} timesteps ({ts})") @@ -98,10 +109,14 @@ def mean_over_land(data, dims, coords, time_coord): temp_target_mean, _ = concat_and_group_diurnal(target_temp) temp_baseline_mean, _ = concat_and_group_diurnal(baseline_temp) temp_pred_mean, temp_pred_std = concat_and_group_diurnal(pred_temp, is_member=True) + if mean_pred_temp: + temp_mean_pred_mean, _ = concat_and_group_diurnal(mean_pred_temp) wind_target_mean, _ = concat_and_group_diurnal(target_wind) wind_baseline_mean, _ = concat_and_group_diurnal(baseline_wind) wind_pred_mean, wind_pred_std = concat_and_group_diurnal(pred_wind, is_member=True) + if mean_pred_wind: + wind_mean_pred_mean, _ = concat_and_group_diurnal(mean_pred_wind) def save_plot(hour, means, stds, labels, ylabel, title, out_path): hrs = np.concatenate([hour.values, [24]]) @@ -124,22 +139,30 @@ def save_plot(hour, means, stds, labels, ylabel, title, out_path): plt.savefig(out_path) plt.close() + data = [temp_target_mean, temp_baseline_mean, temp_pred_mean, temp_mean_pred_mean] if mean_pred_temp else [temp_target_mean, temp_baseline_mean, temp_pred_mean] + labels = ['COSMO-2 Analysis', 'ERA5', 'CorrDiff ± Std(Members)', 'Regression Prediction'] if mean_pred_temp else ['COSMO-2 Analysis', 'ERA5', 'CorrDiff ± Std(Members)'] + stds = [None, None, temp_pred_std, None] if mean_pred_temp else [None, None, temp_pred_std] + # Generate plots save_plot( temp_target_mean.hour, - [temp_target_mean, temp_baseline_mean, temp_pred_mean], - [None, None, temp_pred_std], - ['COSMO-2 Analysis', 'ERA5', 'CorrDiff ± Std(Members)'], + data, + stds, + labels, '2m Temperature [°C]', 'Diurnal Cycle of 2m Temperature', out_root / 'diurnal_cycle_2t.png' ) + data = [wind_target_mean, wind_baseline_mean, wind_pred_mean, wind_mean_pred_mean] if mean_pred_wind else [wind_target_mean, wind_baseline_mean, wind_pred_mean] + labels = ['COSMO-2 Analysis', 'ERA5', 'CorrDiff ± Std(Members)', 'Regression Prediction'] if mean_pred_wind else ['COSMO-2 Analysis', 'ERA5', 'CorrDiff ± Std(Members)'] + stds = [None, None, wind_pred_std, None] if mean_pred_wind else [None, None, wind_pred_std] + save_plot( wind_target_mean.hour, - [wind_target_mean, wind_baseline_mean, wind_pred_mean], - [None, None, wind_pred_std], - ['COSMO-2 Analysis', 'ERA5', 'CorrDiff ± Std(Members)'], + data, + stds, + labels, 'Windspeed [m/s]', 'Diurnal Cycle of Windspeed', out_root / 'diurnal_cycle_windspeed.png' diff --git a/src/hirad/eval/hist.py b/src/hirad/eval/hist.py index bdd241bf..183dee8d 100644 --- a/src/hirad/eval/hist.py +++ b/src/hirad/eval/hist.py @@ -63,14 +63,14 @@ def save_distribution_plot(hist_data_dict, bin_edges, labels, colors, title, yla # Define line styles for percentiles percentile_styles = {99: '--', 99.9: ':', 99.99: '-.'} percentile_labels = {99: '99th all-hour percentiles', 99.9: '99.9th all-hour percentiles', 99.99: '99.99th all-hour percentiles'} - colors = {'target': 'blue', 'baseline': 'orange', 'predictions': 'green'} + colors = {'target': 'blue', 'baseline': 'orange', 'predictions': 'green', 'regression-prediction': 'red'} legend_added = set() # Plot all percentile lines for dataset_name, data in percentiles_data.items(): color = colors[dataset_name] - if dataset_name in ['target', 'baseline']: + if dataset_name in ['target', 'baseline', 'regression-prediction']: # Single dataset for percentile, value in data.items(): linestyle = percentile_styles[percentile] @@ -138,30 +138,33 @@ def main(cfg: DictConfig): all_land_values = {} # -- Process target and baseline -- - for mode in ['target', 'baseline']: + for mode in ['target', 'baseline', 'regression-prediction']: logger.info(f"Processing mode: {mode}") hist_counts = np.zeros(len(bins) - 1) total_samples = 0 all_values = [] - for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: - logger.info(f"Processing timestep {i+1}/{len(times)}") - - data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode == 'target' else tp_in] * CONV_FACTOR_HOURLY * land_mask - - # Apply scaling factor for baseline - if mode == 'baseline': - data = data / 6.0 - - land_values = data.values[~np.isnan(data.values)] - all_values.extend(land_values) - - counts, _ = np.histogram(land_values, bins=bins) - hist_counts += counts - total_samples += len(land_values) - + try: + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Processing timestep {i+1}/{len(times)}") + + data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode in ['target', 'regression-prediction'] else tp_in] * CONV_FACTOR_HOURLY * land_mask + + # Apply scaling factor for baseline + if mode == 'baseline': + data = data / 6.0 + + land_values = data.values[~np.isnan(data.values)] + all_values.extend(land_values) + + counts, _ = np.histogram(land_values, bins=bins) + hist_counts += counts + total_samples += len(land_values) + except: + logger.warning(f"{mode} not available, skipping") + continue # Normalize to probability density bin_widths = np.diff(bins) hist_data[mode] = hist_counts / (total_samples * bin_widths) @@ -212,12 +215,13 @@ def main(cfg: DictConfig): percentiles = {99: 0.99, 99.9: 0.999, 99.99: 0.9999} # Target and baseline percentiles - for mode in ['target', 'baseline']: - data_array = xr.DataArray(all_land_values[mode]) - percentiles_data[mode] = { - key: data_array.quantile(p).item() - for key, p in percentiles.items() - } + for mode in ['target', 'baseline', 'regression-prediction']: + if mode in all_land_values: + data_array = xr.DataArray(all_land_values[mode]) + percentiles_data[mode] = { + key: data_array.quantile(p).item() + for key, p in percentiles.items() + } # Ensemble member percentiles percentiles_data['predictions'] = {} @@ -229,8 +233,8 @@ def main(cfg: DictConfig): } # Create distribution plots - labels = ['COSMO-2 Analysis', 'ERA5', 'CorrDiff Ensemble'] - colors = ['blue', 'orange', 'green'] + labels = ['COSMO-2 Analysis', 'ERA5', 'Regression Prediction', 'CorrDiff Ensemble'] if 'regression-prediction' in hist_data else ['COSMO-2 Analysis', 'ERA5', 'CorrDiff Ensemble'] + colors = ['blue', 'orange', 'red', 'green'] if 'regression-prediction' in hist_data else ['blue', 'orange', 'green'] fn = out_root / 'precipitation_distribution_over_land.png' save_distribution_plot( diff --git a/src/hirad/eval/map_precip_stats.py b/src/hirad/eval/map_precip_stats.py index 26740c1d..47e4125d 100644 --- a/src/hirad/eval/map_precip_stats.py +++ b/src/hirad/eval/map_precip_stats.py @@ -129,7 +129,8 @@ def main(cfg: DictConfig): # Target and baseline modes basic_modes = { 'target': (tp_out, 'COSMO-2 Analysis'), - 'baseline': (tp_in, 'ERA5') + 'baseline': (tp_in, 'ERA5'), + 'regression-prediction': (tp_out, 'Regression Prediction') } logger.info(f"Generating {len(stat_configs)} statistics for {len(basic_modes)} basic modes + predictions") @@ -137,11 +138,15 @@ def main(cfg: DictConfig): logger.info(f"Processing mode: {mode}") # Load all timesteps for this mode data_list = [] - for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: - logger.info(f"Loading {mode} timestep {i+1}/{len(times)}: {ts}") - data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False) * CONV_FACTOR - data_list.append(data[tp_channel]) + try: + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Loading {mode} timestep {i+1}/{len(times)}: {ts}") + data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False) * CONV_FACTOR + data_list.append(data[tp_channel]) + except: + logger.warning(f"{mode} not available, skipping") + continue mode_data = xr.DataArray( np.stack(data_list, axis=0), dims=['time', 'lat', 'lon'], diff --git a/src/hirad/eval/plot_maps.py b/src/hirad/eval/plot_maps.py new file mode 100644 index 00000000..a2230a68 --- /dev/null +++ b/src/hirad/eval/plot_maps.py @@ -0,0 +1,163 @@ +import torch +import yaml +import numpy as np +import os +from pathlib import Path +import argparse +from hirad.eval import plotting +from hirad.utils.inference_utils import calculate_bounds, transform_channel +import hydra +from omegaconf import DictConfig, OmegaConf + +channel_plot_args = { + "2t": {"label": "°C"}, + "10u": {"label": "m/s"}, + "10v": {"label": "m/s"}, + "tp": {"label": "boxcox(mm/h)"}, +} + + +def get_available_time_steps(results_dir): + """Find all available time step directories""" + results_path = Path(results_dir) + time_step_dirs = [d.name for d in results_path.iterdir() if d.is_dir() and not d.name.startswith('plots') and not d.name.startswith('maps')] + return sorted(time_step_dirs) + +def plot_time_step(results_dir, output_dir, time_step, input_channels, output_channels, cfg): + """Plot all channels for a single time step""" + print(f"Processing time step: {time_step}") + + # Set up paths for this time step + ts_results_dir = Path(results_dir) / time_step + + # Load tensors + try: + target = torch.load(ts_results_dir / f"{time_step}-target", weights_only=False) + baseline = torch.load(ts_results_dir / f"{time_step}-baseline", weights_only=False) + predictions = torch.load(ts_results_dir / f"{time_step}-predictions", weights_only=False) + + try: + mean_pred = torch.load(ts_results_dir / f"{time_step}-regression-prediction", weights_only=False) + except FileNotFoundError: + mean_pred = None + print(f" Warning: No mean prediction found for {time_step}") + + except FileNotFoundError as e: + print(f" Error: Missing required file for {time_step}: {e}") + return + + # Create output directory for this time step + ts_output_dir = Path(output_dir) / time_step + ts_output_dir.mkdir(parents=True, exist_ok=True) + + # Plot each channel + for idx, channel in enumerate(output_channels): + print(f" Plotting channel: {channel}") + + # Get input channel index (handle case where input/output channels differ) + try: + input_idx = input_channels.index(channel) + except ValueError: + print(f" Warning: Channel {channel} not in input channels, skipping baseline") + continue + + # Transform data + if channel != "tp" or not cfg.get("plot_box_precipitation", False): + tgt = transform_channel(target[idx], channel) + base = transform_channel(baseline[input_idx], channel) + preds = transform_channel(predictions[:, idx], channel) + mean = transform_channel(mean_pred[idx], channel) if mean_pred is not None else None + if channel == "tp": + threshold = transform_channel(np.array([cfg.get("tp_threshold", 0.002)]), "tp")[0] # Transform threshold too + tgt = np.ma.masked_where(tgt <= threshold, tgt) + base = np.ma.masked_where(base <= threshold, base) + preds = np.ma.masked_where(preds <= threshold, preds) + if mean is not None: + mean = np.ma.masked_where(mean <= threshold, mean) + else: + # For precipitation, use raw values if plotting box precipitation + tgt = target[idx] + base = baseline[input_idx] + preds = predictions[:, idx] + mean = mean_pred[idx] if mean_pred is not None else None + + # Calculate consistent bounds (skip for precipitation) + if channel != "tp" or not cfg.get("plot_box_precipitation", False): + arrays = [tgt, base] + [preds[i] for i in range(preds.shape[0])] + if mean is not None: + arrays.append(mean) + vmin, vmax = calculate_bounds(*arrays) + else: + vmin, vmax = None, None + + base_channel_dir = ts_output_dir / channel + base_channel_dir.mkdir(parents=True, exist_ok=True) + + # Plot target + fname = ts_output_dir / channel / f"target" + if channel == "tp" and cfg.get("plot_box_precipitation", False): + plotting.plot_map_precipitation(tgt, str(fname), title=f"Target - {channel}") + else: + plotting.plot_map(tgt, str(fname), vmin=vmin, vmax=vmax, + title=f"Target - {channel}", **channel_plot_args.get(channel, {})) + + # Plot baseline + fname = ts_output_dir / channel / "baseline" + if channel == "tp" and cfg.get("plot_box_precipitation", False): + plotting.plot_map_precipitation(base, str(fname), title=f"Baseline - {channel}") + else: + plotting.plot_map(base, str(fname), vmin=vmin, vmax=vmax, + title=f"Baseline - {channel}", **channel_plot_args.get(channel, {})) + + # Plot mean prediction if available + if mean is not None: + fname = ts_output_dir / channel / "mean-prediction" + if channel == "tp" and cfg.get("plot_box_precipitation", False): + plotting.plot_map_precipitation(mean, str(fname), title=f"Mean Prediction - {channel}") + else: + plotting.plot_map(mean, str(fname), vmin=vmin, vmax=vmax, + title=f"Mean Prediction - {channel}", **channel_plot_args.get(channel, {})) + + # Plot ensemble members + for member_idx in range(preds.shape[0]): + fname = ts_output_dir / channel / f"prediction_{member_idx:02d}" + if channel == "tp" and cfg.get("plot_box_precipitation", False): + plotting.plot_map_precipitation( + preds[member_idx], str(fname), + title=f"Prediction {member_idx} - {channel}" + ) + else: + plotting.plot_map( + preds[member_idx], str(fname), + vmin=vmin, vmax=vmax, + title=f"Prediction {member_idx} - {channel}", + **channel_plot_args.get(channel, {}) + ) + +@hydra.main(version_base=None, config_path="../conf", config_name="plotting") +def main(cfg: DictConfig) -> None: + OmegaConf.resolve(cfg) + + input_channels = cfg.dataset.input_channel_names + output_channels = cfg.dataset.output_channel_names + + # Set up directories + results_dir = Path(cfg.results_dir) + output_dir = Path(cfg.output_dir) if "output_dir" in cfg and cfg.output_dir else results_dir / "plots" + output_dir.mkdir(exist_ok=True) + + # Determine time steps to process + if cfg.time_steps: + time_steps = cfg.time_steps + else: + time_steps = get_available_time_steps(results_dir) + print(f"Found {len(time_steps)} time steps: {time_steps}") + + # Process each time step + for time_step in time_steps: + plot_time_step(results_dir, output_dir, time_step, input_channels, output_channels, cfg) + + print(f"Plotting complete. Results saved to: {output_dir}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/hirad/eval/probability_of_exceedance.py b/src/hirad/eval/probability_of_exceedance.py index febda1f8..e301016d 100644 --- a/src/hirad/eval/probability_of_exceedance.py +++ b/src/hirad/eval/probability_of_exceedance.py @@ -60,14 +60,14 @@ def save_exceedance_plot(exceedance_data_dict, thresholds, labels, colors, title # Define line styles for percentiles percentile_styles = {99: '--', 99.9: ':', 99.99: '-.'} percentile_labels = {99: '99th all-hour percentiles', 99.9: '99.9th all-hour percentiles', 99.99: '99.99th all-hour percentiles'} - colors_perc = {'target': 'blue', 'baseline': 'orange', 'predictions': 'green'} + colors_perc = {'target': 'blue', 'baseline': 'orange', 'predictions': 'green', 'regression-prediction': 'red'} legend_added = set() # Plot all percentile lines for dataset_name, data in percentiles_data.items(): color = colors_perc[dataset_name] - if dataset_name in ['target', 'baseline']: + if dataset_name in ['target', 'baseline', 'regression-prediction']: # Single dataset for percentile, value in data.items(): linestyle = percentile_styles[percentile] @@ -135,24 +135,28 @@ def main(cfg: DictConfig): all_land_values = {} # -- Process target and baseline -- - for mode in ['target', 'baseline']: + for mode in ['target', 'baseline', 'regression-prediction']: logger.info(f"Processing mode: {mode}") all_values = [] - for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: - logger.info(f"Processing timestep {i+1}/{len(times)}") - - data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode == 'target' else tp_in] * CONV_FACTOR_HOURLY * land_mask - - # Apply scaling factor for baseline - if mode == 'baseline': - data = data / 6.0 - - land_values = data.values[~np.isnan(data.values)] - all_values.extend(land_values) - + try: + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Processing timestep {i+1}/{len(times)}") + + data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode in ['target','regression-prediction'] else tp_in] * CONV_FACTOR_HOURLY * land_mask + + # Apply scaling factor for baseline + if mode == 'baseline': + data = data / 6.0 + + land_values = data.values[~np.isnan(data.values)] + all_values.extend(land_values) + except: + logger.warning(f"{mode} data not found, skipping") + continue + # Compute exceedance probabilities all_values = np.array(all_values) exceedance_probs = [] @@ -204,12 +208,13 @@ def main(cfg: DictConfig): percentiles = {99: 0.99, 99.9: 0.999, 99.99: 0.9999} # Target and baseline percentiles - for mode in ['target', 'baseline']: - data_array = xr.DataArray(all_land_values[mode]) - percentiles_data[mode] = { - key: data_array.quantile(p).item() - for key, p in percentiles.items() - } + for mode in ['target', 'baseline', 'regression-prediction']: + if mode in all_land_values: + data_array = xr.DataArray(all_land_values[mode]) + percentiles_data[mode] = { + key: data_array.quantile(p).item() + for key, p in percentiles.items() + } # Ensemble member percentiles percentiles_data['predictions'] = {} @@ -221,8 +226,8 @@ def main(cfg: DictConfig): } # Create exceedance plots - labels = ['COSMO-2 Analysis', 'ERA5', 'CorrDiff Ensemble'] - colors = ['blue', 'orange', 'green'] + labels = ['COSMO-2 Analysis', 'ERA5', 'Regression Prediction', 'CorrDiff Ensemble'] if 'regression-prediction' in exceedance_data else ['COSMO-2 Analysis', 'ERA5', 'CorrDiff Ensemble'] + colors = ['blue', 'orange', 'red', 'green'] if 'regression-prediction' in exceedance_data else ['blue', 'orange', 'green'] fn = out_root / 'precipitation_exceedance_over_land.png' save_exceedance_plot( diff --git a/src/hirad/eval/snapshots.py b/src/hirad/eval/snapshots.py index e68f956e..08d6e6b3 100644 --- a/src/hirad/eval/snapshots.py +++ b/src/hirad/eval/snapshots.py @@ -139,6 +139,10 @@ def main(cfg: DictConfig) -> None: prediction = files.load(curr_time, f'{curr_time}-predictions') baseline = files.load(curr_time, f'{curr_time}-baseline') target = files.load(curr_time, f'{curr_time}-target') + try: + mean_pred = files.load(curr_time, f'{curr_time}-regression-prediction') + except: + mean_pred = None input_channels = dataset.input_channels() output_channels = dataset.output_channels() @@ -150,7 +154,8 @@ def main(cfg: DictConfig) -> None: vmin, vmax = calculate_bounds( target[idx,:,:], prediction[:,idx,:,:], - baseline[input_channel_idx,:,:] + baseline[input_channel_idx,:,:], + mean_pred[idx,:,:] if mean_pred is not None else None ) metadata = ChannelMeta.get(channel, vmin=vmin, vmax=vmax) @@ -174,11 +179,18 @@ def main(cfg: DictConfig) -> None: "prediction", prediction[0, idx, :, :], metadata, files, channel, curr_time, plot_func=plot_map_precipitation, title=plot_title ) + if mean_pred is not None: + save_field( + "mean-prediction", mean_pred[idx, :, :], metadata, files, channel, curr_time, + plot_func=plot_map_precipitation, title=plot_title + ) continue - # Plot target and baseline + # Plot target and baseline and regression prediction if available save_field("target", target[idx, :, :], metadata, files, channel, curr_time, title=plot_title) save_field("baseline", baseline[input_channel_idx, :, :], metadata, files, channel, curr_time, title=plot_title) + if mean_pred is not None: + save_field("mean-prediction", mean_pred[idx, :, :], metadata, files, channel, curr_time, title=plot_title) # Baseline MAE and ME _, baseline_mae = compute_mae(baseline[input_channel_idx, :, :], target[idx, :, :]) @@ -186,6 +198,13 @@ def main(cfg: DictConfig) -> None: save_field("baseline", baseline_mae.reshape(baseline[input_channel_idx, :, :].shape), metadata, files, channel, curr_time, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title) save_field("baseline", baseline_me, metadata, files, channel, curr_time, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title) + # Regression prediction MAE and ME + if mean_pred is not None: + _, mean_mae = compute_mae(mean_pred[idx, :, :], target[idx, :, :]) + mean_me = (mean_pred[idx, :, :] - target[idx, :, :]) + save_field("mean-prediction", mean_mae.reshape(mean_pred[idx, :, :].shape), metadata, files, channel, curr_time, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title) + save_field("mean-prediction", mean_me, metadata, files, channel, curr_time, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title) + # Ensemble predictions for member_idx in range(prediction.shape[0]): member = prediction[member_idx, idx, :, :] @@ -203,13 +222,16 @@ def main(cfg: DictConfig) -> None: input_idx_10u = output_to_input_channel_map[idx_10u] input_idx_10v = output_to_input_channel_map[idx_10v] - # Compute windspeed and direction for target, baseline, prediction + # Compute windspeed and direction for target, baseline, prediction and mean prediction target_wind_speed = np.hypot(target[idx_10u, :, :], target[idx_10v, :, :]) target_wind_dir = wind_direction(target[idx_10u, :, :], target[idx_10v, :, :]) baseline_wind_speed = np.hypot(baseline[input_idx_10u, :, :], baseline[input_idx_10v, :, :]) baseline_wind_dir = wind_direction(baseline[input_idx_10u, :, :], baseline[input_idx_10v, :, :]) prediction_wind_speed = np.hypot(prediction[:, idx_10u, :, :], prediction[:, idx_10v, :, :]) prediction_wind_dir = wind_direction(prediction[:, idx_10u, :, :], prediction[:, idx_10v, :, :]) + if mean_pred is not None: + mean_wind_speed = np.hypot(mean_pred[idx_10u, :, :], mean_pred[idx_10v, :, :]) + mean_wind_dir = wind_direction(mean_pred[idx_10u, :, :], mean_pred[idx_10v, :, :]) plot_title_speed = f"{format_time_str(curr_time)}: FF10m" plot_title_dir = f"{format_time_str(curr_time)}: DD10m" @@ -237,6 +259,13 @@ def main(cfg: DictConfig) -> None: custom_path=files.wind_file("FF10m", curr_time, "FF10m-prediction", member_idx), plot_func=plot_map, title=plot_title_speed ) + if mean_pred is not None: + save_field( + "FF10m-mean-prediction", mean_wind_speed, wind_meta, files, None, curr_time, + cmap="viridis", vmin=0, vmax=10, extend='max', + custom_path=files.wind_file("FF10m", curr_time, "FF10m-mean-prediction"), + plot_func=plot_map, title=plot_title_speed + ) # Save wind direction plots save_field( @@ -258,6 +287,13 @@ def main(cfg: DictConfig) -> None: custom_path=files.wind_file("DD10m", curr_time, "DD10m-prediction", member_idx), plot_func=plot_map, title=plot_title_dir ) + if mean_pred is not None: + save_field( + "DD10m-mean-prediction", mean_wind_dir, dir_meta, files, None, curr_time, + cmap="twilight", vmin=0, vmax=360, + custom_path=files.wind_file("DD10m", curr_time, "DD10m-mean-prediction"), + plot_func=plot_map, title=plot_title_dir + ) logger.info("Image loading and plotting completed.") From 5a5412e0fd315b3c8926f9a117237efaddcad471 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 27 Aug 2025 08:21:05 +0200 Subject: [PATCH 143/302] one-off script to regrid copernicus data --- src/hirad/input_data/copernicus-tp.sh | 5 + src/hirad/input_data/regrid_copernicus_tp.py | 173 +++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 src/hirad/input_data/copernicus-tp.sh create mode 100644 src/hirad/input_data/regrid_copernicus_tp.py diff --git a/src/hirad/input_data/copernicus-tp.sh b/src/hirad/input_data/copernicus-tp.sh new file mode 100644 index 00000000..943812f6 --- /dev/null +++ b/src/hirad/input_data/copernicus-tp.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +pip install -e . +pip install anemoi.datasets +python src/hirad/input_data/read_tp.py diff --git a/src/hirad/input_data/regrid_copernicus_tp.py b/src/hirad/input_data/regrid_copernicus_tp.py new file mode 100644 index 00000000..a098c688 --- /dev/null +++ b/src/hirad/input_data/regrid_copernicus_tp.py @@ -0,0 +1,173 @@ +import logging +import netCDF4 +import xarray +import numpy as np +import torch +import datetime +from scipy.interpolate import griddata + +from hirad.eval.plotting import plot_map_precipitation, plot_scores_vs_t +from hirad.eval.metrics import absolute_error + +import interpolate_basic + +import sys +from pathlib import Path + +import os +print (os.getcwd()) + +sys.path.insert(0, Path(__file__).parent.as_posix()) + +ANEMOI_1H_FILENAME = "/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr" +ANEMOI_6H_FILENAME = "/scratch/mch/apennino/data/aifs-ea-an-oper-0001-mars-n320-1979-2022-6h-v6.zarr" +COSMO_6H_FILENAME = "/scratch/mch/fzanetta/data/anemoi/datasets/mch-co2-an-archive-0p02-2015-2020-6h-v3-pl13.zarr" +COSMO_1H_FILENAME = "/scratch/mch/fzanetta/data/anemoi/datasets/mch-co2-an-archive-0p02-2015-2020-1h-v3-pl13.zarr" +COSMO_CONFIG_FILE="src/input_data/cosmo.yaml" +CDF_FILENAME_BALFRIN = "/store_new/mch/msopr/hirad-gen/copernicus-datasets/tp-janfeb2020.nc" +CDF_FILENAME_CLARIDEN_TP = "/capstor/scratch/cscs/mmcgloho/datasets/copernicus/tp-2019-2020/data_stream-oper_stepType-accum.nc" +#CDF_FILENAME_CLARIDEN_INSTANT = "/capstor/store/mch/msopr/hirad-gen/copernicus-datasets/surface-janfeb2020-netcdf/data_stream-oper_stepType-instant.nc" +GRIB_FILENAME_BALFRIN = "/store_new/mch/msopr/hirad-gen/copernicus-datasets/surface-janfeb2020.grib" +GRIB_FILENAME_CLARIDEN = "/capstor/store/mch/msopr/hirad-gen/copernicus-datasets/surface-janfeb2020.grib" +COSMO_GRID_FILENAME = " /capstor/store/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-linear-interpolation-full/info/cosmo-lat-lon" +INPUT_DATA_FILEPATH = "mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-linear-interpolation-full/" +BASE_FILEPATH = "/capstor/store/" +OUTPUT_DATA_FILEPATH = "/capstor/store/cscs/swissai/a161/era5-cosmo-1h-linear-interpolation/train/era-interpolated-with-copernicus-tp" +TP_INDEX = 12 + +LAT = np.arange(-4.42, 3.36 + 0.02, 0.02) +LON = np.arange(-6.82, 4.80 + 0.02, 0.02) +RELAX_ZONE = 19 # Number of points dropped on each side (relaxation zone) + +def extract_grib_values(grib_data): + grib_lat = grib_data['latitude'][:] + grib_lon = grib_data['longitude'][:] + grib_t2m = grib_data['t2m'][:] + +def extract_lat_lon(data): + logging.info('extracting lat/lon') + lat = data['latitude'][:] + lon = data['longitude'][:] + output_lat = np.zeros(len(lat)* len(lon)) + output_lon = np.zeros(len(lat) * len(lon)) + for i in range(len(lat)): + if i % 10 == 0: + print(i) + for j in range(len(lon)): + grid_index = i * len(lon) + j + output_lat[grid_index] = lat[i] + output_lon[grid_index] = lon[j] + return output_lat, output_lon + +def extract_values(data, variable): + values = data[variable][:] + return np.reshape(values, (values.shape[0], values.shape[1]*values.shape[2])) + +def reshape_to_cosmo(vals): + return vals.reshape((len(LAT)-RELAX_ZONE*2, len(LON)-RELAX_ZONE*2)) + +def calc_errors(cosmo1, era1): + make_plots = True + + prev_netcdf_regrid = [] + + netcdf_error = np.zeros(cosmo1.dates.shape) + era_norm_error = np.zeros(cosmo1.dates.shape) + netcdf_early_error = np.zeros(cosmo1.dates.shape) + netcdf_late_error = np.zeros(cosmo1.dates.shape) + + output_grid= np.column_stack((cosmo1.longitudes, cosmo1.latitudes)) + + for t in range(4): + #for t in range(len(cosmo1.dates)): + date = cosmo1.dates[t] + era_date = era1.dates[t] + if date != era_date: + logging.error('dates do not match: cosmo date: {date}, era date: {era_date}') + if date != netcdf_data['valid_time'][t]: + logging.error(f'dates do not match: cosmo date: {date}, netcdf: {netcdf_data["valid_time"][t]}') + + + # plot cosmo + if make_plots: + plot_map_precipitation(values=reshape_to_cosmo(cosmo1[t,:]), filename=f'plots/tp/{date}-cosmo1') + + # plot netcdf + netcdf_vals = netcdf_values[t,:].reshape((1,1,netcdf_values.shape[1])) + netcdf_regrid=interpolate_basic.regrid(netcdf_vals, netcdf_grid, output_grid) + if make_plots: + plot_map_precipitation(reshape_to_cosmo(netcdf_regrid), f'plots/tp/{date}-netcdf-refactor') + + # plot era + era_grid = np.column_stack((era1.longitudes, era1.latitudes)) + era1_regrid = interpolate_basic.regrid(era1[t,:], era_grid, output_grid) + if make_plots: + plot_map_precipitation(reshape_to_cosmo(era1_regrid/6), f'plots/tp/{date}-era1-norm') + + #if t % 6 == 0: + # if era6.dates[t//6] != date: + # logging.error(f'dates do not match: era1: {date}, era6: {era6.dates[t//6]}') + # era6_regrid = interpolate_basic.regrid(era6[t//6,:], era_grid, output_grid) + # plot_map_precipitation(reshape_to_cosmo(era6_regrid), f'plots/tp/{date}-era6') + + era_norm_error[t] = np.mean(absolute_error(era1_regrid/6, cosmo1[t,:])) + netcdf_error[t] = np.mean(absolute_error(netcdf_regrid, cosmo1[t,:])) + logging.info(f'era norm error: {era_norm_error[t]} netcdf err: {netcdf_error[t]}') + if t>0: + netcdf_early_error[t] = np.mean(absolute_error(prev_netcdf_regrid, cosmo1[t,:])) + netcdf_late_error[t-1] = np.mean(absolute_error(netcdf_regrid, cosmo1[t-1,:])) + logging.info(f'netcdf early err: {netcdf_early_error[t]}, netcdf late err: {netcdf_late_error[t-1]}') + prev_netcdf_regrid = netcdf_regrid + + maes = {} + maes['era normalized'] = era_norm_error + maes['copernicus'] = netcdf_error + maes['copernicus-early'] = netcdf_early_error + maes['copernicus-late'] = netcdf_late_error + plot_scores_vs_t(maes, times=cosmo1.dates, filename='plots/errors.png') + + +root = logging.getLogger() +root.setLevel(logging.INFO) + +logging.info('loading data') +netcdf_data = netCDF4.Dataset(CDF_FILENAME_CLARIDEN_TP) + +#cosmo1 = open_dataset(COSMO_1H_FILENAME, trim_edge=19, select=['tp'],start='2016-01-01',end='2016-02-29') +#cosmo6 = open_dataset(COSMO_6H_FILENAME, trim_edge=19, select=['tp'],start='2016-01-01',end='2016-02-29') +#era1 = open_dataset(ANEMOI_1H_FILENAME, select=['tp'],start='2016-01-01',end='2016-02-29') +#era6 = open_dataset(ANEMOI_6H_FILENAME, select=['tp'],start='2016-01-01',end='2016-02-29') +logging.info('loading data complete') + +logging.info(os.listdir(os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, 'info'))) + +cosmo_grid = torch.load(os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, 'info/cosmo-lat-lon'), weights_only=False) + + +logging.info('processing netcdf data') +netcdf_latitudes, netcdf_longitudes = extract_lat_lon(netcdf_data) +netcdf_tp_values = extract_values(netcdf_data, 'tp') +netcdf_grid=np.column_stack((netcdf_longitudes, netcdf_latitudes)) + +#for t in range(10): +for t in range(netcdf_tp_values.shape[0]): + netcdf_date = netcdf_data['valid_time'][t] + date_filename = datetime.datetime.fromtimestamp(netcdf_date, datetime.UTC).strftime('%Y%m%d-%H%M') + t1 = datetime.datetime.now() + era_filename = os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, "era-interpolated", date_filename) + if os.path.exists(era_filename) and netcdf_date > 1560229200: + era_data = torch.load(era_filename, weights_only=False) + t2 = datetime.datetime.now() + logging.info(f'regridding {date_filename} (netcdf date: {netcdf_date})') + interpolated_tp = griddata(netcdf_grid, netcdf_tp_values[t,:], cosmo_grid, method='linear') + t3 = datetime.datetime.now() + era_data[TP_INDEX,0,:] = interpolated_tp + torch.save(era_data, os.path.join(OUTPUT_DATA_FILEPATH, date_filename)) + t4 = datetime.datetime.now() + + + + + + + From f1d71017672b95b3aa8097d65fb4a91cce140cbe Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 27 Aug 2025 08:22:47 +0200 Subject: [PATCH 144/302] Add input_data dependencies to container --- ci/docker/Dockerfile.corrdiff | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/docker/Dockerfile.corrdiff b/ci/docker/Dockerfile.corrdiff index 79081979..3187c84f 100644 --- a/ci/docker/Dockerfile.corrdiff +++ b/ci/docker/Dockerfile.corrdiff @@ -6,6 +6,8 @@ RUN pip install --upgrade pip # Install the rest of dependencies. RUN pip install \ + anemoi.datasets \ Cartopy==0.22.0 \ xskillscore \ + scoringrules \ mlflow From 3642980b368c2178aef492207ea8e329a4d4b049 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 27 Aug 2025 16:33:30 +0200 Subject: [PATCH 145/302] add grid variable --- src/hirad/input_data/download_copernicus_tp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hirad/input_data/download_copernicus_tp.py b/src/hirad/input_data/download_copernicus_tp.py index 55031eee..6457eed1 100644 --- a/src/hirad/input_data/download_copernicus_tp.py +++ b/src/hirad/input_data/download_copernicus_tp.py @@ -34,7 +34,8 @@ "21:00", "22:00", "23:00" ], "data_format": "netcdf", - "download_format": "unarchived" + "download_format": "unarchived", + "grid": "N320" } client = cdsapi.Client() From fe621ac9e4d4a7bfa61eef9441d26ef488ab347d Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 1 Sep 2025 10:38:20 +0200 Subject: [PATCH 146/302] more on processing copernicus data --- .../input_data/download_copernicus_tp.py | 11 +- src/hirad/input_data/regrid_copernicus_tp.py | 110 ++++++++++++------ 2 files changed, 84 insertions(+), 37 deletions(-) diff --git a/src/hirad/input_data/download_copernicus_tp.py b/src/hirad/input_data/download_copernicus_tp.py index 6457eed1..6eee50cf 100644 --- a/src/hirad/input_data/download_copernicus_tp.py +++ b/src/hirad/input_data/download_copernicus_tp.py @@ -5,10 +5,14 @@ "product_type": ["reanalysis"], "variable": ["total_precipitation"], "year": [ - "2016" + "2015", "2016", "2017", + "2018", "2019", "2020", ], "month": [ - "01", "02" + "01", "02", "03", + "04", "05", "06", + "07", "08", "09", + "10", "11", "12", ], "day": [ "01", "02", "03", @@ -35,7 +39,8 @@ ], "data_format": "netcdf", "download_format": "unarchived", - "grid": "N320" + "grid": "N320", + "area": [60, 0, 40, 20] } client = cdsapi.Client() diff --git a/src/hirad/input_data/regrid_copernicus_tp.py b/src/hirad/input_data/regrid_copernicus_tp.py index a098c688..93a142f6 100644 --- a/src/hirad/input_data/regrid_copernicus_tp.py +++ b/src/hirad/input_data/regrid_copernicus_tp.py @@ -19,20 +19,17 @@ sys.path.insert(0, Path(__file__).parent.as_posix()) -ANEMOI_1H_FILENAME = "/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr" -ANEMOI_6H_FILENAME = "/scratch/mch/apennino/data/aifs-ea-an-oper-0001-mars-n320-1979-2022-6h-v6.zarr" -COSMO_6H_FILENAME = "/scratch/mch/fzanetta/data/anemoi/datasets/mch-co2-an-archive-0p02-2015-2020-6h-v3-pl13.zarr" -COSMO_1H_FILENAME = "/scratch/mch/fzanetta/data/anemoi/datasets/mch-co2-an-archive-0p02-2015-2020-1h-v3-pl13.zarr" -COSMO_CONFIG_FILE="src/input_data/cosmo.yaml" + CDF_FILENAME_BALFRIN = "/store_new/mch/msopr/hirad-gen/copernicus-datasets/tp-janfeb2020.nc" -CDF_FILENAME_CLARIDEN_TP = "/capstor/scratch/cscs/mmcgloho/datasets/copernicus/tp-2019-2020/data_stream-oper_stepType-accum.nc" -#CDF_FILENAME_CLARIDEN_INSTANT = "/capstor/store/mch/msopr/hirad-gen/copernicus-datasets/surface-janfeb2020-netcdf/data_stream-oper_stepType-instant.nc" -GRIB_FILENAME_BALFRIN = "/store_new/mch/msopr/hirad-gen/copernicus-datasets/surface-janfeb2020.grib" -GRIB_FILENAME_CLARIDEN = "/capstor/store/mch/msopr/hirad-gen/copernicus-datasets/surface-janfeb2020.grib" -COSMO_GRID_FILENAME = " /capstor/store/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-linear-interpolation-full/info/cosmo-lat-lon" -INPUT_DATA_FILEPATH = "mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-linear-interpolation-full/" +#CDF_FILENAME_CLARIDEN_TP = "/capstor/store/cscs/swissai/a161/datasets/copernicus/tp-2019-2020.nc" +#CDF_FILENAME_CLARIDEN_TP = "/capstor/store/cscs/swissai/a161/datasets/copernicus/tp-2017-2018-n320.nc" +CDF_FILENAME_CLARIDEN_TP = "/capstor/store/cscs/swissai/a161/datasets/copernicus/tp-2015-2016.nc" + + BASE_FILEPATH = "/capstor/store/" -OUTPUT_DATA_FILEPATH = "/capstor/store/cscs/swissai/a161/era5-cosmo-1h-linear-interpolation/train/era-interpolated-with-copernicus-tp" +INPUT_DATA_FILEPATH = "mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-linear-interpolation-full/" +OUTPUT_DATA_FILEPATH_ERA_INTERPOLATED = "/capstor/store/cscs/swissai/a161/era5-cosmo-1h-linear-interpolation/train/era-interpolated-with-copernicus-tp" +OUTPUT_DATA_FILEPATH_ERA = "/capstor/store/cscs/swissai/a161/era5-cosmo-1h-linear-interpolation/train/era-with-copernicus-tp" TP_INDEX = 12 LAT = np.arange(-4.42, 3.36 + 0.02, 0.02) @@ -44,10 +41,10 @@ def extract_grib_values(grib_data): grib_lon = grib_data['longitude'][:] grib_t2m = grib_data['t2m'][:] -def extract_lat_lon(data): +def extract_lat_lon_025(data): logging.info('extracting lat/lon') - lat = data['latitude'][:] - lon = data['longitude'][:] + lat = data['latitudes'][:] + lon = data['longitudes'][:] output_lat = np.zeros(len(lat)* len(lon)) output_lon = np.zeros(len(lat) * len(lon)) for i in range(len(lat)): @@ -59,8 +56,27 @@ def extract_lat_lon(data): output_lon[grid_index] = lon[j] return output_lat, output_lon -def extract_values(data, variable): +def extract_lat_lon_n320(data): + lat = data['latitudes'][:] + lon = data['longitudes'][:] + logging.info('extracting lat/lon') + logging.info(f'lat lon shapes {lat.shape} {lon.shape}') + +def extract_values(data, variable, area=None): values = data[variable][:] + print(values.shape) + if area: + lat = data['latitude'][:] + lon = data['longitude'][:] + # https://stackoverflow.com/questions/29135885/netcdf4-extract-for-subset-of-lat-lon + latli = np.argmin( np.abs(lat - area[2])) + latui = np.argmin( np.abs(lat - area[0])) + lonli = np.argmin( np.abs(lon - area[1])) + lonui = np.argmin( np.abs(lon - area[3])) + lat = data['latitude'][latli:latui] + lon = data['longitude'][lonli:lonui] + + values = data[variable][latli:latui,lonli:lonui] return np.reshape(values, (values.shape[0], values.shape[1]*values.shape[2])) def reshape_to_cosmo(vals): @@ -126,6 +142,37 @@ def calc_errors(cosmo1, era1): maes['copernicus-late'] = netcdf_late_error plot_scores_vs_t(maes, times=cosmo1.dates, filename='plots/errors.png') +def process_era_interpolated(netcdf_data, netcdf_tp_values): + #for t in range(10): + for t in range(netcdf_tp_values.shape[0]): + netcdf_date = netcdf_data['valid_time'][t] + date_filename = datetime.datetime.fromtimestamp(netcdf_date, datetime.UTC).strftime('%Y%m%d-%H%M') + t1 = datetime.datetime.now() + era_filename = os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, "era-interpolated", date_filename) + if os.path.exists(era_filename): + era_data = torch.load(era_filename, weights_only=False) + t2 = datetime.datetime.now() + logging.info(f'regridding {date_filename} (netcdf date: {netcdf_date})') + interpolated_tp = griddata(netcdf_grid, netcdf_tp_values[t,:], cosmo_grid, method='linear') + t3 = datetime.datetime.now() + era_data[TP_INDEX,0,:] = interpolated_tp + torch.save(era_data, os.path.join(OUTPUT_DATA_FILEPATH_ERA_INTERPOLATED, date_filename)) + t4 = datetime.datetime.now() + +def process_era(netcdf_data, netcdf_tp_values): + for t in range(netcdf_tp_values.shape[0]): + netcdf_date = netcdf_data['valid_time'][t] + date_filename = datetime.datetime.fromtimestamp(netcdf_date, datetime.UTC).strftime('%Y%m%d-%H%M') + era_filename = os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, "era", date_filename) + if os.path.exists(era_filename): + era_data = torch.load(era_filename, weights_only=False) + t2 = datetime.datetime.now() + logging.info(f'regridding {date_filename} (netcdf date: {netcdf_date})') + interpolated_tp = griddata(netcdf_grid, netcdf_tp_values[t,:], era_grid, method='linear') + t3 = datetime.datetime.now() + era_data[TP_INDEX,0,:] = interpolated_tp + torch.save(era_data, os.path.join(OUTPUT_DATA_FILEPATH_ERA, date_filename)) + t4 = datetime.datetime.now() root = logging.getLogger() root.setLevel(logging.INFO) @@ -139,31 +186,26 @@ def calc_errors(cosmo1, era1): #era6 = open_dataset(ANEMOI_6H_FILENAME, select=['tp'],start='2016-01-01',end='2016-02-29') logging.info('loading data complete') -logging.info(os.listdir(os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, 'info'))) - cosmo_grid = torch.load(os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, 'info/cosmo-lat-lon'), weights_only=False) +era_grid = torch.load(os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, 'info/era-lat-lon'), weights_only=False) logging.info('processing netcdf data') -netcdf_latitudes, netcdf_longitudes = extract_lat_lon(netcdf_data) + +netcdf_latitudes, netcdf_longitudes = extract_lat_lon_n320(netcdf_data) netcdf_tp_values = extract_values(netcdf_data, 'tp') + +#netcdf_latitudes, netcdf_longitudes = extract_lat_lon(netcdf_data, area=[60, 0, 40, 20]) +#netcdf_tp_values = extract_values(netcdf_data, 'tp', area=[60, 0, 40, 20]) netcdf_grid=np.column_stack((netcdf_longitudes, netcdf_latitudes)) +logging.info(f'netcdf shape {netcdf_grid.shape}') +logging.info(f'{netcdf_grid[1:10,:]}') +logging.info(f'era shape {era_grid.shape}') +logging.info(f'{era_grid[1:10,:]}') + +process_era_interpolated(netcdf_data, netcdf_tp_values) +process_era(netcdf_data, netcdf_tp_values) -#for t in range(10): -for t in range(netcdf_tp_values.shape[0]): - netcdf_date = netcdf_data['valid_time'][t] - date_filename = datetime.datetime.fromtimestamp(netcdf_date, datetime.UTC).strftime('%Y%m%d-%H%M') - t1 = datetime.datetime.now() - era_filename = os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, "era-interpolated", date_filename) - if os.path.exists(era_filename) and netcdf_date > 1560229200: - era_data = torch.load(era_filename, weights_only=False) - t2 = datetime.datetime.now() - logging.info(f'regridding {date_filename} (netcdf date: {netcdf_date})') - interpolated_tp = griddata(netcdf_grid, netcdf_tp_values[t,:], cosmo_grid, method='linear') - t3 = datetime.datetime.now() - era_data[TP_INDEX,0,:] = interpolated_tp - torch.save(era_data, os.path.join(OUTPUT_DATA_FILEPATH, date_filename)) - t4 = datetime.datetime.now() From 4aecb65bb54f7d03a4a3ce5bd478b5cea136bd90 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 1 Sep 2025 10:38:33 +0200 Subject: [PATCH 147/302] removing old file --- src/hirad/input_data/read_tp.py | 184 -------------------------------- 1 file changed, 184 deletions(-) delete mode 100644 src/hirad/input_data/read_tp.py diff --git a/src/hirad/input_data/read_tp.py b/src/hirad/input_data/read_tp.py deleted file mode 100644 index 99530869..00000000 --- a/src/hirad/input_data/read_tp.py +++ /dev/null @@ -1,184 +0,0 @@ -import logging -import netCDF4 -import xarray -from anemoi.datasets import open_dataset -import numpy as np -import yaml - -import matplotlib.pyplot as plt -import cartopy.crs as ccrs -import cartopy.feature as cfeature -from matplotlib.colors import BoundaryNorm, ListedColormap - -from hirad.eval.plotting import plot_map_precipitation, plot_scores_vs_t -from hirad.eval.metrics import compute_mae, absolute_error - - -import interpolate_basic - -import sys -from pathlib import Path - -import os -print (os.getcwd()) - -sys.path.insert(0, Path(__file__).parent.as_posix()) - -ANEMOI_1H_FILENAME = "/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr" -ANEMOI_6H_FILENAME = "/scratch/mch/apennino/data/aifs-ea-an-oper-0001-mars-n320-1979-2022-6h-v6.zarr" -COSMO_6H_FILENAME = "/scratch/mch/fzanetta/data/anemoi/datasets/mch-co2-an-archive-0p02-2015-2020-6h-v3-pl13.zarr" -COSMO_1H_FILENAME = "/scratch/mch/fzanetta/data/anemoi/datasets/mch-co2-an-archive-0p02-2015-2020-1h-v3-pl13.zarr" -COSMO_CONFIG_FILE="src/input_data/cosmo.yaml" -CDF_FILENAME = "/store_new/mch/msopr/hirad-gen/copernicus-datasets/tp-janfeb2020.nc" -GRIB_FILENAME = "/store_new/mch/msopr/hirad-gen/copernicus-datasets/surface-janfeb2020.grib" - - -LAT = np.arange(-4.42, 3.36 + 0.02, 0.02) -LON = np.arange(-6.82, 4.80 + 0.02, 0.02) -RELAX_ZONE = 19 # Number of points dropped on each side (relaxation zone) - -def extract_grib_values(grib_data): - grib_lat = grib_data['latitude'][:] - grib_lon = grib_data['longitude'][:] - grib_t2m = grib_data['t2m'][:] - -def extract_lat_lon(data): - lat = data['latitude'][:] - lon = data['longitude'][:] - output_lat = np.zeros(len(lat)* len(lon)) - output_lon = np.zeros(len(lat) * len(lon)) - for i in range(len(lat)): - if i % 10 == 0: - print(i) - for j in range(len(lon)): - grid_index = i * len(lon) + j - output_lat[grid_index] = lat[i] - output_lon[grid_index] = lon[j] - return output_lat, output_lon - -def extract_values(data, variable): - values = data[variable][:] - return np.reshape(values, (values.shape[0], values.shape[1]*values.shape[2])) - -def extract_netcdf_values(netcdf_data): - netcdf_lat = netcdf_data['latitude'][:] - netcdf_lon = netcdf_data['longitude'][:] - netcdf_tp = netcdf_data['tp'][:,:] - logging.info(f'num nonzeros in tp is {np.count_nonzero(netcdf_tp)}') - values = np.zeros((netcdf_tp.shape[0], netcdf_tp.shape[1]*netcdf_tp.shape[2])) - latitudes = np.zeros(values.shape[1]) - longitudes = np.zeros(values.shape[1]) - # You could probably get this by reshaping, but I can't be bothered. - for i in range(len(netcdf_lat)): - if i % 10 == 0: - print(i) - for j in range(len(netcdf_lon)): - grid_index = i * len(netcdf_lon) + j - values[:,grid_index] = netcdf_tp[:,i,j] - latitudes[grid_index] = netcdf_lat[i] - longitudes[grid_index] = netcdf_lon[j] - reshape_values = np.reshape(netcdf_tp, (netcdf_tp.shape[0], netcdf_tp.shape[1]*netcdf_tp.shape[2])) - logging.info(f'Array equal? {np.array_equal(values, reshape_values)}') - return reshape_values, latitudes, longitudes - -def reshape_to_cosmo(vals): - return vals.reshape((len(LAT)-RELAX_ZONE*2, len(LON)-RELAX_ZONE*2)) - -root = logging.getLogger() -root.setLevel(logging.INFO) - -logging.info('loading data') -grib_data = xarray.load_dataset(GRIB_FILENAME) -netcdf_data = netCDF4.Dataset(CDF_FILENAME) -cosmo1 = open_dataset(COSMO_1H_FILENAME, trim_edge=19, select=['tp'],start='2016-01-01',end='2016-02-29') -cosmo6 = open_dataset(COSMO_6H_FILENAME, trim_edge=19, select=['tp'],start='2016-01-01',end='2016-02-29') -era1 = open_dataset(ANEMOI_1H_FILENAME, select=['tp'],start='2016-01-01',end='2016-02-29') -era6 = open_dataset(ANEMOI_6H_FILENAME, select=['tp'],start='2016-01-01',end='2016-02-29') -logging.info('loading data complete') - -output_grid= np.column_stack((cosmo1.longitudes, cosmo1.latitudes)) - -logging.info('processing netcdf data') -netcdf_latitudes, netcdf_longitudes = extract_lat_lon(netcdf_data) -netcdf_values = extract_values(netcdf_data, 'tp') -#netcdf_values, netcdf_latitudes, netcdf_longitudes = extract_netcdf_values(netcdf_data=netcdf_data) -netcdf_grid=np.column_stack((netcdf_longitudes, netcdf_latitudes)) - -make_plots = True - -prev_netcdf_regrid = [] - -netcdf_error = np.zeros(cosmo1.dates.shape) -era_norm_error = np.zeros(cosmo1.dates.shape) -netcdf_early_error = np.zeros(cosmo1.dates.shape) -netcdf_late_error = np.zeros(cosmo1.dates.shape) - -for t in range(4): -#for t in range(len(cosmo1.dates)): - date = cosmo1.dates[t] - era_date = era1.dates[t] - if date != era_date: - logging.error('dates do not match: cosmo date: {date}, era date: {era_date}') - if date != netcdf_data['valid_time'][t]: - logging.error(f'dates do not match: cosmo date: {date}, netcdf: {netcdf_data["valid_time"][t]}') - - - # plot cosmo - if make_plots: - plot_map_precipitation(values=reshape_to_cosmo(cosmo1[t,:]), filename=f'plots/tp/{date}-cosmo1') - if t % 6 == 0: - if cosmo6.dates[t//6] != date: - logging.error(f'dates do not match: cosmo1: {date}, cosmo6: {cosmo6.dates[t//6]}') - plot_map_precipitation(values=reshape_to_cosmo(cosmo6[t//6,:]), filename=f'plots/tp/{date}-cosmo6') - - # plot netcdf - netcdf_vals = netcdf_values[t,:].reshape((1,1,netcdf_values.shape[1])) - netcdf_regrid=interpolate_basic.regrid(netcdf_vals, netcdf_grid, output_grid) - if make_plots: - plot_map_precipitation(reshape_to_cosmo(netcdf_regrid), f'plots/tp/{date}-netcdf-refactor') - - # plot era - era_grid = np.column_stack((era1.longitudes, era1.latitudes)) - era1_regrid = interpolate_basic.regrid(era1[t,:], era_grid, output_grid) - if make_plots: - plot_map_precipitation(reshape_to_cosmo(era1_regrid/6), f'plots/tp/{date}-era1-norm') - - #if t % 6 == 0: - # if era6.dates[t//6] != date: - # logging.error(f'dates do not match: era1: {date}, era6: {era6.dates[t//6]}') - # era6_regrid = interpolate_basic.regrid(era6[t//6,:], era_grid, output_grid) - # plot_map_precipitation(reshape_to_cosmo(era6_regrid), f'plots/tp/{date}-era6') - - era_norm_error[t] = np.mean(absolute_error(era1_regrid/6, cosmo1[t,:])) - netcdf_error[t] = np.mean(absolute_error(netcdf_regrid, cosmo1[t,:])) - logging.info(f'era norm error: {era_norm_error[t]} netcdf err: {netcdf_error[t]}') - if t>0: - netcdf_early_error[t] = np.mean(absolute_error(prev_netcdf_regrid, cosmo1[t,:])) - netcdf_late_error[t-1] = np.mean(absolute_error(netcdf_regrid, cosmo1[t-1,:])) - logging.info(f'netcdf early err: {netcdf_early_error[t]}, netcdf late err: {netcdf_late_error[t-1]}') - prev_netcdf_regrid = netcdf_regrid - -maes = {} -maes['era normalized'] = era_norm_error -maes['copernicus'] = netcdf_error -maes['copernicus-early'] = netcdf_early_error -maes['copernicus-late'] = netcdf_late_error -plot_scores_vs_t(maes, times=cosmo1.dates, filename='plots/errors.png') - -#fig = plt.figure() -#fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) -#interpolate_basic.plot_projection(ax, longitudes=cosmo1.longitudes, latitudes=cosmo1.latitudes, values=cosmo1[0,:]) -#fig.savefig('cosmo1.png') - -#fig = plt.figure() -#fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) -#interpolate_basic.plot_projection(ax, longitudes=cosmo1.longitudes, latitudes=cosmo1.latitudes, values=cosmo6[0,:]) -#fig.savefig('cosmo6.png') - - - - - - - - From 5d12c4ca859d1d2a45bb9be7509692a0bdaeb806 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 1 Sep 2025 12:25:31 +0200 Subject: [PATCH 148/302] Log config into training script logs --- src/hirad/training/train.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index e865bca4..c1ca8b21 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -91,6 +91,7 @@ def main(cfg: DictConfig) -> None: fp16 = fp_optimizations == "fp16" enable_amp = fp_optimizations.startswith("amp") amp_dtype = torch.float16 if (fp_optimizations == "amp-fp16") else torch.bfloat16 + logger0.info(f"Config is: {cfg}") logger0.info(f"Saving the outputs in {os.getcwd()}") checkpoint_dir = os.path.join( cfg.training.io.get("checkpoint_dir", "."), f"checkpoints_{cfg.model.name}" From 0a2880118ae6b0681e720a87e5002386e76fa5e0 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 9 Sep 2025 13:51:59 +0200 Subject: [PATCH 149/302] Make InfiniteSampler start from where it left off when resuming training and not restart from beginning. --- src/hirad/datasets/dataset.py | 7 +++++-- src/hirad/training/train.py | 6 ++++-- src/hirad/utils/function_utils.py | 6 +++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/hirad/datasets/dataset.py b/src/hirad/datasets/dataset.py index 1d36bc22..924951e6 100644 --- a/src/hirad/datasets/dataset.py +++ b/src/hirad/datasets/dataset.py @@ -37,6 +37,7 @@ def init_train_valid_datasets_from_config( batch_size: int = 1, seed: int = 0, train_test_split: bool = True, + sampler_start_idx: int = 0, ) -> Tuple[ DownscalingDataset, Iterable, @@ -52,6 +53,7 @@ def init_train_valid_datasets_from_config( - batch_size (int): The number of samples in each batch of data. Defaults to 1. - seed (int): The random seed for dataset shuffling. Defaults to 0. - train_test_split (bool): A flag to determine whether to create a validation dataset. Defaults to True. + - sampler_start_idx (int): The initial index of the sampler to use for resuming training. Defaults to 0. Returns: - Tuple[base.DownscalingDataset, Iterable, Optional[base.DownscalingDataset], Optional[Iterable]]: A tuple containing the training dataset and iterator, and optionally the validation dataset and iterator if train_test_split is True. @@ -61,7 +63,7 @@ def init_train_valid_datasets_from_config( if 'validation_path' in config: del config['validation_path'] (dataset, dataset_iter) = init_dataset_from_config( - config, dataloader_cfg, batch_size=batch_size, seed=seed + config, dataloader_cfg, batch_size=batch_size, seed=seed, sampler_start_idx=sampler_start_idx, ) if train_test_split: valid_dataset_cfg = copy.deepcopy(dataset_cfg) @@ -81,6 +83,7 @@ def init_dataset_from_config( dataloader_cfg: Union[dict, None] = None, batch_size: int = 1, seed: int = 0, + sampler_start_idx: int = 0, ) -> Tuple[DownscalingDataset, Iterable]: dataset_cfg = copy.deepcopy(dataset_cfg) dataset_type = dataset_cfg.pop("type", "era5_cosmo") @@ -97,7 +100,7 @@ def init_dataset_from_config( dist = DistributedManager() dataset_sampler = InfiniteSampler( - dataset=dataset_obj, rank=dist.rank, num_replicas=dist.world_size, seed=seed + dataset=dataset_obj, rank=dist.rank, num_replicas=dist.world_size, seed=seed, start_idx=sampler_start_idx, ) dataset_iterator = iter( diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index e865bca4..b6a8ca95 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -117,8 +117,9 @@ def main(cfg: DictConfig) -> None: cfg.training.hp.batch_size_per_gpu * dist.world_size ) + cur_nimg = load_checkpoint(path=checkpoint_dir) - set_seed(dist.rank) + set_seed(dist.rank + cur_nimg) configure_cuda_for_consistent_precision() # Instantiate the dataset @@ -138,8 +139,10 @@ def main(cfg: DictConfig) -> None: batch_size=cfg.training.hp.batch_size_per_gpu, seed=0, train_test_split=train_test_split, + sampler_start_idx=cur_nimg, ) logger0.info(f"Training on dataset with size {len(dataset)}") + logger0.info(f"Validating on dataset with size {len(validation_dataset)}") # Parse image configuration & update model args dataset_channels = len(dataset.input_channels()) @@ -172,7 +175,6 @@ def main(cfg: DictConfig) -> None: ) # Parse the patch shape - #TODO figure out patched diffusion and how to use it if ( cfg.model.name == "patched_diffusion" or cfg.model.name == "lt_aware_patched_diffusion" diff --git a/src/hirad/utils/function_utils.py b/src/hirad/utils/function_utils.py index 2da05dbe..65347829 100644 --- a/src/hirad/utils/function_utils.py +++ b/src/hirad/utils/function_utils.py @@ -133,6 +133,8 @@ class InfiniteSampler(torch.utils.data.Sampler[int]): # pragma: no cover window_size : float, default=0.5 Fraction of dataset to use as window for shuffling. Must be between 0 and 1. A larger window means more thorough shuffling but slower iteration. + start_idx : int, default=0 + The initial index to use for the sampler. This is used for resuming training. """ def __init__( @@ -143,6 +145,7 @@ def __init__( shuffle: bool = True, seed: int = 0, window_size: float = 0.5, + start_idx: int = 0, ): if not len(dataset) > 0: raise ValueError("Dataset must contain at least one item") @@ -159,6 +162,7 @@ def __init__( self.shuffle = shuffle self.seed = seed self.window_size = window_size + self.start_idx = start_idx def __iter__(self) -> Iterator[int]: order = np.arange(len(self.dataset)) @@ -169,7 +173,7 @@ def __iter__(self) -> Iterator[int]: rnd.shuffle(order) window = int(np.rint(order.size * self.window_size)) - idx = 0 + idx = self.start_idx while True: i = idx % order.size if idx % self.num_replicas == self.rank: From 19cc78b2498d063d01e0a44e3202fa1bfe5d504e Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 10 Sep 2025 09:07:53 +0200 Subject: [PATCH 150/302] Move model compilation after checkpoint loading and fix checkpoint to strip off optimization layer. --- src/hirad/training/train.py | 18 ++++++------ src/hirad/utils/checkpoint.py | 53 +++++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index b6a8ca95..09227170 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -284,8 +284,8 @@ def main(cfg: DictConfig) -> None: # Enable distributed data parallel if applicable if dist.world_size > 1: - if use_torch_compile: - model = torch.compile(model) + # if use_torch_compile: + # model = torch.compile(model) model = DistributedDataParallel( model, device_ids=[dist.local_rank], @@ -334,13 +334,6 @@ def main(cfg: DictConfig) -> None: else: regression_net = None - # Compile the model and regression net if applicable - if use_torch_compile: - if dist.world_size==1: - model = torch.compile(model) - if regression_net: - regression_net = torch.compile(regression_net) - # Compute the number of required gradient accumulation rounds # It is automatically used if batch_size_per_gpu * dist.world_size < total_batch_size @@ -436,6 +429,13 @@ def main(cfg: DictConfig) -> None: except: cur_nimg = 0 + # Compile the model and regression net if applicable + if use_torch_compile: + if dist.world_size==1: + model = torch.compile(model) + if regression_net: + regression_net = torch.compile(regression_net) + # init the generator for inference to visualize checkpoint results if visualize_checkpoints: generator = Generator( diff --git a/src/hirad/utils/checkpoint.py b/src/hirad/utils/checkpoint.py index 070f8c34..28e65472 100644 --- a/src/hirad/utils/checkpoint.py +++ b/src/hirad/utils/checkpoint.py @@ -174,10 +174,15 @@ def save_checkpoint( Path(path).mkdir(parents=True, exist_ok=True) # == Saving model checkpoint == - if model: + if model is not None: if hasattr(model, "module"): # Strip out DDP layer model = model.module + + # Strip out optimization wrapper if exists + if isinstance(model, torch._dynamo.eval_frame.OptimizedModule): + model = model._orig_mod + # Base name of model is meta.name unless pytorch model name = model.__class__.__name__ # Get full file path / name @@ -223,7 +228,7 @@ def save_checkpoint( def load_checkpoint( path: str, - model: torch.nn.Module, + model: torch.nn.Module = None, optimizer: Union[optimizer, None] = None, scheduler: Union[scheduler, None] = None, scaler: Union[scaler, None] = None, @@ -268,27 +273,33 @@ def load_checkpoint( ) return 0 - # == Loading model checkpoint == - if hasattr(model, "module"): - # Strip out DDP layer - model = model.module - # Base name of model is meta.name unless pytorch model - name = model.__class__.__name__ - # Get full file path / name - file_name = _get_checkpoint_filename( - path, name, index=epoch, - ) - if not Path(file_name).exists(): - checkpoint_logging.warning( - f"Could not find valid model file {file_name}, skipping load" + if model is not None: + # == Loading model checkpoint == + if hasattr(model, "module"): + # Strip out DDP layer + model = model.module + # Strip out optimization wrapper if exists + if isinstance(model, torch._dynamo.eval_frame.OptimizedModule): + model = model._orig_mod + checkpoint_logging.warning( + f"Model {model.__class__.__name__} is already compiled, consider loading first and then compiling." + ) + name = model.__class__.__name__ + # Get full file path / name + file_name = _get_checkpoint_filename( + path, name, index=epoch, ) - else: - # Load state dictionary - model.load_state_dict(torch.load(file_name, map_location=device)) + if not Path(file_name).exists(): + checkpoint_logging.warning( + f"Could not find valid model file {file_name}, skipping load" + ) + else: + # Load state dictionary + model.load_state_dict(torch.load(file_name, map_location=device)) - checkpoint_logging.success( - f"Loaded model state dictionary {file_name} to device {device}" - ) + checkpoint_logging.success( + f"Loaded model state dictionary {file_name} to device {device}" + ) # == Loading training checkpoint == checkpoint_filename = _get_checkpoint_filename(path, index=epoch, model_type="pt") From 1d4865fdcd343cf2c45ac10de0fb2f1e74488124 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 10 Sep 2025 15:08:51 +0200 Subject: [PATCH 151/302] Refactor image_batching and image_fuse to handle input tensor dtype conversion and remove _cast_type function --- src/hirad/utils/patching.py | 60 ++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/src/hirad/utils/patching.py b/src/hirad/utils/patching.py index 6f4bc4d8..bd537af8 100644 --- a/src/hirad/utils/patching.py +++ b/src/hirad/utils/patching.py @@ -591,14 +591,26 @@ def image_batching( ) # (padding_left,padding_right,padding_top,padding_bottom) input_padded = image_padding(input) patch_num = patch_num_x * patch_num_y + + # Cast to float for unfold + if input.dtype == torch.int32: + input_padded = input_padded.view(torch.float32) + elif input.dtype == torch.int64: + input_padded = input_padded.view(torch.float64) + x_unfold = torch.nn.functional.unfold( - input=input_padded.view(_cast_type(input_padded)), # Cast to float + input=input_padded, kernel_size=(patch_shape_y, patch_shape_x), stride=( patch_shape_y - overlap_pix - boundary_pix, patch_shape_x - overlap_pix - boundary_pix, ), - ).to(input_padded.dtype) + ) + + # Cast back to original dtype + if input.dtype in [torch.int32, torch.int64]: + x_unfold = x_unfold.view(input.dtype) + x_unfold = rearrange( x_unfold, "b (c p_h p_w) (nb_p_h nb_p_w) -> (nb_p_w nb_p_h b) c p_h p_w", @@ -608,16 +620,7 @@ def image_batching( nb_p_w=patch_num_x, ) if input_interp is not None: - input_interp_repeated = rearrange( - torch.repeat_interleave( - input=input_interp, - repeats=patch_num, - dim=0, - output_size=x_unfold.shape[0], - ), - "(b p) c h w -> (p b) c h w", - p=patch_num, - ) + input_interp_repeated = input_interp.repeat(patch_num, 1, 1, 1) return torch.cat((x_unfold, input_interp_repeated), dim=1) else: return x_unfold @@ -722,6 +725,13 @@ def image_fuse( nb_p_h=patch_num_y, nb_p_w=patch_num_x, ) + + # Cast to float for fold + if input.dtype == torch.int32: + x = x.view(torch.float32) + elif input.dtype == torch.int64: + x = x.view(torch.float64) + # Stitch patches together (by summing over overlapping patches) x_folded = torch.nn.functional.fold( input=x, @@ -733,6 +743,10 @@ def image_fuse( ), ) + # Cast back to original dtype + if input.dtype in [torch.int32, torch.int64]: + x_folded = x_folded.view(input.dtype) + # Remove padding x_no_padding = x_folded[ ..., pad[2] : pad[2] + img_shape_y, pad[0] : pad[0] + img_shape_x @@ -743,25 +757,3 @@ def image_fuse( # Normalize by overlap count return x_no_padding / overlap_count_no_padding - - -def _cast_type(input: Tensor) -> torch.dtype: - """Return float type based on input tensor type. - - Parameters - ---------- - input : Tensor - Input tensor to determine float type from - - Returns - ------- - torch.dtype - Float type corresponding to input tensor type for int32/64, - otherwise returns original dtype - """ - if input.dtype == torch.int32: - return torch.float32 - elif input.dtype == torch.int64: - return torch.float64 - else: - return input.dtype From 4b574e8a506b5f564e244e893158b2150c82fc07 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 10 Sep 2025 15:23:21 +0200 Subject: [PATCH 152/302] Validate max_patch_per_gpu against batch_size_per_gpu --- src/hirad/training/train.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 09227170..84941b45 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -345,14 +345,17 @@ def main(cfg: DictConfig) -> None: batch_size_per_gpu = cfg.training.hp.batch_size_per_gpu logger0.info(f"Using {num_accumulation_rounds} gradient accumulation rounds") - patch_num = getattr(cfg.training.hp, "patch_num", 1) - max_patch_per_gpu = getattr(cfg.training.hp, "max_patch_per_gpu", 1) - # calculate patch per iter - if hasattr(cfg.training.hp, "max_patch_per_gpu") and max_patch_per_gpu > 1: + patch_num = getattr(cfg.training.hp, "patch_num", 1) + if hasattr(cfg.training.hp, "max_patch_per_gpu"): + max_patch_per_gpu = cfg.training.hp.max_patch_per_gpu + if max_patch_per_gpu // batch_size_per_gpu < 1: + raise ValueError( + f"max_patch_per_gpu ({max_patch_per_gpu}) must be greater or equal to batch_size_per_gpu ({batch_size_per_gpu})." + ) max_patch_num_per_iter = min( patch_num, (max_patch_per_gpu // batch_size_per_gpu) - ) # Ensure at least 1 patch per iter + ) patch_iterations = ( patch_num + max_patch_num_per_iter - 1 ) // max_patch_num_per_iter @@ -360,7 +363,7 @@ def main(cfg: DictConfig) -> None: min(max_patch_num_per_iter, patch_num - i * max_patch_num_per_iter) for i in range(patch_iterations) ] - print( + logger0.info( f"max_patch_num_per_iter is {max_patch_num_per_iter}, patch_iterations is {patch_iterations}, patch_nums_iter is {patch_nums_iter}" ) else: From 40d1792a1cee1e8ef3253ce802b699648452142a Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 16 Sep 2025 12:21:55 +0200 Subject: [PATCH 153/302] Add method to load static variables --- interpolate.sh | 2 +- src/hirad/input_data/interpolate_basic.py | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/interpolate.sh b/interpolate.sh index 607ad66f..73e947da 100755 --- a/interpolate.sh +++ b/interpolate.sh @@ -3,4 +3,4 @@ #SBATCH --partition=postproc #SBATCH --time=23:59:00 -python src/input_data/interpolate_basic.py src/input_data/era-all.yaml src/input_data/cosmo-all.yaml /store_new/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-all-channels/ +python src/hirad/input_data/interpolate_basic.py src/hirad/input_data/era-all.yaml src/hirad/input_data/cosmo-all.yaml /store_new/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-all-channels/ diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index 7ec58017..e5953a1e 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -14,6 +14,7 @@ from scipy.interpolate import griddata import torch import multiprocessing +import xarray # Margin to use for ERA dataset (to avoid nans from interpolation at boundary) ERA_MARGIN_DEGREES = 1.0 @@ -47,7 +48,6 @@ def _read_input(era_config_file: str, cosmo_config_file: str, bound_to_cosmo_are return (era, cosmo) - def regrid(era_for_time: np.ndarray, input_grid: np.ndarray, output_grid: np.ndarray): # shape (channel, ensemble, grid) interpolated_data = np.empty([era_for_time.shape[0], 1, output_grid.shape[0]]) @@ -234,6 +234,12 @@ def plot_tp(path_6h: str, path_1h: str): plt.savefig(filename) plt.close('all') +def load_static(infile_era: str, infile_cosmo: str, output_directory: str): + _, cosmo = _read_input(infile_era, infile_cosmo, bound_to_cosmo_area=True) + + torch.save(cosmo[0,:,:,:], os.path.join(output_directory, 'cosmo-static')) + shutil.copy(infile_cosmo, os.path.join(output_directory, "cosmo-static.yaml")) + def main(): # TODO: Do better arg parsing so it's not as easy to reverse era and cosmo configs. if len(sys.argv) < 4: @@ -242,13 +248,15 @@ def main(): infile_cosmo = sys.argv[2] output_directory = sys.argv[3] - logging.basicConfig( filename=os.path.join(output_directory, 'interpolate_basic.log'), format='%(asctime)s %(levelname)-8s %(message)s', level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') - interpolate_and_save(infile_era, infile_cosmo, output_directory, threaded=False, outfile_plots_path=os.path.join(output_directory, "plots/")) + + #load_static(infile_era, infile_cosmo, output_directory) + #interpolate_and_save(infile_era, infile_cosmo, output_directory, threaded=False, outfile_plots_path=os.path.join(output_directory, "plots/")) + interpolate_and_save(infile_era, infile_cosmo, output_directory, threaded=False, outfile_plots_path=None) if __name__ == "__main__": main() From 4d6b9cbf189da3ddd5d5a86c402d575962ee708e Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 16 Sep 2025 12:22:04 +0200 Subject: [PATCH 154/302] Add static variable config --- src/hirad/input_data/cosmo-static.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/hirad/input_data/cosmo-static.yaml diff --git a/src/hirad/input_data/cosmo-static.yaml b/src/hirad/input_data/cosmo-static.yaml new file mode 100644 index 00000000..9bece97b --- /dev/null +++ b/src/hirad/input_data/cosmo-static.yaml @@ -0,0 +1,8 @@ +dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-co2-an-archive-0p02-2015-2020-1h-v3-pl13.zarr' +select: ['hsurf', 'lsm',] +# Static cosmo channels +trim_edge: 19 # Removes boundary +start: 2016-01-01 +# start: 2015-11-29 +end: 2016-01-01 +# end: 2020-12-31 \ No newline at end of file From 3ed02fa342f2a574106496f2243394bf5c004ae8 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 18 Sep 2025 12:41:45 +0200 Subject: [PATCH 155/302] Add a script to check input data for missing/corrupt/nan data --- src/hirad/input_data/regrid_copernicus_tp.py | 149 ++++++++++++------- src/hirad/input_data/regrid_copernicus_tp.sh | 9 ++ src/hirad/input_data/test_input_data.py | 88 +++++++++++ 3 files changed, 195 insertions(+), 51 deletions(-) create mode 100755 src/hirad/input_data/regrid_copernicus_tp.sh create mode 100644 src/hirad/input_data/test_input_data.py diff --git a/src/hirad/input_data/regrid_copernicus_tp.py b/src/hirad/input_data/regrid_copernicus_tp.py index 93a142f6..7e3f14ff 100644 --- a/src/hirad/input_data/regrid_copernicus_tp.py +++ b/src/hirad/input_data/regrid_copernicus_tp.py @@ -43,8 +43,8 @@ def extract_grib_values(grib_data): def extract_lat_lon_025(data): logging.info('extracting lat/lon') - lat = data['latitudes'][:] - lon = data['longitudes'][:] + lat = data['latitude'][:] + lon = data['longitude'][:] output_lat = np.zeros(len(lat)* len(lon)) output_lon = np.zeros(len(lat) * len(lon)) for i in range(len(lat)): @@ -75,8 +75,7 @@ def extract_values(data, variable, area=None): lonui = np.argmin( np.abs(lon - area[3])) lat = data['latitude'][latli:latui] lon = data['longitude'][lonli:lonui] - - values = data[variable][latli:latui,lonli:lonui] + values = data[variable][latli:latui,lonli:lonui] return np.reshape(values, (values.shape[0], values.shape[1]*values.shape[2])) def reshape_to_cosmo(vals): @@ -142,22 +141,48 @@ def calc_errors(cosmo1, era1): maes['copernicus-late'] = netcdf_late_error plot_scores_vs_t(maes, times=cosmo1.dates, filename='plots/errors.png') -def process_era_interpolated(netcdf_data, netcdf_tp_values): - #for t in range(10): +def process_era_interpolated(netcdf_data, netcdf_tp_values, input_data_filepath, output_interpolated_filepath, netcdf_grid, cosmo_grid): + make_plots = False + #for t in range(100): for t in range(netcdf_tp_values.shape[0]): netcdf_date = netcdf_data['valid_time'][t] date_filename = datetime.datetime.fromtimestamp(netcdf_date, datetime.UTC).strftime('%Y%m%d-%H%M') t1 = datetime.datetime.now() - era_filename = os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, "era-interpolated", date_filename) + era_filename = os.path.join(input_data_filepath, "era-interpolated", date_filename) + output_filename = os.path.join(output_interpolated_filepath, date_filename) if os.path.exists(era_filename): - era_data = torch.load(era_filename, weights_only=False) - t2 = datetime.datetime.now() - logging.info(f'regridding {date_filename} (netcdf date: {netcdf_date})') - interpolated_tp = griddata(netcdf_grid, netcdf_tp_values[t,:], cosmo_grid, method='linear') - t3 = datetime.datetime.now() - era_data[TP_INDEX,0,:] = interpolated_tp - torch.save(era_data, os.path.join(OUTPUT_DATA_FILEPATH_ERA_INTERPOLATED, date_filename)) - t4 = datetime.datetime.now() + requires_processing = False + if date_filename == '20200615-2100': + requires_processing = True + #if os.path.exists(output_filename): + # test the output to make sure it is not corrupted. + #requires_processing = False + #try: + # torch.load(output_filename, weights_only=False) + #except: + # requires_processing = True + if requires_processing: + era_data = torch.load(era_filename, weights_only=False) + t2 = datetime.datetime.now() + #if t % 100 == 0: + logging.info(f'regridding {date_filename} (netcdf date: {netcdf_date})') + interpolated_tp = griddata(netcdf_grid, netcdf_tp_values[t,:], cosmo_grid, method='linear') + t3 = datetime.datetime.now() + if make_plots and max > 0.0002: + nans = np.count_nonzero(np.isnan(interpolated_tp)) + nonzeros = np.count_nonzero(interpolated_tp) + max = np.max(interpolated_tp) + logging.info(f'nonzeros: {nonzeros} nans: {nans} max: {max}') + if max > 0.0002: + cosmo_filename = os.path.join(input_data_filepath, "cosmo", date_filename) + cosmo_data = torch.load(cosmo_filename, weights_only=False) + plot_map_precipitation(reshape_to_cosmo(interpolated_tp), f'plots/tp/{date_filename}-netcdf-regrid') + # 3 is tp index in cosmo data + plot_map_precipitation(reshape_to_cosmo(cosmo_data[3,:]), f'plots/tp/{date_filename}-cosmo') + plot_map_precipitation(reshape_to_cosmo(era_data[TP_INDEX,0,:]/6), f'plots/tp/{date_filename}-era-norm') + era_data[TP_INDEX,0,:] = interpolated_tp + torch.save(era_data, output_filename) + t4 = datetime.datetime.now() def process_era(netcdf_data, netcdf_tp_values): for t in range(netcdf_tp_values.shape[0]): @@ -174,42 +199,64 @@ def process_era(netcdf_data, netcdf_tp_values): torch.save(era_data, os.path.join(OUTPUT_DATA_FILEPATH_ERA, date_filename)) t4 = datetime.datetime.now() -root = logging.getLogger() -root.setLevel(logging.INFO) - -logging.info('loading data') -netcdf_data = netCDF4.Dataset(CDF_FILENAME_CLARIDEN_TP) - -#cosmo1 = open_dataset(COSMO_1H_FILENAME, trim_edge=19, select=['tp'],start='2016-01-01',end='2016-02-29') -#cosmo6 = open_dataset(COSMO_6H_FILENAME, trim_edge=19, select=['tp'],start='2016-01-01',end='2016-02-29') -#era1 = open_dataset(ANEMOI_1H_FILENAME, select=['tp'],start='2016-01-01',end='2016-02-29') -#era6 = open_dataset(ANEMOI_6H_FILENAME, select=['tp'],start='2016-01-01',end='2016-02-29') -logging.info('loading data complete') - -cosmo_grid = torch.load(os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, 'info/cosmo-lat-lon'), weights_only=False) -era_grid = torch.load(os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, 'info/era-lat-lon'), weights_only=False) - - -logging.info('processing netcdf data') - -netcdf_latitudes, netcdf_longitudes = extract_lat_lon_n320(netcdf_data) -netcdf_tp_values = extract_values(netcdf_data, 'tp') - -#netcdf_latitudes, netcdf_longitudes = extract_lat_lon(netcdf_data, area=[60, 0, 40, 20]) -#netcdf_tp_values = extract_values(netcdf_data, 'tp', area=[60, 0, 40, 20]) -netcdf_grid=np.column_stack((netcdf_longitudes, netcdf_latitudes)) -logging.info(f'netcdf shape {netcdf_grid.shape}') -logging.info(f'{netcdf_grid[1:10,:]}') -logging.info(f'era shape {era_grid.shape}') -logging.info(f'{era_grid[1:10,:]}') - -process_era_interpolated(netcdf_data, netcdf_tp_values) -process_era(netcdf_data, netcdf_tp_values) - - - - - +def make_stats(): + #cosmo_files = os.listdir(os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, 'cosmo')) + #era_files = os.listdir(os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, 'cosmo')) + + stats = torch.load(os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, 'info', 'era-stats'), weights_only=False) + print(stats) + set1 = netCDF4.Dataset("/capstor/store/cscs/swissai/a161/datasets/copernicus/tp-2015-2016.nc") + set2 = netCDF4.Dataset("/capstor/store/cscs/swissai/a161/datasets/copernicus/tp-2017-2018.nc") + set3 = netCDF4.Dataset("/capstor/store/cscs/swissai/a161/datasets/copernicus/tp-2019-2020.nc") + set1_tp = extract_values(set1, 'tp') + set2_tp = extract_values(set2, 'tp') + set3_tp = extract_values(set3, 'tp') + all_tp = np.row_stack((set1_tp, set2_tp, set3_tp)) + print(all_tp.shape) + all_tp = all_tp.reshape(all_tp.shape[0] * all_tp.shape[1], 1) + mean = np.mean(all_tp) + max = np.max(all_tp) + min = np.min(all_tp) + stdev = np.std(all_tp) + stats['mean'][TP_INDEX] = mean + stats['maximum'][TP_INDEX] = max + stats['minimum'][TP_INDEX] = min + stats['stdev'][TP_INDEX] = stdev + print(stats) + torch.save(stats, os.path.join(OUTPUT_DATA_FILEPATH_ERA_INTERPOLATED, 'era-stats')) + + +#process_era(netcdf_data, netcdf_tp_values) + + +def main(): + root = logging.getLogger() + root.setLevel(logging.INFO) + + logging.info('loading data') + netcdf_file = sys.argv[1] + input_data_filepath = sys.argv[2] + output_interpolated_filepath = sys.argv[3] + + netcdf_data = netCDF4.Dataset(netcdf_file) + logging.info(netcdf_data) + + logging.info('processing netcdf data') + netcdf_latitudes, netcdf_longitudes = extract_lat_lon_025(netcdf_data) + netcdf_grid=np.column_stack((netcdf_longitudes, netcdf_latitudes)) + cosmo_grid = torch.load(os.path.join(input_data_filepath, 'info/cosmo-lat-lon'), weights_only=False) + cosmo_grid = np.column_stack((cosmo_grid[:,1], cosmo_grid[:,0])) + logging.info(f'netcdf grid shape {netcdf_grid.shape}') + logging.info(f'{netcdf_grid[1:10,:]}') + logging.info(f'cosmo grid shape {cosmo_grid.shape}') + logging.info(f'{cosmo_grid[1:10,:]}') + + netcdf_tp_values = extract_values(netcdf_data, 'tp') + + process_era_interpolated(netcdf_data, netcdf_tp_values, input_data_filepath, output_interpolated_filepath, netcdf_grid, cosmo_grid) + +if __name__ == "__main__": + main() diff --git a/src/hirad/input_data/regrid_copernicus_tp.sh b/src/hirad/input_data/regrid_copernicus_tp.sh new file mode 100755 index 00000000..f7fc5501 --- /dev/null +++ b/src/hirad/input_data/regrid_copernicus_tp.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +pip install -e . +pip install anemoi.datasets + +python src/hirad/input_data/regrid_copernicus_tp.py \ + /capstor/store/cscs/swissai/a161/datasets/copernicus/tp-2019-2020.nc \ + /capstor/store/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-linear-interpolation-full/ \ + /capstor/store/cscs/swissai/a161/era5-cosmo-1h-linear-interpolation/train/era-interpolated-with-copernicus-tp/ \ No newline at end of file diff --git a/src/hirad/input_data/test_input_data.py b/src/hirad/input_data/test_input_data.py new file mode 100644 index 00000000..fa134911 --- /dev/null +++ b/src/hirad/input_data/test_input_data.py @@ -0,0 +1,88 @@ +import logging +import os +import sys + +import datetime +import torch +import numpy as np + + +from hirad.eval.plotting import plot_map_precipitation, plot_scores_vs_t + +def load_all_data(filepath: str): + files = os.listdir(filepath) + example = torch.load(os.path.join(filepath, files[0]), weights_only=False) + dims = (len(files),) + example.shape + data = np.zeros(dims) + for f in range(100): + #for f in range(len(files)): + if f % 100 == 0: + logging.info(f) + curr = torch.load(os.path.join(filepath, files[f]), weights_only=False) + data[f,:] = curr + return data + +def count_nans(data: np.array): + nans = np.count_nonzero(np.isnan(data)) + return nans + +def make_stats(filepath: str): + data = load_all_data(filepath) + stats = {} + num_channels = data.shape[1] + stats['mean'] = np.zeros(num_channels) + stats['stdev'] = np.zeros(num_channels) + stats['minimum'] = np.zeros(num_channels) + stats['maximum'] = np.zeros(num_channels) + for k in range(num_channels): + logging.info(f'channel {k}') + stats['mean'][k] = np.mean(data[:,k,:,:]) + stats['minimum'][k] = np.min(data[:,k,:,:]) + stats['maximum'][k] = np.max(data[:,k,:,:]) + stats['stdev'][k] = np.std(data[:,k,:,:]) + return stats + +def main(): + root = logging.getLogger() + root.setLevel(logging.INFO) + logging.info('starting main') + input_directory = sys.argv[1] + + missing_data = [] + corrupt_data = [] + nan_data = [] + check_for_nans = False + + + + files = os.listdir(input_directory) + files.sort() + start_date = datetime.datetime.strptime(files[0],'%Y%m%d-%H%M') + next_date = datetime.datetime.strptime(files[1],'%Y%m%d-%H%M') + delta = next_date - start_date + prev_date = start_date - delta + + for f in files: + curr_date = datetime.datetime.strptime(f,'%Y%m%d-%H%M') + if curr_date - prev_date != delta: + logging.info(f'missing data: {prev_date} and {curr_date} not {delta} apart') + expected_date = prev_date + delta + while (expected_date < curr_date): + missing_data.append(datetime.datetime.strftime(expected_date, '%Y%m%d-%H%M')) + expected_date = expected_date + delta + try: + data = torch.load(os.path.join(input_directory, f), weights_only=False) + except: + logging.info(f'corrupt data: {curr_date}') + corrupt_data.append(curr_date) + if check_for_nans: + if count_nans(data): + logging.info(f'data nans: {curr_date}') + nan_data.append(curr_date) + prev_date = curr_date + logging.info(f'missing data: {missing_data}') + logging.info(f'corrupt data: {corrupt_data}') + logging.info(f'nan data: {nan_data}') + +if __name__ == "__main__": + main() From 9d858ebff380b3ead65f014517ae85f06b6e2eac Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 19 Sep 2025 17:25:39 +0200 Subject: [PATCH 156/302] Enhance ERA5_COSMO dataset class with static channels option and Box-Cox transformation support for total precipitation; update histogram and exceedance scripts to remove baseline scaling factor. --- src/hirad/datasets/era5_cosmo.py | 87 +++++++++++++++++-- .../diurnal_cycle_precip_mean_wet-hour.py | 2 +- src/hirad/eval/diurnal_cycle_precip_p99.py | 4 +- src/hirad/eval/hist.py | 6 +- src/hirad/eval/map_precip_stats.py | 4 +- src/hirad/eval/probability_of_exceedance.py | 6 +- src/hirad/eval/snapshots.py | 8 +- 7 files changed, 98 insertions(+), 19 deletions(-) diff --git a/src/hirad/datasets/era5_cosmo.py b/src/hirad/datasets/era5_cosmo.py index 76d19827..6957877b 100644 --- a/src/hirad/datasets/era5_cosmo.py +++ b/src/hirad/datasets/era5_cosmo.py @@ -15,9 +15,35 @@ def __init__(self, dataset_path: str, input_channel_names: List[str] = [], outpu self._era5_path = os.path.join(dataset_path, 'era-interpolated') self._cosmo_path = os.path.join(dataset_path, 'cosmo') self._info_path = os.path.join(dataset_path, 'info') + self._static_path = '/capstor/store/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-linear-interpolation-full/static'# os.path.join(dataset_path, 'static') + self._zarr_path = os.path.join(dataset_path, 'dataset.zarr') # load file list (each file is one date-time state) - self._file_list = os.listdir(self._cosmo_path) + self._file_list = sorted(os.listdir(self._cosmo_path)) + + # open zarr store + # self._zarr_store = zarr.open(self._zarr_path, mode='r') + # self.era5 = self._zarr_store['era5'] + # self.cosmo = self._zarr_store['cosmo'] + + # Load static info and channel names + if static_channel_names: + with open(os.path.join(self._static_path, 'cosmo-static.yaml'), 'r') as file: + self._static_info = yaml.safe_load(file) + self._static_indeces = [self._static_info['select'].index(name) for name in static_channel_names] + self._static_channels = [ChannelMetadata(name) if len(name.split('_'))==1 + else ChannelMetadata(name.split('_')[0],name.split('_')[1]) + for name in self._static_info['select'] if name in static_channel_names] + static_data = torch.load(os.path.join(self._static_path,'cosmo-static'), weights_only=False)[self._static_indeces] + orig_shape = self.image_shape() + self.static_data = np.flip(static_data \ + .squeeze() \ + .reshape(-1,*orig_shape), + 1) + self.static_mean = self.static_data.mean(axis=(1,2)) + self.static_std = self.static_data.std(axis=(1,2)) + else: + self.static_data = None # Load cosmo info and channel names with open(os.path.join(self._info_path,'cosmo.yaml'), 'r') as file: @@ -52,7 +78,37 @@ def __init__(self, dataset_path: str, input_channel_names: List[str] = [], outpu era_stats = torch.load(os.path.join(self._info_path,'era-stats'), weights_only=False) self.input_mean = era_stats['mean'][self._era_indeces] self.input_std = era_stats['stdev'][self._era_indeces] + if self.static_data is not None: + self.input_mean = np.concatenate((self.input_mean, self.static_mean), axis=0) + self.input_std = np.concatenate((self.input_std, self.static_std), axis=0) + + # FEATURE: load the mean and std values for transformed channels and update the normalization statistics + self.input_transforms = {} + self.input_inverse_transforms = {} + self.output_transforms = {} + self.output_inverse_transforms = {} + for transform_descriptor in transform_channels: + channel, transformation = transform_descriptor.split('-') + input_channel_idx = self._era_info['select'].index(channel) if channel in self._era_info['select'] else None + output_channel_idx = self._cosmo_info['select'].index(channel) if channel in self._cosmo_info['select'] else None + if transformation.startswith('box_cox'): + lmbda_str = transformation.split('_')[-1] + lmbda = float(transformation.split('_')[-1])/(10**(len(lmbda_str)-1)) + print(f"Applying Box-Cox transformation with lambda={lmbda} to channel {channel} (input idx: {input_channel_idx}, output idx: {output_channel_idx})") + if input_channel_idx is not None: + self.input_transforms[input_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_transform(x, lmbda) + self.input_inverse_transforms[input_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_inverse_transform(x, lmbda) + self.input_mean[input_channel_idx] = torch.load(os.path.join("/users/pstamenk/HiRAD-Gen",f"era5-{transform_descriptor}-mean"), weights_only=False) + self.input_std[input_channel_idx] = torch.load(os.path.join("/users/pstamenk/HiRAD-Gen",f"era5-{transform_descriptor}-std"), weights_only=False) + if output_channel_idx is not None: + self.output_transforms[output_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_transform(x, lmbda) + self.output_inverse_transforms[output_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_inverse_transform(x, lmbda) + self.output_mean[output_channel_idx] = torch.load(os.path.join("/users/pstamenk/HiRAD-Gen",f"cosmo-{transform_descriptor}-mean"), weights_only=False) + self.output_std[output_channel_idx] = torch.load(os.path.join("/users/pstamenk/HiRAD-Gen",f"cosmo-{transform_descriptor}-std"), weights_only=False) + else: + raise ValueError(f"Transformation: {transformation} for channel {channel} not implemented.") + def __getitem__(self, idx): """Get cosmo and era5 interpolated to cosmo grid""" # get data point @@ -66,6 +122,7 @@ def __getitem__(self, idx): .squeeze() \ .reshape(-1,*orig_shape), 1) + era5_data = np.concatenate((era5_data, self.static_data), axis=0) if self.static_data is not None else era5_data era5_data = self.normalize_input(era5_data) cosmo_data = torch.load(os.path.join(self._cosmo_path,self._file_list[idx]), weights_only=False)[self._cosmo_indeces] @@ -96,7 +153,7 @@ def latitude(self) -> np.ndarray: def input_channels(self) -> List[ChannelMetadata]: """Metadata for the input channels. A list of ChannelMetadata, one for each channel""" - return self._era_channels + return self._era_channels + self._static_channels if self.static_data is not None else self._era_channels def output_channels(self) -> List[ChannelMetadata]: @@ -118,23 +175,43 @@ def image_shape(self) -> Tuple[int, int]: def normalize_input(self, x: np.ndarray) -> np.ndarray: """Convert input from physical units to normalized data.""" + for channel_idx, transform in self.input_transforms.items(): + x[channel_idx,::] = transform(x[channel_idx,::]) return (x - self.input_mean.reshape((self.input_mean.shape[0],1,1))) \ / self.input_std.reshape((self.input_std.shape[0],1,1)) def denormalize_input(self, x: np.ndarray) -> np.ndarray: """Convert input from normalized data to physical units.""" - return x * self.input_std.reshape((self.input_std.shape[0],1,1)) \ + x = x * self.input_std.reshape((self.input_std.shape[0],1,1)) \ + self.input_mean.reshape((self.input_mean.shape[0],1,1)) + for channel_idx, inverse_transform in self.input_inverse_transforms.items(): + x[:,channel_idx,::] = inverse_transform(x[:,channel_idx,::]) + return x def normalize_output(self, x: np.ndarray) -> np.ndarray: """Convert output from physical units to normalized data.""" + for channel_idx, transform in self.output_transforms.items(): + x[channel_idx,::] = transform(x[channel_idx,::]) return (x - self.output_mean.reshape((self.output_mean.shape[0],1,1))) \ / self.output_std.reshape((self.output_std.shape[0],1,1)) def denormalize_output(self, x: np.ndarray) -> np.ndarray: """Convert output from normalized data to physical units.""" - return x * self.output_std.reshape((self.output_std.shape[0],1,1)) \ - + self.output_mean.reshape((self.output_mean.shape[0],1,1)) \ No newline at end of file + x = x * self.output_std.reshape((self.output_std.shape[0],1,1)) \ + + self.output_mean.reshape((self.output_mean.shape[0],1,1)) + for channel_idx, inverse_transform in self.output_inverse_transforms.items(): + x[:,channel_idx,::] = inverse_transform(x[:,channel_idx,::]) + return x + + def box_cox_transform(self, channel_array: np.ndarray, lmbda: float) -> np.ndarray: + """Apply Box-Cox transformation to the data.""" + channel_array = np.clip(channel_array, 0, None) + return (np.power(channel_array, lmbda) - 1) / lmbda + + def box_cox_inverse_transform(self, channel_array: np.ndarray, lmbda: float) -> np.ndarray: + """Apply inverse Box-Cox transformation to the data.""" + channel_array = np.clip(channel_array, -1/lmbda, None) + return np.power((lmbda * channel_array) + 1, 1 / lmbda) \ No newline at end of file diff --git a/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py b/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py index 988024a0..03e7d702 100644 --- a/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py +++ b/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py @@ -72,7 +72,7 @@ def main(cfg: DictConfig): for idx, ts in enumerate(times, 1): dt = datetimes[idx-1] target = torch.load(out_root/ts/f"{ts}-target", weights_only=False)[tp_out] * CONV_FACTOR - baseline = torch.load(out_root/ts/f"{ts}-baseline", weights_only=False)[tp_in] * CONV_FACTOR / 6. # 6 because 1h -> accumulation period is 6h in hourly ERA5 dataset + baseline = torch.load(out_root/ts/f"{ts}-baseline", weights_only=False)[tp_in] * CONV_FACTOR # / 6. # 6 because 1h -> accumulation period is 6h in hourly ERA5 dataset preds = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False)[:, tp_out, :, :] * CONV_FACTOR try: mean_pred = torch.load(out_root/ts/f"{ts}-regression-prediction", weights_only=False)[tp_out] * CONV_FACTOR diff --git a/src/hirad/eval/diurnal_cycle_precip_p99.py b/src/hirad/eval/diurnal_cycle_precip_p99.py index 316a387b..9c54eb7a 100644 --- a/src/hirad/eval/diurnal_cycle_precip_p99.py +++ b/src/hirad/eval/diurnal_cycle_precip_p99.py @@ -103,8 +103,8 @@ def main(cfg: DictConfig): hourly_p99 = da.groupby('time.hour').quantile(0.99, dim='time') # Apply scaling factor for baseline - if mode == 'baseline': - hourly_p99 = hourly_p99 / 6.0 + # if mode == 'baseline': + # hourly_p99 = hourly_p99 / 6.0 pct99_mean[mode] = hourly_p99.mean(dim=['lat', 'lon']) diff --git a/src/hirad/eval/hist.py b/src/hirad/eval/hist.py index 183dee8d..78d7395b 100644 --- a/src/hirad/eval/hist.py +++ b/src/hirad/eval/hist.py @@ -131,7 +131,7 @@ def main(cfg: DictConfig): land_mask = load_land_sea_mask() # Define histogram bins - bins = np.logspace(-1, 1, 50) # Log-spaced bins for precipitation + bins = np.logspace(-1, 3.3, 200) # Log-spaced bins for precipitation # Storage for histogram data and land values hist_data = {} @@ -153,8 +153,8 @@ def main(cfg: DictConfig): data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode in ['target', 'regression-prediction'] else tp_in] * CONV_FACTOR_HOURLY * land_mask # Apply scaling factor for baseline - if mode == 'baseline': - data = data / 6.0 + # if mode == 'baseline': + # data = data / 6.0 land_values = data.values[~np.isnan(data.values)] all_values.extend(land_values) diff --git a/src/hirad/eval/map_precip_stats.py b/src/hirad/eval/map_precip_stats.py index 47e4125d..d17b8f93 100644 --- a/src/hirad/eval/map_precip_stats.py +++ b/src/hirad/eval/map_precip_stats.py @@ -152,8 +152,8 @@ def main(cfg: DictConfig): dims=['time', 'lat', 'lon'], coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} ) - if mode == 'baseline': - mode_data = mode_data / 6.0 + # if mode == 'baseline': + # mode_data = mode_data / 6.0 # Compute and plot all statistics for this mode for stat_config in stat_configs: logger.info(f"Computing {stat_config['title_stat']} for {mode}...") diff --git a/src/hirad/eval/probability_of_exceedance.py b/src/hirad/eval/probability_of_exceedance.py index e301016d..f56b5518 100644 --- a/src/hirad/eval/probability_of_exceedance.py +++ b/src/hirad/eval/probability_of_exceedance.py @@ -128,7 +128,7 @@ def main(cfg: DictConfig): land_mask = load_land_sea_mask() # Define thresholds for exceedance calculation - thresholds = np.logspace(-2, 2, 200) # From 0.01 to 100 mm/h + thresholds = np.logspace(-2, 2.1, 200) # From 0.01 to 100 mm/h # Storage for exceedance data and land values exceedance_data = {} @@ -148,8 +148,8 @@ def main(cfg: DictConfig): data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode in ['target','regression-prediction'] else tp_in] * CONV_FACTOR_HOURLY * land_mask # Apply scaling factor for baseline - if mode == 'baseline': - data = data / 6.0 + # if mode == 'baseline': + # data = data / 6.0 land_values = data.values[~np.isnan(data.values)] all_values.extend(land_values) diff --git a/src/hirad/eval/snapshots.py b/src/hirad/eval/snapshots.py index 08d6e6b3..f6c7892b 100644 --- a/src/hirad/eval/snapshots.py +++ b/src/hirad/eval/snapshots.py @@ -29,7 +29,7 @@ class ChannelMeta: vmin: float = None vmax: float = None extend: str = "both" - precip_kwargs: dict = field(default_factory=lambda: {"threshold": 0.1, "rfac": 100.0}) + precip_kwargs: dict = field(default_factory=lambda: {"threshold": 0.1, "rfac": 1000.0}) @classmethod def get(cls, ch_or_name: "ChannelMeta | str | None", *, vmin=None, vmax=None) -> "ChannelMeta": @@ -40,7 +40,7 @@ def get(cls, ch_or_name: "ChannelMeta | str | None", *, vmin=None, vmax=None) -> return base CHANNELS = { - "tp": ChannelMeta(name="tp", cmap=None, unit="mm/h", extend="max", precip_kwargs={"threshold": 0.1, "rfac": 100.0}), + "tp": ChannelMeta(name="tp", cmap=None, unit="mm/h", extend="max", precip_kwargs={"threshold": 0.1, "rfac": 1000.0}), "2t": ChannelMeta(name="2t", cmap="RdYlBu_r", me_cmap="RdBu", unit="K", err_vmin=-4.5, err_vmax=4.5), "10u": ChannelMeta(name="10u", cmap="BrBG", me_cmap="BrBG", unit="m/s", err_vmin=-10, err_vmax=10, vmin=-10, vmax=10), "10v": ChannelMeta(name="10v", cmap="BrBG", me_cmap="BrBG", unit="m/s", err_vmin=-10, err_vmax=10, vmin=-10, vmax=10), @@ -124,7 +124,9 @@ def main(cfg: DictConfig) -> None: logger = logging.getLogger("plot_maps") if cfg.generation.times_range: - times = get_time_from_range(cfg.generation.times_range, time_format="%Y%m%d-%H%M") + times = get_time_from_range(cfg.generation.times_range, time_format="%Y%m%d-%H%M") + else: + times = cfg.generation.times dataset_cfg = OmegaConf.to_container(cfg.dataset) has_lead_time = cfg.generation.get("has_lead_time", False) From caaa424e4685dcd49bf1cbdf171200fce0c4ba0f Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 19 Sep 2025 17:30:29 +0200 Subject: [PATCH 157/302] Add lead time label param in RegressionLoss for consistency and refactor noise parameter computation in ResidualLoss; enhance logging in training script for dataset details. --- src/hirad/losses/loss.py | 74 +++++++++++++++++++++++++++++-------- src/hirad/models/unet.py | 44 ++++++++++++++++++++-- src/hirad/training/train.py | 4 +- 3 files changed, 102 insertions(+), 20 deletions(-) diff --git a/src/hirad/losses/loss.py b/src/hirad/losses/loss.py index fb659607..2c2123b0 100644 --- a/src/hirad/losses/loss.py +++ b/src/hirad/losses/loss.py @@ -380,6 +380,7 @@ def __call__( augment_pipe: Optional[ Callable[[torch.Tensor], Tuple[torch.Tensor, Optional[torch.Tensor]]] ] = None, + lead_time_label: Optional[torch.Tensor] = None, ) -> torch.Tensor: """ Calculate and return the regression loss for @@ -439,7 +440,23 @@ def __call__( y_lr = y_tot[:, img_clean.shape[1] :, :, :] zero_input = torch.zeros_like(y, device=img_clean.device) - D_yn = net(zero_input, y_lr, force_fp32=False, augment_labels=augment_labels) + + if lead_time_label is not None: + D_yn = net( + zero_input, + y_lr, + force_fp32=False, + lead_time_label=lead_time_label, + augment_labels=augment_labels, + ) + else: + D_yn = net( + zero_input, + y_lr, + force_fp32=False, + augment_labels=augment_labels, + ) + loss = weight * ((D_yn - y) ** 2) return loss @@ -518,6 +535,32 @@ def __init__( self.hr_mean_conditioning = hr_mean_conditioning self.y_mean = None + def get_noise_params(self, y: torch.Tensor) -> torch.Tensor: + """ + Compute the noise parameters to apply denoising score matching. + + Parameters + ---------- + y : torch.Tensor + Latent state of shape :math:`(B, *)`. Only used to determine the shape of + the noise and create tensors on the same device. + + Returns + ------- + Tuple[torch.Tensor, torch.Tensor, torch.Tensor] + - Noise ``n`` of shape :math:`(B, *)` to be added to the latent state. + - Noise level ``sigma`` of shape :math:`(B, 1, 1, 1)`. + - Weight ``weight`` of shape :math:`(B, 1, 1, 1)` to multiply the loss. + """ + # Sample noise level + rnd_normal = torch.randn([y.shape[0], 1, 1, 1], device=y.device) + sigma = (rnd_normal * self.P_std + self.P_mean).exp() + # Loss weight + weight = (sigma**2 + self.sigma_data**2) / (sigma * self.sigma_data) ** 2 + # Sample noise + n = torch.randn_like(y) * sigma + return n, sigma, weight + def __call__( self, net: torch.nn.Module, @@ -712,35 +755,34 @@ def __call__( y = y_patched y_lr = y_lr_patched - # Noise - rnd_normal = torch.randn([y.shape[0], 1, 1, 1], device=img_clean.device) - sigma = (rnd_normal * self.P_std + self.P_mean).exp() - weight = (sigma**2 + self.sigma_data**2) / (sigma * self.sigma_data) ** 2 - - # Input + noise - latent = y + torch.randn_like(y) * sigma + # Add noise to the latent state + n, sigma, weight = self.get_noise_params(y) if lead_time_label is not None: D_yn = net( - latent, + y + n, y_lr, sigma, embedding_selector=None, - global_index=patching.global_index(batch_size, img_clean.device) - if patching is not None - else None, + global_index=( + patching.global_index(batch_size, img_clean.device) + if patching is not None + else None + ), lead_time_label=lead_time_label, augment_labels=augment_labels, ) else: D_yn = net( - latent, + y + n, y_lr, sigma, embedding_selector=None, - global_index=patching.global_index(batch_size, img_clean.device) - if patching is not None - else None, + global_index=( + patching.global_index(batch_size, img_clean.device) + if patching is not None + else None + ), augment_labels=augment_labels, ) loss = weight * ((D_yn - y) ** 2) diff --git a/src/hirad/models/unet.py b/src/hirad/models/unet.py index e0a447aa..f1edc6b1 100644 --- a/src/hirad/models/unet.py +++ b/src/hirad/models/unet.py @@ -69,9 +69,9 @@ class UNet(nn.Module): # TODO a lot of redundancy, need to clean up See Also -------- For information on model types and their usage: - :class:`~physicsnemo.models.diffusion.SongUNet`: Basic U-Net for diffusion models - :class:`~physicsnemo.models.diffusion.SongUNetPosEmbd`: U-Net with positional embeddings - :class:`~physicsnemo.models.diffusion.SongUNetPosLtEmbd`: U-Net with positional and lead-time embeddings + :class:`~models.song_unet.SongUNet`: Basic U-Net for diffusion models + :class:`~models.song_unet.SongUNetPosEmbd`: U-Net with positional embeddings + :class:`~models.song_unet.SongUNetPosLtEmbd`: U-Net with positional and lead-time embeddings Please refer to the documentation of these classes for details on how to call and use these models directly. @@ -152,6 +152,44 @@ def __init__( **model_kwargs, ) + @property + def use_fp16(self): + """ + bool: Whether the model uses float16 precision. + + Returns + ------- + bool + True if the model is in float16 mode, False otherwise. + """ + return self._use_fp16 + + @use_fp16.setter + def use_fp16(self, value: bool): + """ + Set whether the model should use float16 precision. + + Parameters + ---------- + value : bool + If True, moves the model to torch.float16. If False, moves to torch.float32. + + Raises + ------ + ValueError + If `value` is not a boolean. + """ + # NOTE: allow 0/1 values for older checkpoints + if not (isinstance(value, bool) or value in [0, 1]): + raise ValueError( + f"`use_fp16` must be a boolean, but got {type(value).__name__}." + ) + self._use_fp16 = value + if value: + self.to(torch.float16) + else: + self.to(torch.float32) + def forward( self, x: torch.Tensor, diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 84941b45..b9092f2d 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -151,7 +151,9 @@ def main(cfg: DictConfig) -> None: img_out_channels = len(dataset.output_channels()) if cfg.model.hr_mean_conditioning: img_in_channels += img_out_channels - + logger0.info(f"Training on dataset with grid size {img_shape[0]}x{img_shape[1]}, {img_in_channels} input channels and {img_out_channels} output channels.") + logger0.info(f"Input channels: {dataset.input_channels()}") + logger0.info(f"Output channels: {dataset.output_channels()}") if cfg.model.name == "lt_aware_ce_regression": prob_channels = dataset.get_prob_channel_index() #TODO figure out what prob_channel are and update dataloader From 7ecc3d98bb9ba2088ae01600294f07129a3e00fc Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 19 Sep 2025 17:33:35 +0200 Subject: [PATCH 158/302] fix bug in dataset initialization --- src/hirad/datasets/era5_cosmo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hirad/datasets/era5_cosmo.py b/src/hirad/datasets/era5_cosmo.py index 6957877b..a4fa82fa 100644 --- a/src/hirad/datasets/era5_cosmo.py +++ b/src/hirad/datasets/era5_cosmo.py @@ -7,7 +7,7 @@ import torch.nn.functional as F class ERA5_COSMO(DownscalingDataset): - def __init__(self, dataset_path: str, input_channel_names: List[str] = [], output_channel_names: List[str] = []): + def __init__(self, dataset_path: str, input_channel_names: List[str] = [], output_channel_names: List[str] = [], static_channel_names: List[str] = [], transform_channels: List[str] = []): super().__init__() #TODO switch hanbdling paths to Path rather than pure strings From b9a843e6882cc95ac0ba0b026cbed32b84ad207a Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 24 Sep 2025 19:53:23 +0200 Subject: [PATCH 159/302] Add a less heavyweight Dockerfile that doesn't use nvidia stuff, for input data processing. --- ci/docker/Dockerfile.python | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 ci/docker/Dockerfile.python diff --git a/ci/docker/Dockerfile.python b/ci/docker/Dockerfile.python new file mode 100644 index 00000000..80ea1eb1 --- /dev/null +++ b/ci/docker/Dockerfile.python @@ -0,0 +1,29 @@ +# Following some suggestions in https://meteoswiss.atlassian.net/wiki/spaces/APN/pages/719684202/Clariden+Alps+environment+setup + +FROM ubuntu:22.04 as builder +#FROM nvcr.io/nvidia/pytorch:25.01-py3 + + +# setup +RUN apt-get update && apt-get install python3-pip python3-venv -y +RUN pip install --upgrade \ + pip + #ninja + #wheel + #packaging + #setuptools + +# install the rest of dependencies +# TODO: Factor pydeps into a separate file(s) +# TODO: Add versions for things +RUN pip install \ + anemoi-datasets \ + cartopy \ + matplotlib \ + numpy \ + pandas \ + scipy \ + torch + + + From 3a1ae7c4d9090f503e6a711a537a4b226237c95f Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 24 Sep 2025 19:54:47 +0200 Subject: [PATCH 160/302] Modifications to input data tests to optionally load the torch files --- src/hirad/input_data/test_input_data.py | 30 ++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/hirad/input_data/test_input_data.py b/src/hirad/input_data/test_input_data.py index fa134911..d8d0f6ee 100644 --- a/src/hirad/input_data/test_input_data.py +++ b/src/hirad/input_data/test_input_data.py @@ -45,13 +45,15 @@ def make_stats(filepath: str): def main(): root = logging.getLogger() root.setLevel(logging.INFO) - logging.info('starting main') input_directory = sys.argv[1] + + logging.info(f'checking input directory {input_directory}') missing_data = [] corrupt_data = [] nan_data = [] check_for_nans = False + check_for_corrupt = False @@ -70,19 +72,21 @@ def main(): while (expected_date < curr_date): missing_data.append(datetime.datetime.strftime(expected_date, '%Y%m%d-%H%M')) expected_date = expected_date + delta - try: - data = torch.load(os.path.join(input_directory, f), weights_only=False) - except: - logging.info(f'corrupt data: {curr_date}') - corrupt_data.append(curr_date) - if check_for_nans: - if count_nans(data): - logging.info(f'data nans: {curr_date}') - nan_data.append(curr_date) + if check_for_corrupt: + try: + data = torch.load(os.path.join(input_directory, f), weights_only=False) + except: + logging.info(f'corrupt data: {curr_date}') + corrupt_data.append(curr_date) + if check_for_nans or curr_date == start_date: + if count_nans(data): + logging.info(f'data nans: {curr_date}') + nan_data.append(curr_date) prev_date = curr_date - logging.info(f'missing data: {missing_data}') - logging.info(f'corrupt data: {corrupt_data}') - logging.info(f'nan data: {nan_data}') + logging.info(f'missing data size {len(missing_data)}: {missing_data}') + logging.info(f'corrupt data size {len(corrupt_data)}: {corrupt_data}') + if check_for_nans: + logging.info(f'nan data: {nan_data}') if __name__ == "__main__": main() From e77c61ae188e66ba057886758e07cefc82f65ebd Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 24 Sep 2025 19:55:09 +0200 Subject: [PATCH 161/302] Scripts used to reprocess data for various runs --- .../input_data/reprocess_change_tp_accum.py | 58 +++++++++++++++++++ src/hirad/input_data/reprocess_exclude_tp.py | 43 ++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 src/hirad/input_data/reprocess_change_tp_accum.py create mode 100644 src/hirad/input_data/reprocess_exclude_tp.py diff --git a/src/hirad/input_data/reprocess_change_tp_accum.py b/src/hirad/input_data/reprocess_change_tp_accum.py new file mode 100644 index 00000000..410c351e --- /dev/null +++ b/src/hirad/input_data/reprocess_change_tp_accum.py @@ -0,0 +1,58 @@ +import logging +import os +import sys + +import torch +import numpy as np + +# Reprocess ERA-interpolated data to exclude the tp variable. + +# 6H data is all channels, but with 6h accumulation +DATA_SOURCE_6H = "/capstor/scratch/cscs/mmcgloho/datasets/processed/era5-cosmo-1h-all-channels/era-interpolated" +STATS_FILEPATH_6H = "/capstor/scratch/cscs/mmcgloho/datasets/processed/era5-cosmo-1h-all-channels/info" +# 1h data is the updated +DATA_SOURCE_1H = "/capstor/store/cscs/swissai/a161/era5-cosmo-1h-linear-interpolation/train/era-interpolated-with-copernicus-tp/" +STATS_FILEPATH_1H = "/capstor/store/cscs/swissai/a161/era5-cosmo-1h-linear-interpolation/train/info" +OUTPUT_DIR = "/iopsstor/scratch/cscs/mmcgloho/run-1_4/train/era-interpolated" +OUTPUT_STATS_FILEPATH = "/iopsstor/scratch/cscs/mmcgloho/run-1_4/train/info/" +TP_INDEX_6H = 34 # in era-all.yaml +TP_INDEX_1H = 12 # in era.yaml + +def process(input_directory_6h: str, input_directory_1h: str, output_directory: str): + input_6h_filepath = os.path.join(input_directory_6h) + files = os.listdir(input_6h_filepath) + files.sort() + for f in range(len(files)): + if f % 100 == 0: + logging.info(f) + input_1h_file = os.path.join(input_directory_1h, files[f]) + input_6h_file = os.path.join(input_directory_6h, files[f]) + outfile = os.path.join(output_directory, files[f]) + in_data_6h = torch.load(input_6h_file, weights_only=False) + in_data_1h = torch.load(input_1h_file, weights_only=False) + in_data_6h[TP_INDEX_6H,:] = in_data_1h[TP_INDEX_1H,:] + torch.save(in_data_6h, outfile) + +def edit_info(info_6h_filepath: str, info_1h_filepath: str, output_filepath: str): + stats_6h = torch.load(os.path.join(info_6h_filepath, 'era-stats'), weights_only=False) + stats_1h = torch.load(os.path.join(info_1h_filepath, 'era-stats'), weights_only=False) + logging.info(f'6h stats: {stats_6h}') + logging.info(f'1h stats: {stats_1h}') + for k in stats_6h.keys(): + logging.info(k, stats_6h[k]) + tmp = stats_6h[k] + tmp[TP_INDEX_6H] = stats_1h[k][TP_INDEX_1H] + stats_6h[k] = tmp + logging.info(stats_6h) + torch.save(stats_6h, os.path.join(output_filepath, 'era-stats')) + +def main(): + logging.basicConfig( + format='%(asctime)s %(levelname)-8s %(message)s', + level=logging.INFO, + datefmt='%Y-%m-%d %H:%M:%S') + process(DATA_SOURCE_6H, DATA_SOURCE_1H, OUTPUT_DIR) + edit_info(STATS_FILEPATH_6H, STATS_FILEPATH_1H, OUTPUT_STATS_FILEPATH) + +if __name__ == "__main__": + main() diff --git a/src/hirad/input_data/reprocess_exclude_tp.py b/src/hirad/input_data/reprocess_exclude_tp.py new file mode 100644 index 00000000..190a4742 --- /dev/null +++ b/src/hirad/input_data/reprocess_exclude_tp.py @@ -0,0 +1,43 @@ +import logging +import os +import sys + +import torch +import numpy as np + +# Reprocess ERA-interpolated data to exclude the tp variable. + +TP_INDEX = 12 + +def process(input_directory: str, output_directory: str): + input_filepath = os.path.join(input_directory, 'era-interpolated') + files = os.listdir(input_filepath) + files.sort() + for f in range(len(files)): + if f % 100 == 0: + logging.info(f) + outfile = os.path.join(output_directory, 'era-interpolated', files[f]) + if (not os.path.exists(outfile)) or (os.path.getsize(outfile) < 26000000): + in_data = torch.load(os.path.join(input_filepath, files[f]), weights_only=False) + out_data = in_data[0:TP_INDEX,:] + torch.save(out_data, outfile) + +def edit_info(input_filepath: str, output_filepath: str): + stats = torch.load(os.path.join(input_filepath, '/info', 'era-stats'), weights_only=False) + logging.info(stats) + for k in stats.keys(): + logging.info(k, stats[k]) + stats[k] = stats[k][0:TP_INDEX] + logging.info(stats) + torch.save(stats, os.path.join(output_filepath, "/info", "era-stats")) + +def main(): + root = logging.getLogger() + root.setLevel(logging.INFO) + input_directory = sys.argv[1] + output_directory = sys.argv[2] + process(input_directory, output_directory) + #edit_info(input_directory, output_directory) + +if __name__ == "__main__": + main() From 88dcc011d29bf49a772e3f4669a4df4cb00af843 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 2 Oct 2025 12:31:09 +0200 Subject: [PATCH 162/302] Add reprocessing script --- src/hirad/input_data/reprocess_change_tp_accum.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hirad/input_data/reprocess_change_tp_accum.py b/src/hirad/input_data/reprocess_change_tp_accum.py index 410c351e..56423000 100644 --- a/src/hirad/input_data/reprocess_change_tp_accum.py +++ b/src/hirad/input_data/reprocess_change_tp_accum.py @@ -11,16 +11,16 @@ DATA_SOURCE_6H = "/capstor/scratch/cscs/mmcgloho/datasets/processed/era5-cosmo-1h-all-channels/era-interpolated" STATS_FILEPATH_6H = "/capstor/scratch/cscs/mmcgloho/datasets/processed/era5-cosmo-1h-all-channels/info" # 1h data is the updated -DATA_SOURCE_1H = "/capstor/store/cscs/swissai/a161/era5-cosmo-1h-linear-interpolation/train/era-interpolated-with-copernicus-tp/" -STATS_FILEPATH_1H = "/capstor/store/cscs/swissai/a161/era5-cosmo-1h-linear-interpolation/train/info" -OUTPUT_DIR = "/iopsstor/scratch/cscs/mmcgloho/run-1_4/train/era-interpolated" -OUTPUT_STATS_FILEPATH = "/iopsstor/scratch/cscs/mmcgloho/run-1_4/train/info/" +DATA_SOURCE_1H = "/capstor/store/cscs/swissai/a161/era5-cosmo-1h-linear-interpolation/validation/era-interpolated-with-copernicus-tp/" +STATS_FILEPATH_1H = "/capstor/store/cscs/swissai/a161/era5-cosmo-1h-linear-interpolation/validation/info" +OUTPUT_DIR = "/iopsstor/scratch/cscs/mmcgloho/run-1_4/validation/era-interpolated" +OUTPUT_STATS_FILEPATH = "/iopsstor/scratch/cscs/mmcgloho/run-1_4/validation/info/" TP_INDEX_6H = 34 # in era-all.yaml TP_INDEX_1H = 12 # in era.yaml def process(input_directory_6h: str, input_directory_1h: str, output_directory: str): - input_6h_filepath = os.path.join(input_directory_6h) - files = os.listdir(input_6h_filepath) + input_1h_filepath = os.path.join(input_directory_1h) + files = os.listdir(input_1h_filepath) files.sort() for f in range(len(files)): if f % 100 == 0: From 425050f0650522474ff867dde95b0c579b287256 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Thu, 2 Oct 2025 15:55:03 +0200 Subject: [PATCH 163/302] add functionality to have hour of the day and month of the year embedding as conditioning inputs --- src/hirad/datasets/era5_cosmo.py | 109 +++++++++++++++++++++++++---- src/hirad/utils/inference_utils.py | 9 ++- 2 files changed, 99 insertions(+), 19 deletions(-) diff --git a/src/hirad/datasets/era5_cosmo.py b/src/hirad/datasets/era5_cosmo.py index a4fa82fa..1db87168 100644 --- a/src/hirad/datasets/era5_cosmo.py +++ b/src/hirad/datasets/era5_cosmo.py @@ -5,18 +5,35 @@ from typing import List, Tuple import yaml import torch.nn.functional as F +import time +# import zarr + +from hirad.utils.console import PythonLogger + +logger = PythonLogger(__name__) + +DATASET_ORIG_PATH = '/capstor/store/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-linear-interpolation-full' class ERA5_COSMO(DownscalingDataset): - def __init__(self, dataset_path: str, input_channel_names: List[str] = [], output_channel_names: List[str] = [], static_channel_names: List[str] = [], transform_channels: List[str] = []): + def __init__(self, + dataset_path: str, + input_channel_names: List[str] = [], + output_channel_names: List[str] = [], + static_channel_names: List[str] = [], + transform_channels: List[str] = [], + n_month_hour_channels: int = None, + ): super().__init__() #TODO switch hanbdling paths to Path rather than pure strings + self._n_month_hour_channels = n_month_hour_channels self._dataset_path = dataset_path self._era5_path = os.path.join(dataset_path, 'era-interpolated') self._cosmo_path = os.path.join(dataset_path, 'cosmo') - self._info_path = os.path.join(dataset_path, 'info') - self._static_path = '/capstor/store/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-linear-interpolation-full/static'# os.path.join(dataset_path, 'static') - self._zarr_path = os.path.join(dataset_path, 'dataset.zarr') + self._info_path = os.path.join(DATASET_ORIG_PATH, 'info') + # self._static_path = '/capstor/store/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-linear-interpolation-full/static'# os.path.join(dataset_path, 'static') + self._static_path = os.path.join(DATASET_ORIG_PATH, 'static')# os.path.join(dataset_path, 'static') + # self._zarr_path = os.path.join(dataset_path, 'dataset.zarr') # load file list (each file is one date-time state) self._file_list = sorted(os.listdir(self._cosmo_path)) @@ -99,13 +116,13 @@ def __init__(self, dataset_path: str, input_channel_names: List[str] = [], outpu if input_channel_idx is not None: self.input_transforms[input_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_transform(x, lmbda) self.input_inverse_transforms[input_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_inverse_transform(x, lmbda) - self.input_mean[input_channel_idx] = torch.load(os.path.join("/users/pstamenk/HiRAD-Gen",f"era5-{transform_descriptor}-mean"), weights_only=False) - self.input_std[input_channel_idx] = torch.load(os.path.join("/users/pstamenk/HiRAD-Gen",f"era5-{transform_descriptor}-std"), weights_only=False) + self.input_mean[input_channel_idx] = torch.load(os.path.join(self._info_path,f"era5-{transform_descriptor}-mean"), weights_only=False) + self.input_std[input_channel_idx] = torch.load(os.path.join(self._info_path,f"era5-{transform_descriptor}-std"), weights_only=False) if output_channel_idx is not None: self.output_transforms[output_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_transform(x, lmbda) self.output_inverse_transforms[output_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_inverse_transform(x, lmbda) - self.output_mean[output_channel_idx] = torch.load(os.path.join("/users/pstamenk/HiRAD-Gen",f"cosmo-{transform_descriptor}-mean"), weights_only=False) - self.output_std[output_channel_idx] = torch.load(os.path.join("/users/pstamenk/HiRAD-Gen",f"cosmo-{transform_descriptor}-std"), weights_only=False) + self.output_mean[output_channel_idx] = torch.load(os.path.join(self._info_path,f"cosmo-{transform_descriptor}-mean"), weights_only=False) + self.output_std[output_channel_idx] = torch.load(os.path.join(self._info_path,f"cosmo-{transform_descriptor}-std"), weights_only=False) else: raise ValueError(f"Transformation: {transformation} for channel {channel} not implemented.") @@ -117,7 +134,11 @@ def __getitem__(self, idx): # flip so that it starts in top-left corner (by default it is bottom left) # orig_shape = [350,542] #TODO currently padding to be divisible by 16 orig_shape = self.image_shape() - era5_data = torch.load(os.path.join(self._era5_path,self._file_list[idx]), weights_only=False)[self._era_indeces] + try: + era5_data = torch.load(os.path.join(self._era5_path,self._file_list[idx]), weights_only=False)[self._era_indeces] + except: + logger.error(f"Error loading file {os.path.join(self._era5_path,self._file_list[idx])}") + raise era5_data = np.flip(era5_data \ .squeeze() \ .reshape(-1,*orig_shape), @@ -125,15 +146,29 @@ def __getitem__(self, idx): era5_data = np.concatenate((era5_data, self.static_data), axis=0) if self.static_data is not None else era5_data era5_data = self.normalize_input(era5_data) - cosmo_data = torch.load(os.path.join(self._cosmo_path,self._file_list[idx]), weights_only=False)[self._cosmo_indeces] + try: + cosmo_data = torch.load(os.path.join(self._cosmo_path,self._file_list[idx]), weights_only=False)[self._cosmo_indeces] + except: + logger.error(f"Error loading file {os.path.join(self._cosmo_path,self._file_list[idx])}") + raise cosmo_data = np.flip(cosmo_data\ .squeeze() \ .reshape(-1,*orig_shape), 1) cosmo_data = self.normalize_output(cosmo_data) + if self._n_month_hour_channels is not None and self._n_month_hour_channels>0: + # extract month and hour from filename + filename = self._file_list[idx] + date_str, hour_str = filename.split('-') + month = int(date_str[4:6]) + hour = int(hour_str[0:2]) + + time_grid = self.make_time_grids(hour, month) + era5_data = np.concatenate((era5_data, time_grid), axis=0) + return torch.tensor(cosmo_data),\ - torch.tensor(era5_data), + torch.tensor(era5_data) def __len__(self): return len(self._file_list) @@ -153,8 +188,13 @@ def latitude(self) -> np.ndarray: def input_channels(self) -> List[ChannelMetadata]: """Metadata for the input channels. A list of ChannelMetadata, one for each channel""" - return self._era_channels + self._static_channels if self.static_data is not None else self._era_channels - + channels = self._era_channels + self._static_channels if self.static_data is not None else self._era_channels + if self._n_month_hour_channels is not None and self._n_month_hour_channels>0: + for i in range(self._n_month_hour_channels): + channels.append(ChannelMetadata("hour-enc",f"{i}")) + for i in range(self._n_month_hour_channels): + channels.append(ChannelMetadata("month-enc",f"{i}")) + return channels def output_channels(self) -> List[ChannelMetadata]: """Metadata for the output channels. A list of ChannelMetadata, one for each channel""" @@ -183,6 +223,8 @@ def normalize_input(self, x: np.ndarray) -> np.ndarray: def denormalize_input(self, x: np.ndarray) -> np.ndarray: """Convert input from normalized data to physical units.""" + if self._n_month_hour_channels is not None and self._n_month_hour_channels>0: + x = x[:,:-2*self._n_month_hour_channels,:,:] x = x * self.input_std.reshape((self.input_std.shape[0],1,1)) \ + self.input_mean.reshape((self.input_mean.shape[0],1,1)) for channel_idx, inverse_transform in self.input_inverse_transforms.items(): @@ -214,4 +256,43 @@ def box_cox_transform(self, channel_array: np.ndarray, lmbda: float) -> np.ndarr def box_cox_inverse_transform(self, channel_array: np.ndarray, lmbda: float) -> np.ndarray: """Apply inverse Box-Cox transformation to the data.""" channel_array = np.clip(channel_array, -1/lmbda, None) - return np.power((lmbda * channel_array) + 1, 1 / lmbda) \ No newline at end of file + return np.power((lmbda * channel_array) + 1, 1 / lmbda) + + def make_time_grids(self, hour, month): + """ + Create multi-frequency cyclic sin/cos feature grids for hour and month. + + Parameters + ---------- + hour : int + Hour of day, 0-23 + month : int + Month of year, 1-12 + + Returns + ------- + grid : np.ndarray, shape (C, H, W) + Channels = [sin(k*hour), cos(k*hour), sin(k*month), cos(k*month) for each k frequency] + """ + H, W = self.image_shape() + hour_freqs = np.arange(1, self._n_month_hour_channels//2 + 1) + month_freqs = np.arange(1, self._n_month_hour_channels//2 + 1) + + channels = [] + + # --- hour encodings --- + for k in hour_freqs: + angle = 2 * np.pi * k * (hour % 24) / 24.0 + channels.append(np.sin(angle)) + channels.append(np.cos(angle)) + + # --- month encodings --- + for k in month_freqs: + angle = 2 * np.pi * k * ((month - 1) % 12) / 12.0 + channels.append(np.sin(angle)) + channels.append(np.cos(angle)) + + channels = np.array(channels, dtype=np.float32) + grid = np.tile(channels[:, None, None], (1, H, W)) # (C, H, W) + + return grid \ No newline at end of file diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index a792c704..0cdfd985 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -208,13 +208,12 @@ def diffusion_step( def save_results_as_torch(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): - os.makedirs(output_path, exist_ok=True) - target = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze()),1) - prediction_ensemble = np.flip(dataset.denormalize_output(image_pred.squeeze()),-2) - baseline = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze()),1) + target = np.flip(dataset.denormalize_output(image_hr)[0,::].squeeze(),1) + prediction_ensemble = np.flip(dataset.denormalize_output(image_pred).squeeze(),-2) + baseline = np.flip(dataset.denormalize_input(image_lr)[0,::].squeeze(),1) if mean_pred is not None: - mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze()),1) + mean_pred = np.flip(dataset.denormalize_output(mean_pred)[0,::].squeeze(),1) torch.save(mean_pred, os.path.join(output_path, f'{time_step}-regression-prediction')) torch.save(target, os.path.join(output_path, f'{time_step}-target')) torch.save(prediction_ensemble, os.path.join(output_path, f'{time_step}-predictions')) From 6a09fb63d539539d873a926f40ec771d4003454d Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Thu, 2 Oct 2025 16:58:38 +0200 Subject: [PATCH 164/302] update config to latest features --- src/hirad/conf/dataset/era_cosmo.yaml | 9 ++++++--- src/hirad/conf/dataset/era_cosmo_inference.yaml | 8 ++++++-- src/hirad/conf/model/era_cosmo_diffusion.yaml | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/hirad/conf/dataset/era_cosmo.yaml b/src/hirad/conf/dataset/era_cosmo.yaml index 37b3df2a..fc81714d 100644 --- a/src/hirad/conf/dataset/era_cosmo.yaml +++ b/src/hirad/conf/dataset/era_cosmo.yaml @@ -1,6 +1,9 @@ type: era5_cosmo -dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/train +dataset_path: /iopsstor/scratch/cscs/mmcgloho/run-1_2/train/ # dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-all-channels -validation_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/validation +validation_path: /iopsstor/scratch/cscs/mmcgloho/run-1_2/validation/ input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] -output_channel_names: [2t, 10u, 10v, tp] \ No newline at end of file +output_channel_names: [2t, 10u, 10v, tp] +static_channel_names: ['hsurf'] +transform_channels: ['tp-box_cox_025'] +n_month_hour_channels: 4 \ No newline at end of file diff --git a/src/hirad/conf/dataset/era_cosmo_inference.yaml b/src/hirad/conf/dataset/era_cosmo_inference.yaml index bca045dc..71b0db15 100644 --- a/src/hirad/conf/dataset/era_cosmo_inference.yaml +++ b/src/hirad/conf/dataset/era_cosmo_inference.yaml @@ -1,4 +1,8 @@ type: era5_cosmo -dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/validation +# dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/validation +dataset_path: /iopsstor/scratch/cscs/mmcgloho/run-1_2/validation input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] -output_channel_names: [2t, 10u, 10v, tp] \ No newline at end of file +output_channel_names: [2t, 10u, 10v, tp] +static_channel_names: ['hsurf'] +transform_channels: ['tp-box_cox_025'] +n_month_hour_channels: 4 \ No newline at end of file diff --git a/src/hirad/conf/model/era_cosmo_diffusion.yaml b/src/hirad/conf/model/era_cosmo_diffusion.yaml index 441239e1..7a060e8b 100644 --- a/src/hirad/conf/model/era_cosmo_diffusion.yaml +++ b/src/hirad/conf/model/era_cosmo_diffusion.yaml @@ -10,6 +10,6 @@ model_args: # Controls how positional information is encoded. N_grid_channels: 4 # Number of channels for positional grid embeddings - embedding_type: "zero" + embedding_type: "positional" # Type of timestep embedding: 'positional' for DDPM++, 'fourier' for NCSN++, # 'zero' for none \ No newline at end of file From ba08752638f2a7252febb7806985a91ccd0378e0 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Wed, 8 Oct 2025 10:08:32 +0200 Subject: [PATCH 165/302] remove scoringrules packeage, not installed --- src/hirad/eval/metrics.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hirad/eval/metrics.py b/src/hirad/eval/metrics.py index 8c8afded..af9d5a9a 100644 --- a/src/hirad/eval/metrics.py +++ b/src/hirad/eval/metrics.py @@ -3,7 +3,6 @@ import numpy as np import torch import xskillscore -import scoringrules as sr from scipy.signal import periodogram import xskillscore From febbfe8edbe73e19b7762ba5741992542fe70f89 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Wed, 8 Oct 2025 10:10:48 +0200 Subject: [PATCH 166/302] put snapshot maps into own sbatch script --- src/hirad/eval_precip.sh | 1 - src/hirad/snapshots.sh | 50 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/hirad/snapshots.sh diff --git a/src/hirad/eval_precip.sh b/src/hirad/eval_precip.sh index db9b190e..7574b07e 100644 --- a/src/hirad/eval_precip.sh +++ b/src/hirad/eval_precip.sh @@ -57,5 +57,4 @@ srun --environment=./ci/edf/modulus_env.toml bash -c " # Maps python src/hirad/eval/map_precip_stats.py --config-name=generate_era_cosmo.yaml - # python src/hirad/eval/snapshots.py --config-name=generate_era_cosmo.yaml " \ No newline at end of file diff --git a/src/hirad/snapshots.sh b/src/hirad/snapshots.sh new file mode 100644 index 00000000..16a12de4 --- /dev/null +++ b/src/hirad/snapshots.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +#SBATCH --job-name="eval_precip" + +### HARDWARE ### +#SBATCH --partition=normal +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=2 +#SBATCH --gpus-per-node=2 +#SBATCH --cpus-per-task=72 +#SBATCH --time=05:00:00 +#SBATCH --no-requeue +#SBATCH --exclusive + +### OUTPUT ### +#SBATCH --output=./logs/plots_precip.log + +### ENVIRONMENT #### +#SBATCH -A a161 + +# Choose method to initialize dist in pythorch +export DISTRIBUTED_INITIALIZATION_METHOD=SLURM + +MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" +echo "Master node : $MASTER_ADDR" +# Get IP for hostname. +MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" +echo "Master address : $MASTER_ADDR" +export MASTER_ADDR +export MASTER_PORT=29500 +echo "Master port: $MASTER_PORT" + +# Get number of physical cores using Python +# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") +# # Use SLURM_NTASKS (number of processes to be launched by torchrun) +# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} +# # Compute threads per process +# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) +# export OMP_NUM_THREADS=$OMP_THREADS +export OMP_NUM_THREADS=72 +# echo "Physical cores: $PHYSICAL_CORES" +# echo "Local processes: $LOCAL_PROCS" +# echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" + + +srun --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . --no-dependencies + + python src/hirad/eval/snapshots.py --config-name=generate_era_cosmo.yaml +" \ No newline at end of file From 778f52b114428856bb3967412b9d6017e6361d25 Mon Sep 17 00:00:00 2001 From: David Leutwyler <14977216+leuty@users.noreply.github.com> Date: Thu, 9 Oct 2025 15:25:04 +0200 Subject: [PATCH 167/302] Eval More 10m winds (#23) --- src/hirad/eval/map_precip_stats.py | 2 +- src/hirad/eval/map_wind_stats.py | 405 ++++++++++++++++++ .../eval/probability_of_exceedance_wind.py | 344 +++++++++++++++ src/hirad/eval_precip.sh | 1 - src/hirad/eval_wind.sh | 53 +++ 5 files changed, 803 insertions(+), 2 deletions(-) create mode 100644 src/hirad/eval/map_wind_stats.py create mode 100644 src/hirad/eval/probability_of_exceedance_wind.py create mode 100644 src/hirad/eval_wind.sh diff --git a/src/hirad/eval/map_precip_stats.py b/src/hirad/eval/map_precip_stats.py index d17b8f93..257cfcda 100644 --- a/src/hirad/eval/map_precip_stats.py +++ b/src/hirad/eval/map_precip_stats.py @@ -61,7 +61,7 @@ def plot_stat_map(data, filename, stat_config, label): plot_map( data, filename, title=f'{label}: {stat_config["title_stat"]} (%)', - label='Wet-Hour Frequency [%]', vmin=0, vmax=10, cmap='PuBu', extend='max' + label='Wet-Hour Frequency [%]', vmin=0, vmax=30, cmap='PuBu', extend='max' ) elif stat_config['type'] == 'cdd': plot_map( diff --git a/src/hirad/eval/map_wind_stats.py b/src/hirad/eval/map_wind_stats.py new file mode 100644 index 00000000..ee6855a0 --- /dev/null +++ b/src/hirad/eval/map_wind_stats.py @@ -0,0 +1,405 @@ +import logging +from datetime import datetime +from pathlib import Path + +import hydra +import numpy as np +import torch +from omegaconf import DictConfig, OmegaConf +import xarray as xr + +from hirad.datasets import get_dataset_and_sampler_inference +from hirad.distributed import DistributedManager +from hirad.utils.function_utils import get_time_from_range +from hirad.eval.plotting import plot_map, get_channel_indices, LOG_INTERVAL + + +def compute_wind_speed(u, v): + """Compute wind speed from U and V components.""" + return np.hypot(u, v) + + +def compute_wind_direction(u, v): + """Compute wind direction in degrees (meteorological convention: direction FROM which wind blows). + + Returns angle in degrees: 0° = North, 90° = East, 180° = South, 270° = West + """ + # atan2(u, v) gives mathematical angle, convert to meteorological + direction = np.rad2deg(np.arctan2(u, v)) + 180 + direction = np.mod(direction, 360) + return direction + + +def circular_mean_direction(directions, weights=None): + """Calculate circular mean of wind directions in degrees. + + Args: + directions: Array of directions in degrees + weights: Optional weights (e.g., wind speeds) + """ + # Convert to radians + rad = np.deg2rad(directions) + + # Calculate weighted mean of sin and cos components + if weights is not None: + sin_mean = np.average(np.sin(rad), weights=weights, axis=0) + cos_mean = np.average(np.cos(rad), weights=weights, axis=0) + else: + sin_mean = np.mean(np.sin(rad), axis=0) + cos_mean = np.mean(np.cos(rad), axis=0) + + # Calculate mean direction + mean_dir = np.arctan2(sin_mean, cos_mean) + mean_dir_deg = np.rad2deg(mean_dir) + mean_dir_deg = np.mod(mean_dir_deg, 360) + + return mean_dir_deg + + +def circular_std(directions): + """Calculate circular standard deviation of wind directions in degrees. + + Returns values from 0 (perfect alignment) to ~81.03 degrees (uniform distribution) + """ + rad = np.deg2rad(directions) + + # Calculate mean resultant length + sin_mean = np.mean(np.sin(rad), axis=0) + cos_mean = np.mean(np.cos(rad), axis=0) + R = np.hypot(sin_mean, cos_mean) + + # Circular standard deviation + # Handle R=0 case to avoid log(0) + R = np.clip(R, 1e-10, 1.0) + circ_std = np.rad2deg(np.sqrt(-2 * np.log(R))) + + return circ_std + + +def apply_wind_statistic(u_data, v_data, stat_type, stat_param=None): + """Apply a wind statistic to U and V components along the time dimension. + + Args: + u_data: xarray DataArray of U wind component + v_data: xarray DataArray of V wind component + stat_type: Type of statistic to compute + stat_param: Optional parameter for the statistic (e.g., quantile value) + + Returns: + Result as numpy array + """ + # Compute wind speed + speed = compute_wind_speed(u_data.values, v_data.values) + + if stat_type == 'mean_speed': + return np.mean(speed, axis=0) + + if stat_type == 'quantile_speed': + return np.quantile(speed, stat_param, axis=0) + + if stat_type == 'max_speed': + return np.max(speed, axis=0) + + if stat_type == 'wind_power': + # Wind power density is proportional to cube of wind speed + return np.mean(speed**3, axis=0) + + if stat_type == 'calm_freq': + # Frequency of calm conditions (< 2 m/s, Beaufort 0-1) + calm_threshold = 2.0 + return np.mean(speed < calm_threshold, axis=0) * 100 + + if stat_type == 'light_breeze_freq': + # Frequency of light breeze (> 1.6 m/s, Beaufort 2+) + light_breeze_threshold = 1.6 + return np.mean(speed > light_breeze_threshold, axis=0) * 100 + + if stat_type == 'moderate_breeze_freq': + # Frequency of moderate breeze (> 5.5 m/s, Beaufort 4+) + moderate_breeze_threshold = 5.5 + return np.mean(speed > moderate_breeze_threshold, axis=0) * 100 + + if stat_type == 'strong_breeze_freq': + # Frequency of strong breeze (> 10.8 m/s, Beaufort 6+) + strong_breeze_threshold = 10.8 + return np.mean(speed > strong_breeze_threshold, axis=0) * 100 + + if stat_type == 'gale_freq': + # Frequency of fresh gale (> 17.2 m/s, Beaufort 8+) + gale_threshold = 17.2 + return np.mean(speed > gale_threshold, axis=0) * 100 + + if stat_type == 'prevailing_direction': + # Compute wind directions + direction = compute_wind_direction(u_data.values, v_data.values) + # Weight by wind speed for more meaningful prevailing direction + return circular_mean_direction(direction, weights=speed) + + if stat_type == 'direction_variability': + # Circular standard deviation of wind direction + direction = compute_wind_direction(u_data.values, v_data.values) + return circular_std(direction) + + if stat_type == 'mean_u': + return np.mean(u_data.values, axis=0) + + if stat_type == 'mean_v': + return np.mean(v_data.values, axis=0) + + raise ValueError(f"Unsupported wind statistic type: {stat_type}") + + +def plot_wind_stat_map(data, filename, stat_config, label): + """Plot a single wind statistic map with appropriate styling.""" + + if stat_config['type'] == 'mean_speed': + plot_map( + data, filename, + title=f'{label}: {stat_config["title_stat"]}', + label='Wind Speed [m/s]', vmin=0, vmax=10, cmap='inferno', extend='max' + ) + elif stat_config['type'] in ['quantile_speed', 'max_speed']: + plot_map( + data, filename, + title=f'{label}: {stat_config["title_stat"]}', + label='Wind Speed [m/s]', vmin=0, vmax=30, cmap='inferno', extend='max' + ) + elif stat_config['type'] == 'wind_power': + plot_map( + data, filename, + title=f'{label}: {stat_config["title_stat"]}', + label='Wind Power Density [m³/s³]', vmin=0, vmax=1000, cmap='plasma', extend='max' + ) + elif stat_config['type'] in ['calm_freq', 'light_breeze_freq', 'moderate_breeze_freq', 'strong_breeze_freq', 'gale_freq']: + plot_map( + data, filename, + title=f'{label}: {stat_config["title_stat"]}', + label='Frequency [%]', vmin=0, vmax=80, cmap='GnBu', extend='max' + ) + elif stat_config['type'] == 'prevailing_direction': + plot_map( + data, filename, + title=f'{label}: {stat_config["title_stat"]}', + label='Direction [degrees from N]', vmin=0, vmax=360, cmap='twilight', extend='neither' + ) + elif stat_config['type'] == 'direction_variability': + plot_map( + data, filename, + title=f'{label}: {stat_config["title_stat"]}', + label='Circular Std Dev [degrees]', vmin=20, vmax=140, cmap='viridis', extend='max' + ) + elif stat_config['type'] in ['mean_u', 'mean_v']: + plot_map( + data, filename, + title=f'{label}: {stat_config["title_stat"]}', + label='Wind Component [m/s]', vmin=-10, vmax=10, cmap='RdBu_r', extend='both' + ) + else: + plot_map( + data, filename, + title=f'{label}: {stat_config["title_stat"]}', + label='Value', vmin=None, vmax=None, cmap='viridis', extend='neither' + ) + + +@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") +def main(cfg: DictConfig): + # Setup and config + DistributedManager.initialize() + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + + logger.info("Starting wind statistics generation") + times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + logger.info(f"Processing {len(times)} timesteps") + + ds_cfg = OmegaConf.to_container(cfg.dataset) + dataset, _ = get_dataset_and_sampler_inference( + ds_cfg, times, cfg.generation.get('has_lead_time', False) + ) + out_root = Path(cfg.generation.io.output_path or './outputs') + indices = get_channel_indices(dataset) + + # Get U and V wind component indices + u10_out = indices['output'].get('10u') + v10_out = indices['output'].get('10v') + u10_in = indices['input'].get('10u', u10_out) + v10_in = indices['input'].get('10v', v10_out) + + + # Wind statistic configurations + WIND_STATISTICS_CONFIG = { + 'mean_speed': { + 'type': 'mean_speed', + 'title': 'Mean Wind Speed' + }, + 'p90_speed': { + 'type': 'quantile_speed', + 'param': 0.90, + 'title': '90th Percentile Wind Speed' + }, + 'max_speed': { + 'type': 'max_speed', + 'title': 'Maximum Wind Speed' + }, + 'wind_power': { + 'type': 'wind_power', + 'title': 'Mean Wind Power Density' + }, + 'calm_freq': { + 'type': 'calm_freq', + 'title': 'Calm Frequency (<2 m/s, Beaufort 0-1)' + }, + 'light_breeze_freq': { + 'type': 'light_breeze_freq', + 'title': 'Light Breeze Frequency (>1.6 m/s, Beaufort 2+)' + }, + 'moderate_breeze_freq': { + 'type': 'moderate_breeze_freq', + 'title': 'Moderate Breeze Frequency (>5.5 m/s, Beaufort 4+)' + }, + 'strong_breeze_freq': { + 'type': 'strong_breeze_freq', + 'title': 'Strong Breeze Frequency (>10.8 m/s, Beaufort 6+)' + }, + 'gale_freq': { + 'type': 'gale_freq', + 'title': 'Gale Frequency (>17.2 m/s, Beaufort 8+)' + }, + 'prevailing_dir': { + 'type': 'prevailing_direction', + 'title': 'Prevailing Wind Direction' + }, + 'dir_variability': { + 'type': 'direction_variability', + 'title': 'Wind Direction Variability' + }, + 'mean_u': { + 'type': 'mean_u', + 'title': 'Mean U-Component' + }, + 'mean_v': { + 'type': 'mean_v', + 'title': 'Mean V-Component' + } + } + + stat_configs = [ + { + 'stat_name': name, + 'title_stat': config['title'], + 'param': config.get('param'), + **config + } + for name, config in WIND_STATISTICS_CONFIG.items() + ] + + # Target and baseline modes + basic_modes = { + 'target': ((u10_out, v10_out), 'COSMO-2 Analysis'), + 'baseline': ((u10_in, v10_in), 'ERA5'), + 'regression-prediction': ((u10_out, v10_out), 'Regression Prediction') + } + logger.info(f"Generating {len(stat_configs)} wind statistics for {len(basic_modes)} basic modes + predictions") + + for mode, (wind_channels, label) in basic_modes.items(): + logger.info(f"Processing mode: {mode}") + u_channel, v_channel = wind_channels + + # Load all timesteps for this mode + u_data_list = [] + v_data_list = [] + try: + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Loading {mode} timestep {i+1}/{len(times)}: {ts}") + data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False) + u_data_list.append(data[u_channel]) + v_data_list.append(data[v_channel]) + except Exception as e: + logger.warning(f"{mode} not available, skipping: {e}") + continue + + # Create xarray DataArrays + u_mode_data = xr.DataArray( + np.stack(u_data_list, axis=0), + dims=['time', 'lat', 'lon'], + coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} + ) + v_mode_data = xr.DataArray( + np.stack(v_data_list, axis=0), + dims=['time', 'lat', 'lon'], + coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} + ) + + # Compute and plot all statistics for this mode + for stat_config in stat_configs: + logger.info(f"Computing {stat_config['title_stat']} for {mode}...") + result = apply_wind_statistic( + u_mode_data, v_mode_data, + stat_config['type'], stat_config['param'] + ) + + map_output_dir = out_root / f"maps_wind_{stat_config['stat_name']}" + map_output_dir.mkdir(parents=True, exist_ok=True) + plot_wind_stat_map( + result, + str(map_output_dir / f'{mode}_{stat_config["stat_name"]}'), + stat_config, + label + ) + + # Predictions mode: process each member separately to save memory + logger.info("Processing predictions mode...") + try: + data = torch.load(out_root/times[0]/f"{times[0]}-predictions", weights_only=False) + n_members = data.shape[0] + logger.info(f"Found {n_members} ensemble members") + + for member_idx in range(n_members): + logger.info(f"Processing prediction member {member_idx+1}/{n_members}") + + # Load all timesteps for this member + u_data_list = [] + v_data_list = [] + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Loading prediction member {member_idx} timestep {i+1}/{len(times)}: {ts}") + pred_data = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) + u_data_list.append(pred_data[member_idx, u10_out]) + v_data_list.append(pred_data[member_idx, v10_out]) + + u_member_data = xr.DataArray( + np.stack(u_data_list, axis=0), + dims=['time', 'lat', 'lon'], + coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} + ) + v_member_data = xr.DataArray( + np.stack(v_data_list, axis=0), + dims=['time', 'lat', 'lon'], + coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} + ) + + # Compute and plot all statistics for this member + for stat_config in stat_configs: + logger.info(f"Computing {stat_config['title_stat']} for member {member_idx+1}...") + member_result = apply_wind_statistic( + u_member_data, v_member_data, + stat_config['type'], stat_config['param'] + ) + + # Create map + map_output_dir = out_root / f"maps_wind_{stat_config['stat_name']}" + map_output_dir.mkdir(parents=True, exist_ok=True) + member_filename = str(map_output_dir / f'prediction_member_{member_idx:02d}_{stat_config["stat_name"]}') + member_label = f'CorrDiff Member {member_idx+1}' + plot_wind_stat_map(member_result, member_filename, stat_config, member_label) + + except Exception as e: + logger.warning(f"Predictions not available, skipping: {e}") + + logger.info("All wind statistics maps generated successfully") + + +if __name__ == '__main__': + main() diff --git a/src/hirad/eval/probability_of_exceedance_wind.py b/src/hirad/eval/probability_of_exceedance_wind.py new file mode 100644 index 00000000..6fe3ff70 --- /dev/null +++ b/src/hirad/eval/probability_of_exceedance_wind.py @@ -0,0 +1,344 @@ +"""Probability of exceedance for wind speed and components.""" +import logging +from pathlib import Path + +import hydra +import matplotlib.pyplot as plt +import numpy as np +import torch +from omegaconf import DictConfig, OmegaConf +import xarray as xr + +from hirad.datasets import get_dataset_and_sampler_inference +from hirad.distributed import DistributedManager +from hirad.utils.function_utils import get_time_from_range +from hirad.eval.plotting import get_channel_indices, LOG_INTERVAL + + +def compute_wind_speed(u, v): + """Compute wind speed from U and V components.""" + return np.hypot(u, v) + + +def compute_exceedance_probs(values, thresholds, use_abs=False): + """Compute exceedance probabilities.""" + if use_abs: + return np.array([np.mean(np.abs(values) > t) for t in thresholds]) + else: + return np.array([np.mean(values > t) for t in thresholds]) + + +def update_exceedance_counts(counts, total, values, thresholds, use_abs=False): + """Update exceedance counts incrementally.""" + data = np.abs(values) if use_abs else values + for i, threshold in enumerate(thresholds): + counts[i] += np.sum(data > threshold) + total += len(values) + return counts, total + + +def compute_percentiles(values, percentile_dict, use_abs=False): + """Compute percentiles.""" + data = np.abs(values) if use_abs else values + data_array = xr.DataArray(data) + return {key: data_array.quantile(p).item() for key, p in percentile_dict.items()} + + +def save_exceedance_plot(exceedance_data_dict, thresholds, labels, colors, title, ylabel, out_path, percentiles_data=None): + """Save probability of exceedance plot.""" + Path(out_path).parent.mkdir(parents=True, exist_ok=True) + + plt.figure(figsize=(10, 6)) + + # Plot exceedance curves + for (key, exceedance_data), label, color in zip(exceedance_data_dict.items(), labels, colors): + if isinstance(exceedance_data, tuple): # Handle ensemble data + # Plot individual members with transparency + for i, member_exceedance in enumerate(exceedance_data): + alpha = 0.5 if i > 0 else 0.7 + label_member = label if i == 0 else None + plt.plot(thresholds, member_exceedance, alpha=alpha, color=color, + label=label_member, linewidth=1) + else: + # Plot single dataset + plt.plot(thresholds, exceedance_data, alpha=0.7, color=color, + label=label, linewidth=2) + + plt.xscale('log') + plt.xlim(thresholds[1], thresholds[-1]) + plt.yscale('log') + plt.xlabel(ylabel) + plt.ylabel('Probability of Exceedance') + plt.ylim(1e-8, 1) + plt.title(title) + plt.grid(True, alpha=0.3) + + # Add percentile lines if provided + if percentiles_data: + # Calculate y-range for percentile lines (lowest 10% of log scale) + y_bottom, y_top = plt.ylim() + log_bottom, log_top = np.log10(y_bottom), np.log10(y_top) + vline_ymax = 10**(log_bottom + 0.1 * (log_top - log_bottom)) + vline_ymin = y_bottom + + # Define line styles for percentiles + percentile_styles = {99: '--', 99.9: ':', 99.99: '-.'} + percentile_labels = {99: '99th all-hour percentiles', 99.9: '99.9th all-hour percentiles', 99.99: '99.99th all-hour percentiles'} + colors_perc = {'target': 'blue', 'baseline': 'orange', 'predictions': 'green', 'regression-prediction': 'red'} + legend_added = set() + + # Plot all percentile lines + for dataset_name, data in percentiles_data.items(): + color = colors_perc[dataset_name] + + if dataset_name in ['target', 'baseline', 'regression-prediction']: + # Single dataset + for percentile, value in data.items(): + linestyle = percentile_styles[percentile] + legend_added.add(percentile) # Track percentiles for black legend entries + + plt.vlines(x=value, colors=color, ymin=vline_ymin, ymax=vline_ymax, + linestyles=linestyle, alpha=0.8) # No label here + else: + # Ensemble members + for member_data in data.values(): + for percentile, value in member_data.items(): + linestyle = percentile_styles[percentile] + legend_added.add(percentile) # Track percentiles for black legend entries + + plt.vlines(x=value, colors=color, ymin=vline_ymin, ymax=vline_ymax, + linestyles=linestyle, alpha=0.6) # No label here + + # Add black legend entries for percentiles (override the colored ones) + for percentile in [99, 99.9, 99.99]: + if percentile in legend_added: + plt.plot([], [], color='black', linestyle=percentile_styles[percentile], + label=percentile_labels[percentile]) + + plt.legend() + plt.tight_layout() + plt.savefig(out_path, dpi=300, bbox_inches='tight') + plt.close() + + +@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") +def main(cfg: DictConfig): + # Setup logging + DistributedManager.initialize() + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger(__name__) + + logger.info("Starting computation for probability of exceedance for wind speed") + times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + logger.info(f"Loaded {len(times)} timesteps to process") + + # Initialize dataset + ds_cfg = OmegaConf.to_container(cfg.dataset) + dataset, _ = get_dataset_and_sampler_inference( + ds_cfg, times, cfg.generation.get('has_lead_time', False) + ) + logger.info("Dataset and sampler initialized") + + # Output root + out_root = Path(cfg.generation.io.output_path or './outputs') + + # Find channel indices for wind components + indices = get_channel_indices(dataset) + u10_out = indices['output'].get('10u') + v10_out = indices['output'].get('10v') + u10_in = indices['input'].get('10u', u10_out) + v10_in = indices['input'].get('10v', v10_out) + + if u10_out is None or v10_out is None: + logger.error("Wind components (10u, 10v) not found in dataset!") + return + + logger.info(f"Wind component channel indices - output: 10u={u10_out}, 10v={v10_out}, input: 10u={u10_in}, 10v={v10_in}") + + # Define thresholds for exceedance calculation (same for all variables) + thresholds = np.logspace(-1, 1.5, 200) # From 0.1 to ~31.6 m/s + n_thresholds = len(thresholds) + + # Storage for exceedance counts (incremental computation) + exceedance_counts = { + 'speed': {}, 'u': {}, 'v': {} + } + totals = {'speed': {}, 'u': {}, 'v': {}} + + # Storage for percentile computation (collect samples) + percentile_samples = {'speed': {}, 'u': {}, 'v': {}} + + # -- Process target and baseline -- + for mode in ['target', 'baseline', 'regression-prediction']: + logger.info(f"Processing mode: {mode}") + + # Initialize counts + for var in ['speed', 'u', 'v']: + exceedance_counts[var][mode] = np.zeros(n_thresholds, dtype=np.int64) + totals[var][mode] = 0 + percentile_samples[var][mode] = [] + + try: + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Processing timestep {i+1}/{len(times)}") + + data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False) + + # Extract wind components + if mode in ['target', 'regression-prediction']: + u = data[u10_out] + v = data[v10_out] + else: # baseline + u = data[u10_in] + v = data[v10_in] + + wind_speed = compute_wind_speed(u, v) + + # Get valid values + valid_mask = ~np.isnan(wind_speed) + speed_vals = wind_speed[valid_mask].flatten() + u_vals = u[valid_mask].flatten() + v_vals = v[valid_mask].flatten() + + # Update exceedance counts incrementally + exceedance_counts['speed'][mode], totals['speed'][mode] = update_exceedance_counts( + exceedance_counts['speed'][mode], totals['speed'][mode], speed_vals, thresholds, use_abs=False + ) + exceedance_counts['u'][mode], totals['u'][mode] = update_exceedance_counts( + exceedance_counts['u'][mode], totals['u'][mode], u_vals, thresholds, use_abs=True + ) + exceedance_counts['v'][mode], totals['v'][mode] = update_exceedance_counts( + exceedance_counts['v'][mode], totals['v'][mode], v_vals, thresholds, use_abs=True + ) + + # Collect samples for percentiles (subsample to save memory) + sample_rate = max(1, len(speed_vals) // 10000) # Keep ~10k samples per timestep + percentile_samples['speed'][mode].extend(speed_vals[::sample_rate]) + percentile_samples['u'][mode].extend(u_vals[::sample_rate]) + percentile_samples['v'][mode].extend(v_vals[::sample_rate]) + + except Exception as e: + logger.warning(f"{mode} data not found or error occurred, skipping: {e}") + continue + + logger.info(f"Processed {totals['speed'][mode]} values for {mode}") + + # -- Process predictions: compute exceedance for each ensemble member -- + logger.info("Processing predictions") + + n_members = None + member_counts = {'speed': [], 'u': [], 'v': []} + member_totals = {'speed': [], 'u': [], 'v': []} + member_samples = {'speed': [], 'u': [], 'v': []} + + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Processing timestep {i+1}/{len(times)}") + + preds = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) # [n_members, n_channels, lat, lon] + + if n_members is None: + n_members = preds.shape[0] + for var in ['speed', 'u', 'v']: + member_counts[var] = [np.zeros(n_thresholds, dtype=np.int64) for _ in range(n_members)] + member_totals[var] = [0 for _ in range(n_members)] + member_samples[var] = [[] for _ in range(n_members)] + + for member_idx in range(n_members): + u = preds[member_idx, u10_out] + v = preds[member_idx, v10_out] + wind_speed = compute_wind_speed(u, v) + + valid_mask = ~np.isnan(wind_speed) + speed_vals = wind_speed[valid_mask].flatten() + u_vals = u[valid_mask].flatten() + v_vals = v[valid_mask].flatten() + + # Update counts + member_counts['speed'][member_idx], member_totals['speed'][member_idx] = update_exceedance_counts( + member_counts['speed'][member_idx], member_totals['speed'][member_idx], speed_vals, thresholds, use_abs=False + ) + member_counts['u'][member_idx], member_totals['u'][member_idx] = update_exceedance_counts( + member_counts['u'][member_idx], member_totals['u'][member_idx], u_vals, thresholds, use_abs=True + ) + member_counts['v'][member_idx], member_totals['v'][member_idx] = update_exceedance_counts( + member_counts['v'][member_idx], member_totals['v'][member_idx], v_vals, thresholds, use_abs=True + ) + + # Collect samples for percentiles + sample_rate = max(1, len(speed_vals) // 10000) + member_samples['speed'][member_idx].extend(speed_vals[::sample_rate]) + member_samples['u'][member_idx].extend(u_vals[::sample_rate]) + member_samples['v'][member_idx].extend(v_vals[::sample_rate]) + + logger.info(f"Collected {n_members} ensemble members for predictions") + + # Convert counts to probabilities + exceedance_data = {'speed': {}, 'u': {}, 'v': {}} + + for var in ['speed', 'u', 'v']: + # Single datasets + for mode in ['target', 'baseline', 'regression-prediction']: + if mode in exceedance_counts[var] and totals[var][mode] > 0: + exceedance_data[var][mode] = exceedance_counts[var][mode] / totals[var][mode] + + # Ensemble members + member_probs = [] + for member_idx in range(n_members): + if member_totals[var][member_idx] > 0: + member_probs.append(member_counts[var][member_idx] / member_totals[var][member_idx]) + exceedance_data[var]['predictions'] = tuple(member_probs) + + # Compute percentiles for all datasets and variables + percentiles = {99: 0.99, 99.9: 0.999, 99.99: 0.9999} + percentiles_data = {'speed': {}, 'u': {}, 'v': {}} + + # Single datasets (target, baseline, regression-prediction) + for var in ['speed', 'u', 'v']: + use_abs = (var in ['u', 'v']) + for mode in ['target', 'baseline', 'regression-prediction']: + if mode in percentile_samples[var] and len(percentile_samples[var][mode]) > 0: + percentiles_data[var][mode] = compute_percentiles( + np.array(percentile_samples[var][mode]), percentiles, use_abs + ) + + # Ensemble members + percentiles_data[var]['predictions'] = {} + for member_idx in range(n_members): + if len(member_samples[var][member_idx]) > 0: + percentiles_data[var]['predictions'][f'member_{member_idx}'] = compute_percentiles( + np.array(member_samples[var][member_idx]), percentiles, use_abs + ) + + # Create exceedance plots + labels = ['COSMO-2 Analysis', 'ERA5', 'Regression Prediction', 'CorrDiff Ensemble'] if 'regression-prediction' in exceedance_data['speed'] else ['COSMO-2 Analysis', 'ERA5', 'CorrDiff Ensemble'] + colors = ['blue', 'orange', 'red', 'green'] if 'regression-prediction' in exceedance_data['speed'] else ['blue', 'orange', 'green'] + + # Define plot configurations + plot_configs = [ + ('windspeed_exceedance.png', 'speed', 'Probability of Exceedance for Wind Speed', + 'All-hour Wind Speed [m/s] (Pooled Data)'), + ('wind_u_exceedance.png', 'u', 'Probability of Exceedance for abs(10u)', + 'All-hour 10u Component [m/s] (Pooled Data)'), + ('wind_v_exceedance.png', 'v', 'Probability of Exceedance for abs(10v)', + 'All-hour 10v Component [m/s] (Pooled Data)'), + ] + + for filename, var, title, ylabel in plot_configs: + fn = out_root / filename + save_exceedance_plot( + exceedance_data[var], + thresholds, + labels, + colors, + title, + ylabel, + fn, + percentiles_data[var] + ) + logger.info(f"{var.capitalize()} exceedance plot saved: {fn}") + + +if __name__ == '__main__': + main() diff --git a/src/hirad/eval_precip.sh b/src/hirad/eval_precip.sh index 7574b07e..ec522d9d 100644 --- a/src/hirad/eval_precip.sh +++ b/src/hirad/eval_precip.sh @@ -49,7 +49,6 @@ srun --environment=./ci/edf/modulus_env.toml bash -c " # Diurnal cycle python src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py --config-name=generate_era_cosmo.yaml python src/hirad/eval/diurnal_cycle_precip_p99.py --config-name=generate_era_cosmo.yaml - python src/hirad/eval/diurnal_cycle_temp_wind.py --config-name=generate_era_cosmo.yaml # TODO: Transfer to relevant script. # Histograms python src/hirad/eval/hist.py --config-name=generate_era_cosmo.yaml diff --git a/src/hirad/eval_wind.sh b/src/hirad/eval_wind.sh new file mode 100644 index 00000000..37194a61 --- /dev/null +++ b/src/hirad/eval_wind.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +#SBATCH --job-name="eval_wind" + +### HARDWARE ### +#SBATCH --partition=normal +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=2 +#SBATCH --gpus-per-node=2 +#SBATCH --cpus-per-task=72 +#SBATCH --time=6:00:00 +#SBATCH --no-requeue +#SBATCH --exclusive + +### OUTPUT ### +#SBATCH --output=./logs/plots_wind.log + +### ENVIRONMENT #### +#SBATCH -A a161 + +# Choose method to initialize dist in pythorch +export DISTRIBUTED_INITIALIZATION_METHOD=SLURM + +MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" +echo "Master node : $MASTER_ADDR" +# Get IP for hostname. +MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" +echo "Master address : $MASTER_ADDR" +export MASTER_ADDR +export MASTER_PORT=29500 +echo "Master port: $MASTER_PORT" + +# Get number of physical cores using Python +# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") +# # Use SLURM_NTASKS (number of processes to be launched by torchrun) +# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} +# # Compute threads per process +# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) +# export OMP_NUM_THREADS=$OMP_THREADS +export OMP_NUM_THREADS=72 + +srun --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . --no-dependencies + + # Diurnal cycle + python src/hirad/eval/diurnal_cycle_temp_wind.py --config-name=generate_era_cosmo.yaml + + # Maps + python src/hirad/eval/map_wind_stats.py --config-name=generate_era_cosmo.yaml + + # Probability of exceedance + python src/hirad/eval/probability_of_exceedance_wind.py --config-name=generate_era_cosmo.yaml +" \ No newline at end of file From 9783e101729fb8e0e08933e0dcac517740c83149 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 20 Oct 2025 13:10:33 +0200 Subject: [PATCH 168/302] first draft of regridding REA-L-CH1 --- src/hirad/input_data/regrid_realch1.py | 100 +++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/hirad/input_data/regrid_realch1.py diff --git a/src/hirad/input_data/regrid_realch1.py b/src/hirad/input_data/regrid_realch1.py new file mode 100644 index 00000000..b19c890c --- /dev/null +++ b/src/hirad/input_data/regrid_realch1.py @@ -0,0 +1,100 @@ + + +import datetime +import logging +import os +import shutil +import sys +import yaml +import array + +from anemoi.datasets import open_dataset +from anemoi.datasets.data.dataset import Dataset +import netCDF4 +import numpy as np +from pandas import to_datetime +from scipy.interpolate import griddata +from meteodatalab import icon_grid +from meteodatalab.operators import regrid +import torch +import multiprocessing +import xarray as xr +from meteodatalab import ogd_api + +def anemoi_to_xarray(anemoi_data: Dataset, variable): + lon = anemoi_data.longitudes + lat = anemoi_data.latitudes + eps = [0] # deterministic + time = generate_times(anemoi_data) + var_index = anemoi_data.variables.index(variable) + metadata = getMetadataFromOGD() + + ds = xr.Dataset( + data_vars=dict( + variable=(["time", "eps", "cell"], np.array(anemoi_data.data[:,var_index,:,:])), + ), + coords=dict( + eps=eps, + time=time, + lon=("cell", lon), + lat=("cell", lat), + ), + attrs=dict(description=f'xarray from anemoi dataset for {variable}', + metadata=metadata), + ) + print(ds) + return ds + +def getMetadataFromOGD(): + lead_times = ["P0DT0H"] + req = ogd_api.Request( + collection="ogd-forecasting-icon-ch1", + variable="TOT_PREC", + ref_time="latest", + perturbed=False, + lead_time=lead_times, + ) + tot_prec = ogd_api.get_from_ogd(req) + return tot_prec.metadata + +def generate_times(anemoi_data: Dataset): + times = [] + curr_time = anemoi_data.start_date.item() + while curr_time <= anemoi_data.end_date: + times.append(curr_time) + curr_time = curr_time + anemoi_data.frequency + return times + + + +def get_coeffs_path(model: str): + return coeffs_path + # TODO some value error check file avialable ofr sth. + +def remap(): + # get UUID for 1-km native grid + #icon_grid_uuid = get_uuid('icon-ch1-eps') + + coeffs_path = f'/store_new/mch/msopr/icon_workflow_2/iconremap-weights/{model}-rotlatlon.nc' + coeffs = xr.open_dataset(coeffs_path) + + indices = coeffs["rbf_B_glbidx"].values + weights = coeffs["rbf_B_wgt"].values + geo = { + "gridType": "rotated_ll", + "longitudeOfSouthernPoleInDegrees": coeffs.north_pole_lon - 180, + "latitudeOfSouthernPoleInDegrees": -1 * coeffs.north_pole_lat, + } + dst = RegularGrid( + crs=_get_crs(geo), + nx=coeffs.nx, + ny=coeffs.ny, + xmin=coeffs.xmin, + ymin=coeffs.ymin, + xmax=coeffs.xmax, + ymax=coeffs.ymax, + ) + +realch1 = open_dataset('/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.1.zarr') +myxarray = anemoi_to_xarray(realch1, "TOT_PREC") +regrid.icon2rotlatlon(myxarray) From 2d0d0f8111789fb3532ae33dbc5aa265e80c12ec Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 20 Oct 2025 13:11:05 +0200 Subject: [PATCH 169/302] remove unused function --- src/hirad/input_data/regrid_realch1.py | 30 -------------------------- 1 file changed, 30 deletions(-) diff --git a/src/hirad/input_data/regrid_realch1.py b/src/hirad/input_data/regrid_realch1.py index b19c890c..3aabef6c 100644 --- a/src/hirad/input_data/regrid_realch1.py +++ b/src/hirad/input_data/regrid_realch1.py @@ -64,36 +64,6 @@ def generate_times(anemoi_data: Dataset): times.append(curr_time) curr_time = curr_time + anemoi_data.frequency return times - - - -def get_coeffs_path(model: str): - return coeffs_path - # TODO some value error check file avialable ofr sth. - -def remap(): - # get UUID for 1-km native grid - #icon_grid_uuid = get_uuid('icon-ch1-eps') - - coeffs_path = f'/store_new/mch/msopr/icon_workflow_2/iconremap-weights/{model}-rotlatlon.nc' - coeffs = xr.open_dataset(coeffs_path) - - indices = coeffs["rbf_B_glbidx"].values - weights = coeffs["rbf_B_wgt"].values - geo = { - "gridType": "rotated_ll", - "longitudeOfSouthernPoleInDegrees": coeffs.north_pole_lon - 180, - "latitudeOfSouthernPoleInDegrees": -1 * coeffs.north_pole_lat, - } - dst = RegularGrid( - crs=_get_crs(geo), - nx=coeffs.nx, - ny=coeffs.ny, - xmin=coeffs.xmin, - ymin=coeffs.ymin, - xmax=coeffs.xmax, - ymax=coeffs.ymax, - ) realch1 = open_dataset('/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.1.zarr') myxarray = anemoi_to_xarray(realch1, "TOT_PREC") From 2aaa067e7d408101cfdd32b42bf893706771f9f4 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 20 Oct 2025 20:51:28 +0200 Subject: [PATCH 170/302] add conversion to geo coords --- src/hirad/input_data/regrid_realch1.py | 72 ++++++++++++++++++++------ 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/src/hirad/input_data/regrid_realch1.py b/src/hirad/input_data/regrid_realch1.py index 3aabef6c..3a9770fc 100644 --- a/src/hirad/input_data/regrid_realch1.py +++ b/src/hirad/input_data/regrid_realch1.py @@ -1,25 +1,18 @@ -import datetime import logging -import os -import shutil -import sys -import yaml -import array from anemoi.datasets import open_dataset from anemoi.datasets.data.dataset import Dataset -import netCDF4 import numpy as np -from pandas import to_datetime -from scipy.interpolate import griddata -from meteodatalab import icon_grid from meteodatalab.operators import regrid -import torch -import multiprocessing import xarray as xr from meteodatalab import ogd_api +from hirad.input_data.interpolate_basic import plot_projection + +import matplotlib.pyplot as plt +import cartopy.crs as ccrs +from earthkit.geo.rotate import unrotate def anemoi_to_xarray(anemoi_data: Dataset, variable): lon = anemoi_data.longitudes @@ -65,6 +58,55 @@ def generate_times(anemoi_data: Dataset): curr_time = curr_time + anemoi_data.frequency return times -realch1 = open_dataset('/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.1.zarr') -myxarray = anemoi_to_xarray(realch1, "TOT_PREC") -regrid.icon2rotlatlon(myxarray) +def get_geo_coords(regridded_data: xr.Dataset): + xmin = regridded_data.metadata.get("longitudeOfFirstGridPointInDegrees") + xmax = regridded_data.metadata.get("longitudeOfLastGridPointInDegrees") + dx = regridded_data.metadata.get("iDirectionIncrementInDegrees") + ymin = regridded_data.metadata.get("latitudeOfFirstGridPointInDegrees") + ymax = regridded_data.metadata.get("latitudeOfLastGridPointInDegrees") + dy = regridded_data.metadata.get("jDirectionIncrementInDegrees") + y = np.arange(ymin,ymax+dy,dy) + x = np.arange(xmin,xmax+dx,dx) + sp_lat = regridded_data.metadata.get("latitudeOfSouthernPoleInDegrees") + sp_lon = regridded_data.metadata.get("longitudeOfSouthernPoleInDegrees") + xcoords = np.meshgrid(x,y)[0].flatten() + ycoords = np.meshgrid(x,y)[1].flatten() + geo_coords = unrotate(ycoords, xcoords, sp_lat, sp_lon) + return geo_coords + +def plot_projection(ax, longitudes: np.array, latitudes: np.array, values: np.array, cmap=None, vmin = None, vmax = None): + p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) + ax.coastlines() + ax.gridlines(draw_labels=False) + plt.colorbar(p, orientation="horizontal") + +def plot_and_save_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, projection=ccrs.PlateCarree(), cmap=None, vmin = None, vmax = None): + """Plot observed or interpolated data in a scatter plot.""" + # TODO: Refactor this somehow, it's not really generalizing well across variables. + fig = plt.figure() + fig, ax = plt.subplots(subplot_kw={"projection": projection}) + logging.info(f'plotting values to {filename}') + plot_projection(ax, longitudes, latitudes, values, cmap, vmin, vmax) + #p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) + #ax.coastlines() + #ax.gridlines(draw_labels=True) + #plt.colorbar(p, orientation="horizontal") + plt.savefig(filename) + plt.close('all') + + +realch1 = open_dataset('/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.1.zarr', + ) +myxarray = anemoi_to_xarray(realch1, "TOT_PREC").to_dataarray() +regridded=regrid.icon2rotlatlon(myxarray) +plot_and_save_projection(realch1.longitudes, realch1.latitudes, + realch1[0,56,0,:], "anemoi.png") +plot_and_save_projection(myxarray.lon, myxarray.lat, + myxarray[0,0,0,:], "xarray.png") +# South pole rotation of lon=10, latitude=-43 +#rotated_crs = ccrs.RotatedPole( +# pole_longitude=190, pole_latitude=43 +#) +geo_coords = get_geo_coords(regridded) +plot_and_save_projection(geo_coords[1], geo_coords[0], + regridded[0,0,0,:], "regridded.png") \ No newline at end of file From 608b988165a54bceba1ec3acec354d0afd13effb Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 21 Oct 2025 17:15:44 +0200 Subject: [PATCH 171/302] use plotting function from interpolate_baisic --- src/hirad/input_data/interpolate_basic.py | 9 ++++---- src/hirad/input_data/regrid_realch1.py | 27 +++-------------------- 2 files changed, 8 insertions(+), 28 deletions(-) diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index e5953a1e..604d5e48 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -15,6 +15,7 @@ import torch import multiprocessing import xarray +from earthkit.geo.rotate import unrotate # Margin to use for ERA dataset (to avoid nans from interpolation at boundary) ERA_MARGIN_DEGREES = 1.0 @@ -76,11 +77,11 @@ def _interpolate_task(i: int, era: Dataset, cosmo: Dataset, input_grid: np.ndarr logging.info(f'plotting {datestr} to {outfile_plots_path}') for j,var in enumerate(era.variables): # plot era original - _plot_and_save_projection(era.longitudes, era.latitudes, era[i, j, 0, :], f'{outfile_plots_path}{era.variables[j]}-{datestr}-era.jpg') + plot_and_save_projection(era.longitudes, era.latitudes, era[i, j, 0, :], f'{outfile_plots_path}{era.variables[j]}-{datestr}-era.jpg') - _plot_and_save_projection(cosmo.longitudes, cosmo.latitudes, interpolated_data[j, 0, :], f'{outfile_plots_path}{era.variables[j]}-{datestr}-era-interpolated.jpg') + plot_and_save_projection(cosmo.longitudes, cosmo.latitudes, interpolated_data[j, 0, :], f'{outfile_plots_path}{era.variables[j]}-{datestr}-era-interpolated.jpg') for j,var in enumerate(cosmo.variables): - _plot_and_save_projection(cosmo.longitudes, cosmo.latitudes, cosmo[i, j, 0, :], f'{outfile_plots_path}{cosmo.variables[j]}-{datestr}-cosmo.jpg') + plot_and_save_projection(cosmo.longitudes, cosmo.latitudes, cosmo[i, j, 0, :], f'{outfile_plots_path}{cosmo.variables[j]}-{datestr}-cosmo.jpg') @@ -163,7 +164,7 @@ def plot_projection(ax, longitudes: np.array, latitudes: np.array, values: np.ar ax.gridlines(draw_labels=False) plt.colorbar(p, orientation="horizontal") -def _plot_and_save_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, cmap=None, vmin = None, vmax = None): +def plot_and_save_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, cmap=None, vmin = None, vmax = None): """Plot observed or interpolated data in a scatter plot.""" # TODO: Refactor this somehow, it's not really generalizing well across variables. fig = plt.figure() diff --git a/src/hirad/input_data/regrid_realch1.py b/src/hirad/input_data/regrid_realch1.py index 3a9770fc..e81d12b8 100644 --- a/src/hirad/input_data/regrid_realch1.py +++ b/src/hirad/input_data/regrid_realch1.py @@ -8,7 +8,7 @@ from meteodatalab.operators import regrid import xarray as xr from meteodatalab import ogd_api -from hirad.input_data.interpolate_basic import plot_projection +from hirad.input_data.interpolate_basic import plot_and_save_projection import matplotlib.pyplot as plt import cartopy.crs as ccrs @@ -67,6 +67,7 @@ def get_geo_coords(regridded_data: xr.Dataset): dy = regridded_data.metadata.get("jDirectionIncrementInDegrees") y = np.arange(ymin,ymax+dy,dy) x = np.arange(xmin,xmax+dx,dx) + # TODO, this parameter is not producing what I want it to. sp_lat = regridded_data.metadata.get("latitudeOfSouthernPoleInDegrees") sp_lon = regridded_data.metadata.get("longitudeOfSouthernPoleInDegrees") xcoords = np.meshgrid(x,y)[0].flatten() @@ -74,29 +75,7 @@ def get_geo_coords(regridded_data: xr.Dataset): geo_coords = unrotate(ycoords, xcoords, sp_lat, sp_lon) return geo_coords -def plot_projection(ax, longitudes: np.array, latitudes: np.array, values: np.array, cmap=None, vmin = None, vmax = None): - p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) - ax.coastlines() - ax.gridlines(draw_labels=False) - plt.colorbar(p, orientation="horizontal") - -def plot_and_save_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, projection=ccrs.PlateCarree(), cmap=None, vmin = None, vmax = None): - """Plot observed or interpolated data in a scatter plot.""" - # TODO: Refactor this somehow, it's not really generalizing well across variables. - fig = plt.figure() - fig, ax = plt.subplots(subplot_kw={"projection": projection}) - logging.info(f'plotting values to {filename}') - plot_projection(ax, longitudes, latitudes, values, cmap, vmin, vmax) - #p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) - #ax.coastlines() - #ax.gridlines(draw_labels=True) - #plt.colorbar(p, orientation="horizontal") - plt.savefig(filename) - plt.close('all') - - -realch1 = open_dataset('/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.1.zarr', - ) +realch1 = open_dataset('/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.1.zarr') myxarray = anemoi_to_xarray(realch1, "TOT_PREC").to_dataarray() regridded=regrid.icon2rotlatlon(myxarray) plot_and_save_projection(realch1.longitudes, realch1.latitudes, From 705e652193801167ac4f9c068e12c2a9ec333e0c Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 21 Oct 2025 17:16:08 +0200 Subject: [PATCH 172/302] pip install meteodata-lab --- ci/docker/Dockerfile.corrdiff | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/docker/Dockerfile.corrdiff b/ci/docker/Dockerfile.corrdiff index 3187c84f..f4be0712 100644 --- a/ci/docker/Dockerfile.corrdiff +++ b/ci/docker/Dockerfile.corrdiff @@ -10,4 +10,5 @@ RUN pip install \ Cartopy==0.22.0 \ xskillscore \ scoringrules \ - mlflow + mlflow \ + meteodata-lab From 3a54d18e4910686586d971ea64615d6d1510856b Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 21 Oct 2025 17:16:58 +0200 Subject: [PATCH 173/302] config file for REA-L-CH1 --- src/hirad/input_data/realch1.yaml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/hirad/input_data/realch1.yaml diff --git a/src/hirad/input_data/realch1.yaml b/src/hirad/input_data/realch1.yaml new file mode 100644 index 00000000..6b0f7765 --- /dev/null +++ b/src/hirad/input_data/realch1.yaml @@ -0,0 +1,26 @@ +dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.1.zarr' +select: ['CLCH', 'CLCL', 'CLCM', 'CLCT', + 'FI_100', 'FI_1000', 'FI_150', 'FI_200', 'FI_250', 'FI_300', 'FI_400', + 'FI_50', 'FI_500', 'FI_600', 'FI_700', 'FI_850', 'FI_925', + 'FR_LAND', 'HSURF', + 'OMEGA_100', 'OMEGA_1000', 'OMEGA_150', 'OMEGA_200', 'OMEGA_250', + 'OMEGA_300', 'OMEGA_400', 'OMEGA_50', 'OMEGA_500', 'OMEGA_600', + 'OMEGA_700', 'OMEGA_850', 'OMEGA_925', + 'PLCOV', 'PMSL', 'PS', + 'QV_100', 'QV_1000', 'QV_150', 'QV_200', 'QV_250', 'QV_300', 'QV_400', + 'QV_50', 'QV_500', 'QV_600', 'QV_700', 'QV_850', 'QV_925', + 'SKC', 'SKT', 'SOILTYP', 'SSO_GAMMA', 'SSO_SIGMA', 'SSO_STDH', + 'SSO_THETA', 'TD_2M', 'TOT_PREC', 'TOT_PREC_6H', + 'T_100', 'T_1000', 'T_150', 'T_200', 'T_250', 'T_2M', 'T_300', 'T_400', + 'T_50', 'T_500', 'T_600', 'T_700', 'T_850', 'T_925', + 'U_100', 'U_1000', 'U_10M', 'U_150', 'U_200', 'U_250', 'U_300', 'U_400', + 'U_50', 'U_500', 'U_600', 'U_700', 'U_850', 'U_925', + 'V_100', 'V_1000', 'V_10M', 'V_150', 'V_200', 'V_250', 'V_300', 'V_400', + 'V_50', 'V_500', 'V_600', 'V_700', 'V_850', 'V_925', + 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', + 'insolation', 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude'] +# ALL REALCH1 CHANNELS. +start: 2020-01-01 +# start: 2015-11-29 +end: 2020-01-01 +# end: 2020-12-31 From 7e06e91055329faccf2523a3a37dbeeb2943e778 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 21 Oct 2025 17:18:33 +0200 Subject: [PATCH 174/302] skeleton for realch1 interpolatoin task --- src/hirad/input_data/interpolate_realch1.py | 80 +++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/hirad/input_data/interpolate_realch1.py diff --git a/src/hirad/input_data/interpolate_realch1.py b/src/hirad/input_data/interpolate_realch1.py new file mode 100644 index 00000000..13348659 --- /dev/null +++ b/src/hirad/input_data/interpolate_realch1.py @@ -0,0 +1,80 @@ + + +import datetime +import logging +import os +import shutil +import sys +import yaml +import array + +from anemoi.datasets import open_dataset +from anemoi.datasets.data.dataset import Dataset +import netCDF4 +import numpy as np +from pandas import to_datetime +from scipy.interpolate import griddata +from meteodatalab.operators import regrid +import torch +import multiprocessing +import xarray + +# Margin to use for ERA dataset (to avoid nans from interpolation at boundary) +ERA_MARGIN_DEGREES = 1.0 +COPERNICUS_FILES = ['/store_new/mch/msopr/hirad-gen/copernicus-datasets/tp-2015-2016.nc', + '/store_new/mch/msopr/hirad-gen/copernicus-datasets/tp-2017-2018.nc', + '/store_new/mch/msopr/hirad-gen/copernicus-datasets/tp-2019-2020.nc'] + +def _read_input(era_config_file: str, realch1_config_file: str, ) -> tuple[Dataset, Dataset, array.array]: + """ + Read both ERA and REA-L-CH1 data, and return the 2m + temperature values for the time range under COSMO. + """ + # trim edge removes boundary, we will use the same + with open(realch1_config_file) as realch1_file: + realch1_config = yaml.safe_load(realch1_file) + realch1 = open_dataset(realch1_config) + with open(era_config_file) as era_file: + era_config = yaml.safe_load(era_file) + era = open_dataset(era_config) + # Subset the ERA dataset to have REAL-CH-1 area/dates. + start_date = realch1.metadata()['start_date'] + end_date = realch1.metadata()['end_date'] + # load era5 2m-temperature in the time-range of cosmo + # area = N, W, S, E + min_lat = min(realch1.latitudes) - ERA_MARGIN_DEGREES + max_lat = max(realch1.latitudes) + ERA_MARGIN_DEGREES + min_lon = min(realch1.longitudes) - ERA_MARGIN_DEGREES + max_lon = max(realch1.longitudes) + ERA_MARGIN_DEGREES + era = open_dataset(era, start=start_date, end=end_date, + area=(max_lat, min_lon, min_lat, max_lon)) + + + copernicus_netcdf = [] + for f in COPERNICUS_FILES: + netcdf_data = netCDF4.Dataset(f) + copernicus_netcdf.append(netcdf_data) + + return (era, realch1, copernicus_netcdf) + + +def regrid_all(era: Dataset, realch1: Dataset, copernicus: array.array): + # iterate through the dates + realch1 = regrid_realch1 + # convert to xarray.dataarray + regrid.icon2rotlatlon + + pass + +def regrid_realch1(): + # Use the meteodatalab functions to regrid the realch1 anemoi data (one time point) + # onto the rotated lat lon + # save the output as torch + # return the np array + pass + +def regrid_era(): + # Take the output grid from realch1-regrid (rotated lat lon). + # regrid all variables *except* tp directly from era5 data + # regrid the pt variable from the netcdf data + # save the output as torch \ No newline at end of file From 1829e93486bab53896fa0ff664d67819a3d3418e Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 22 Oct 2025 09:51:43 +0200 Subject: [PATCH 175/302] Enhance generation process with randomized sampler over time. --- src/hirad/conf/generation/era_cosmo.yaml | 8 +- src/hirad/conf/model_size/normal.yaml | 2 +- src/hirad/inference/generate.py | 9 +- src/hirad/inference/generator.py | 14 +- src/hirad/utils/deterministic_sampler.py | 341 ----------------------- src/hirad/utils/stochastic_sampler.py | 277 ------------------ 6 files changed, 27 insertions(+), 624 deletions(-) delete mode 100644 src/hirad/utils/deterministic_sampler.py delete mode 100644 src/hirad/utils/stochastic_sampler.py diff --git a/src/hirad/conf/generation/era_cosmo.yaml b/src/hirad/conf/generation/era_cosmo.yaml index 3396d43d..a5302ff1 100644 --- a/src/hirad/conf/generation/era_cosmo.yaml +++ b/src/hirad/conf/generation/era_cosmo.yaml @@ -6,7 +6,13 @@ inference_mode: all # Choose between "all" (regression + diffusion), "regression" or "diffusion" # Patch size. Patch-based sampling will be utilized if these dimensions differ from # img_shape_x and img_shape_y -# overlap_pixels: 0 +randomize: True + # Whether to randomize the random seeds for each generation. If false, fixed seeds + # from 0 to num_ensembles-1 will be used for each time step in times/times_range. +random_seed: 129 + # Base random seed. This is only used when randomize is True. + # random seed will be set for numpy random module to have reproducible randomized generative process. + # Number of overlapping pixels between adjacent patches # boundary_pixels: 0 # Number of boundary pixels to be cropped out. 2 is recommanded to address the boundary diff --git a/src/hirad/conf/model_size/normal.yaml b/src/hirad/conf/model_size/normal.yaml index 96c29fbc..e746a2c6 100644 --- a/src/hirad/conf/model_size/normal.yaml +++ b/src/hirad/conf/model_size/normal.yaml @@ -24,4 +24,4 @@ model_args: # Per-resolution multipliers for the number of channels. channel_mult: [1, 2, 2, 2, 2] # Resolutions at which self-attention layers are applied. - attn_resolutions: [28] \ No newline at end of file + attn_resolutions: [22] \ No newline at end of file diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index 3790820e..f90f974c 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -239,7 +239,14 @@ def elapsed_time(self, _): ) image_tar = image_tar.to(device=device).to(torch.float32) # image_out, image_reg = generate_fn(image_lr,lead_time_label) - image_out, image_reg = generator.generate(image_lr,lead_time_label) + random_seed = cfg.generation.get("random_seed", None)+index if cfg.generation.get("randomize", False) and cfg.generation.get("random_seed", None) is not None else None + # print(f"On rank {dist.rank} using base random seed: {random_seed} for time index {time_index}") + image_out, image_reg = generator.generate( + image_lr, + lead_time_label, + randomize=cfg.generation.get("randomize", False), + random_seed=random_seed + ) if dist.rank == 0: batch_size = image_out.shape[0] diff --git a/src/hirad/inference/generator.py b/src/hirad/inference/generator.py index ad051a95..d3c95c57 100644 --- a/src/hirad/inference/generator.py +++ b/src/hirad/inference/generator.py @@ -2,6 +2,7 @@ from functools import partial import nvtx import numpy as np +import random import torch from torch.distributed import gather from hirad.utils.inference_utils import regression_step, diffusion_step @@ -32,8 +33,9 @@ def __init__(self, self.get_rank_batches() self.patching = None - def get_rank_batches(self): - seeds = list(np.arange(self.ensemble_size)) + def get_rank_batches(self, seeds=None): + if seeds is None: + seeds = list(np.arange(self.ensemble_size)) num_batches = ( (len(seeds) - 1) // (self.batch_size * self.dist.world_size) + 1 ) * self.dist.world_size @@ -63,7 +65,7 @@ def initialize_patching(self, img_shape, patch_shape, boundary_pix, overlap_pix) overlap_pix=overlap_pix, ) - def generate(self, image_lr, lead_time_label=None): + def generate(self, image_lr, lead_time_label=None, randomize=False, random_seed=None): with nvtx.annotate("generate_fn", color="green"): # (1, C, H, W) img_shape = image_lr.shape[-2:] @@ -86,6 +88,12 @@ def generate(self, image_lr, lead_time_label=None): mean_hr = image_reg[0:1] else: mean_hr = None + if randomize: + # Set random seed for numpy + if random_seed is not None: + np.random.seed((random_seed) % (1 << 31)) + seeds = np.random.randint(0, 1<<31, size=self.ensemble_size) + self.get_rank_batches(seeds=seeds) with nvtx.annotate("diffusion model", color="purple"): image_res = diffusion_step( net=self.net_res, diff --git a/src/hirad/utils/deterministic_sampler.py b/src/hirad/utils/deterministic_sampler.py deleted file mode 100644 index e502875e..00000000 --- a/src/hirad/utils/deterministic_sampler.py +++ /dev/null @@ -1,341 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. -# SPDX-FileCopyrightText: All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Callable, Literal, Optional - -import numpy as np -import nvtx -import torch - -from hirad.models import EDMPrecond - -# ruff: noqa: E731 - - -@nvtx.annotate(message="deterministic_sampler", color="red") -def deterministic_sampler( - net: torch.nn.Module, - latents: torch.Tensor, - img_lr: torch.Tensor, - class_labels: Optional[torch.Tensor] = None, - randn_like: Callable = torch.randn_like, - num_steps: int = 18, - sigma_min: Optional[float] = None, - sigma_max: Optional[float] = None, - rho: float = 7.0, - solver: Literal["heun", "euler"] = "heun", - discretization: Literal["vp", "ve", "iddpm", "edm"] = "edm", - schedule: Literal["vp", "ve", "linear"] = "linear", - scaling: Literal["vp", "none"] = "none", - epsilon_s: float = 1e-3, - C_1: float = 0.001, - C_2: float = 0.008, - M: int = 1000, - alpha: float = 1.0, - S_churn: int = 0, - S_min: float = 0.0, - S_max: float = float("inf"), - S_noise: float = 1.0, -) -> torch.Tensor: - """ - Generalized sampler, representing the superset of all sampling methods - discussed in the paper "Elucidating the Design Space of Diffusion-Based - Generative Models" (EDM). - - https://arxiv.org/abs/2206.00364 - - This function integrates an ODE (probability flow) or SDE over multiple - time-steps to generate samples from the diffusion model provided by the - argument 'net'. It can be used to combine multiple choices to - design a custom sampler, including multiple integration solver, - discretization method, noise schedule, and so on. - - Parameters: - ----------- - net : torch.nn.Module - The diffusion model to use in the sampling process. - latents : torch.Tensor - The latent random noise used as the initial condition for the - stochastic ODE. - img_lr : torch.Tensor - Low-resolution input image for conditioning the diffusion process. - Passed as a keywork argument to the model 'net'. - class_labels : Optional[torch.Tensor] - Labels of the classes used as input to a class-conditionned - diffusion model. Passed as a keyword argument to the model 'net'. - If provided, it must be a tensor containing integer values. - Defaults to None, in which case it is ignored. - randn_like: Callable - Random Number Generator to generate random noise that is added - during the stochastic sampling. Must have the same signature as - torch.randn_like and return torch.Tensor. Defaults to - torch.randn_like. - num_steps : Optional[int] - Number of time-steps for the stochastic ODE integration. Defaults - to 18. - sigma_min : Optional[float] - Minimum noise level for the diffusion process. 'sigma_min', - 'sigma_max', and 'rho' are used to compute the time-step - discretization, based on the choice of discretization. For the - default choice ("discretization='heun'"), the noise level schedule - is computed as: - :math:`\sigma_i = (\sigma_{max}^{1/\rho} + i / (num_steps - 1) * (\sigma_{min}^{1/\rho} - \sigma_{max}^{1/\rho}))^{rho}`. - For other choices of 'discretization', see details in the EDM - paper. Defaults to None, in which case defaults values depending - of the specified discretization are used. - sigma_max : Optional[float] - Maximum noise level for the diffusion process. See sigma_min for - details. Defaults to None, in which case defaults values depending - of the specified discretization are used. - rho : float, optional - Exponent used in the noise schedule. See sigma_min for details. - Only used when 'discretization' is 'heun'. Values in the range [5, - 10] produce better images. Lower values lead to truncation errors - equalized over all time steps. Defaults to 7. - solver : Literal["heun", "euler"] - The numerical method used to integrate the stochastic ODE. "euler" - is 1st order solver, which is faster but produces lower-quality - images. "heun" is 2nd order, more expensive, but produces - higher-quality images. Defaults to "heun". - discretization : Literal["vp", "ve", "iddpm", "edm"] - The method to discretize time-steps :math:`t_i` in the - diffusion process. See the EDM papper for details. Defaults to - "edm". - schedule : Literal["vp", "ve", "linear"] - The type of noise level schedule. Defaults to "linear". If - schedule='ve', then :math:`\sigma(t) = \sqrt{t}`. If - schedule='linear', then :math:`\sigma(t) = t`. If schedule='vp', - see EDM paper for details. Defaults to "linear". - scaling : Literal["vp", "none"] - The type of time-dependent signal scaling :math:`s(t)`, such that - :math:`x = s(t) \hat{x}`. See EDM paper for details on the 'vp' - scaling. Defaults to 'none', in which case :math:`s(t)=1`. - epsilon_s : float, optional - Parameter to compute both the noise level schedule and the - time-step discetization. Only used when discretization='vp' or - schedule='vp'. Ignored in other cases. Defaults to 1e-3. - C_1 : float, optional - Parameters to compute the time-step discetization. Only used when - discretization='iddpm'. Defaults to 0.001. - C_2 : float, optional - Same as for C_1. Only used when discretization='iddpm'. Defaults to - 0.008. - M : int, optional - Same as for C_1 and C_2. Only used when discretization='iddpm'. - Defaults to 1000. - alpha : float, optional - Controls (i.e. multiplies) the step size :math:`t_{i+1} - - \hat{t}_i` in the stochastic sampler, where :math:`\hat{t}_i` is - the temporarily increased noise level. Defaults to 1.0, which is - the recommended value. - S_churn : int, optional - Controls the amount of stochasticty injected in the SDE in the - stochatsic sampler. Larger values of S_churn lead to larger values - of :math:`\hat{t}_i`, which in turn lead to injecting more - stochasticity in the SDE by Defaults to 0, which means no - stochasticity is injected. - S_min : float, optional - S_min and S_max control the time-step range obver which - stochasticty is injected in the SDE. Stochasticity is injected - through `\hat{t}_i` for time-steps :math:`t_i` such that - :math:`S_{min} \leq t_i \leq S_{max}`. Defaults to 0.0. - S_max : float, optional - See S_min. Defaults to float("inf"). - S_noise : float, optional - Controls the amount of stochasticty injected in the SDE in the - stochatsic sampler. Added signal noise is proportinal to - :math:`\epsilon_i` where `\epsilon_i ~ N(0, S_{noise}^2)`. Defaults - to 1.0. - - Returns - ------- - torch.Tensor: - Generated batch of samples. Same shape as the input 'latents'. - """ - - # conditioning - x_lr = img_lr - - if solver not in ["euler", "heun"]: - raise ValueError(f"Unknown solver {solver}") - if discretization not in ["vp", "ve", "iddpm", "edm"]: - raise ValueError(f"Unknown discretization {discretization}") - if schedule not in ["vp", "ve", "linear"]: - raise ValueError(f"Unknown schedule {schedule}") - if scaling not in ["vp", "none"]: - raise ValueError(f"Unknown scaling {scaling}") - - # Helper functions for VP & VE noise level schedules. - vp_sigma = ( - lambda beta_d, beta_min: lambda t: ( - np.e ** (0.5 * beta_d * (t**2) + beta_min * t) - 1 - ) - ** 0.5 - ) - vp_sigma_deriv = ( - lambda beta_d, beta_min: lambda t: 0.5 - * (beta_min + beta_d * t) - * (sigma(t) + 1 / sigma(t)) - ) - vp_sigma_inv = ( - lambda beta_d, beta_min: lambda sigma: ( - (beta_min**2 + 2 * beta_d * (sigma**2 + 1).log()).sqrt() - beta_min - ) - / beta_d - ) - ve_sigma = lambda t: t.sqrt() - ve_sigma_deriv = lambda t: 0.5 / t.sqrt() - ve_sigma_inv = lambda sigma: sigma**2 - - # Select default noise level range based on the specified time step discretization. - if sigma_min is None: - vp_def = vp_sigma(beta_d=19.1, beta_min=0.1)(t=epsilon_s) - sigma_min = {"vp": vp_def, "ve": 0.02, "iddpm": 0.002, "edm": 0.002}[ - discretization - ] - if sigma_max is None: - vp_def = vp_sigma(beta_d=19.1, beta_min=0.1)(t=1) - sigma_max = {"vp": vp_def, "ve": 100, "iddpm": 81, "edm": 80}[discretization] - - # Adjust noise levels based on what's supported by the network. - sigma_min = max(sigma_min, net.sigma_min) - sigma_max = min(sigma_max, net.sigma_max) - - # Compute corresponding betas for VP. - vp_beta_d = ( - 2 - * (np.log(sigma_min**2 + 1) / epsilon_s - np.log(sigma_max**2 + 1)) - / (epsilon_s - 1) - ) - vp_beta_min = np.log(sigma_max**2 + 1) - 0.5 * vp_beta_d - - # Define time steps in terms of noise level. - step_indices = torch.arange(num_steps, dtype=torch.float64, device=latents.device) - if discretization == "vp": - orig_t_steps = 1 + step_indices / (num_steps - 1) * (epsilon_s - 1) - sigma_steps = vp_sigma(vp_beta_d, vp_beta_min)(orig_t_steps) - elif discretization == "ve": - orig_t_steps = (sigma_max**2) * ( - (sigma_min**2 / sigma_max**2) ** (step_indices / (num_steps - 1)) - ) - sigma_steps = ve_sigma(orig_t_steps) - elif discretization == "iddpm": - u = torch.zeros(M + 1, dtype=torch.float64, device=latents.device) - alpha_bar = lambda j: (0.5 * np.pi * j / M / (C_2 + 1)).sin() ** 2 - for j in torch.arange(M, 0, -1, device=latents.device): # M, ..., 1 - u[j - 1] = ( - (u[j] ** 2 + 1) / (alpha_bar(j - 1) / alpha_bar(j)).clip(min=C_1) - 1 - ).sqrt() - u_filtered = u[torch.logical_and(u >= sigma_min, u <= sigma_max)] - sigma_steps = u_filtered[ - ((len(u_filtered) - 1) / (num_steps - 1) * step_indices) - .round() - .to(torch.int64) - ] - else: - sigma_steps = ( - sigma_max ** (1 / rho) - + step_indices - / (num_steps - 1) - * (sigma_min ** (1 / rho) - sigma_max ** (1 / rho)) - ) ** rho - - # Define noise level schedule. - if schedule == "vp": - sigma = vp_sigma(vp_beta_d, vp_beta_min) - sigma_deriv = vp_sigma_deriv(vp_beta_d, vp_beta_min) - sigma_inv = vp_sigma_inv(vp_beta_d, vp_beta_min) - elif schedule == "ve": - sigma = ve_sigma - sigma_deriv = ve_sigma_deriv - sigma_inv = ve_sigma_inv - else: - sigma = lambda t: t - sigma_deriv = lambda t: 1 - sigma_inv = lambda sigma: sigma - - # Define scaling schedule. - if scaling == "vp": - s = lambda t: 1 / (1 + sigma(t) ** 2).sqrt() - s_deriv = lambda t: -sigma(t) * sigma_deriv(t) * (s(t) ** 3) - else: - s = lambda t: 1 - s_deriv = lambda t: 0 - - # Compute final time steps based on the corresponding noise levels. - t_steps = sigma_inv(net.round_sigma(sigma_steps)) - t_steps = torch.cat([t_steps, torch.zeros_like(t_steps[:1])]) # t_N = 0 - - # Main sampling loop. - t_next = t_steps[0] - x_next = latents.to(torch.float64) * (sigma(t_next) * s(t_next)) - for i, (t_cur, t_next) in enumerate(zip(t_steps[:-1], t_steps[1:])): # 0, ..., N-1 - x_cur = x_next - - # Increase noise temporarily. - gamma = ( - min(S_churn / num_steps, np.sqrt(2) - 1) - if S_min <= sigma(t_cur) <= S_max - else 0 - ) - t_hat = sigma_inv(net.round_sigma(sigma(t_cur) + gamma * sigma(t_cur))) - x_hat = s(t_hat) / s(t_cur) * x_cur + ( - sigma(t_hat) ** 2 - sigma(t_cur) ** 2 - ).clip(min=0).sqrt() * s(t_hat) * S_noise * randn_like(x_cur) - - # Euler step. - h = t_next - t_hat - if isinstance(net, EDMPrecond): - # Conditioning info is passed as keyword arg - denoised = net( - x_hat / s(t_hat), - sigma(t_hat), - condition=x_lr, - class_labels=class_labels, - ).to(torch.float64) - else: - denoised = net(x_hat / s(t_hat), x_lr, sigma(t_hat), class_labels).to( - torch.float64 - ) - d_cur = ( - sigma_deriv(t_hat) / sigma(t_hat) + s_deriv(t_hat) / s(t_hat) - ) * x_hat - sigma_deriv(t_hat) * s(t_hat) / sigma(t_hat) * denoised - x_prime = x_hat + alpha * h * d_cur - t_prime = t_hat + alpha * h - - # Apply 2nd order correction. - if solver == "euler" or i == num_steps - 1: - x_next = x_hat + h * d_cur - else: - if isinstance(net, EDMPrecond): - # Conditioning info is passed as keyword arg - denoised = net( - x_prime / s(t_prime), - sigma(t_prime), - condition=x_lr, - class_labels=class_labels, - ).to(torch.float64) - else: - denoised = net( - x_prime / s(t_prime), x_lr, sigma(t_prime), class_labels - ).to(torch.float64) - d_prime = ( - sigma_deriv(t_prime) / sigma(t_prime) + s_deriv(t_prime) / s(t_prime) - ) * x_prime - sigma_deriv(t_prime) * s(t_prime) / sigma(t_prime) * denoised - x_next = x_hat + h * ( - (1 - 1 / (2 * alpha)) * d_cur + 1 / (2 * alpha) * d_prime - ) - - return x_next diff --git a/src/hirad/utils/stochastic_sampler.py b/src/hirad/utils/stochastic_sampler.py deleted file mode 100644 index 198fde43..00000000 --- a/src/hirad/utils/stochastic_sampler.py +++ /dev/null @@ -1,277 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. -# SPDX-FileCopyrightText: All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from typing import Callable, Optional - -import torch -from torch import Tensor - -from hirad.utils.patching import GridPatching2D - - -def stochastic_sampler( - net: torch.nn.Module, - latents: torch.Tensor, - img_lr: torch.Tensor, - class_labels: Optional[Tensor] = None, - randn_like: Callable[[Tensor], Tensor] = torch.randn_like, - patching: Optional[GridPatching2D] = None, - mean_hr: Optional[torch.Tensor] = None, - lead_time_label: Optional[torch.Tensor] = None, - num_steps: int = 18, - sigma_min: float = 0.002, - sigma_max: float = 800, - rho: float = 7, - S_churn: float = 0, - S_min: float = 0, - S_max: float = float("inf"), - S_noise: float = 1, -) -> torch.Tensor: - """ - Proposed EDM sampler (Algorithm 2) with minor changes to enable - super-resolution and patch-based diffusion. - - Parameters - ---------- - net : torch.nn.Module - The neural network model that generates denoised images from noisy - inputs. - Expected signature: `net(x, x_lr, t_hat, class_labels, - lead_time_label=lead_time_label, embedding_selector=embedding_selector)`, - where: - x (torch.Tensor): Noisy input of shape (batch_size, C_out, H, W) - x_lr (torch.Tensor): Conditioning input of shape (batch_size, C_cond, H, W) - t_hat (torch.Tensor): Noise level of shape (batch_size, 1, 1, 1) or scalar - class_labels (torch.Tensor, optional): Optional class labels - lead_time_label (torch.Tensor, optional): Optional lead time labels - embedding_selector (callable, optional): Function to select - positional embeddings. Used for patch-based diffusion. - Returns: - torch.Tensor: Denoised prediction of shape (batch_size, C_out, H, W) - - Required attributes: - sigma_min (float): Minimum supported noise level for the model - sigma_max (float): Maximum supported noise level for the model - round_sigma (callable): Method to convert sigma values to tensor representation - latents : Tensor - The latent variables (e.g., noise) used as the initial input for the - sampler. Has shape (batch_size, C_out, img_shape_y, img_shape_x). - img_lr : Tensor - Low-resolution input image for conditioning the super-resolution - process. Must have shape (batch_size, C_lr, img_lr_ shape_y, - img_lr_shape_x). - class_labels : Optional[Tensor], optional - Class labels for conditional generation, if required by the model. By - default None. - randn_like : Callable[[Tensor], Tensor] - Function to generate random noise with the same shape as the input - tensor. - By default torch.randn_like. - patching : Optional[GridPatching2D], optional - A patching utility for patch-based diffusion. Implements methods to - extract patches from an image and batch the patches along `dim=0`. - Should also implement a `fuse` method to reconstruct the original image - from a batch of patches. See - :class:`physicsnemo.utils.patching.GridPatching2D` for details. By - default None, in which case non-patched diffusion is used. - mean_hr : Optional[Tensor], optional - Optional tensor containing mean high-resolution images for - conditioning. Must have same height and width as `img_lr`, with shape - (B_hr, C_hr, img_lr_shape_y, img_lr_shape_x) where the batch dimension - B_hr can be either 1, either equal to batch_size, or can be omitted. If - B_hr = 1 or is omitted, `mean_hr` will be expanded to match the shape - of `img_lr`. By default None. - lead_time_label : Optional[Tensor], optional - Optional lead time labels. By default None. - num_steps : int - Number of time steps for the sampler. By default 18. - sigma_min : float - Minimum noise level. By default 0.002. - sigma_max : float - Maximum noise level. By default 800. - rho : float - Exponent used in the time step discretization. By default 7. - S_churn : float - Churn parameter controlling the level of noise added in each step. By - default 0. - S_min : float - Minimum time step for applying churn. By default 0. - S_max : float - Maximum time step for applying churn. By default float("inf"). - S_noise : float - Noise scaling factor applied during the churn step. By default 1. - - Returns - ------- - Tensor - The final denoised image produced by the sampler. Same shape as - `latents`: (batch_size, C_out, img_shape_y, img_shape_x). - - See Also - -------- - :class:`physicsnemo.models.diffusion.EDMPrecondSuperResolution`: A model - wrapper that provides preconditioning for super-resolution diffusion - models and implements the required interface for this sampler. - """ - - # Adjust noise levels based on what's supported by the network. - # Proposed EDM sampler (Algorithm 2) with minor changes to enable super-resolution. - sigma_min = max(sigma_min, net.sigma_min) - sigma_max = min(sigma_max, net.sigma_max) - - if patching is not None and not isinstance(patching, GridPatching2D): - raise ValueError("patching must be an instance of GridPatching2D.") - - # Safety check: if patching is used then img_lr and latents must have same - # height and width, otherwise there is mismatch in the number - # of patches extracted to form the final batch_size. - if patching: - if img_lr.shape[-2:] != latents.shape[-2:]: - raise ValueError( - f"img_lr and latents must have the same height and width, " - f"but found {img_lr.shape[-2:]} vs {latents.shape[-2:]}. " - ) - # img_lr and latents must also have the same batch_size, otherwise mismatch - # when processed by the network - if img_lr.shape[0] != latents.shape[0]: - raise ValueError( - f"img_lr and latents must have the same batch size, but found " - f"{img_lr.shape[0]} vs {latents.shape[0]}." - ) - - # Time step discretization. - step_indices = torch.arange(num_steps, dtype=torch.float64, device=latents.device) - t_steps = ( - sigma_max ** (1 / rho) - + step_indices - / (num_steps - 1) - * (sigma_min ** (1 / rho) - sigma_max ** (1 / rho)) - ) ** rho - t_steps = torch.cat( - [net.round_sigma(t_steps), torch.zeros_like(t_steps[:1])] - ) # t_N = 0 - - batch_size = img_lr.shape[0] - - # conditioning = [mean_hr, img_lr, global_lr, pos_embd] - x_lr = img_lr - if mean_hr is not None: - if mean_hr.shape[-2:] != img_lr.shape[-2:]: - raise ValueError( - f"mean_hr and img_lr must have the same height and width, " - f"but found {mean_hr.shape[-2:]} vs {img_lr.shape[-2:]}." - ) - x_lr = torch.cat((mean_hr.expand(x_lr.shape[0], -1, -1, -1), x_lr), dim=1) - - # input and position padding + patching - if patching: - # Patched conditioning [x_lr, mean_hr] - # (batch_size * patch_num, C_in + C_out, patch_shape_y, patch_shape_x) - x_lr = patching.apply(input=x_lr, additional_input=img_lr) - - # Function to select the correct positional embedding for each patch - def patch_embedding_selector(emb): - # emb: (N_pe, image_shape_y, image_shape_x) - # return: (batch_size * patch_num, N_pe, patch_shape_y, patch_shape_x) - return patching.apply(emb[None].expand(batch_size, -1, -1, -1)) - - else: - patch_embedding_selector = None - - # Main sampling loop. - x_next = latents.to(torch.float64) * t_steps[0] - for i, (t_cur, t_next) in enumerate(zip(t_steps[:-1], t_steps[1:])): # 0, ..., N-1 - x_cur = x_next - # Increase noise temporarily. - gamma = S_churn / num_steps if S_min <= t_cur <= S_max else 0 - t_hat = net.round_sigma(t_cur + gamma * t_cur) - - x_hat = x_cur + (t_hat**2 - t_cur**2).sqrt() * S_noise * randn_like(x_cur) - - # Euler step. Perform patching operation on score tensor if patch-based - # generation is used denoised = net(x_hat, t_hat, - # class_labels,lead_time_label=lead_time_label).to(torch.float64) - - x_hat_batch = (patching.apply(input=x_hat) if patching else x_hat).to( - latents.device - ) - x_lr = x_lr.to(latents.device) - - if lead_time_label is not None: - denoised = net( - x_hat_batch, - x_lr, - t_hat, - class_labels, - lead_time_label=lead_time_label, - embedding_selector=patch_embedding_selector, - ).to(torch.float64) - else: - # print("Sizes") - # print(x_hat_batch.shape) - # print(x_lr.shape) - # print(t_hat) - # print(class_labels) - # print(global_index) - denoised = net( - x_hat_batch, - x_lr, - t_hat, - class_labels, - embedding_selector=patch_embedding_selector, - ).to(torch.float64) - if patching: - # Un-patch the denoised image - # (batch_size, C_out, img_shape_y, img_shape_x) - denoised = patching.fuse(input=denoised, batch_size=batch_size) - - d_cur = (x_hat - denoised) / t_hat - x_next = x_hat + (t_next - t_hat) * d_cur - - # Apply 2nd order correction. - if i < num_steps - 1: - # Patched input - # (batch_size * patch_num, C_out, patch_shape_y, patch_shape_x) - x_next_batch = (patching.apply(input=x_next) if patching else x_next).to( - latents.device - ) - - if lead_time_label is not None: - denoised = net( - x_next_batch, - x_lr, - t_next, - class_labels, - lead_time_label=lead_time_label, - embedding_selector=patch_embedding_selector, - ).to(torch.float64) - else: - denoised = net( - x_next_batch, - x_lr, - t_next, - class_labels, - embedding_selector=patch_embedding_selector, - ).to(torch.float64) - if patching: - # Un-patch the denoised image - # (batch_size, C_out, img_shape_y, img_shape_x) - denoised = patching.fuse(input=denoised, batch_size=batch_size) - - d_prime = (x_next - denoised) / t_next - x_next = x_hat + (t_next - t_hat) * (0.5 * d_cur + 0.5 * d_prime) - return x_next From ae6d8ffb836e0878eb84eb26e668026300fdf6e5 Mon Sep 17 00:00:00 2001 From: David Leutwyler <14977216+leuty@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:53:47 +0100 Subject: [PATCH 176/302] Wind eval streaming (#24) --- src/hirad/eval/map_wind_stats.py | 454 ++++++++++++++++--------------- src/hirad/eval_wind.sh | 2 +- 2 files changed, 242 insertions(+), 214 deletions(-) diff --git a/src/hirad/eval/map_wind_stats.py b/src/hirad/eval/map_wind_stats.py index ee6855a0..ac632243 100644 --- a/src/hirad/eval/map_wind_stats.py +++ b/src/hirad/eval/map_wind_stats.py @@ -1,12 +1,10 @@ import logging -from datetime import datetime from pathlib import Path import hydra import numpy as np import torch from omegaconf import DictConfig, OmegaConf -import xarray as xr from hirad.datasets import get_dataset_and_sampler_inference from hirad.distributed import DistributedManager @@ -15,150 +13,123 @@ def compute_wind_speed(u, v): - """Compute wind speed from U and V components.""" + """Compute wind speed from U and V.""" return np.hypot(u, v) -def compute_wind_direction(u, v): - """Compute wind direction in degrees (meteorological convention: direction FROM which wind blows). - - Returns angle in degrees: 0° = North, 90° = East, 180° = South, 270° = West - """ - # atan2(u, v) gives mathematical angle, convert to meteorological - direction = np.rad2deg(np.arctan2(u, v)) + 180 - direction = np.mod(direction, 360) - return direction - - -def circular_mean_direction(directions, weights=None): - """Calculate circular mean of wind directions in degrees. - - Args: - directions: Array of directions in degrees - weights: Optional weights (e.g., wind speeds) - """ - # Convert to radians - rad = np.deg2rad(directions) - - # Calculate weighted mean of sin and cos components - if weights is not None: - sin_mean = np.average(np.sin(rad), weights=weights, axis=0) - cos_mean = np.average(np.cos(rad), weights=weights, axis=0) - else: - sin_mean = np.mean(np.sin(rad), axis=0) - cos_mean = np.mean(np.cos(rad), axis=0) - - # Calculate mean direction - mean_dir = np.arctan2(sin_mean, cos_mean) - mean_dir_deg = np.rad2deg(mean_dir) - mean_dir_deg = np.mod(mean_dir_deg, 360) - - return mean_dir_deg - - -def circular_std(directions): - """Calculate circular standard deviation of wind directions in degrees. - - Returns values from 0 (perfect alignment) to ~81.03 degrees (uniform distribution) - """ - rad = np.deg2rad(directions) - - # Calculate mean resultant length - sin_mean = np.mean(np.sin(rad), axis=0) - cos_mean = np.mean(np.cos(rad), axis=0) - R = np.hypot(sin_mean, cos_mean) - - # Circular standard deviation - # Handle R=0 case to avoid log(0) - R = np.clip(R, 1e-10, 1.0) - circ_std = np.rad2deg(np.sqrt(-2 * np.log(R))) - - return circ_std +def compute_wind_direction(u, v, calm_threshold=0.0): + """Compute wind direction in degrees from N.""" + dir_deg = (np.degrees(np.arctan2(-u, -v)) % 360) + if calm_threshold > 0: + speed = np.hypot(u, v) + dir_deg = np.where(speed <= calm_threshold, np.nan, dir_deg) + return dir_deg -def apply_wind_statistic(u_data, v_data, stat_type, stat_param=None): - """Apply a wind statistic to U and V components along the time dimension. +def apply_wind_statistic_streaming(times, out_root, mode, u_channel, v_channel, stat_type, stat_param=None): + """Compute wind statistic by streaming through timesteps.""" + accumulator = None + count = 0 + sin_acc = cos_acc = speed_acc = None - Args: - u_data: xarray DataArray of U wind component - v_data: xarray DataArray of V wind component - stat_type: Type of statistic to compute - stat_param: Optional parameter for the statistic (e.g., quantile value) - - Returns: - Result as numpy array - """ - # Compute wind speed - speed = compute_wind_speed(u_data.values, v_data.values) - - if stat_type == 'mean_speed': - return np.mean(speed, axis=0) - - if stat_type == 'quantile_speed': - return np.quantile(speed, stat_param, axis=0) - - if stat_type == 'max_speed': - return np.max(speed, axis=0) - - if stat_type == 'wind_power': - # Wind power density is proportional to cube of wind speed - return np.mean(speed**3, axis=0) - - if stat_type == 'calm_freq': - # Frequency of calm conditions (< 2 m/s, Beaufort 0-1) - calm_threshold = 2.0 - return np.mean(speed < calm_threshold, axis=0) * 100 - - if stat_type == 'light_breeze_freq': - # Frequency of light breeze (> 1.6 m/s, Beaufort 2+) - light_breeze_threshold = 1.6 - return np.mean(speed > light_breeze_threshold, axis=0) * 100 - - if stat_type == 'moderate_breeze_freq': - # Frequency of moderate breeze (> 5.5 m/s, Beaufort 4+) - moderate_breeze_threshold = 5.5 - return np.mean(speed > moderate_breeze_threshold, axis=0) * 100 - - if stat_type == 'strong_breeze_freq': - # Frequency of strong breeze (> 10.8 m/s, Beaufort 6+) - strong_breeze_threshold = 10.8 - return np.mean(speed > strong_breeze_threshold, axis=0) * 100 - - if stat_type == 'gale_freq': - # Frequency of fresh gale (> 17.2 m/s, Beaufort 8+) - gale_threshold = 17.2 - return np.mean(speed > gale_threshold, axis=0) * 100 + for ts in times: + data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False) + u = data[u_channel].cpu().numpy() if torch.is_tensor(data[u_channel]) else data[u_channel] + v = data[v_channel].cpu().numpy() if torch.is_tensor(data[v_channel]) else data[v_channel] + + if stat_type == 'mean_speed': + speed = compute_wind_speed(u, v) + if accumulator is None: + accumulator = np.zeros_like(speed) + accumulator += speed + elif stat_type == 'max_speed': + speed = compute_wind_speed(u, v) + if accumulator is None: + accumulator = np.full_like(speed, -np.inf) + accumulator = np.maximum(accumulator, speed) + elif stat_type == 'wind_power': + speed = compute_wind_speed(u, v) + if accumulator is None: + accumulator = np.zeros_like(speed) + accumulator += speed**3 + elif stat_type == 'mean_u': + if accumulator is None: + accumulator = np.zeros_like(u) + accumulator += u + elif stat_type == 'mean_v': + if accumulator is None: + accumulator = np.zeros_like(v) + accumulator += v + elif stat_type in ['calm_freq', 'light_breeze_freq', 'moderate_breeze_freq', + 'strong_breeze_freq', 'gale_freq']: + speed = compute_wind_speed(u, v) + thresholds = { + 'calm_freq': 2.0, + 'light_breeze_freq': 1.6, + 'moderate_breeze_freq': 5.5, + 'strong_breeze_freq': 10.8, + 'gale_freq': 17.2 + } + threshold = thresholds[stat_type] + if accumulator is None: + accumulator = np.zeros_like(speed) + if stat_type == 'calm_freq': + accumulator += (speed < threshold).astype(float) + else: + accumulator += (speed > threshold).astype(float) + elif stat_type == 'prevailing_direction': + speed = compute_wind_speed(u, v) + direction = compute_wind_direction(u, v, calm_threshold=1.0) + rad = np.deg2rad(direction) + weighted_sin = np.sin(rad) * speed + weighted_cos = np.cos(rad) * speed + + if sin_acc is None: + sin_acc = np.zeros_like(weighted_sin) + cos_acc = np.zeros_like(weighted_cos) + speed_acc = np.zeros_like(speed) + + sin_acc += np.nan_to_num(weighted_sin, 0) + cos_acc += np.nan_to_num(weighted_cos, 0) + speed_acc += speed + elif stat_type == 'direction_variability': + direction = compute_wind_direction(u, v) + rad = np.deg2rad(direction) + + if sin_acc is None: + sin_acc = np.zeros_like(np.sin(rad)) + cos_acc = np.zeros_like(np.cos(rad)) + + sin_acc += np.sin(rad) + cos_acc += np.cos(rad) + + count += 1 + del data, u, v if stat_type == 'prevailing_direction': - # Compute wind directions - direction = compute_wind_direction(u_data.values, v_data.values) - # Weight by wind speed for more meaningful prevailing direction - return circular_mean_direction(direction, weights=speed) - - if stat_type == 'direction_variability': - # Circular standard deviation of wind direction - direction = compute_wind_direction(u_data.values, v_data.values) - return circular_std(direction) - - if stat_type == 'mean_u': - return np.mean(u_data.values, axis=0) - - if stat_type == 'mean_v': - return np.mean(v_data.values, axis=0) - - raise ValueError(f"Unsupported wind statistic type: {stat_type}") + mean_dir = np.arctan2(sin_acc / (speed_acc + 1e-10), cos_acc / (speed_acc + 1e-10)) + return np.mod(np.rad2deg(mean_dir), 360) + elif stat_type == 'direction_variability': + R = np.clip(np.hypot(sin_acc / count, cos_acc / count), 1e-10, 1.0) + return np.rad2deg(np.sqrt(-2 * np.log(R))) + elif stat_type == 'max_speed': + return accumulator + elif stat_type in ['calm_freq', 'light_breeze_freq', 'moderate_breeze_freq', + 'strong_breeze_freq', 'gale_freq']: + return (accumulator / count) * 100 + else: + return accumulator / count def plot_wind_stat_map(data, filename, stat_config, label): - """Plot a single wind statistic map with appropriate styling.""" - + """Plot wind statistic map.""" if stat_config['type'] == 'mean_speed': plot_map( data, filename, title=f'{label}: {stat_config["title_stat"]}', label='Wind Speed [m/s]', vmin=0, vmax=10, cmap='inferno', extend='max' ) - elif stat_config['type'] in ['quantile_speed', 'max_speed']: + elif stat_config['type'] == 'max_speed': plot_map( data, filename, title=f'{label}: {stat_config["title_stat"]}', @@ -192,7 +163,7 @@ def plot_wind_stat_map(data, filename, stat_config, label): plot_map( data, filename, title=f'{label}: {stat_config["title_stat"]}', - label='Wind Component [m/s]', vmin=-10, vmax=10, cmap='RdBu_r', extend='both' + label='Wind Component [m/s]', vmin=-5, vmax=5, cmap='RdBu_r', extend='both' ) else: plot_map( @@ -204,7 +175,6 @@ def plot_wind_stat_map(data, filename, stat_config, label): @hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") def main(cfg: DictConfig): - # Setup and config DistributedManager.initialize() logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -220,24 +190,17 @@ def main(cfg: DictConfig): out_root = Path(cfg.generation.io.output_path or './outputs') indices = get_channel_indices(dataset) - # Get U and V wind component indices u10_out = indices['output'].get('10u') v10_out = indices['output'].get('10v') u10_in = indices['input'].get('10u', u10_out) v10_in = indices['input'].get('10v', v10_out) - - # Wind statistic configurations + WIND_STATISTICS_CONFIG = { 'mean_speed': { 'type': 'mean_speed', 'title': 'Mean Wind Speed' }, - 'p90_speed': { - 'type': 'quantile_speed', - 'param': 0.90, - 'title': '90th Percentile Wind Speed' - }, 'max_speed': { 'type': 'max_speed', 'title': 'Maximum Wind Speed' @@ -294,111 +257,176 @@ def main(cfg: DictConfig): for name, config in WIND_STATISTICS_CONFIG.items() ] - # Target and baseline modes basic_modes = { 'target': ((u10_out, v10_out), 'COSMO-2 Analysis'), 'baseline': ((u10_in, v10_in), 'ERA5'), 'regression-prediction': ((u10_out, v10_out), 'Regression Prediction') } - logger.info(f"Generating {len(stat_configs)} wind statistics for {len(basic_modes)} basic modes + predictions") + logger.info(f"Generating {len(stat_configs)} statistics for {len(basic_modes)} modes + predictions") for mode, (wind_channels, label) in basic_modes.items(): logger.info(f"Processing mode: {mode}") u_channel, v_channel = wind_channels - # Load all timesteps for this mode - u_data_list = [] - v_data_list = [] try: - for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: - logger.info(f"Loading {mode} timestep {i+1}/{len(times)}: {ts}") - data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False) - u_data_list.append(data[u_channel]) - v_data_list.append(data[v_channel]) + test_data = torch.load(out_root/times[0]/f"{times[0]}-{mode}", weights_only=False) + del test_data except Exception as e: - logger.warning(f"{mode} not available, skipping: {e}") + logger.warning(f"{mode} not available: {e}") continue - # Create xarray DataArrays - u_mode_data = xr.DataArray( - np.stack(u_data_list, axis=0), - dims=['time', 'lat', 'lon'], - coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} - ) - v_mode_data = xr.DataArray( - np.stack(v_data_list, axis=0), - dims=['time', 'lat', 'lon'], - coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} - ) - - # Compute and plot all statistics for this mode for stat_config in stat_configs: logger.info(f"Computing {stat_config['title_stat']} for {mode}...") - result = apply_wind_statistic( - u_mode_data, v_mode_data, - stat_config['type'], stat_config['param'] - ) - - map_output_dir = out_root / f"maps_wind_{stat_config['stat_name']}" - map_output_dir.mkdir(parents=True, exist_ok=True) - plot_wind_stat_map( - result, - str(map_output_dir / f'{mode}_{stat_config["stat_name"]}'), - stat_config, - label - ) + try: + result = apply_wind_statistic_streaming( + times, out_root, mode, u_channel, v_channel, + stat_config['type'], stat_config.get('param') + ) + + map_output_dir = out_root / f"maps_wind_{stat_config['stat_name']}" + map_output_dir.mkdir(parents=True, exist_ok=True) + plot_wind_stat_map( + result, + str(map_output_dir / f'{mode}_{stat_config["stat_name"]}'), + stat_config, + label + ) + del result + except Exception as e: + logger.error(f"Failed {stat_config['title_stat']} for {mode}: {e}") + continue - # Predictions mode: process each member separately to save memory logger.info("Processing predictions mode...") try: data = torch.load(out_root/times[0]/f"{times[0]}-predictions", weights_only=False) n_members = data.shape[0] + del data logger.info(f"Found {n_members} ensemble members") for member_idx in range(n_members): - logger.info(f"Processing prediction member {member_idx+1}/{n_members}") - - # Load all timesteps for this member - u_data_list = [] - v_data_list = [] - for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: - logger.info(f"Loading prediction member {member_idx} timestep {i+1}/{len(times)}: {ts}") - pred_data = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) - u_data_list.append(pred_data[member_idx, u10_out]) - v_data_list.append(pred_data[member_idx, v10_out]) - - u_member_data = xr.DataArray( - np.stack(u_data_list, axis=0), - dims=['time', 'lat', 'lon'], - coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} - ) - v_member_data = xr.DataArray( - np.stack(v_data_list, axis=0), - dims=['time', 'lat', 'lon'], - coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} - ) + logger.info(f"Processing member {member_idx+1}/{n_members}") - # Compute and plot all statistics for this member for stat_config in stat_configs: logger.info(f"Computing {stat_config['title_stat']} for member {member_idx+1}...") - member_result = apply_wind_statistic( - u_member_data, v_member_data, - stat_config['type'], stat_config['param'] - ) + try: + def load_member_data(ts): + pred_data = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) + u_data = pred_data[member_idx, u10_out] + v_data = pred_data[member_idx, v10_out] + u = u_data.cpu().numpy() if torch.is_tensor(u_data) else u_data + v = v_data.cpu().numpy() if torch.is_tensor(v_data) else v_data + del pred_data + return u, v + + accumulator = None + count = 0 + sin_acc = cos_acc = speed_acc = None + + for i, ts in enumerate(times): + if i % LOG_INTERVAL == 0: + logger.info(f"Loading prediction member {member_idx} timestep {i+1}/{len(times)}: {ts}") + + u, v = load_member_data(ts) + + if stat_config['type'] == 'mean_speed': + speed = compute_wind_speed(u, v) + if accumulator is None: + accumulator = np.zeros_like(speed) + accumulator += speed + elif stat_config['type'] == 'max_speed': + speed = compute_wind_speed(u, v) + if accumulator is None: + accumulator = np.full_like(speed, -np.inf) + accumulator = np.maximum(accumulator, speed) + elif stat_config['type'] == 'wind_power': + speed = compute_wind_speed(u, v) + if accumulator is None: + accumulator = np.zeros_like(speed) + accumulator += speed**3 + elif stat_config['type'] == 'mean_u': + if accumulator is None: + accumulator = np.zeros_like(u) + accumulator += u + elif stat_config['type'] == 'mean_v': + if accumulator is None: + accumulator = np.zeros_like(v) + accumulator += v + elif stat_config['type'] in ['calm_freq', 'light_breeze_freq', + 'moderate_breeze_freq', 'strong_breeze_freq', + 'gale_freq']: + speed = compute_wind_speed(u, v) + thresholds = { + 'calm_freq': 2.0, + 'light_breeze_freq': 1.6, + 'moderate_breeze_freq': 5.5, + 'strong_breeze_freq': 10.8, + 'gale_freq': 17.2 + } + threshold = thresholds[stat_config['type']] + if accumulator is None: + accumulator = np.zeros_like(speed) + if stat_config['type'] == 'calm_freq': + accumulator += (speed < threshold).astype(float) + else: + accumulator += (speed > threshold).astype(float) + elif stat_config['type'] == 'prevailing_direction': + speed = compute_wind_speed(u, v) + direction = compute_wind_direction(u, v, calm_threshold=1.0) + rad = np.deg2rad(direction) + weighted_sin = np.sin(rad) * speed + weighted_cos = np.cos(rad) * speed + + if sin_acc is None: + sin_acc = np.zeros_like(weighted_sin) + cos_acc = np.zeros_like(weighted_cos) + speed_acc = np.zeros_like(speed) + + sin_acc += np.nan_to_num(weighted_sin, 0) + cos_acc += np.nan_to_num(weighted_cos, 0) + speed_acc += speed + elif stat_config['type'] == 'direction_variability': + direction = compute_wind_direction(u, v) + rad = np.deg2rad(direction) + + if sin_acc is None: + sin_acc = np.zeros_like(np.sin(rad)) + cos_acc = np.zeros_like(np.cos(rad)) + + sin_acc += np.sin(rad) + cos_acc += np.cos(rad) + + count += 1 + del u, v + + if stat_config['type'] == 'prevailing_direction': + mean_dir = np.arctan2(sin_acc / (speed_acc + 1e-10), cos_acc / (speed_acc + 1e-10)) + member_result = np.mod(np.rad2deg(mean_dir), 360) + elif stat_config['type'] == 'direction_variability': + R = np.clip(np.hypot(sin_acc / count, cos_acc / count), 1e-10, 1.0) + member_result = np.rad2deg(np.sqrt(-2 * np.log(R))) + elif stat_config['type'] == 'max_speed': + member_result = accumulator + elif stat_config['type'] in ['calm_freq', 'light_breeze_freq', + 'moderate_breeze_freq', 'strong_breeze_freq', + 'gale_freq']: + member_result = (accumulator / count) * 100 + else: + member_result = accumulator / count + + map_output_dir = out_root / f"maps_wind_{stat_config['stat_name']}" + map_output_dir.mkdir(parents=True, exist_ok=True) + member_filename = str(map_output_dir / f'prediction_member_{member_idx:02d}_{stat_config["stat_name"]}') + plot_wind_stat_map(member_result, member_filename, stat_config, f'CorrDiff Member {member_idx+1}') + del member_result - # Create map - map_output_dir = out_root / f"maps_wind_{stat_config['stat_name']}" - map_output_dir.mkdir(parents=True, exist_ok=True) - member_filename = str(map_output_dir / f'prediction_member_{member_idx:02d}_{stat_config["stat_name"]}') - member_label = f'CorrDiff Member {member_idx+1}' - plot_wind_stat_map(member_result, member_filename, stat_config, member_label) + except Exception as e: + logger.error(f"Failed {stat_config['title_stat']} for member {member_idx+1}: {e}") + continue except Exception as e: - logger.warning(f"Predictions not available, skipping: {e}") + logger.warning(f"Predictions not available: {e}") - logger.info("All wind statistics maps generated successfully") + logger.info("Wind statistics generation complete") if __name__ == '__main__': diff --git a/src/hirad/eval_wind.sh b/src/hirad/eval_wind.sh index 37194a61..df113e87 100644 --- a/src/hirad/eval_wind.sh +++ b/src/hirad/eval_wind.sh @@ -8,7 +8,7 @@ #SBATCH --ntasks-per-node=2 #SBATCH --gpus-per-node=2 #SBATCH --cpus-per-task=72 -#SBATCH --time=6:00:00 +#SBATCH --time=12:00:00 #SBATCH --no-requeue #SBATCH --exclusive From 68c3ae810278344ca9ea6bc04815b03ba7ca4398 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 28 Oct 2025 14:52:13 +0100 Subject: [PATCH 177/302] some updates to regridding (still not working) --- src/hirad/input_data/interpolate_basic.py | 12 ++-- src/hirad/input_data/regrid_realch1.py | 73 +++++++++++++++-------- 2 files changed, 55 insertions(+), 30 deletions(-) diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index 604d5e48..b68219b8 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -158,19 +158,19 @@ def _get_plot_indices(era: Dataset, cosmo: Dataset) -> np.ndarray[np.intp]: indices = np.where(box_lon*box_lat) return indices -def plot_projection(ax, longitudes: np.array, latitudes: np.array, values: np.array, cmap=None, vmin = None, vmax = None): - p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) +def plot_projection(ax, longitudes: np.array, latitudes: np.array, values: np.array, cmap=None, vmin = None, vmax = None, s = None): + p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax, s=s) ax.coastlines() - ax.gridlines(draw_labels=False) + ax.gridlines(draw_labels=True) plt.colorbar(p, orientation="horizontal") -def plot_and_save_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, cmap=None, vmin = None, vmax = None): +def plot_and_save_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, projection=ccrs.PlateCarree(), cmap=None, vmin = None, vmax = None, s = None): """Plot observed or interpolated data in a scatter plot.""" # TODO: Refactor this somehow, it's not really generalizing well across variables. fig = plt.figure() - fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) + fig, ax = plt.subplots(subplot_kw={"projection": projection}) logging.info(f'plotting values to {filename}') - plot_projection(ax, longitudes, latitudes, values, cmap, vmin, vmax) + plot_projection(ax, longitudes, latitudes, values, cmap, vmin, vmax, s) #p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) #ax.coastlines() #ax.gridlines(draw_labels=True) diff --git a/src/hirad/input_data/regrid_realch1.py b/src/hirad/input_data/regrid_realch1.py index e81d12b8..6f4c6807 100644 --- a/src/hirad/input_data/regrid_realch1.py +++ b/src/hirad/input_data/regrid_realch1.py @@ -35,7 +35,6 @@ def anemoi_to_xarray(anemoi_data: Dataset, variable): attrs=dict(description=f'xarray from anemoi dataset for {variable}', metadata=metadata), ) - print(ds) return ds def getMetadataFromOGD(): @@ -58,34 +57,60 @@ def generate_times(anemoi_data: Dataset): curr_time = curr_time + anemoi_data.frequency return times -def get_geo_coords(regridded_data: xr.Dataset): - xmin = regridded_data.metadata.get("longitudeOfFirstGridPointInDegrees") - xmax = regridded_data.metadata.get("longitudeOfLastGridPointInDegrees") - dx = regridded_data.metadata.get("iDirectionIncrementInDegrees") - ymin = regridded_data.metadata.get("latitudeOfFirstGridPointInDegrees") - ymax = regridded_data.metadata.get("latitudeOfLastGridPointInDegrees") - dy = regridded_data.metadata.get("jDirectionIncrementInDegrees") - y = np.arange(ymin,ymax+dy,dy) - x = np.arange(xmin,xmax+dx,dx) +def get_geo_coords(regridded_data: xr.Dataset, sp_lat=None, sp_lon=None): + xmin = regridded_data.metadata.get("longitudeOfFirstGridPointInDegrees") + xmax = regridded_data.metadata.get("longitudeOfLastGridPointInDegrees") + dx = regridded_data.metadata.get("iDirectionIncrementInDegrees") + ymin = regridded_data.metadata.get("latitudeOfFirstGridPointInDegrees") + ymax = regridded_data.metadata.get("latitudeOfLastGridPointInDegrees") + dy = regridded_data.metadata.get("jDirectionIncrementInDegrees") + y = np.arange(ymin,ymax+dy,dy) + x = np.arange(xmin,xmax+dx,dx) # TODO, this parameter is not producing what I want it to. - sp_lat = regridded_data.metadata.get("latitudeOfSouthernPoleInDegrees") - sp_lon = regridded_data.metadata.get("longitudeOfSouthernPoleInDegrees") - xcoords = np.meshgrid(x,y)[0].flatten() - ycoords = np.meshgrid(x,y)[1].flatten() - geo_coords = unrotate(ycoords, xcoords, sp_lat, sp_lon) - return geo_coords + if not sp_lat or not sp_lon: + sp_lat = regridded_data.metadata.get("latitudeOfSouthernPoleInDegrees") # -43.0. north_pole_lat = 43.0 + sp_lon = regridded_data.metadata.get("longitudeOfSouthernPoleInDegrees") # 10.0. north_pole_lon = 190.0 + xcoords = np.meshgrid(x,y)[0].flatten() + ycoords = np.meshgrid(x,y)[1].flatten() + logging.info(f'sp_lat = {sp_lat}, sp_lon = {sp_lon}') + #geo_coords = unrotate(ycoords, xcoords, sp_lat*-1, sp_lon-180) + #geo_coords = unrotate(ycoords, xcoords, 43, 190) + #geo_coords = unrotate(ycoords, xcoords, -43, 190) # close + #geo_coords = unrotate(ycoords, xcoords, -43, 10) # produces range: 26W-8W, 41S-51.5S. image correct orientation. + geo_coords = unrotate(ycoords, xcoords, sp_lat, sp_lon) + + return geo_coords, ycoords, xcoords +logging.basicConfig(level=logging.INFO) realch1 = open_dataset('/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.1.zarr') -myxarray = anemoi_to_xarray(realch1, "TOT_PREC").to_dataarray() +var = 'TD_2M' +var_index = realch1.variables.index(var) +myxarray = anemoi_to_xarray(realch1, var).to_dataarray() regridded=regrid.icon2rotlatlon(myxarray) + plot_and_save_projection(realch1.longitudes, realch1.latitudes, - realch1[0,56,0,:], "anemoi.png") + realch1[0,var_index,0,:], f'{var}-icon.png', s=0.01) plot_and_save_projection(myxarray.lon, myxarray.lat, - myxarray[0,0,0,:], "xarray.png") + myxarray[0,0,0,:], f'{var}-xarray.png', s=0.005) + +geo_coords, ycoords, xcoords = get_geo_coords(regridded) +logging.info(geo_coords) # South pole rotation of lon=10, latitude=-43 -#rotated_crs = ccrs.RotatedPole( -# pole_longitude=190, pole_latitude=43 -#) -geo_coords = get_geo_coords(regridded) +rotated_crs = ccrs.RotatedPole( + pole_longitude=190, pole_latitude=43 +) +plot_and_save_projection(xcoords, ycoords, + regridded[0,0,0,:], f'{var}-regridded-rotated-projection.png', + projection=rotated_crs) # picture looks accurate. plot_and_save_projection(geo_coords[1], geo_coords[0], - regridded[0,0,0,:], "regridded.png") \ No newline at end of file + regridded[0,0,0,:], f'{var}-regridded.png') + +# None of these work. +for sp_lat in [-43, 43]: + for sp_lon in [10, 170, 190, 350]: + geo_coords, ycoords, xcoords = get_geo_coords(regridded, sp_lat=sp_lat, sp_lon=sp_lon) + logging.info(geo_coords) + plot_and_save_projection(geo_coords[1], geo_coords[0], + regridded[0,0,0,:], f'{var}-regridded_{sp_lat}_{sp_lon}.png') + plot_and_save_projection(geo_coords[0], geo_coords[1], + regridded[0,0,0,:], f'{var}-regridded-reversed_{sp_lat}_{sp_lon}.png') \ No newline at end of file From 603f3d82af5d49a39c0d277934024b869eb9e2f3 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 28 Oct 2025 18:05:56 +0100 Subject: [PATCH 178/302] regridding is actually working now. --- src/hirad/input_data/regrid_realch1.py | 142 ++++++++++++++----------- 1 file changed, 77 insertions(+), 65 deletions(-) diff --git a/src/hirad/input_data/regrid_realch1.py b/src/hirad/input_data/regrid_realch1.py index 6f4c6807..7975c9d3 100644 --- a/src/hirad/input_data/regrid_realch1.py +++ b/src/hirad/input_data/regrid_realch1.py @@ -1,6 +1,7 @@ import logging +import sys from anemoi.datasets import open_dataset from anemoi.datasets.data.dataset import Dataset @@ -9,34 +10,43 @@ import xarray as xr from meteodatalab import ogd_api from hirad.input_data.interpolate_basic import plot_and_save_projection +import yaml import matplotlib.pyplot as plt import cartopy.crs as ccrs from earthkit.geo.rotate import unrotate -def anemoi_to_xarray(anemoi_data: Dataset, variable): - lon = anemoi_data.longitudes - lat = anemoi_data.latitudes - eps = [0] # deterministic - time = generate_times(anemoi_data) - var_index = anemoi_data.variables.index(variable) - metadata = getMetadataFromOGD() - - ds = xr.Dataset( - data_vars=dict( - variable=(["time", "eps", "cell"], np.array(anemoi_data.data[:,var_index,:,:])), - ), - coords=dict( - eps=eps, - time=time, - lon=("cell", lon), - lat=("cell", lat), - ), - attrs=dict(description=f'xarray from anemoi dataset for {variable}', - metadata=metadata), - ) - return ds +# Take anemoi dataset and provide xarray dataarrays for a set of variables. +# returns: list of xarray dataarrays, and list of variable indices (anemoi) +def anemoi_to_xarray(anemoi_data: Dataset, variables): + lon = anemoi_data.longitudes + lat = anemoi_data.latitudes + eps = [0] # deterministic + time = generate_times(anemoi_data) + metadata = getMetadataFromOGD() + dataarrays = [] + var_indices = [] + for variable in variables: + var_index = anemoi_data.variables.index(variable) + var_indices.append(var_index) + + ds = xr.Dataset( + data_vars=dict( + variable=(["time", "eps", "cell"], np.array(anemoi_data.data[:,var_index,:,:])), + ), + coords=dict( + eps=eps, + time=time, + lon=("cell", lon), + lat=("cell", lat), + ), + attrs=dict(description=f'xarray from anemoi dataset for {variable}', + metadata=metadata), + ) + dataarrays.append(ds.to_dataarray()) + return dataarrays, var_indices +# Run a request to get the metadata, so that we can fake out an xarray. def getMetadataFromOGD(): lead_times = ["P0DT0H"] req = ogd_api.Request( @@ -49,6 +59,7 @@ def getMetadataFromOGD(): tot_prec = ogd_api.get_from_ogd(req) return tot_prec.metadata +# Get array of times from the anemoi dataset def generate_times(anemoi_data: Dataset): times = [] curr_time = anemoi_data.start_date.item() @@ -57,7 +68,9 @@ def generate_times(anemoi_data: Dataset): curr_time = curr_time + anemoi_data.frequency return times -def get_geo_coords(regridded_data: xr.Dataset, sp_lat=None, sp_lon=None): +# get the geo coordinates for the rotated lat/lon dataset. +# returns np.array of lats and array of lons +def get_geo_coords(regridded_data: xr.Dataset): xmin = regridded_data.metadata.get("longitudeOfFirstGridPointInDegrees") xmax = regridded_data.metadata.get("longitudeOfLastGridPointInDegrees") dx = regridded_data.metadata.get("iDirectionIncrementInDegrees") @@ -66,51 +79,50 @@ def get_geo_coords(regridded_data: xr.Dataset, sp_lat=None, sp_lon=None): dy = regridded_data.metadata.get("jDirectionIncrementInDegrees") y = np.arange(ymin,ymax+dy,dy) x = np.arange(xmin,xmax+dx,dx) - # TODO, this parameter is not producing what I want it to. - if not sp_lat or not sp_lon: - sp_lat = regridded_data.metadata.get("latitudeOfSouthernPoleInDegrees") # -43.0. north_pole_lat = 43.0 - sp_lon = regridded_data.metadata.get("longitudeOfSouthernPoleInDegrees") # 10.0. north_pole_lon = 190.0 + sp_lat = regridded_data.metadata.get("latitudeOfSouthernPoleInDegrees") # -43.0. north_pole_lat = 43.0 + sp_lon = regridded_data.metadata.get("longitudeOfSouthernPoleInDegrees") # 10.0. north_pole_lon = 190.0 xcoords = np.meshgrid(x,y)[0].flatten() ycoords = np.meshgrid(x,y)[1].flatten() + # Expect south pole rotation of lon=10, latitude=-43 logging.info(f'sp_lat = {sp_lat}, sp_lon = {sp_lon}') - #geo_coords = unrotate(ycoords, xcoords, sp_lat*-1, sp_lon-180) - #geo_coords = unrotate(ycoords, xcoords, 43, 190) - #geo_coords = unrotate(ycoords, xcoords, -43, 190) # close - #geo_coords = unrotate(ycoords, xcoords, -43, 10) # produces range: 26W-8W, 41S-51.5S. image correct orientation. - geo_coords = unrotate(ycoords, xcoords, sp_lat, sp_lon) - - return geo_coords, ycoords, xcoords + rotated_crs = ccrs.RotatedPole( + pole_longitude=(sp_lon + 180) % 360, pole_latitude=sp_lat * -1 # 190, 43 + ) + # Project onto PlateCarree. Geodetic produces similar coordinates (within 10 nanometers) + dst_grid = ccrs.PlateCarree() + geo_coords = dst_grid.transform_points(rotated_crs, xcoords, ycoords) + lats = geo_coords[:,1] + lons = geo_coords[:,0] + return lats, lons + +def main(): + # yml format + realch1_config_file = sys.argv[1] + output_directory = sys.argv[2] + + with open(realch1_config_file) as realch1_file: + realch1_config = yaml.safe_load(realch1_file) + realch1 = open_dataset(realch1_config) logging.basicConfig(level=logging.INFO) + realch1 = open_dataset('/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.1.zarr') -var = 'TD_2M' -var_index = realch1.variables.index(var) -myxarray = anemoi_to_xarray(realch1, var).to_dataarray() -regridded=regrid.icon2rotlatlon(myxarray) - -plot_and_save_projection(realch1.longitudes, realch1.latitudes, - realch1[0,var_index,0,:], f'{var}-icon.png', s=0.01) -plot_and_save_projection(myxarray.lon, myxarray.lat, - myxarray[0,0,0,:], f'{var}-xarray.png', s=0.005) - -geo_coords, ycoords, xcoords = get_geo_coords(regridded) -logging.info(geo_coords) -# South pole rotation of lon=10, latitude=-43 -rotated_crs = ccrs.RotatedPole( - pole_longitude=190, pole_latitude=43 -) -plot_and_save_projection(xcoords, ycoords, - regridded[0,0,0,:], f'{var}-regridded-rotated-projection.png', - projection=rotated_crs) # picture looks accurate. -plot_and_save_projection(geo_coords[1], geo_coords[0], - regridded[0,0,0,:], f'{var}-regridded.png') - -# None of these work. -for sp_lat in [-43, 43]: - for sp_lon in [10, 170, 190, 350]: - geo_coords, ycoords, xcoords = get_geo_coords(regridded, sp_lat=sp_lat, sp_lon=sp_lon) - logging.info(geo_coords) - plot_and_save_projection(geo_coords[1], geo_coords[0], - regridded[0,0,0,:], f'{var}-regridded_{sp_lat}_{sp_lon}.png') - plot_and_save_projection(geo_coords[0], geo_coords[1], - regridded[0,0,0,:], f'{var}-regridded-reversed_{sp_lat}_{sp_lon}.png') \ No newline at end of file +variables = ['TD_2M', 'TOT_PREC'] +myxarrays, var_indices = anemoi_to_xarray(realch1, variables) + +for i in range(len(variables)): + myxarray = myxarrays[i] + regridded=regrid.icon2rotlatlon(myxarray) + plot_and_save_projection(realch1.longitudes, realch1.latitudes, + realch1[0,var_indices[i],0,:], f'{variables[i]}-icon.png', s=0.005) + plot_and_save_projection(myxarray.lon, myxarray.lat, + myxarray[0,0,0,:], f'{variables[i]}-xarray.png', s=0.005) + + lats, lons = get_geo_coords(regridded) + + plot_and_save_projection(lons, lats, + regridded[0,0,0,:], f'{variables[i]}-regridded.png', s=0.005) + + +if __name__ == "__main__": + main() \ No newline at end of file From 87c5206d01c9094164ecad28e9d1d87240d016ab Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 28 Oct 2025 19:05:14 +0100 Subject: [PATCH 179/302] skeleton of full regrid script --- src/hirad/input_data/regrid_realch1.py | 71 ++++++++++++++++---------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/src/hirad/input_data/regrid_realch1.py b/src/hirad/input_data/regrid_realch1.py index 7975c9d3..b0fb0faf 100644 --- a/src/hirad/input_data/regrid_realch1.py +++ b/src/hirad/input_data/regrid_realch1.py @@ -1,6 +1,7 @@ import logging +import os import sys from anemoi.datasets import open_dataset @@ -17,22 +18,20 @@ from earthkit.geo.rotate import unrotate # Take anemoi dataset and provide xarray dataarrays for a set of variables. -# returns: list of xarray dataarrays, and list of variable indices (anemoi) -def anemoi_to_xarray(anemoi_data: Dataset, variables): +# returns: list of xarray dataarrays +def anemoi_to_xarray(anemoi_data: Dataset): lon = anemoi_data.longitudes lat = anemoi_data.latitudes eps = [0] # deterministic - time = generate_times(anemoi_data) + time = generate_times(anemoi_data) # anemoi_data.dates? metadata = getMetadataFromOGD() dataarrays = [] - var_indices = [] - for variable in variables: - var_index = anemoi_data.variables.index(variable) - var_indices.append(var_index) + variables = anemoi_data.variables + for var_index in range(anemoi_data.shape[1]): ds = xr.Dataset( data_vars=dict( - variable=(["time", "eps", "cell"], np.array(anemoi_data.data[:,var_index,:,:])), + variable=(["time", "eps", "cell"], np.array(anemoi_data[:,var_index,:,:])), ), coords=dict( eps=eps, @@ -40,18 +39,18 @@ def anemoi_to_xarray(anemoi_data: Dataset, variables): lon=("cell", lon), lat=("cell", lat), ), - attrs=dict(description=f'xarray from anemoi dataset for {variable}', + attrs=dict(description=f'xarray from anemoi dataset for {variables[var_index]}', metadata=metadata), ) dataarrays.append(ds.to_dataarray()) - return dataarrays, var_indices + return dataarrays # Run a request to get the metadata, so that we can fake out an xarray. def getMetadataFromOGD(): lead_times = ["P0DT0H"] req = ogd_api.Request( collection="ogd-forecasting-icon-ch1", - variable="TOT_PREC", + variable="TOT_PREC", #assuming this won't cause problems; we're only using grid info ref_time="latest", perturbed=False, lead_time=lead_times, @@ -99,29 +98,47 @@ def main(): # yml format realch1_config_file = sys.argv[1] output_directory = sys.argv[2] + if not os.path.exists(output_directory): + os.mkdir(output_directory) + for subdir in ['info', 'plots', 'realch1']: + if not os.path.exists(os.path.join(output_directory, subdir)): + os.mkdir(os.path.join(output_directory, subdir)) with open(realch1_config_file) as realch1_file: realch1_config = yaml.safe_load(realch1_file) realch1 = open_dataset(realch1_config) + variables = realch1.variables -logging.basicConfig(level=logging.INFO) - -realch1 = open_dataset('/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.1.zarr') -variables = ['TD_2M', 'TOT_PREC'] -myxarrays, var_indices = anemoi_to_xarray(realch1, variables) - -for i in range(len(variables)): - myxarray = myxarrays[i] - regridded=regrid.icon2rotlatlon(myxarray) - plot_and_save_projection(realch1.longitudes, realch1.latitudes, - realch1[0,var_indices[i],0,:], f'{variables[i]}-icon.png', s=0.005) - plot_and_save_projection(myxarray.lon, myxarray.lat, - myxarray[0,0,0,:], f'{variables[i]}-xarray.png', s=0.005) + logging.basicConfig(level=logging.INFO) + xarrays = anemoi_to_xarray(realch1) + + # Get the lat/lon info by regridding first variable + regridded=regrid.icon2rotlatlon(xarrays[0]) lats, lons = get_geo_coords(regridded) - - plot_and_save_projection(lons, lats, - regridded[0,0,0,:], f'{variables[i]}-regridded.png', s=0.005) + logging.info(regridded) + logging.info(regridded.data) + logging.info(regridded.data.shape) + # TODO: Save lat/lon info + + # regridded is in shape (eps, time, variable, x, y) + # want this in shape (time,channel,ensemble,grid) + torch_data = np.zeros([len(realch1.dates), len(realch1.variables), 1, len(lats)]) + torch_data[:,0,:,:] = regridded.data.reshape(regridded.shape[1], regridded.shape[0], regridded.shape[3]*regridded.shape[4]) + + for i in range(1, len(xarrays)): + xarray = xarrays[i] + regridded=regrid.icon2rotlatlon(xarray) + torch_data[:,i,:,:] = regridded.data.reshape(regridded.shape[1], regridded.shape[0], regridded.shape[3]*regridded.shape[4]) + + # TODO: output each time point into torch file + + # Output plots + for i in range(torch_data.shape[1]): + plot_and_save_projection(realch1.longitudes, realch1.latitudes, + realch1[0,i,0,:], f'{variables[i]}-icon.png', s=0.005) + plot_and_save_projection(lons, lats, + torch_data[0,i,0,:], f'{variables[i]}-regridded.png', s=0.005) if __name__ == "__main__": From dd79f5e673d4323ebc02616bb508864078c4dfda Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 28 Oct 2025 19:25:26 +0100 Subject: [PATCH 180/302] Regridding REA-L-CH1 script complete --- src/hirad/input_data/realch1-all.yaml | 26 +++++++++++++++++++++ src/hirad/input_data/realch1.yaml | 24 +------------------- src/hirad/input_data/regrid_realch1.py | 31 +++++++++++++++++++------- 3 files changed, 50 insertions(+), 31 deletions(-) create mode 100644 src/hirad/input_data/realch1-all.yaml diff --git a/src/hirad/input_data/realch1-all.yaml b/src/hirad/input_data/realch1-all.yaml new file mode 100644 index 00000000..6b0f7765 --- /dev/null +++ b/src/hirad/input_data/realch1-all.yaml @@ -0,0 +1,26 @@ +dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.1.zarr' +select: ['CLCH', 'CLCL', 'CLCM', 'CLCT', + 'FI_100', 'FI_1000', 'FI_150', 'FI_200', 'FI_250', 'FI_300', 'FI_400', + 'FI_50', 'FI_500', 'FI_600', 'FI_700', 'FI_850', 'FI_925', + 'FR_LAND', 'HSURF', + 'OMEGA_100', 'OMEGA_1000', 'OMEGA_150', 'OMEGA_200', 'OMEGA_250', + 'OMEGA_300', 'OMEGA_400', 'OMEGA_50', 'OMEGA_500', 'OMEGA_600', + 'OMEGA_700', 'OMEGA_850', 'OMEGA_925', + 'PLCOV', 'PMSL', 'PS', + 'QV_100', 'QV_1000', 'QV_150', 'QV_200', 'QV_250', 'QV_300', 'QV_400', + 'QV_50', 'QV_500', 'QV_600', 'QV_700', 'QV_850', 'QV_925', + 'SKC', 'SKT', 'SOILTYP', 'SSO_GAMMA', 'SSO_SIGMA', 'SSO_STDH', + 'SSO_THETA', 'TD_2M', 'TOT_PREC', 'TOT_PREC_6H', + 'T_100', 'T_1000', 'T_150', 'T_200', 'T_250', 'T_2M', 'T_300', 'T_400', + 'T_50', 'T_500', 'T_600', 'T_700', 'T_850', 'T_925', + 'U_100', 'U_1000', 'U_10M', 'U_150', 'U_200', 'U_250', 'U_300', 'U_400', + 'U_50', 'U_500', 'U_600', 'U_700', 'U_850', 'U_925', + 'V_100', 'V_1000', 'V_10M', 'V_150', 'V_200', 'V_250', 'V_300', 'V_400', + 'V_50', 'V_500', 'V_600', 'V_700', 'V_850', 'V_925', + 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', + 'insolation', 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude'] +# ALL REALCH1 CHANNELS. +start: 2020-01-01 +# start: 2015-11-29 +end: 2020-01-01 +# end: 2020-12-31 diff --git a/src/hirad/input_data/realch1.yaml b/src/hirad/input_data/realch1.yaml index 6b0f7765..d6ce166c 100644 --- a/src/hirad/input_data/realch1.yaml +++ b/src/hirad/input_data/realch1.yaml @@ -1,26 +1,4 @@ dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.1.zarr' -select: ['CLCH', 'CLCL', 'CLCM', 'CLCT', - 'FI_100', 'FI_1000', 'FI_150', 'FI_200', 'FI_250', 'FI_300', 'FI_400', - 'FI_50', 'FI_500', 'FI_600', 'FI_700', 'FI_850', 'FI_925', - 'FR_LAND', 'HSURF', - 'OMEGA_100', 'OMEGA_1000', 'OMEGA_150', 'OMEGA_200', 'OMEGA_250', - 'OMEGA_300', 'OMEGA_400', 'OMEGA_50', 'OMEGA_500', 'OMEGA_600', - 'OMEGA_700', 'OMEGA_850', 'OMEGA_925', - 'PLCOV', 'PMSL', 'PS', - 'QV_100', 'QV_1000', 'QV_150', 'QV_200', 'QV_250', 'QV_300', 'QV_400', - 'QV_50', 'QV_500', 'QV_600', 'QV_700', 'QV_850', 'QV_925', - 'SKC', 'SKT', 'SOILTYP', 'SSO_GAMMA', 'SSO_SIGMA', 'SSO_STDH', - 'SSO_THETA', 'TD_2M', 'TOT_PREC', 'TOT_PREC_6H', - 'T_100', 'T_1000', 'T_150', 'T_200', 'T_250', 'T_2M', 'T_300', 'T_400', - 'T_50', 'T_500', 'T_600', 'T_700', 'T_850', 'T_925', - 'U_100', 'U_1000', 'U_10M', 'U_150', 'U_200', 'U_250', 'U_300', 'U_400', - 'U_50', 'U_500', 'U_600', 'U_700', 'U_850', 'U_925', - 'V_100', 'V_1000', 'V_10M', 'V_150', 'V_200', 'V_250', 'V_300', 'V_400', - 'V_50', 'V_500', 'V_600', 'V_700', 'V_850', 'V_925', - 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', - 'insolation', 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude'] -# ALL REALCH1 CHANNELS. +select: ['TD_2M', 'U_10M', 'V_10M', 'TOT_PREC'] start: 2020-01-01 -# start: 2015-11-29 end: 2020-01-01 -# end: 2020-12-31 diff --git a/src/hirad/input_data/regrid_realch1.py b/src/hirad/input_data/regrid_realch1.py index b0fb0faf..7d0b65d3 100644 --- a/src/hirad/input_data/regrid_realch1.py +++ b/src/hirad/input_data/regrid_realch1.py @@ -2,6 +2,7 @@ import logging import os +import shutil import sys from anemoi.datasets import open_dataset @@ -12,6 +13,8 @@ from meteodatalab import ogd_api from hirad.input_data.interpolate_basic import plot_and_save_projection import yaml +import torch +from pandas import to_datetime import matplotlib.pyplot as plt import cartopy.crs as ccrs @@ -104,6 +107,9 @@ def main(): if not os.path.exists(os.path.join(output_directory, subdir)): os.mkdir(os.path.join(output_directory, subdir)) + # Copy the realch1.yml file to the info directory + shutil.copy(realch1_config_file, os.path.join(output_directory, 'info')) + with open(realch1_config_file) as realch1_file: realch1_config = yaml.safe_load(realch1_file) realch1 = open_dataset(realch1_config) @@ -116,13 +122,14 @@ def main(): # Get the lat/lon info by regridding first variable regridded=regrid.icon2rotlatlon(xarrays[0]) lats, lons = get_geo_coords(regridded) - logging.info(regridded) - logging.info(regridded.data) - logging.info(regridded.data.shape) - # TODO: Save lat/lon info + + # Save lat/lon info + grid = np.column_stack((lats, lons)) + torch.save(grid, os.path.join(output_directory, 'info', 'realch1-lat-lon')) # regridded is in shape (eps, time, variable, x, y) # want this in shape (time,channel,ensemble,grid) + # nervous about the reshaping screwing things up, but that's why we plot the interpolated data to visually check. torch_data = np.zeros([len(realch1.dates), len(realch1.variables), 1, len(lats)]) torch_data[:,0,:,:] = regridded.data.reshape(regridded.shape[1], regridded.shape[0], regridded.shape[3]*regridded.shape[4]) @@ -131,14 +138,22 @@ def main(): regridded=regrid.icon2rotlatlon(xarray) torch_data[:,i,:,:] = regridded.data.reshape(regridded.shape[1], regridded.shape[0], regridded.shape[3]*regridded.shape[4]) - # TODO: output each time point into torch file + # Output each time point into torch file + for t in range(torch_data.shape[0]): + fmtdate = to_datetime(realch1.dates[t]).strftime('%Y%m%d-%H%M') + torch.save(torch_data[t,:], os.path.join(output_directory, 'realch1', fmtdate)) + - # Output plots + # Output plots for each variable, for first time point for i in range(torch_data.shape[1]): plot_and_save_projection(realch1.longitudes, realch1.latitudes, - realch1[0,i,0,:], f'{variables[i]}-icon.png', s=0.005) + realch1[0,i,0,:], + os.path.join(output_directory, 'plots', f'{variables[i]}-iconnative.png'), + s=0.005) plot_and_save_projection(lons, lats, - torch_data[0,i,0,:], f'{variables[i]}-regridded.png', s=0.005) + torch_data[0,i,0,:], + os.path.join(output_directory, 'plots', f'{variables[i]}-rotlatlon.png'), + s=0.005) if __name__ == "__main__": From a5acccd8398eb4e2da94d193d078b85b693b9b5b Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 29 Oct 2025 17:19:52 +0100 Subject: [PATCH 181/302] Add trim_edge functionality to regridding realch1 --- src/hirad/input_data/regrid_realch1.py | 59 +++++++++++++++++--------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/src/hirad/input_data/regrid_realch1.py b/src/hirad/input_data/regrid_realch1.py index 7d0b65d3..c4f8bc12 100644 --- a/src/hirad/input_data/regrid_realch1.py +++ b/src/hirad/input_data/regrid_realch1.py @@ -1,5 +1,5 @@ - +import datetime import logging import os import shutil @@ -20,18 +20,19 @@ import cartopy.crs as ccrs from earthkit.geo.rotate import unrotate +TRIM_EDGE = 41 + # Take anemoi dataset and provide xarray dataarrays for a set of variables. # returns: list of xarray dataarrays def anemoi_to_xarray(anemoi_data: Dataset): lon = anemoi_data.longitudes lat = anemoi_data.latitudes eps = [0] # deterministic - time = generate_times(anemoi_data) # anemoi_data.dates? + time = anemoi_data.dates metadata = getMetadataFromOGD() dataarrays = [] variables = anemoi_data.variables for var_index in range(anemoi_data.shape[1]): - ds = xr.Dataset( data_vars=dict( variable=(["time", "eps", "cell"], np.array(anemoi_data[:,var_index,:,:])), @@ -61,18 +62,9 @@ def getMetadataFromOGD(): tot_prec = ogd_api.get_from_ogd(req) return tot_prec.metadata -# Get array of times from the anemoi dataset -def generate_times(anemoi_data: Dataset): - times = [] - curr_time = anemoi_data.start_date.item() - while curr_time <= anemoi_data.end_date: - times.append(curr_time) - curr_time = curr_time + anemoi_data.frequency - return times - # get the geo coordinates for the rotated lat/lon dataset. # returns np.array of lats and array of lons -def get_geo_coords(regridded_data: xr.Dataset): +def get_geo_coords(regridded_data: xr.Dataset, trim_edge=0): xmin = regridded_data.metadata.get("longitudeOfFirstGridPointInDegrees") xmax = regridded_data.metadata.get("longitudeOfLastGridPointInDegrees") dx = regridded_data.metadata.get("iDirectionIncrementInDegrees") @@ -81,6 +73,11 @@ def get_geo_coords(regridded_data: xr.Dataset): dy = regridded_data.metadata.get("jDirectionIncrementInDegrees") y = np.arange(ymin,ymax+dy,dy) x = np.arange(xmin,xmax+dx,dx) + # trim x and y according to trim_edge. + # (Have manually verified that when doing this, the outputs are the same as + # trimming post-projection) + y = y[trim_edge:len(y)-trim_edge] + x = x[trim_edge:len(x)-trim_edge] sp_lat = regridded_data.metadata.get("latitudeOfSouthernPoleInDegrees") # -43.0. north_pole_lat = 43.0 sp_lon = regridded_data.metadata.get("longitudeOfSouthernPoleInDegrees") # 10.0. north_pole_lon = 190.0 xcoords = np.meshgrid(x,y)[0].flatten() @@ -97,6 +94,17 @@ def get_geo_coords(regridded_data: xr.Dataset): lons = geo_coords[:,0] return lats, lons +def regridded_to_numpy(regridded: xr.DataArray, trim_edge=0): + # regridded is in shape (eps, time, variable, x, y) + # want this in shape (time, channel, ensemble, grid) + # First, trim the edge + data = regridded.data[:,:,:, + trim_edge:regridded.data.shape[3]-trim_edge, + trim_edge:regridded.data.shape[4]-trim_edge] + # reshape to (time,channel,ensemble,grid) + data = data.reshape(data.shape[1], data.shape[0], data.shape[3]*data.shape[4]) + return data + def main(): # yml format realch1_config_file = sys.argv[1] @@ -107,6 +115,12 @@ def main(): if not os.path.exists(os.path.join(output_directory, subdir)): os.mkdir(os.path.join(output_directory, subdir)) + logging.basicConfig( + filename=os.path.join(output_directory, 'regrid_realch1.log'), + format='%(asctime)s %(levelname)-8s %(message)s', + level=logging.INFO, + datefmt='%Y-%m-%d %H:%M:%S') + # Copy the realch1.yml file to the info directory shutil.copy(realch1_config_file, os.path.join(output_directory, 'info')) @@ -115,13 +129,16 @@ def main(): realch1 = open_dataset(realch1_config) variables = realch1.variables - logging.basicConfig(level=logging.INFO) - xarrays = anemoi_to_xarray(realch1) # Get the lat/lon info by regridding first variable + logging.info(f'regridding {variables[0]} for time {realch1.start_date} to {realch1.end_date}') + start = datetime.datetime.now() regridded=regrid.icon2rotlatlon(xarrays[0]) - lats, lons = get_geo_coords(regridded) + end = datetime.datetime.now() + logging.info(f' regridding took {end-start} seconds') + logging.info('getting geo coords') + lats, lons = get_geo_coords(regridded, trim_edge=TRIM_EDGE) # Save lat/lon info grid = np.column_stack((lats, lons)) @@ -131,19 +148,23 @@ def main(): # want this in shape (time,channel,ensemble,grid) # nervous about the reshaping screwing things up, but that's why we plot the interpolated data to visually check. torch_data = np.zeros([len(realch1.dates), len(realch1.variables), 1, len(lats)]) - torch_data[:,0,:,:] = regridded.data.reshape(regridded.shape[1], regridded.shape[0], regridded.shape[3]*regridded.shape[4]) + torch_data[:,0,:,:] = regridded_to_numpy(regridded, trim_edge=TRIM_EDGE) for i in range(1, len(xarrays)): + logging.info(f'regridding {variables[i]} for time {realch1.start_date} to {realch1.end_date}') xarray = xarrays[i] + start = datetime.datetime.now() regridded=regrid.icon2rotlatlon(xarray) - torch_data[:,i,:,:] = regridded.data.reshape(regridded.shape[1], regridded.shape[0], regridded.shape[3]*regridded.shape[4]) + end = datetime.datetime.now() + logging.info(f' regridding took {end-start} seconds') + torch_data[:,i,:,:] = regridded_to_numpy(regridded, trim_edge=TRIM_EDGE) # Output each time point into torch file + logging.info('saving torch data') for t in range(torch_data.shape[0]): fmtdate = to_datetime(realch1.dates[t]).strftime('%Y%m%d-%H%M') torch.save(torch_data[t,:], os.path.join(output_directory, 'realch1', fmtdate)) - # Output plots for each variable, for first time point for i in range(torch_data.shape[1]): plot_and_save_projection(realch1.longitudes, realch1.latitudes, From 93b5a5fdb3195cf7822958463b653d9df3c195f6 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 30 Oct 2025 16:21:24 +0100 Subject: [PATCH 182/302] do rea-l-ch1 regridding in batches to improve performance/memory usage --- src/hirad/input_data/regrid_realch1.py | 66 +++++++++++++------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/src/hirad/input_data/regrid_realch1.py b/src/hirad/input_data/regrid_realch1.py index c4f8bc12..8e84593b 100644 --- a/src/hirad/input_data/regrid_realch1.py +++ b/src/hirad/input_data/regrid_realch1.py @@ -21,21 +21,28 @@ from earthkit.geo.rotate import unrotate TRIM_EDGE = 41 +XARRAY_BATCH = 4 # Take anemoi dataset and provide xarray dataarrays for a set of variables. # returns: list of xarray dataarrays -def anemoi_to_xarray(anemoi_data: Dataset): +def anemoi_to_xarray(anemoi_data: Dataset, start_date_index=-1, end_date_index=-1): + if start_date_index == -1: + start_date_index = 0 + if end_date_index == -1: + end_date_index = len(anemoi_data.dates) lon = anemoi_data.longitudes lat = anemoi_data.latitudes eps = [0] # deterministic - time = anemoi_data.dates + time = anemoi_data.dates[start_date_index:end_date_index] metadata = getMetadataFromOGD() dataarrays = [] variables = anemoi_data.variables for var_index in range(anemoi_data.shape[1]): + logging.info(f'building xarray for {variables[var_index]}') ds = xr.Dataset( data_vars=dict( - variable=(["time", "eps", "cell"], np.array(anemoi_data[:,var_index,:,:])), + variable=(["time", "eps", "cell"], + np.array(anemoi_data[start_date_index:end_date_index,var_index,:,:])), ), coords=dict( eps=eps, @@ -129,41 +136,36 @@ def main(): realch1 = open_dataset(realch1_config) variables = realch1.variables - xarrays = anemoi_to_xarray(realch1) - - # Get the lat/lon info by regridding first variable - logging.info(f'regridding {variables[0]} for time {realch1.start_date} to {realch1.end_date}') - start = datetime.datetime.now() + # Get the lat/lon info by regridding one variable + xarrays = anemoi_to_xarray(realch1, 0, 1) regridded=regrid.icon2rotlatlon(xarrays[0]) - end = datetime.datetime.now() - logging.info(f' regridding took {end-start} seconds') logging.info('getting geo coords') lats, lons = get_geo_coords(regridded, trim_edge=TRIM_EDGE) - # Save lat/lon info + # Save grid to file grid = np.column_stack((lats, lons)) torch.save(grid, os.path.join(output_directory, 'info', 'realch1-lat-lon')) - - # regridded is in shape (eps, time, variable, x, y) - # want this in shape (time,channel,ensemble,grid) - # nervous about the reshaping screwing things up, but that's why we plot the interpolated data to visually check. - torch_data = np.zeros([len(realch1.dates), len(realch1.variables), 1, len(lats)]) - torch_data[:,0,:,:] = regridded_to_numpy(regridded, trim_edge=TRIM_EDGE) - - for i in range(1, len(xarrays)): - logging.info(f'regridding {variables[i]} for time {realch1.start_date} to {realch1.end_date}') - xarray = xarrays[i] - start = datetime.datetime.now() - regridded=regrid.icon2rotlatlon(xarray) - end = datetime.datetime.now() - logging.info(f' regridding took {end-start} seconds') - torch_data[:,i,:,:] = regridded_to_numpy(regridded, trim_edge=TRIM_EDGE) - - # Output each time point into torch file - logging.info('saving torch data') - for t in range(torch_data.shape[0]): - fmtdate = to_datetime(realch1.dates[t]).strftime('%Y%m%d-%H%M') - torch.save(torch_data[t,:], os.path.join(output_directory, 'realch1', fmtdate)) + + # Split regridding into batches; too many time points seems to not scale well. + for i in range(0, len(realch1.dates), XARRAY_BATCH): + start_index = i + end_index = min(i+XARRAY_BATCH, len(realch1.dates)) + torch_data = np.zeros([end_index-start_index, len(realch1.variables), 1, len(lats)]) + + xarrays = anemoi_to_xarray(realch1, start_index, end_index) + for j in range(len(xarrays)): + logging.info(f'regridding {variables[j]} for time {realch1.dates[start_index]} to {realch1.dates[end_index]}') + xarray = xarrays[j] + start = datetime.datetime.now() + regridded=regrid.icon2rotlatlon(xarray) + end = datetime.datetime.now() + logging.info(f' regridding took {end-start} seconds') + torch_data[0:end_index-start_index,j,:,:] = regridded_to_numpy(regridded, trim_edge=TRIM_EDGE) + + logging.info('saving torch data') + for k in range(torch_data.shape[0]): + fmtdate = to_datetime(realch1.dates[start_index + k]).strftime('%Y%m%d-%H%M') + torch.save(torch_data[k,:], os.path.join(output_directory, 'realch1', fmtdate)) # Output plots for each variable, for first time point for i in range(torch_data.shape[1]): From 9f42baada836d14a2c67dabc91a2a0aa2ed22ad7 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 30 Oct 2025 19:21:04 +0100 Subject: [PATCH 183/302] small refactoring and cleanup so it is easier to re-use era5-cosmo interpolation methods --- src/hirad/input_data/interpolate_basic.py | 96 +++++++---------------- 1 file changed, 29 insertions(+), 67 deletions(-) diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index b68219b8..f88b5e1f 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -20,34 +20,39 @@ # Margin to use for ERA dataset (to avoid nans from interpolation at boundary) ERA_MARGIN_DEGREES = 1.0 -def _read_input(era_config_file: str, cosmo_config_file: str, bound_to_cosmo_area=True) -> tuple[Dataset, Dataset]: +def _read_era5_cosmo(era_config_file: str, cosmo_config_file: str) -> tuple[Dataset, Dataset]: """ Read both ERA and COSMO data, optionally bounding to the COSMO data area, and return the 2m temperature values for the time range under COSMO. """ # trim edge removes boundary + cosmo = read_cosmo_anemoi(cosmo_config_file) + # area = N, W, S, E + min_lat = min(cosmo.latitudes) - ERA_MARGIN_DEGREES + max_lat = max(cosmo.latitudes) + ERA_MARGIN_DEGREES + min_lon = min(cosmo.longitudes) - ERA_MARGIN_DEGREES + max_lon = max(cosmo.longitudes) + ERA_MARGIN_DEGREES + start_date = cosmo.metadata()['start_date'] + end_date = cosmo.metadata()['end_date'] + era = read_era5_anemoi(era_config_file, + start_date = start_date, end_date = end_date, + area=(max_lat, min_lon, min_lat, max_lon)) + return (era, cosmo) + +def read_cosmo_anemoi(cosmo_config_file: str): with open(cosmo_config_file) as cosmo_file: cosmo_config = yaml.safe_load(cosmo_file) cosmo = open_dataset(cosmo_config) + return cosmo + +def read_era5_anemoi(era_config_file: str, start_date = None, + end_date = None, area=None): with open(era_config_file) as era_file: era_config = yaml.safe_load(era_file) era = open_dataset(era_config) - # Subset the ERA dataset to have COSMO area/dates. - start_date = cosmo.metadata()['start_date'] - end_date = cosmo.metadata()['end_date'] - # load era5 2m-temperature in the time-range of cosmo - # area = N, W, S, E - if bound_to_cosmo_area: - min_lat = min(cosmo.latitudes) - ERA_MARGIN_DEGREES - max_lat = max(cosmo.latitudes) + ERA_MARGIN_DEGREES - min_lon = min(cosmo.longitudes) - ERA_MARGIN_DEGREES - max_lon = max(cosmo.longitudes) + ERA_MARGIN_DEGREES - era = open_dataset(era, start=start_date, end=end_date, - area=(max_lat, min_lon, min_lat, max_lon)) - else: - era = open_dataset(era, start=start_date, end=end_date) - - return (era, cosmo) + era = open_dataset(era, start=start_date, end=end_date, + area=area) + return era def regrid(era_for_time: np.ndarray, input_grid: np.ndarray, output_grid: np.ndarray): # shape (channel, ensemble, grid) @@ -58,7 +63,7 @@ def regrid(era_for_time: np.ndarray, input_grid: np.ndarray, output_grid: np.nda interpolated_data[j,0,:] = regrid return interpolated_data -def _interpolate_task(i: int, era: Dataset, cosmo: Dataset, input_grid: np.ndarray, output_grid: np.ndarray, intermediate_files_path: str, outfile_plots_path: str = None, plot_indices=[0]): +def _interpolate_era5_cosmo_task(i: int, era: Dataset, cosmo: Dataset, input_grid: np.ndarray, output_grid: np.ndarray, intermediate_files_path: str, outfile_plots_path: str = None, plot_indices=[0]): logging.info('interpolating time point ' + _format_date(cosmo.dates[i])) interpolated_data = np.empty([era.shape[1], 1, cosmo.shape[3]]) for j in range(era.shape[1]): @@ -85,7 +90,7 @@ def _interpolate_task(i: int, era: Dataset, cosmo: Dataset, input_grid: np.ndarr -def _interpolate_basic(era: Dataset, cosmo: Dataset, intermediate_files_path: str, threaded = True, outfile_plots_path: str =None, plot_indices=[0]): +def _interpolate_era5_cosmo_basic(era: Dataset, cosmo: Dataset, intermediate_files_path: str, threaded = True, outfile_plots_path: str =None, plot_indices=[0]): """Perform simple interpolation from ERA5 to COSMO grid for all data points in the COSMO date range. Parameters: @@ -113,13 +118,13 @@ def _interpolate_basic(era: Dataset, cosmo: Dataset, intermediate_files_path: st if (threaded): pool = multiprocessing.Pool() for i in dates: - pool.apply_async(_interpolate_task, (i, era, cosmo, input_grid, output_grid, intermediate_files_path, outfile_plots_path, plot_indices)) + pool.apply_async(_interpolate_era5_cosmo_task, (i, era, cosmo, input_grid, output_grid, intermediate_files_path, outfile_plots_path, plot_indices)) pool.close() pool.join() else: for i in dates: - _interpolate_task(i, era, cosmo, input_grid, output_grid, intermediate_files_path, outfile_plots_path, plot_indices) + _interpolate_era5_cosmo_task(i, era, cosmo, input_grid, output_grid, intermediate_files_path, outfile_plots_path, plot_indices) return @@ -138,26 +143,6 @@ def _save_latlon_grid(dataset: Dataset, filename: str): def _save_stats(dataset: Dataset, filename: str): torch.save(dataset.statistics, filename) -def _save_interpolation(values: np.ndarray[np.intp], filename: str): - """Output interpolated data to a given filename, in PyTorch tensor format.""" - torch_data = torch.from_numpy(values) - torch.save(torch_data, filename) - -def _get_plot_indices(era: Dataset, cosmo: Dataset) -> np.ndarray[np.intp]: - """ - Get indices of ERA5 data that is in the bounding rectangle of COSMO data. - This is useful for plotting in the case where read_input(..., bound_to_cosmo_area=False) was used. - In this case, one would then feed e.g. era.latitudes[indices] into _plot_projection. - """ - min_lat_cosmo = min(cosmo.latitudes) - max_lat_cosmo = max(cosmo.latitudes) - min_lon_cosmo = min(cosmo.longitudes) - max_lon_cosmo = max(cosmo.longitudes) - box_lat = np.logical_and(era.latitudes>=min_lat_cosmo,era.latitudes<=max_lat_cosmo) - box_lon = np.logical_and(era.longitudes>=min_lon_cosmo,era.longitudes<=max_lon_cosmo) - indices = np.where(box_lon*box_lat) - return indices - def plot_projection(ax, longitudes: np.array, latitudes: np.array, values: np.array, cmap=None, vmin = None, vmax = None, s = None): p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax, s=s) ax.coastlines() @@ -171,14 +156,10 @@ def plot_and_save_projection(longitudes: np.array, latitudes: np.array, values: fig, ax = plt.subplots(subplot_kw={"projection": projection}) logging.info(f'plotting values to {filename}') plot_projection(ax, longitudes, latitudes, values, cmap, vmin, vmax, s) - #p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) - #ax.coastlines() - #ax.gridlines(draw_labels=True) - #plt.colorbar(p, orientation="horizontal") plt.savefig(filename) plt.close('all') -def interpolate_and_save(infile_era: str, infile_cosmo: str, outfile_data_path: str, threaded=True, outfile_plots_path: str = None, plot_indices=[0]): +def interpolate_era5_cosmo_and_save(infile_era: str, infile_cosmo: str, outfile_data_path: str, threaded=True, outfile_plots_path: str = None, plot_indices=[0]): """Read both ERA and COSMO data and perform basic interpolation. Save output into Pytorch format, and (optionally) plot ERA, COSMO, and interpolated data. @@ -221,25 +202,8 @@ def interpolate_and_save(infile_era: str, infile_cosmo: str, outfile_data_path: shutil.copy(infile_era, os.path.join(outfile_data_path, "info/era.yaml")) # generate interpolated data - _interpolate_basic(era, cosmo, outfile_data_path, threaded=threaded, outfile_plots_path=outfile_plots_path, plot_indices=plot_indices) - -def plot_tp(path_6h: str, path_1h: str): - fig, axs = plt.subplots(2, 3, subplot_kw={"projection": ccrs.PlateCarree()}) - - - logging.info(f'plotting values to {filename}') - p = ax.scatter(x=longitudes, y=latitudes, c=values) - ax.coastlines() - ax.gridlines(draw_labels=True) - plt.colorbar(p, label="absolute error", orientation="horizontal") - plt.savefig(filename) - plt.close('all') + _interpolate_era5_cosmo_basic(era, cosmo, outfile_data_path, threaded=threaded, outfile_plots_path=outfile_plots_path, plot_indices=plot_indices) -def load_static(infile_era: str, infile_cosmo: str, output_directory: str): - _, cosmo = _read_input(infile_era, infile_cosmo, bound_to_cosmo_area=True) - - torch.save(cosmo[0,:,:,:], os.path.join(output_directory, 'cosmo-static')) - shutil.copy(infile_cosmo, os.path.join(output_directory, "cosmo-static.yaml")) def main(): # TODO: Do better arg parsing so it's not as easy to reverse era and cosmo configs. @@ -255,9 +219,7 @@ def main(): level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') - #load_static(infile_era, infile_cosmo, output_directory) - #interpolate_and_save(infile_era, infile_cosmo, output_directory, threaded=False, outfile_plots_path=os.path.join(output_directory, "plots/")) - interpolate_and_save(infile_era, infile_cosmo, output_directory, threaded=False, outfile_plots_path=None) + interpolate_era5_cosmo_and_save(infile_era, infile_cosmo, output_directory, threaded=False, outfile_plots_path=None) if __name__ == "__main__": main() From 7b9364f84836b8474daaa985a6a166a82814f6cf Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 3 Nov 2025 10:38:49 +0100 Subject: [PATCH 184/302] bit more skeleton to new inteprolation --- src/hirad/input_data/interpolate_realch1.py | 82 ++++++++++++++------ src/hirad/input_data/regrid_copernicus_tp.py | 23 +++--- 2 files changed, 72 insertions(+), 33 deletions(-) diff --git a/src/hirad/input_data/interpolate_realch1.py b/src/hirad/input_data/interpolate_realch1.py index 13348659..2a2e1d7c 100644 --- a/src/hirad/input_data/interpolate_realch1.py +++ b/src/hirad/input_data/interpolate_realch1.py @@ -1,4 +1,5 @@ - +import hirad.input_data.interpolate_basic as interpolate_basic +import hirad.input_data.regrid_copernicus_tp as regrid_copernicus_tp import datetime import logging @@ -25,37 +26,29 @@ '/store_new/mch/msopr/hirad-gen/copernicus-datasets/tp-2017-2018.nc', '/store_new/mch/msopr/hirad-gen/copernicus-datasets/tp-2019-2020.nc'] -def _read_input(era_config_file: str, realch1_config_file: str, ) -> tuple[Dataset, Dataset, array.array]: +def _read_input(era_config_file: str, realch1_latlon_file: str) -> tuple[Dataset, Dataset, array.array, np.ndarray]: """ - Read both ERA and REA-L-CH1 data, and return the 2m - temperature values for the time range under COSMO. + Read ERA data, and return the values for the area under REA-L-CH1 (plus a margin). """ - # trim edge removes boundary, we will use the same - with open(realch1_config_file) as realch1_file: - realch1_config = yaml.safe_load(realch1_file) - realch1 = open_dataset(realch1_config) + # read the lat/lon data for REA-L-CH1 + realch1_latlon = torch.load(realch1_latlon_file) + # we expect the start and end dates to be specified in config. with open(era_config_file) as era_file: era_config = yaml.safe_load(era_file) era = open_dataset(era_config) - # Subset the ERA dataset to have REAL-CH-1 area/dates. - start_date = realch1.metadata()['start_date'] - end_date = realch1.metadata()['end_date'] - # load era5 2m-temperature in the time-range of cosmo + # Subset the ERA dataset to have REAL-CH-1 area. # area = N, W, S, E - min_lat = min(realch1.latitudes) - ERA_MARGIN_DEGREES - max_lat = max(realch1.latitudes) + ERA_MARGIN_DEGREES - min_lon = min(realch1.longitudes) - ERA_MARGIN_DEGREES - max_lon = max(realch1.longitudes) + ERA_MARGIN_DEGREES - era = open_dataset(era, start=start_date, end=end_date, + min_lat = min(realch1_latlon[:,0]) - ERA_MARGIN_DEGREES + max_lat = max(realch1_latlon[:,0]) + ERA_MARGIN_DEGREES + min_lon = min(realch1_latlon[:,1]) - ERA_MARGIN_DEGREES + max_lon = max(realch1_latlon[:,1]) + ERA_MARGIN_DEGREES + era = open_dataset(era, area=(max_lat, min_lon, min_lat, max_lon)) - - copernicus_netcdf = [] for f in COPERNICUS_FILES: netcdf_data = netCDF4.Dataset(f) copernicus_netcdf.append(netcdf_data) - - return (era, realch1, copernicus_netcdf) + return (era, copernicus_netcdf, realch1_latlon) def regrid_all(era: Dataset, realch1: Dataset, copernicus: array.array): @@ -77,4 +70,49 @@ def regrid_era(): # Take the output grid from realch1-regrid (rotated lat lon). # regrid all variables *except* tp directly from era5 data # regrid the pt variable from the netcdf data - # save the output as torch \ No newline at end of file + # save the output as torch + pass + +def main(): + # read REA-L-CH1 latlon grid + era_config_file = sys.argv[1] + realch1_latlon_file = sys.argv[2] + netcdf_file = sys.argv[3] + output_directory = sys.argv[4] + + realch1_grid = torch.load(realch1_latlon_file, weights_only=False) + # read ERA input + min_lat = min(realch1_grid[:,0]) - interpolate_basic.ERA_MARGIN_DEGREES + max_lat = min(realch1_grid[:,0]) + interpolate_basic.ERA_MARGIN_DEGREES + min_lon = min(realch1_grid[:,1]) - interpolate_basic.ERA_MARGIN_DEGREES + max_lon = min(realch1_grid[:,1]) + interpolate_basic.ERA_MARGIN_DEGREES + era = interpolate_basic.read_era5_anemoi(era_config_file, + area=(max_lat, min_lon, min_lat, max_lon)) + era_grid = np.column_stack((era.longitudes, era.latitudes)) + + # read copernicus input for tp variable + netcdf_data = netCDF4.Dataset(netcdf_file) + logging.info('processing netcdf data') + netcdf_latitudes, netcdf_longitudes = regrid_copernicus_tp.extract_lat_lon_025(netcdf_data) + netcdf_grid=np.column_stack((netcdf_longitudes, netcdf_latitudes)) + # TODO: start and end date functionality + netcdf_tp_values = regrid_copernicus_tp.extract_values(netcdf_data, 'tp', start_date=era.start_date, end_date=era.end_date) + assert(netcdf_tp_values.shape[0] == era.shape[0]) + + + # Iterate over ERA time range, which should be subsetted in configuration. + for i in range(era.shape[0]): + t = era.dates[i] + # Get everything but the tp variable + tp_index = era.variables.index('tp') + logging.info('tp index' + tp_index) + # shape time, channel, ensemble, grid + era_for_time = np.delete(era[i,:,:,:], tp_index, axis=0) + era_regridded = interpolate_basic.regrid(era_for_time, era_grid, realch1_grid) + copernicus_regridded = interpolate_basic.regrid(netcdf_data[0,:], netcdf_grid, realch1_grid) + output=np.stack((era_regridded, copernicus_regridded), axis=1) + filename = os.path.join(output_directory, 'era-copernicus-interpolated', + interpolate_basic._format_date(t)) + torch.save(output, filename) + + return 0 \ No newline at end of file diff --git a/src/hirad/input_data/regrid_copernicus_tp.py b/src/hirad/input_data/regrid_copernicus_tp.py index 7e3f14ff..9686601e 100644 --- a/src/hirad/input_data/regrid_copernicus_tp.py +++ b/src/hirad/input_data/regrid_copernicus_tp.py @@ -62,20 +62,21 @@ def extract_lat_lon_n320(data): logging.info('extracting lat/lon') logging.info(f'lat lon shapes {lat.shape} {lon.shape}') -def extract_values(data, variable, area=None): +def extract_values(data: netCDF4.Dataset, variable, start_date=None, end_date=None, area=None): values = data[variable][:] print(values.shape) - if area: - lat = data['latitude'][:] - lon = data['longitude'][:] + #if area: + # Not sure this is working. + # lat = data['latitude'][:] + # lon = data['longitude'][:] # https://stackoverflow.com/questions/29135885/netcdf4-extract-for-subset-of-lat-lon - latli = np.argmin( np.abs(lat - area[2])) - latui = np.argmin( np.abs(lat - area[0])) - lonli = np.argmin( np.abs(lon - area[1])) - lonui = np.argmin( np.abs(lon - area[3])) - lat = data['latitude'][latli:latui] - lon = data['longitude'][lonli:lonui] - values = data[variable][latli:latui,lonli:lonui] + # latli = np.argmin( np.abs(lat - area[2])) + # latui = np.argmin( np.abs(lat - area[0])) + # lonli = np.argmin( np.abs(lon - area[1])) + # lonui = np.argmin( np.abs(lon - area[3])) + # lat = data['latitude'][latli:latui] + # lon = data['longitude'][lonli:lonui] + # values = data[variable][latli:latui,lonli:lonui] return np.reshape(values, (values.shape[0], values.shape[1]*values.shape[2])) def reshape_to_cosmo(vals): From d2ca5e50f9a4e7d99f1ad38f7792703db21f3ae4 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 4 Nov 2025 16:59:07 +0100 Subject: [PATCH 185/302] working script --- src/hirad/input_data/interpolate_basic.py | 12 +-- src/hirad/input_data/interpolate_realch1.py | 107 +++++++++++++------- 2 files changed, 77 insertions(+), 42 deletions(-) diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index f88b5e1f..5a77429b 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -136,11 +136,11 @@ def _save_datetime_file(values: np.ndarray[np.intp], variables: np.ndarray, date filename = filepath + _format_date(date) torch.save(values, filename) -def _save_latlon_grid(dataset: Dataset, filename: str): +def save_anemoi_latlon_grid(dataset: Dataset, filename: str): grid = np.column_stack((dataset.latitudes, dataset.longitudes)) torch.save(grid, filename) -def _save_stats(dataset: Dataset, filename: str): +def save_anemoi_stats(dataset: Dataset, filename: str): torch.save(dataset.statistics, filename) def plot_projection(ax, longitudes: np.array, latitudes: np.array, values: np.array, cmap=None, vmin = None, vmax = None, s = None): @@ -192,10 +192,10 @@ def interpolate_era5_cosmo_and_save(infile_era: str, infile_cosmo: str, outfile_ logging.info('Successfully read input') # Output stats and grid - _save_stats(era, os.path.join(outfile_data_path, "info/era-stats")) - _save_stats(cosmo, os.path.join(outfile_data_path, "info/cosmo-stats")) - _save_latlon_grid(cosmo, os.path.join(outfile_data_path, "info/cosmo-lat-lon")) - _save_latlon_grid(era, os.path.join(outfile_data_path, "info/era-lat-lon")) + save_anemoi_stats(era, os.path.join(outfile_data_path, "info/era-stats")) + save_anemoi_stats(cosmo, os.path.join(outfile_data_path, "info/cosmo-stats")) + save_anemoi_latlon_grid(cosmo, os.path.join(outfile_data_path, "info/cosmo-lat-lon")) + save_anemoi_latlon_grid(era, os.path.join(outfile_data_path, "info/era-lat-lon")) # Copy the .yaml files over for recording purposes shutil.copy(infile_cosmo, os.path.join(outfile_data_path, "info/cosmo.yaml")) diff --git a/src/hirad/input_data/interpolate_realch1.py b/src/hirad/input_data/interpolate_realch1.py index 2a2e1d7c..e1a17bc0 100644 --- a/src/hirad/input_data/interpolate_realch1.py +++ b/src/hirad/input_data/interpolate_realch1.py @@ -51,28 +51,6 @@ def _read_input(era_config_file: str, realch1_latlon_file: str) -> tuple[Dataset return (era, copernicus_netcdf, realch1_latlon) -def regrid_all(era: Dataset, realch1: Dataset, copernicus: array.array): - # iterate through the dates - realch1 = regrid_realch1 - # convert to xarray.dataarray - regrid.icon2rotlatlon - - pass - -def regrid_realch1(): - # Use the meteodatalab functions to regrid the realch1 anemoi data (one time point) - # onto the rotated lat lon - # save the output as torch - # return the np array - pass - -def regrid_era(): - # Take the output grid from realch1-regrid (rotated lat lon). - # regrid all variables *except* tp directly from era5 data - # regrid the pt variable from the netcdf data - # save the output as torch - pass - def main(): # read REA-L-CH1 latlon grid era_config_file = sys.argv[1] @@ -80,39 +58,96 @@ def main(): netcdf_file = sys.argv[3] output_directory = sys.argv[4] - realch1_grid = torch.load(realch1_latlon_file, weights_only=False) + logging.basicConfig( + filename=os.path.join(output_directory, 'interpolate_realch1.log'), + format='%(asctime)s %(levelname)-8s %(message)s', + level=logging.INFO, + datefmt='%Y-%m-%d %H:%M:%S') + + # Copy ERA yml file + shutil.copy(era_config_file, os.path.join(output_directory, 'info')) + + logging.info('reading realch1 lat/lon') + realch1_latlon = torch.load(realch1_latlon_file, weights_only=False) + realch1_lat = realch1_latlon[:,0] + realch1_lon = realch1_latlon[:,1] # read ERA input - min_lat = min(realch1_grid[:,0]) - interpolate_basic.ERA_MARGIN_DEGREES - max_lat = min(realch1_grid[:,0]) + interpolate_basic.ERA_MARGIN_DEGREES - min_lon = min(realch1_grid[:,1]) - interpolate_basic.ERA_MARGIN_DEGREES - max_lon = min(realch1_grid[:,1]) + interpolate_basic.ERA_MARGIN_DEGREES + min_lat = min(realch1_lat) - interpolate_basic.ERA_MARGIN_DEGREES + max_lat = max(realch1_lat) + interpolate_basic.ERA_MARGIN_DEGREES + min_lon = min(realch1_lon) - interpolate_basic.ERA_MARGIN_DEGREES + max_lon = max(realch1_lon) + interpolate_basic.ERA_MARGIN_DEGREES + logging.info('reading era') + era = interpolate_basic.read_era5_anemoi(era_config_file, area=(max_lat, min_lon, min_lat, max_lon)) era_grid = np.column_stack((era.longitudes, era.latitudes)) + realch1_grid = np.column_stack((realch1_lon, realch1_lat)) + logging.info(f'lat lon area is {min_lat}-{max_lat} {min_lon}-{max_lon}') + + # save era stats and lat lon + interpolate_basic.save_anemoi_latlon_grid(era, os.path.join(output_directory, 'info', 'era-lat-lon')) + interpolate_basic.save_anemoi_stats(era, os.path.join(output_directory, 'info', 'era-stats')) # read copernicus input for tp variable + logging.info('reading copernicus') netcdf_data = netCDF4.Dataset(netcdf_file) logging.info('processing netcdf data') netcdf_latitudes, netcdf_longitudes = regrid_copernicus_tp.extract_lat_lon_025(netcdf_data) netcdf_grid=np.column_stack((netcdf_longitudes, netcdf_latitudes)) + # TODO: start and end date functionality netcdf_tp_values = regrid_copernicus_tp.extract_values(netcdf_data, 'tp', start_date=era.start_date, end_date=era.end_date) assert(netcdf_tp_values.shape[0] == era.shape[0]) + # todo: incorporate this somehow + netcdf_tp_values = netcdf_tp_values.reshape((netcdf_tp_values.shape[0], 1,1, netcdf_tp_values.shape[1])) + # save copernicus stats and lat lon + torch.save(np.column_stack((netcdf_grid[:,1], netcdf_grid[:,0])), + os.path.join(output_directory, 'info', 'copernicus-lat-lon')) + regrid_copernicus_tp.make_stats(os.path.join(output_directory, 'info'), + os.path.join(output_directory, 'info'), + netcdf_tp_values) # Iterate over ERA time range, which should be subsetted in configuration. + tp_index = era.variables.index('tp') + logging.info(f'tp index {tp_index}') + + plot_indices = {12} + + logging.info('interpolating') + #for i in plot_indices: for i in range(era.shape[0]): + # T t = era.dates[i] # Get everything but the tp variable - tp_index = era.variables.index('tp') - logging.info('tp index' + tp_index) - # shape time, channel, ensemble, grid - era_for_time = np.delete(era[i,:,:,:], tp_index, axis=0) + era_for_time = era[i,:,:,:] era_regridded = interpolate_basic.regrid(era_for_time, era_grid, realch1_grid) - copernicus_regridded = interpolate_basic.regrid(netcdf_data[0,:], netcdf_grid, realch1_grid) - output=np.stack((era_regridded, copernicus_regridded), axis=1) + # Regrid TP from copernicus + copernicus_regridded = interpolate_basic.regrid(netcdf_tp_values[i,:], netcdf_grid, realch1_grid) + # Concatenate and save + era_regridded[tp_index,:] = copernicus_regridded + #output=np.concatenate((era_regridded, copernicus_regridded), axis=0) + datefmt = interpolate_basic._format_date(t) filename = os.path.join(output_directory, 'era-copernicus-interpolated', - interpolate_basic._format_date(t)) - torch.save(output, filename) + datefmt) + torch.save(era_regridded, filename) + + if i in plot_indices: + realch1var = ['t2m', '10u', '10v', 'tp'] + realch1_data = torch.load(os.path.join(output_directory, 'realch1', datefmt), weights_only=False) + for j in range(realch1_data.shape[0]): + interpolate_basic.plot_and_save_projection(realch1_lon, realch1_lat, realch1_data[j,:], + os.path.join(output_directory, 'plots', + f'{datefmt}-{realch1var[j]}-realch1')) + for j in range(era_regridded.shape[0]): + interpolate_basic.plot_and_save_projection(era.longitudes, era.latitudes, era_for_time[j,:], + os.path.join(output_directory, 'plots', + f'{datefmt}-{era.variables[j]}-era')) + interpolate_basic.plot_and_save_projection(realch1_lon, realch1_lat, + era_regridded[j,0,:], + os.path.join(output_directory, 'plots', + f'{datefmt}-{era.variables[j]}-interpolated')) + return 0 - return 0 \ No newline at end of file +if __name__ == "__main__": + main() From d0b9c1fd65a365f1297018a239532b273777ec6e Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 5 Nov 2025 13:07:20 +0100 Subject: [PATCH 186/302] changes to copernicus regrid --- src/hirad/input_data/regrid_copernicus_tp.py | 37 +++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/hirad/input_data/regrid_copernicus_tp.py b/src/hirad/input_data/regrid_copernicus_tp.py index 9686601e..adcea16c 100644 --- a/src/hirad/input_data/regrid_copernicus_tp.py +++ b/src/hirad/input_data/regrid_copernicus_tp.py @@ -62,9 +62,9 @@ def extract_lat_lon_n320(data): logging.info('extracting lat/lon') logging.info(f'lat lon shapes {lat.shape} {lon.shape}') +# Get values for a given date range (inclusive) def extract_values(data: netCDF4.Dataset, variable, start_date=None, end_date=None, area=None): values = data[variable][:] - print(values.shape) #if area: # Not sure this is working. # lat = data['latitude'][:] @@ -77,6 +77,15 @@ def extract_values(data: netCDF4.Dataset, variable, start_date=None, end_date=No # lat = data['latitude'][latli:latui] # lon = data['longitude'][lonli:lonui] # values = data[variable][latli:latui,lonli:lonui] + date_indices = range(values.shape[0]) + if start_date and end_date: + date_indices = np.intersect1d( + np.where(data['valid_time'][:] >= start_date.astype(np.int64)), + np.where(data['valid_time'][:] <= end_date.astype(np.int64))) + values = values[date_indices,:] + if len(date_indices) == 0: + raise KeyError(f'{start_date} and {end_date} not valid range') + return np.reshape(values, (values.shape[0], values.shape[1]*values.shape[2])) def reshape_to_cosmo(vals): @@ -200,12 +209,7 @@ def process_era(netcdf_data, netcdf_tp_values): torch.save(era_data, os.path.join(OUTPUT_DATA_FILEPATH_ERA, date_filename)) t4 = datetime.datetime.now() -def make_stats(): - #cosmo_files = os.listdir(os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, 'cosmo')) - #era_files = os.listdir(os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, 'cosmo')) - - stats = torch.load(os.path.join(BASE_FILEPATH, INPUT_DATA_FILEPATH, 'info', 'era-stats'), weights_only=False) - print(stats) +def extract_all_values(): set1 = netCDF4.Dataset("/capstor/store/cscs/swissai/a161/datasets/copernicus/tp-2015-2016.nc") set2 = netCDF4.Dataset("/capstor/store/cscs/swissai/a161/datasets/copernicus/tp-2017-2018.nc") set3 = netCDF4.Dataset("/capstor/store/cscs/swissai/a161/datasets/copernicus/tp-2019-2020.nc") @@ -214,17 +218,24 @@ def make_stats(): set3_tp = extract_values(set3, 'tp') all_tp = np.row_stack((set1_tp, set2_tp, set3_tp)) print(all_tp.shape) - all_tp = all_tp.reshape(all_tp.shape[0] * all_tp.shape[1], 1) - mean = np.mean(all_tp) - max = np.max(all_tp) - min = np.min(all_tp) - stdev = np.std(all_tp) + return all_tp + +# Get stats from ERA and replace the TP variable with stats from Copernicus +def make_stats(input_stats_directory: str, output_stats_directory: str, extracted_tp_values: np.ndarray): + stats = torch.load(os.path.join(input_stats_directory, 'era-stats'), weights_only=False) + print(stats) + #extracted_tp_values = extracted_tp_values.reshape(extracted_tp_values.shape[0] * extracted_tp_values.shape[1], 1) + flat_values = extracted_tp_values.flatten() + mean = np.mean(flat_values) + max = np.max(flat_values) + min = np.min(flat_values) + stdev = np.std(flat_values) stats['mean'][TP_INDEX] = mean stats['maximum'][TP_INDEX] = max stats['minimum'][TP_INDEX] = min stats['stdev'][TP_INDEX] = stdev print(stats) - torch.save(stats, os.path.join(OUTPUT_DATA_FILEPATH_ERA_INTERPOLATED, 'era-stats')) + torch.save(stats, os.path.join(output_stats_directory, 'era-copernicus-stats')) #process_era(netcdf_data, netcdf_tp_values) From c9b0f37baaa2276193a5ff79cba13d4bff27423d Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 5 Nov 2025 13:07:50 +0100 Subject: [PATCH 187/302] config updates --- src/hirad/input_data/era.yaml | 7 +++++-- src/hirad/input_data/realch1.yaml | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/hirad/input_data/era.yaml b/src/hirad/input_data/era.yaml index 3234321a..9018031a 100644 --- a/src/hirad/input_data/era.yaml +++ b/src/hirad/input_data/era.yaml @@ -1,4 +1,7 @@ -dataset: '/scratch/mch/apennino/data/aifs-ea-an-oper-0001-mars-n320-1979-2022-6h-v6.zarr' +#dataset: '/scratch/mch/apennino/data/aifs-ea-an-oper-0001-mars-n320-1979-2022-6h-v6.zarr' +dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' select: ['2t', '10u', '10v', 'tcw', 't_850', 'z_850', 'u_850', 'v_850', 't_500', 'z_500', 'u_500', 'v_500', 'tp'] # See table S2 from corrdiff paper for the inputs. -# Note: Bounding dates/area will be done in .py code. \ No newline at end of file +# Note: Bounding dates/area will be done in .py code. +start: 2020-01-01 +end: 2020-01-31 \ No newline at end of file diff --git a/src/hirad/input_data/realch1.yaml b/src/hirad/input_data/realch1.yaml index d6ce166c..c536704b 100644 --- a/src/hirad/input_data/realch1.yaml +++ b/src/hirad/input_data/realch1.yaml @@ -1,4 +1,4 @@ -dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.1.zarr' -select: ['TD_2M', 'U_10M', 'V_10M', 'TOT_PREC'] -start: 2020-01-01 -end: 2020-01-01 +dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.2.zarr' +select: ['TD_2M', 'U_10M', 'V_10M', 'TOT_PREC_1H'] # TOT_PREC in v0.1 +#start: 2020-01-01 +#end: 2020-01-01 From 72ecbdec6e68b88bee6dca40d08cc0d4577a37ff Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Fri, 7 Nov 2025 21:34:43 +0100 Subject: [PATCH 188/302] updates to CRPS eval --- src/hirad/eval/compute_eval.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/hirad/eval/compute_eval.py b/src/hirad/eval/compute_eval.py index 8a01db60..00e524e3 100644 --- a/src/hirad/eval/compute_eval.py +++ b/src/hirad/eval/compute_eval.py @@ -47,9 +47,10 @@ def main(cfg: DictConfig) -> None: dataset, sampler = get_dataset_and_sampler_inference( dataset_cfg=dataset_cfg, times=times, has_lead_time=has_lead_time ) - output_path = getattr(cfg.generation.io, "output_path", "./outputs") + pred_path = getattr(cfg.generation.io, "output_path", "./outputs") + output_path = './plots/analysis202511' - compute_crps_per_time(times, dataset, output_path) + compute_crps_per_time(times, dataset, pred_path, output_path) compute_crps_over_time_and_area(times, output_path) plot_crps_over_time_and_area(times, dataset, output_path) @@ -66,14 +67,14 @@ def save_data(data, output_path, time=None, filename=None): path = _get_data_path(output_path, time, filename) torch.save(data, path) -def compute_crps_per_time(times, dataset, output_path): +def compute_crps_per_time(times, dataset, pred_path, output_path): logging.info('Computing CRPS for each time point') input_channels = dataset.input_channels() output_channels = dataset.output_channels() start_time=times[0] # Load one prediction ensemble to get the shape - prediction_ensemble = torch.load(os.path.join(output_path, start_time, f'{start_time}-predictions'), weights_only=False) + prediction_ensemble = torch.load(os.path.join(pred_path, start_time, f'{start_time}-predictions'), weights_only=False) # Get a map of output to input channel, for building baseline errors output_to_input_channel_map = {} @@ -89,9 +90,9 @@ def compute_crps_per_time(times, dataset, output_path): curr_time = times[i] if i % (24*5) == 0: logging.info(f'on time {curr_time}') - prediction_ensemble = load_data(output_path, time=curr_time, filename=f'{curr_time}-predictions') - baseline = load_data(output_path, time=curr_time, filename=f'{curr_time}-baseline') - target = load_data(output_path, time=curr_time, filename=f'{curr_time}-target') + prediction_ensemble = load_data(pred_path, time=curr_time, filename=f'{curr_time}-predictions') + baseline = load_data(pred_path, time=curr_time, filename=f'{curr_time}-baseline') + target = load_data(pred_path, time=curr_time, filename=f'{curr_time}-target') # Calculate ensemble mean error ensemble_mean = np.mean(prediction_ensemble, 0) @@ -107,12 +108,12 @@ def compute_crps_per_time(times, dataset, output_path): # Calculate persistence error (baseline #2) persistence_error = np.zeros(target.shape) if i > 0: - prev = load_data(output_path, time=times[i-1], filename=f'{times[i-1]}-target') + prev = load_data(pred_path, time=times[i-1], filename=f'{times[i-1]}-target') persistence_error = absolute_error(prev, target) else: # for the first time point, persist the next-time-point target. # This is fiction but it keeps the plots from looking weird. - prev = load_data(output_path, time=times[i+1], filename=f'{times[i+1]}-target') + prev = load_data(pred_path, time=times[i+1], filename=f'{times[i+1]}-target') persistence_error = absolute_error(prev, target) From 0047a68481d249f71204b23a720fce3ec48dc6e4 Mon Sep 17 00:00:00 2001 From: David Leutwyler Date: Mon, 10 Nov 2025 10:40:22 +0100 Subject: [PATCH 189/302] fix shading --- src/hirad/eval/plotting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index e8630531..e83a625c 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -67,7 +67,7 @@ def concat_and_group_diurnal(list_of_da, is_member=False, scale=1.0): if is_member: timmean = da.mean(dim='time') * scale mean = timmean.mean(dim='member') - std = timmean.std(dim='member') + std = da.std(dim='member').mean(dim='time') * scale else: mean = da.mean(dim='time') * scale std = None From eb6bf2cefbf58e1944664235e619400f860986fe Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 11 Nov 2025 11:00:37 +0100 Subject: [PATCH 190/302] remove unused libraries --- src/hirad/input_data/interpolate_basic.py | 2 -- src/hirad/input_data/interpolate_realch1.py | 3 +-- src/hirad/input_data/regrid_realch1.py | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index 5a77429b..b7e4658d 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -14,8 +14,6 @@ from scipy.interpolate import griddata import torch import multiprocessing -import xarray -from earthkit.geo.rotate import unrotate # Margin to use for ERA dataset (to avoid nans from interpolation at boundary) ERA_MARGIN_DEGREES = 1.0 diff --git a/src/hirad/input_data/interpolate_realch1.py b/src/hirad/input_data/interpolate_realch1.py index e1a17bc0..7ce9992f 100644 --- a/src/hirad/input_data/interpolate_realch1.py +++ b/src/hirad/input_data/interpolate_realch1.py @@ -15,7 +15,6 @@ import numpy as np from pandas import to_datetime from scipy.interpolate import griddata -from meteodatalab.operators import regrid import torch import multiprocessing import xarray @@ -65,7 +64,7 @@ def main(): datefmt='%Y-%m-%d %H:%M:%S') # Copy ERA yml file - shutil.copy(era_config_file, os.path.join(output_directory, 'info')) + shutil.copy(era_config_file, os.path.join(output_directory, 'info', 'era.yaml')) logging.info('reading realch1 lat/lon') realch1_latlon = torch.load(realch1_latlon_file, weights_only=False) diff --git a/src/hirad/input_data/regrid_realch1.py b/src/hirad/input_data/regrid_realch1.py index 8e84593b..a4939b39 100644 --- a/src/hirad/input_data/regrid_realch1.py +++ b/src/hirad/input_data/regrid_realch1.py @@ -18,7 +18,6 @@ import matplotlib.pyplot as plt import cartopy.crs as ccrs -from earthkit.geo.rotate import unrotate TRIM_EDGE = 41 XARRAY_BATCH = 4 From b59425a3b9cb1032f91e3b99193d13549eaf2ccf Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 14 Nov 2025 11:39:07 +0100 Subject: [PATCH 191/302] fix mlflow logging of learning rate --- src/hirad/training/train.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 5f584ae9..42472977 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -596,9 +596,7 @@ def main(cfg: DictConfig) -> None: g["lr"] = cfg.training.hp.lr * min(cur_nimg / lr_rampup, 1) if cur_nimg >= lr_rampup: g["lr"] *= cfg.training.hp.lr_decay ** ((cur_nimg - lr_rampup) // cfg.training.hp.lr_decay_rate) - current_lr = g["lr"] - if dist.rank == 0 and cfg.logging.method == "mlflow": - mlflow.log_metric("learning_rate", current_lr, cur_nimg) + current_lr = g["lr"] handle_and_clip_gradients( model, grad_clip_threshold=cfg.training.hp.grad_clip_threshold ) @@ -648,6 +646,7 @@ def main(cfg: DictConfig) -> None: average_loss_running_mean, cur_nimg, ) + mlflow.log_metric("learning_rate", current_lr, cur_nimg) # reset running mean of average loss average_loss_running_mean = 0 n_average_loss_running_mean = 1 From aa1e1a07804fa87eb888b2cd9de464c067b23929 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 17 Nov 2025 18:39:54 +0100 Subject: [PATCH 192/302] hdf5 --- src/hirad/input_data/generate_lmdb.py | 17 +++++ src/hirad/input_data/hdf5.py | 104 ++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 src/hirad/input_data/generate_lmdb.py create mode 100644 src/hirad/input_data/hdf5.py diff --git a/src/hirad/input_data/generate_lmdb.py b/src/hirad/input_data/generate_lmdb.py new file mode 100644 index 00000000..6df35390 --- /dev/null +++ b/src/hirad/input_data/generate_lmdb.py @@ -0,0 +1,17 @@ +import lmdb +import os +import torch + +print('starting') +in_dir = '/store_new/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-all-channels/era-interpolated' +filename='/store_new/mch/msopr/hirad-gen/all-channels-lmdb.db' + +lmdb_env = lmdb.open(filename, map_size=int(1e10)) +lmdb_txn = lmdb_env.begin(write=True) +files = sorted(os.listdir(in_dir)) +for i in range(100): + f = files[i] + print(i) + torchdata = torch.load(os.path.join(in_dir, f), weights_only=False) + lmdb_txn.put(f.encode(), torchdata) +lmdb_txn.commit() \ No newline at end of file diff --git a/src/hirad/input_data/hdf5.py b/src/hirad/input_data/hdf5.py new file mode 100644 index 00000000..8e8980cf --- /dev/null +++ b/src/hirad/input_data/hdf5.py @@ -0,0 +1,104 @@ +import h5py +import os +import torch +import logging +import numpy as np + +IN_DIR = '/store_new/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-all-channels/era-interpolated' +DB_FILENAME='/store_new/mch/msopr/hirad-gen/all-channels-hdf5.db' + +class HiradHdf5: + def __init__(self, in_dir: str, db_path: str, data_shape: tuple, to_write = False): + self.in_dir = in_dir + self.files = sorted(os.listdir(in_dir)) + self.db_path = db_path + shape = list(data_shape) + shape.insert(0,len(self.files)) + self.db_shape = tuple(shape) + self.to_write = to_write + if to_write: + self.dbf = h5py.File(DB_FILENAME, "w") + self.dset = self.dbf.create_dataset("all-channels", self.db_shape, dtype='float64') + else: + self.dbf = h5py.File(DB_FILENAME, "r") + self.dset = self.dbf.require_dataset("all-channels", self.db_shape, dtype='float64', exact=True) + return + + def fill_database(self): + if not self.to_write: + raise PermissionError('database not opened with write') + logging.info('filling database') + with h5py.File(DB_FILENAME, "w") as dbf: + dset = dbf.create_dataset("all-channels", self.db_shape, dtype='float64') + #for i in range(len(self.files)): + for i in range(10): + f = self.files[i] + logging.info(f'reading {f}') + torchdata = torch.load(os.path.join(IN_DIR, f), weights_only=False) + dset[i,:] = torchdata + + def read_index(self, i: int): + return self.dset[i,:] + + def read_datetime(self, datefmt: str): + # Raises ValueError if not found + i = self.files.index(datefmt) + return self.read_index(i) + + def read_database(self): + for i in range(10): + nparray = self.dset[i,:] + f = self.files[i] + torchdata = torch.load(os.path.join(IN_DIR, f), weights_only=False) + logging.info(nparray.shape) + logging.info(nparray.dtype) + logging.info(np.equal(torchdata, nparray)) + + +def fill_database(): + print('starting') + files = sorted(os.listdir(IN_DIR)) + with h5py.File(DB_FILENAME, "w") as dbf: + dset = dbf.create_dataset("all-channels", (len(files), 101, 1, 191488,), dtype='float64') + + for i in range(len(files)): + #for i in range(10): + f = files[i] + logging.info(f'reading {f}') + torchdata = torch.load(os.path.join(IN_DIR, f), weights_only=False) + dset[i,:] = torchdata + + +def get_data_for_time(datefmt: str): + pass + + +def read_database(): + + files = sorted(os.listdir(IN_DIR)) + + with h5py.File(DB_FILENAME, "r") as dbf: + dset = dbf.require_dataset("all-channels", (len(files), 101, 1, 191488,), dtype='float64', exact=True) + for i in range(10): + nparray = dset[i,:] + f = files[i] + torchdata = torch.load(os.path.join(IN_DIR, f), weights_only=False) + logging.info(nparray.shape) + logging.info(nparray.dtype) + logging.info(np.equal(torchdata, nparray)) + + +def main(): + logging.basicConfig( + format='%(asctime)s %(levelname)-8s %(message)s', + level=logging.INFO, + datefmt='%Y-%m-%d %H:%M:%S') + hdf5db = HiradHdf5(IN_DIR, DB_FILENAME, (101, 1, 191488), True) + hdf5db.read_database() + #read_database() + #nparray = get_data_for_time('20160101-0900') + #print(nparray.dtype) + + +if __name__ == "__main__": + main() \ No newline at end of file From c34ea084fc40be23a5022db09f69f30c86e2c188 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 17 Nov 2025 19:34:52 +0100 Subject: [PATCH 193/302] cleanup hdf5 --- src/hirad/input_data/hdf5.py | 59 +++++++++--------------------------- 1 file changed, 14 insertions(+), 45 deletions(-) diff --git a/src/hirad/input_data/hdf5.py b/src/hirad/input_data/hdf5.py index 8e8980cf..10363e9d 100644 --- a/src/hirad/input_data/hdf5.py +++ b/src/hirad/input_data/hdf5.py @@ -1,4 +1,4 @@ -import h5py +from h5py import File import os import torch import logging @@ -17,10 +17,10 @@ def __init__(self, in_dir: str, db_path: str, data_shape: tuple, to_write = Fals self.db_shape = tuple(shape) self.to_write = to_write if to_write: - self.dbf = h5py.File(DB_FILENAME, "w") + self.dbf = File(DB_FILENAME, "w") self.dset = self.dbf.create_dataset("all-channels", self.db_shape, dtype='float64') else: - self.dbf = h5py.File(DB_FILENAME, "r") + self.dbf = File(DB_FILENAME, "r") self.dset = self.dbf.require_dataset("all-channels", self.db_shape, dtype='float64', exact=True) return @@ -28,14 +28,12 @@ def fill_database(self): if not self.to_write: raise PermissionError('database not opened with write') logging.info('filling database') - with h5py.File(DB_FILENAME, "w") as dbf: - dset = dbf.create_dataset("all-channels", self.db_shape, dtype='float64') - #for i in range(len(self.files)): - for i in range(10): - f = self.files[i] - logging.info(f'reading {f}') - torchdata = torch.load(os.path.join(IN_DIR, f), weights_only=False) - dset[i,:] = torchdata + #for i in range(len(self.files)): + for i in range(10): + f = self.files[i] + logging.info(f'reading {f}') + torchdata = torch.load(os.path.join(IN_DIR, f), weights_only=False) + self.dset[i,:] = torchdata def read_index(self, i: int): return self.dset[i,:] @@ -54,47 +52,18 @@ def read_database(self): logging.info(nparray.dtype) logging.info(np.equal(torchdata, nparray)) - -def fill_database(): - print('starting') - files = sorted(os.listdir(IN_DIR)) - with h5py.File(DB_FILENAME, "w") as dbf: - dset = dbf.create_dataset("all-channels", (len(files), 101, 1, 191488,), dtype='float64') - - for i in range(len(files)): - #for i in range(10): - f = files[i] - logging.info(f'reading {f}') - torchdata = torch.load(os.path.join(IN_DIR, f), weights_only=False) - dset[i,:] = torchdata - - -def get_data_for_time(datefmt: str): - pass - - -def read_database(): - - files = sorted(os.listdir(IN_DIR)) - - with h5py.File(DB_FILENAME, "r") as dbf: - dset = dbf.require_dataset("all-channels", (len(files), 101, 1, 191488,), dtype='float64', exact=True) - for i in range(10): - nparray = dset[i,:] - f = files[i] - torchdata = torch.load(os.path.join(IN_DIR, f), weights_only=False) - logging.info(nparray.shape) - logging.info(nparray.dtype) - logging.info(np.equal(torchdata, nparray)) - - def main(): logging.basicConfig( format='%(asctime)s %(levelname)-8s %(message)s', level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') + # write DB hdf5db = HiradHdf5(IN_DIR, DB_FILENAME, (101, 1, 191488), True) + + hdf5db = HiradHdf5(IN_DIR, DB_FILENAME, (101, 1, 191488), False) hdf5db.read_database() + hdf5db.read_index(0) + hdf5db.read_datetime('20160101-0900') #read_database() #nparray = get_data_for_time('20160101-0900') #print(nparray.dtype) From 886949fd3ff4d73e8d5284d1047b00d5c333cf99 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 17 Nov 2025 19:35:58 +0100 Subject: [PATCH 194/302] lmdb --- src/hirad/input_data/generate_lmdb.py | 81 ++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/src/hirad/input_data/generate_lmdb.py b/src/hirad/input_data/generate_lmdb.py index 6df35390..04134fc2 100644 --- a/src/hirad/input_data/generate_lmdb.py +++ b/src/hirad/input_data/generate_lmdb.py @@ -1,17 +1,72 @@ import lmdb import os import torch +import numpy as np -print('starting') -in_dir = '/store_new/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-all-channels/era-interpolated' -filename='/store_new/mch/msopr/hirad-gen/all-channels-lmdb.db' - -lmdb_env = lmdb.open(filename, map_size=int(1e10)) -lmdb_txn = lmdb_env.begin(write=True) -files = sorted(os.listdir(in_dir)) -for i in range(100): - f = files[i] - print(i) - torchdata = torch.load(os.path.join(in_dir, f), weights_only=False) - lmdb_txn.put(f.encode(), torchdata) -lmdb_txn.commit() \ No newline at end of file +IN_DIR = '/store_new/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-all-channels/era-interpolated' +DB_FILENAME='/store_new/mch/msopr/hirad-gen/all-channels-lmdb.db' + +class HiradLmdb: + def __init__(self, db_path: str, to_write = False): + self.env = lmdb.open(db_path) + self.txn = self.env.begin(write=to_write) + self.cursor = self.txn.cursor() + return + + def get_next(self) -> (str, np.ndarray): + return key, nparray + + +def fill_database(): + print('starting') + + lmdb_env = lmdb.open(DB_FILENAME, map_size=int(1e11)) + lmdb_txn = lmdb_env.begin(write=True) + files = sorted(os.listdir(IN_DIR)) + #for i in range(len(files)): + for i in range(10): + f = files[i] + print(f) + torchdata = torch.load(os.path.join(IN_DIR, f), weights_only=False) + lmdb_txn.put(f.encode(), torchdata) + print(torchdata.shape) + print(torchdata.dtype) + lmdb_txn.commit() + lmdb_env.close() + +def get_data_for_time(datefmt: str): + lmdb_env = lmdb.open(DB_FILENAME) + lmdb_txn = lmdb_env.begin(write=False) + data = lmdb_txn.get(datefmt.encode()) + if data == None: + raise KeyError(f'date {datefmt} not found in database') + data = np.frombuffer(data, dtype=np.float64).reshape([101,1,191488]) + lmdb_txn.commit() + lmdb_env.close() + return data + + + +def read_database(): + lmdb_env = lmdb.open(DB_FILENAME) + lmdb_txn = lmdb_env.begin(write=False) + lmdb_cursor = lmdb_txn.cursor() + nsamples = 0 + for key, value in lmdb_cursor: + print (type(key)) + print(key) + print (type(value)) + nsamples = nsamples + 1 + print(nsamples) + + + +def main(): + fill_database() + read_database() + nparray = get_data_for_time('20160101-0900') + print(nparray.dtype) + + +if __name__ == "__main__": + main() \ No newline at end of file From e07ec1602d60ee64f2166c1f055517b3b9f4c352 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 18 Nov 2025 11:11:13 +0100 Subject: [PATCH 195/302] corrected dimensions in hdf db (and new path) --- src/hirad/input_data/hdf5.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/hirad/input_data/hdf5.py b/src/hirad/input_data/hdf5.py index 10363e9d..538f2907 100644 --- a/src/hirad/input_data/hdf5.py +++ b/src/hirad/input_data/hdf5.py @@ -5,7 +5,7 @@ import numpy as np IN_DIR = '/store_new/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-all-channels/era-interpolated' -DB_FILENAME='/store_new/mch/msopr/hirad-gen/all-channels-hdf5.db' +DB_FILENAME='/store_new/mch/msopr/hirad-gen/input-data-hdf5.db' class HiradHdf5: def __init__(self, in_dir: str, db_path: str, data_shape: tuple, to_write = False): @@ -28,10 +28,10 @@ def fill_database(self): if not self.to_write: raise PermissionError('database not opened with write') logging.info('filling database') - #for i in range(len(self.files)): - for i in range(10): + for i in range(len(self.files)): + #for i in range(10): f = self.files[i] - logging.info(f'reading {f}') + logging.info(f'saving {f}') torchdata = torch.load(os.path.join(IN_DIR, f), weights_only=False) self.dset[i,:] = torchdata @@ -48,9 +48,7 @@ def read_database(self): nparray = self.dset[i,:] f = self.files[i] torchdata = torch.load(os.path.join(IN_DIR, f), weights_only=False) - logging.info(nparray.shape) - logging.info(nparray.dtype) - logging.info(np.equal(torchdata, nparray)) + logging.info(np.array_equal(torchdata, nparray)) def main(): logging.basicConfig( @@ -59,7 +57,8 @@ def main(): datefmt='%Y-%m-%d %H:%M:%S') # write DB hdf5db = HiradHdf5(IN_DIR, DB_FILENAME, (101, 1, 191488), True) - + hdf5db.fill_database() + # read DB hdf5db = HiradHdf5(IN_DIR, DB_FILENAME, (101, 1, 191488), False) hdf5db.read_database() hdf5db.read_index(0) From 50e209a87bcb1ee2b1326946613776a24d338d3b Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 18 Nov 2025 11:13:26 +0100 Subject: [PATCH 196/302] un-comment the write db commands --- src/hirad/input_data/hdf5.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/hirad/input_data/hdf5.py b/src/hirad/input_data/hdf5.py index 538f2907..b45c29da 100644 --- a/src/hirad/input_data/hdf5.py +++ b/src/hirad/input_data/hdf5.py @@ -43,7 +43,7 @@ def read_datetime(self, datefmt: str): i = self.files.index(datefmt) return self.read_index(i) - def read_database(self): + def test_read_database(self): for i in range(10): nparray = self.dset[i,:] f = self.files[i] @@ -56,16 +56,15 @@ def main(): level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') # write DB - hdf5db = HiradHdf5(IN_DIR, DB_FILENAME, (101, 1, 191488), True) - hdf5db.fill_database() + #hdf5db = HiradHdf5(IN_DIR, DB_FILENAME, (101, 1, 191488), True) + #hdf5db.fill_database() # read DB hdf5db = HiradHdf5(IN_DIR, DB_FILENAME, (101, 1, 191488), False) - hdf5db.read_database() - hdf5db.read_index(0) - hdf5db.read_datetime('20160101-0900') - #read_database() - #nparray = get_data_for_time('20160101-0900') - #print(nparray.dtype) + hdf5db.test_read_database() + jan10000 = hdf5db.read_index(0) + jan10900 = hdf5db.read_datetime('20160101-0900') + logging.info(jan10000.shape) + logging.info(jan10900.shape) if __name__ == "__main__": From 6b34c32af8bc5ca9756e0506cd4adb4524bf80f3 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 18 Nov 2025 17:07:43 +0100 Subject: [PATCH 197/302] Make cosmo dataset optional. --- src/hirad/input_data/interpolate_basic.py | 47 ++++++++++++++--------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index b7e4658d..7e4825a5 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -60,10 +60,11 @@ def regrid(era_for_time: np.ndarray, input_grid: np.ndarray, output_grid: np.nda regrid = griddata(input_grid, values, output_grid, method='linear') # interpolate era5 to cosmo grid using scipy griddata linear interpolated_data[j,0,:] = regrid return interpolated_data + -def _interpolate_era5_cosmo_task(i: int, era: Dataset, cosmo: Dataset, input_grid: np.ndarray, output_grid: np.ndarray, intermediate_files_path: str, outfile_plots_path: str = None, plot_indices=[0]): - logging.info('interpolating time point ' + _format_date(cosmo.dates[i])) - interpolated_data = np.empty([era.shape[1], 1, cosmo.shape[3]]) +def _interpolate_era5_cosmo_task(i: int, era: Dataset, cosmo: Dataset | None, input_grid: np.ndarray, output_grid: np.ndarray, intermediate_files_path: str, outfile_plots_path: str = None, plot_indices=[0]): + logging.info('interpolating time point ' + _format_date(era.dates[i])) + interpolated_data = np.empty([era.shape[1], 1, output_grid.shape[0]]) for j in range(era.shape[1]): values = np.array(era[i,j,0,:]) # get era grid values on the given date-time and channel regrid = griddata(input_grid, values, output_grid, method='linear') # interpolate era5 to cosmo grid using scipy griddata linear @@ -72,23 +73,25 @@ def _interpolate_era5_cosmo_task(i: int, era: Dataset, cosmo: Dataset, input_gri if (intermediate_files_path): _save_datetime_file(interpolated_data, era.variables, era.dates[i], os.path.join(intermediate_files_path, "era-interpolated/")) _save_datetime_file(era[i,:,:,:], era.variables, era.dates[i], os.path.join(intermediate_files_path, "era/")) - _save_datetime_file(cosmo[i,:,:,:], cosmo.variables, cosmo.dates[i], os.path.join(intermediate_files_path, "cosmo/")) - logging.info(f'finished writing time point { _format_date(cosmo.dates[i])}') + if cosmo: + _save_datetime_file(cosmo[i,:,:,:], cosmo.variables, cosmo.dates[i], os.path.join(intermediate_files_path, "cosmo/")) + logging.info(f'finished writing time point { _format_date(era.dates[i])}') if outfile_plots_path and i in plot_indices: datestr = _format_date(era.dates[i]) logging.info(f'plotting {datestr} to {outfile_plots_path}') for j,var in enumerate(era.variables): # plot era original - plot_and_save_projection(era.longitudes, era.latitudes, era[i, j, 0, :], f'{outfile_plots_path}{era.variables[j]}-{datestr}-era.jpg') + plot_and_save_projection(input_grid[0,:], input_grid[1,:], era[i, j, 0, :], f'{outfile_plots_path}{era.variables[j]}-{datestr}-era.jpg') - plot_and_save_projection(cosmo.longitudes, cosmo.latitudes, interpolated_data[j, 0, :], f'{outfile_plots_path}{era.variables[j]}-{datestr}-era-interpolated.jpg') - for j,var in enumerate(cosmo.variables): - plot_and_save_projection(cosmo.longitudes, cosmo.latitudes, cosmo[i, j, 0, :], f'{outfile_plots_path}{cosmo.variables[j]}-{datestr}-cosmo.jpg') + plot_and_save_projection(output_grid[0,:], output_grid[1,:], interpolated_data[j, 0, :], f'{outfile_plots_path}{era.variables[j]}-{datestr}-era-interpolated.jpg') + if cosmo: + for j,var in enumerate(cosmo.variables): + plot_and_save_projection(output_grid[0,:], output_grid[1,:], cosmo[i, j, 0, :], f'{outfile_plots_path}{cosmo.variables[j]}-{datestr}-cosmo.jpg') -def _interpolate_era5_cosmo_basic(era: Dataset, cosmo: Dataset, intermediate_files_path: str, threaded = True, outfile_plots_path: str =None, plot_indices=[0]): +def _interpolate_era5_cosmo_basic(era: Dataset, cosmo: Dataset | None, intermediate_files_path: str, threaded = True, outfile_plots_path: str =None, plot_indices=[0]): """Perform simple interpolation from ERA5 to COSMO grid for all data points in the COSMO date range. Parameters: @@ -104,14 +107,20 @@ def _interpolate_era5_cosmo_basic(era: Dataset, cosmo: Dataset, intermediate_fil 4-D array of interpolated values. (date, variable, ensemble, grid-point) """ # Check that our date ranges do in fact line up. - assert (era.start_date == cosmo.start_date and - era.end_date == cosmo.end_date and - era.frequency == cosmo.frequency and - era.shape[0] == cosmo.shape[0]), "ERA and COSMO date ranges or frequencies do not align." + if cosmo: + assert (era.start_date == cosmo.start_date and + era.end_date == cosmo.end_date and + era.frequency == cosmo.frequency and + era.shape[0] == cosmo.shape[0]), "ERA and COSMO date ranges or frequencies do not align." input_grid = np.column_stack((era.longitudes, era.latitudes)) # stack lon-lat columns of era5 points - output_grid = np.column_stack((cosmo.longitudes, cosmo.latitudes)) # stack lon-lat column of cosmo points - - dates = range(cosmo.shape[0]) + output_grid = None + if cosmo: + output_grid = np.column_stack((cosmo.longitudes, cosmo.latitudes)) # stack lon-lat column of cosmo points + else: + cosmo_latlon = torch.load(os.path.join(intermediate_files_path, 'info', 'cosmo-lat-lon'), weights_only=False) + output_grid = np.column_stack(cosmo_latlon[1,:], cosmo_latlon[0,:]) + + dates = range(era.shape[0]) if (threaded): pool = multiprocessing.Pool() @@ -186,7 +195,7 @@ def interpolate_era5_cosmo_and_save(infile_era: str, infile_cosmo: str, outfile_ os.makedirs(outfile_plots_path, exist_ok=True) logging.info(f'reading input according to configs {infile_era} and {infile_cosmo}') - era, cosmo = _read_input(infile_era, infile_cosmo, bound_to_cosmo_area=True) + era, cosmo = _read_era5_cosmo(infile_era, infile_cosmo, bound_to_cosmo_area=True) logging.info('Successfully read input') # Output stats and grid @@ -216,6 +225,8 @@ def main(): format='%(asctime)s %(levelname)-8s %(message)s', level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') + + logging.info('running {sys.argv}') interpolate_era5_cosmo_and_save(infile_era, infile_cosmo, output_directory, threaded=False, outfile_plots_path=None) From 853fca0610af3798df7a25649af9bc7ccd6dbaf5 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 18 Nov 2025 17:09:18 +0100 Subject: [PATCH 198/302] update era location --- src/hirad/input_data/era.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hirad/input_data/era.yaml b/src/hirad/input_data/era.yaml index 9018031a..4a161130 100644 --- a/src/hirad/input_data/era.yaml +++ b/src/hirad/input_data/era.yaml @@ -1,7 +1,8 @@ #dataset: '/scratch/mch/apennino/data/aifs-ea-an-oper-0001-mars-n320-1979-2022-6h-v6.zarr' -dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' select: ['2t', '10u', '10v', 'tcw', 't_850', 'z_850', 'u_850', 'v_850', 't_500', 'z_500', 'u_500', 'v_500', 'tp'] # See table S2 from corrdiff paper for the inputs. # Note: Bounding dates/area will be done in .py code. -start: 2020-01-01 -end: 2020-01-31 \ No newline at end of file +start: 2019-01-01 +end: 2019-04-30 \ No newline at end of file From 3fd375dbabc7f7596253736595acd5f9341e2163 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 18 Nov 2025 18:36:06 +0100 Subject: [PATCH 199/302] correct dims for output grid --- src/hirad/input_data/interpolate_basic.py | 39 ++++++++++++++++------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index 7e4825a5..259d7258 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -69,7 +69,7 @@ def _interpolate_era5_cosmo_task(i: int, era: Dataset, cosmo: Dataset | None, in values = np.array(era[i,j,0,:]) # get era grid values on the given date-time and channel regrid = griddata(input_grid, values, output_grid, method='linear') # interpolate era5 to cosmo grid using scipy griddata linear interpolated_data[j,0,:] = regrid - logging.info(f'writing time point { _format_date(cosmo.dates[i])} to files in path {intermediate_files_path}') + logging.info(f'writing time point { _format_date(era.dates[i])} to files in path {intermediate_files_path}') if (intermediate_files_path): _save_datetime_file(interpolated_data, era.variables, era.dates[i], os.path.join(intermediate_files_path, "era-interpolated/")) _save_datetime_file(era[i,:,:,:], era.variables, era.dates[i], os.path.join(intermediate_files_path, "era/")) @@ -82,12 +82,12 @@ def _interpolate_era5_cosmo_task(i: int, era: Dataset, cosmo: Dataset | None, in logging.info(f'plotting {datestr} to {outfile_plots_path}') for j,var in enumerate(era.variables): # plot era original - plot_and_save_projection(input_grid[0,:], input_grid[1,:], era[i, j, 0, :], f'{outfile_plots_path}{era.variables[j]}-{datestr}-era.jpg') + plot_and_save_projection(input_grid[:,0], input_grid[:,1], era[i, j, 0, :], f'{outfile_plots_path}{era.variables[j]}-{datestr}-era.jpg') - plot_and_save_projection(output_grid[0,:], output_grid[1,:], interpolated_data[j, 0, :], f'{outfile_plots_path}{era.variables[j]}-{datestr}-era-interpolated.jpg') + plot_and_save_projection(output_grid[:,0], output_grid[:,1], interpolated_data[j, 0, :], f'{outfile_plots_path}{era.variables[j]}-{datestr}-era-interpolated.jpg') if cosmo: for j,var in enumerate(cosmo.variables): - plot_and_save_projection(output_grid[0,:], output_grid[1,:], cosmo[i, j, 0, :], f'{outfile_plots_path}{cosmo.variables[j]}-{datestr}-cosmo.jpg') + plot_and_save_projection(output_grid[:,0], output_grid[:,1], cosmo[i, j, 0, :], f'{outfile_plots_path}{cosmo.variables[j]}-{datestr}-cosmo.jpg') @@ -118,7 +118,7 @@ def _interpolate_era5_cosmo_basic(era: Dataset, cosmo: Dataset | None, intermedi output_grid = np.column_stack((cosmo.longitudes, cosmo.latitudes)) # stack lon-lat column of cosmo points else: cosmo_latlon = torch.load(os.path.join(intermediate_files_path, 'info', 'cosmo-lat-lon'), weights_only=False) - output_grid = np.column_stack(cosmo_latlon[1,:], cosmo_latlon[0,:]) + output_grid = np.column_stack((cosmo_latlon[:,1], cosmo_latlon[:,0])) dates = range(era.shape[0]) @@ -195,17 +195,32 @@ def interpolate_era5_cosmo_and_save(infile_era: str, infile_cosmo: str, outfile_ os.makedirs(outfile_plots_path, exist_ok=True) logging.info(f'reading input according to configs {infile_era} and {infile_cosmo}') - era, cosmo = _read_era5_cosmo(infile_era, infile_cosmo, bound_to_cosmo_area=True) + era = None + cosmo = None + if infile_cosmo.endswith('yaml'): + era, cosmo = _read_era5_cosmo(infile_era, infile_cosmo, bound_to_cosmo_area=True) + save_anemoi_stats(cosmo, os.path.join(outfile_data_path, "info/cosmo-stats")) + save_anemoi_latlon_grid(cosmo, os.path.join(outfile_data_path, "info/cosmo-lat-lon")) + shutil.copy(infile_cosmo, os.path.join(outfile_data_path, "info/cosmo.yaml")) + else: + cosmo_latlon = torch.load(infile_cosmo, weights_only=False) + lats = cosmo_latlon[:,0] + lons = cosmo_latlon[:,1] + min_lat = min(lats) - ERA_MARGIN_DEGREES + max_lat = max(lats) + ERA_MARGIN_DEGREES + min_lon = min(lons) - ERA_MARGIN_DEGREES + max_lon = max(lons) + ERA_MARGIN_DEGREES + area=(max_lat, min_lon, min_lat, max_lon) + logging.info(f'projecting onto era area {area}') + era = read_era5_anemoi(infile_era, area = area) + logging.info('Successfully read input') # Output stats and grid save_anemoi_stats(era, os.path.join(outfile_data_path, "info/era-stats")) - save_anemoi_stats(cosmo, os.path.join(outfile_data_path, "info/cosmo-stats")) - save_anemoi_latlon_grid(cosmo, os.path.join(outfile_data_path, "info/cosmo-lat-lon")) save_anemoi_latlon_grid(era, os.path.join(outfile_data_path, "info/era-lat-lon")) # Copy the .yaml files over for recording purposes - shutil.copy(infile_cosmo, os.path.join(outfile_data_path, "info/cosmo.yaml")) shutil.copy(infile_era, os.path.join(outfile_data_path, "info/era.yaml")) # generate interpolated data @@ -226,9 +241,11 @@ def main(): level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') - logging.info('running {sys.argv}') + logging.info(f'running {sys.argv}') + outfile_plots_path = None + #outfile_plots_path = os.path.join(output_directory, 'plots') - interpolate_era5_cosmo_and_save(infile_era, infile_cosmo, output_directory, threaded=False, outfile_plots_path=None) + interpolate_era5_cosmo_and_save(infile_era, infile_cosmo, output_directory, threaded=False, outfile_plots_path=outfile_plots_path) if __name__ == "__main__": main() From e91ad5798ceee5b58db186e0e8bf75751be81514 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 20 Nov 2025 09:31:47 +0100 Subject: [PATCH 200/302] add bunch of configs for batches --- .../input_data/configs/era-all-201901.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-201902.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-201903.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-201904.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-201905.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-201906.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-201907.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-201908.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-201909.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-201910.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-201911.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-201912.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-202004.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-202005.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-202006.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-202007.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-202008.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-202009.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-202010.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-202011.yaml | 20 +++++++++++++++++++ .../input_data/configs/era-all-202012.yaml | 20 +++++++++++++++++++ src/hirad/input_data/cosmo-all.yaml | 3 ++- src/hirad/input_data/era-all.yaml | 7 +++++-- src/hirad/interpolate-batches.sh | 8 ++++++++ 24 files changed, 435 insertions(+), 3 deletions(-) create mode 100755 src/hirad/input_data/configs/era-all-201901.yaml create mode 100755 src/hirad/input_data/configs/era-all-201902.yaml create mode 100755 src/hirad/input_data/configs/era-all-201903.yaml create mode 100755 src/hirad/input_data/configs/era-all-201904.yaml create mode 100755 src/hirad/input_data/configs/era-all-201905.yaml create mode 100755 src/hirad/input_data/configs/era-all-201906.yaml create mode 100755 src/hirad/input_data/configs/era-all-201907.yaml create mode 100755 src/hirad/input_data/configs/era-all-201908.yaml create mode 100755 src/hirad/input_data/configs/era-all-201909.yaml create mode 100755 src/hirad/input_data/configs/era-all-201910.yaml create mode 100755 src/hirad/input_data/configs/era-all-201911.yaml create mode 100755 src/hirad/input_data/configs/era-all-201912.yaml create mode 100755 src/hirad/input_data/configs/era-all-202004.yaml create mode 100755 src/hirad/input_data/configs/era-all-202005.yaml create mode 100755 src/hirad/input_data/configs/era-all-202006.yaml create mode 100755 src/hirad/input_data/configs/era-all-202007.yaml create mode 100755 src/hirad/input_data/configs/era-all-202008.yaml create mode 100755 src/hirad/input_data/configs/era-all-202009.yaml create mode 100755 src/hirad/input_data/configs/era-all-202010.yaml create mode 100755 src/hirad/input_data/configs/era-all-202011.yaml create mode 100755 src/hirad/input_data/configs/era-all-202012.yaml create mode 100644 src/hirad/interpolate-batches.sh diff --git a/src/hirad/input_data/configs/era-all-201901.yaml b/src/hirad/input_data/configs/era-all-201901.yaml new file mode 100755 index 00000000..748769d4 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-201901.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2019-01-01 +end: 2019-01-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-201902.yaml b/src/hirad/input_data/configs/era-all-201902.yaml new file mode 100755 index 00000000..4a7c9431 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-201902.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2019-02-01 +end: 2019-02-28 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-201903.yaml b/src/hirad/input_data/configs/era-all-201903.yaml new file mode 100755 index 00000000..e96ec7e5 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-201903.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2019-03-01 +end: 2019-03-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-201904.yaml b/src/hirad/input_data/configs/era-all-201904.yaml new file mode 100755 index 00000000..6a17a0ac --- /dev/null +++ b/src/hirad/input_data/configs/era-all-201904.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2019-04-01 +end: 2019-04-30 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-201905.yaml b/src/hirad/input_data/configs/era-all-201905.yaml new file mode 100755 index 00000000..a5ac343c --- /dev/null +++ b/src/hirad/input_data/configs/era-all-201905.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2019-05-01 +end: 2019-05-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-201906.yaml b/src/hirad/input_data/configs/era-all-201906.yaml new file mode 100755 index 00000000..1dc7d618 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-201906.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2019-06-01 +end: 2019-06-30 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-201907.yaml b/src/hirad/input_data/configs/era-all-201907.yaml new file mode 100755 index 00000000..32b7de07 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-201907.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2019-07-01 +end: 2019-07-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-201908.yaml b/src/hirad/input_data/configs/era-all-201908.yaml new file mode 100755 index 00000000..4803ba56 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-201908.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2019-08-01 +end: 2019-08-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-201909.yaml b/src/hirad/input_data/configs/era-all-201909.yaml new file mode 100755 index 00000000..1cd54366 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-201909.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2019-09-01 +end: 2019-09-30 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-201910.yaml b/src/hirad/input_data/configs/era-all-201910.yaml new file mode 100755 index 00000000..44df24e0 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-201910.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2019-10-01 +end: 2019-10-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-201911.yaml b/src/hirad/input_data/configs/era-all-201911.yaml new file mode 100755 index 00000000..0615b2e6 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-201911.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2019-11-01 +end: 2019-11-30 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-201912.yaml b/src/hirad/input_data/configs/era-all-201912.yaml new file mode 100755 index 00000000..58fb5a66 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-201912.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2019-12-01 +end: 2019-12-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-202004.yaml b/src/hirad/input_data/configs/era-all-202004.yaml new file mode 100755 index 00000000..4a76c512 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-202004.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2020-04-01 +end: 2020-04-30 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-202005.yaml b/src/hirad/input_data/configs/era-all-202005.yaml new file mode 100755 index 00000000..a8356dbd --- /dev/null +++ b/src/hirad/input_data/configs/era-all-202005.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2020-05-01 +end: 2020-05-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-202006.yaml b/src/hirad/input_data/configs/era-all-202006.yaml new file mode 100755 index 00000000..e98a075c --- /dev/null +++ b/src/hirad/input_data/configs/era-all-202006.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2020-06-01 +end: 2020-06-30 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-202007.yaml b/src/hirad/input_data/configs/era-all-202007.yaml new file mode 100755 index 00000000..5d009358 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-202007.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2020-07-01 +end: 2020-07-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-202008.yaml b/src/hirad/input_data/configs/era-all-202008.yaml new file mode 100755 index 00000000..d9fd8d65 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-202008.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2020-08-01 +end: 2020-08-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-202009.yaml b/src/hirad/input_data/configs/era-all-202009.yaml new file mode 100755 index 00000000..e63731df --- /dev/null +++ b/src/hirad/input_data/configs/era-all-202009.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2020-09-01 +end: 2020-09-30 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-202010.yaml b/src/hirad/input_data/configs/era-all-202010.yaml new file mode 100755 index 00000000..e566ace6 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-202010.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2020-10-01 +end: 2020-10-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-202011.yaml b/src/hirad/input_data/configs/era-all-202011.yaml new file mode 100755 index 00000000..a681b3c1 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-202011.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2020-11-01 +end: 2020-11-30 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-202012.yaml b/src/hirad/input_data/configs/era-all-202012.yaml new file mode 100755 index 00000000..00f76db3 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-202012.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2020-12-01 +end: 2020-12-31 \ No newline at end of file diff --git a/src/hirad/input_data/cosmo-all.yaml b/src/hirad/input_data/cosmo-all.yaml index 034210e8..ebc19dd3 100644 --- a/src/hirad/input_data/cosmo-all.yaml +++ b/src/hirad/input_data/cosmo-all.yaml @@ -1,4 +1,5 @@ -dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-co2-an-archive-0p02-2015-2020-1h-v3-pl13.zarr' +#dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-co2-an-archive-0p02-2015-2020-1h-v3-pl13.zarr' +dataset: '/capstor/store/mch/msopr/hirad-gen/anemoi-datasets/mch-co2-an-archive-0p02-2015-2020-1h-v3-pl13.zarr' select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'hsurf', 'insolation', 'lsm', 'msl', 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', diff --git a/src/hirad/input_data/era-all.yaml b/src/hirad/input_data/era-all.yaml index bca216c3..3bb3f8df 100644 --- a/src/hirad/input_data/era-all.yaml +++ b/src/hirad/input_data/era-all.yaml @@ -1,6 +1,7 @@ #dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' -dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', 'lsm', 'msl', 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', @@ -14,4 +15,6 @@ select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_ ] # ALL ERA CHANNELS # Intersection between ERA and COSMO excludes cp, sdor, slor, tcw -# Note: Bounding dates/area will be done in .py code. \ No newline at end of file +# Note: Bounding dates/area will be done in .py code. +start: 2020-01-01 +end: 2020-01-31 \ No newline at end of file diff --git a/src/hirad/interpolate-batches.sh b/src/hirad/interpolate-batches.sh new file mode 100644 index 00000000..798e3965 --- /dev/null +++ b/src/hirad/interpolate-batches.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +for cfgfile in $(ls src/hirad/input_data/configs/era-all-2020*.yaml); +do + cmd="sbatch -A a161 -t 12:00:00 -n 1 -c 1 --environment=modulus_env src/hirad/interpolate.sh ${cfgfile}" + echo $cmd + $cmd +done \ No newline at end of file From 249bab58c560423475ba7e8e6afec7abe24340b3 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 20 Nov 2025 09:31:55 +0100 Subject: [PATCH 201/302] correct some things --- src/hirad/input_data/interpolate_basic.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index 259d7258..0c18e528 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -80,14 +80,16 @@ def _interpolate_era5_cosmo_task(i: int, era: Dataset, cosmo: Dataset | None, in if outfile_plots_path and i in plot_indices: datestr = _format_date(era.dates[i]) logging.info(f'plotting {datestr} to {outfile_plots_path}') - for j,var in enumerate(era.variables): + #for j,var in enumerate(era.variables): + for j in [0]: + var = era.variables[j] # plot era original - plot_and_save_projection(input_grid[:,0], input_grid[:,1], era[i, j, 0, :], f'{outfile_plots_path}{era.variables[j]}-{datestr}-era.jpg') + plot_and_save_projection(input_grid[:,0], input_grid[:,1], era[i, j, 0, :], f'{outfile_plots_path}/{era.variables[j]}-{datestr}-era.jpg') - plot_and_save_projection(output_grid[:,0], output_grid[:,1], interpolated_data[j, 0, :], f'{outfile_plots_path}{era.variables[j]}-{datestr}-era-interpolated.jpg') + plot_and_save_projection(output_grid[:,0], output_grid[:,1], interpolated_data[j, 0, :], f'{outfile_plots_path}/{era.variables[j]}-{datestr}-era-interpolated.jpg') if cosmo: for j,var in enumerate(cosmo.variables): - plot_and_save_projection(output_grid[:,0], output_grid[:,1], cosmo[i, j, 0, :], f'{outfile_plots_path}{cosmo.variables[j]}-{datestr}-cosmo.jpg') + plot_and_save_projection(output_grid[:,0], output_grid[:,1], cosmo[i, j, 0, :], f'{outfile_plots_path}/{cosmo.variables[j]}-{datestr}-cosmo.jpg') @@ -198,7 +200,7 @@ def interpolate_era5_cosmo_and_save(infile_era: str, infile_cosmo: str, outfile_ era = None cosmo = None if infile_cosmo.endswith('yaml'): - era, cosmo = _read_era5_cosmo(infile_era, infile_cosmo, bound_to_cosmo_area=True) + era, cosmo = _read_era5_cosmo(infile_era, infile_cosmo) save_anemoi_stats(cosmo, os.path.join(outfile_data_path, "info/cosmo-stats")) save_anemoi_latlon_grid(cosmo, os.path.join(outfile_data_path, "info/cosmo-lat-lon")) shutil.copy(infile_cosmo, os.path.join(outfile_data_path, "info/cosmo.yaml")) @@ -235,15 +237,17 @@ def main(): infile_cosmo = sys.argv[2] output_directory = sys.argv[3] + erashortname = infile_era.split('/')[-1].split('.')[0] + logging.basicConfig( - filename=os.path.join(output_directory, 'interpolate_basic.log'), + filename=os.path.join(output_directory, f'interpolate_basic-{erashortname}.log'), format='%(asctime)s %(levelname)-8s %(message)s', level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') logging.info(f'running {sys.argv}') - outfile_plots_path = None - #outfile_plots_path = os.path.join(output_directory, 'plots') + #outfile_plots_path = None + outfile_plots_path = os.path.join(output_directory, 'plots') interpolate_era5_cosmo_and_save(infile_era, infile_cosmo, output_directory, threaded=False, outfile_plots_path=outfile_plots_path) From e8452766efbe147a5b436646569331d380f94dea Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 20 Nov 2025 09:33:20 +0100 Subject: [PATCH 202/302] remove unused script --- interpolate.sh | 6 ------ 1 file changed, 6 deletions(-) delete mode 100755 interpolate.sh diff --git a/interpolate.sh b/interpolate.sh deleted file mode 100755 index 73e947da..00000000 --- a/interpolate.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -#SBATCH --partition=postproc -#SBATCH --time=23:59:00 - -python src/hirad/input_data/interpolate_basic.py src/hirad/input_data/era-all.yaml src/hirad/input_data/cosmo-all.yaml /store_new/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-all-channels/ From a2245f1207f277ca042ba34e1a71f53001aebdf9 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 20 Nov 2025 09:53:03 +0100 Subject: [PATCH 203/302] add interpolate script --- .gitignore | 1 - src/hirad/interpolate.sh | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100755 src/hirad/interpolate.sh diff --git a/.gitignore b/.gitignore index 94740d19..1b9f90c7 100644 --- a/.gitignore +++ b/.gitignore @@ -184,7 +184,6 @@ outputs/* temp.* # local script -interpolate.sh cosmo-grid.npz *.out .conda/* diff --git a/src/hirad/interpolate.sh b/src/hirad/interpolate.sh new file mode 100755 index 00000000..90a8d419 --- /dev/null +++ b/src/hirad/interpolate.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +#SBATCH --time=12:00:00 + + +#srun -A a161 -t 12:00:00 --environment=modulus_env bash -c " +# pip install -e . --no-dependencies +# pip install anemoi.datasets +# python src/hirad/input_data/interpolate_basic.py src/hirad/input_data/era-all.yaml src/hirad/input_data/cosmo-all.yaml /capstor/scratch/cscs/mmcgloho/datasets/processed/era5-cosmo-1h-all-channels/ +#" +pip install -e . --no-dependencies +pip install anemoi.datasets +#pip install meteodata-lab +#python src/hirad/input_data/interpolate_realch1.py \ +# src/hirad/input_data/era.yaml \ +# /capstor/store/mch/msopr/hirad-gen/basic-torch/era5-realch1/v0.2/info/realch1-lat-lon \ +# /capstor/store/mch/msopr/hirad-gen/copernicus-datasets/tp-2023-2024.nc \ +# /capstor/scratch/cscs/mmcgloho/basic-torch/era5-realch1/v1.0/ +python src/hirad/input_data/interpolate_basic.py \ +# src/hirad/input_data/era-all.yaml \ + $1 \ + /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/info/cosmo-lat-lon \ + /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/ From 564a0285dc162af3bccd59dd7cab4c3897aea49f Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 20 Nov 2025 10:09:13 +0100 Subject: [PATCH 204/302] oops commented line in the middle of the script isn't good --- src/hirad/interpolate.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hirad/interpolate.sh b/src/hirad/interpolate.sh index 90a8d419..7fc9ffa7 100755 --- a/src/hirad/interpolate.sh +++ b/src/hirad/interpolate.sh @@ -17,7 +17,6 @@ pip install anemoi.datasets # /capstor/store/mch/msopr/hirad-gen/copernicus-datasets/tp-2023-2024.nc \ # /capstor/scratch/cscs/mmcgloho/basic-torch/era5-realch1/v1.0/ python src/hirad/input_data/interpolate_basic.py \ -# src/hirad/input_data/era-all.yaml \ $1 \ /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/info/cosmo-lat-lon \ /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/ From 4e851177693ed9933c8d50457791ab4a4b95e53c Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 20 Nov 2025 12:51:16 +0100 Subject: [PATCH 205/302] refactor interpolate_basic script to be less complex --- .../input_data/configs/era-all-2016q2.yaml | 18 ++ .../input_data/configs/era-all-2016q3.yaml | 18 ++ src/hirad/input_data/interpolate_basic.py | 209 ++++++++++-------- src/hirad/input_data/interpolate_realch1.py | 2 +- 4 files changed, 153 insertions(+), 94 deletions(-) create mode 100644 src/hirad/input_data/configs/era-all-2016q2.yaml create mode 100644 src/hirad/input_data/configs/era-all-2016q3.yaml diff --git a/src/hirad/input_data/configs/era-all-2016q2.yaml b/src/hirad/input_data/configs/era-all-2016q2.yaml new file mode 100644 index 00000000..5d2d5ca9 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-2016q2.yaml @@ -0,0 +1,18 @@ +#dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +dataset: '/scratch/mch/miccatta/ml_datasets/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2016-04-01 +end: 2016-06-30 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-2016q3.yaml b/src/hirad/input_data/configs/era-all-2016q3.yaml new file mode 100644 index 00000000..ae3384e2 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-2016q3.yaml @@ -0,0 +1,18 @@ +#dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +dataset: '/scratch/mch/miccatta/ml_datasets/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2016-07-01 +end: 2016-09-30 \ No newline at end of file diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index 0c18e528..42114868 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -18,39 +18,18 @@ # Margin to use for ERA dataset (to avoid nans from interpolation at boundary) ERA_MARGIN_DEGREES = 1.0 -def _read_era5_cosmo(era_config_file: str, cosmo_config_file: str) -> tuple[Dataset, Dataset]: - """ - Read both ERA and COSMO data, optionally bounding to the COSMO data area, and return the 2m - temperature values for the time range under COSMO. - """ - # trim edge removes boundary - cosmo = read_cosmo_anemoi(cosmo_config_file) - # area = N, W, S, E - min_lat = min(cosmo.latitudes) - ERA_MARGIN_DEGREES - max_lat = max(cosmo.latitudes) + ERA_MARGIN_DEGREES - min_lon = min(cosmo.longitudes) - ERA_MARGIN_DEGREES - max_lon = max(cosmo.longitudes) + ERA_MARGIN_DEGREES - start_date = cosmo.metadata()['start_date'] - end_date = cosmo.metadata()['end_date'] - era = read_era5_anemoi(era_config_file, - start_date = start_date, end_date = end_date, - area=(max_lat, min_lon, min_lat, max_lon)) - return (era, cosmo) - -def read_cosmo_anemoi(cosmo_config_file: str): - with open(cosmo_config_file) as cosmo_file: - cosmo_config = yaml.safe_load(cosmo_file) - cosmo = open_dataset(cosmo_config) - return cosmo - -def read_era5_anemoi(era_config_file: str, start_date = None, - end_date = None, area=None): - with open(era_config_file) as era_file: - era_config = yaml.safe_load(era_file) - era = open_dataset(era_config) - era = open_dataset(era, start=start_date, end=end_date, - area=area) - return era +def read_anemoi_ds(config_file: str, start_date = None, end_date = None, area = None) -> Dataset: + with open(config_file) as cfg_file: + config = yaml.safe_load(cfg_file) + ds = open_dataset(config, start=start_date, end=end_date, area=area) + return ds + +def save_anemoi_latlon_grid(dataset: Dataset, filename: str): + grid = np.column_stack((dataset.latitudes, dataset.longitudes)) + torch.save(grid, filename) + +def save_anemoi_stats(dataset: Dataset, filename: str): + torch.save(dataset.statistics, filename) def regrid(era_for_time: np.ndarray, input_grid: np.ndarray, output_grid: np.ndarray): # shape (channel, ensemble, grid) @@ -60,37 +39,53 @@ def regrid(era_for_time: np.ndarray, input_grid: np.ndarray, output_grid: np.nda regrid = griddata(input_grid, values, output_grid, method='linear') # interpolate era5 to cosmo grid using scipy griddata linear interpolated_data[j,0,:] = regrid return interpolated_data - -def _interpolate_era5_cosmo_task(i: int, era: Dataset, cosmo: Dataset | None, input_grid: np.ndarray, output_grid: np.ndarray, intermediate_files_path: str, outfile_plots_path: str = None, plot_indices=[0]): - logging.info('interpolating time point ' + _format_date(era.dates[i])) - interpolated_data = np.empty([era.shape[1], 1, output_grid.shape[0]]) - for j in range(era.shape[1]): - values = np.array(era[i,j,0,:]) # get era grid values on the given date-time and channel - regrid = griddata(input_grid, values, output_grid, method='linear') # interpolate era5 to cosmo grid using scipy griddata linear - interpolated_data[j,0,:] = regrid - logging.info(f'writing time point { _format_date(era.dates[i])} to files in path {intermediate_files_path}') - if (intermediate_files_path): - _save_datetime_file(interpolated_data, era.variables, era.dates[i], os.path.join(intermediate_files_path, "era-interpolated/")) - _save_datetime_file(era[i,:,:,:], era.variables, era.dates[i], os.path.join(intermediate_files_path, "era/")) - if cosmo: - _save_datetime_file(cosmo[i,:,:,:], cosmo.variables, cosmo.dates[i], os.path.join(intermediate_files_path, "cosmo/")) - logging.info(f'finished writing time point { _format_date(era.dates[i])}') - - if outfile_plots_path and i in plot_indices: - datestr = _format_date(era.dates[i]) - logging.info(f'plotting {datestr} to {outfile_plots_path}') - #for j,var in enumerate(era.variables): +def format_date(dt64: np.datetime64) -> str: + """Makes date string from date time point, for saving files.""" + return to_datetime(dt64).strftime('%Y%m%d-%H%M') + +def _save_datetime_file(values: np.ndarray[np.intp], variables: np.ndarray, date: np.datetime64, filepath: str): + filename = filepath + format_date(date) + torch.save(values, filename) + +def plot_projection(ax, longitudes: np.array, latitudes: np.array, values: np.array, cmap=None, vmin = None, vmax = None, s = None): + p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax, s=s) + ax.coastlines() + ax.gridlines(draw_labels=True) + plt.colorbar(p, orientation="horizontal") + +def plot_and_save_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, projection=ccrs.PlateCarree(), cmap=None, vmin = None, vmax = None, s = None): + """Plot observed or interpolated data in a scatter plot.""" + # TODO: Refactor this somehow, it's not really generalizing well across variables. + fig = plt.figure() + fig, ax = plt.subplots(subplot_kw={"projection": projection}) + logging.info(f'plotting values to {filename}') + plot_projection(ax, longitudes, latitudes, values, cmap, vmin, vmax, s) + plt.savefig(filename) + plt.close('all') + +def interpolate_era_time_point_to_grid(i: int, era: Dataset, input_grid: np.ndarray, output_grid: np.ndarray, output_data_path: str, output_plots_path: str = None, plot_indices=[0]): + logging.info('interpolating time point ' + format_date(era.dates[i])) + interpolated_data = regrid(era[i,:,:,:], input_grid=input_grid, output_grid=output_grid) + logging.info(f'writing time point { format_date(era.dates[i])} to files in path {output_data_path}') + _save_datetime_file(interpolated_data, era.variables, era.dates[i], os.path.join(output_data_path)) + if output_plots_path and i in plot_indices: + datestr = format_date(era.dates[i]) + logging.info(f'plotting {datestr} to {output_plots_path}') for j in [0]: + #for j in range(len(era.variables)): var = era.variables[j] - # plot era original - plot_and_save_projection(input_grid[:,0], input_grid[:,1], era[i, j, 0, :], f'{outfile_plots_path}/{era.variables[j]}-{datestr}-era.jpg') - - plot_and_save_projection(output_grid[:,0], output_grid[:,1], interpolated_data[j, 0, :], f'{outfile_plots_path}/{era.variables[j]}-{datestr}-era-interpolated.jpg') - if cosmo: - for j,var in enumerate(cosmo.variables): - plot_and_save_projection(output_grid[:,0], output_grid[:,1], cosmo[i, j, 0, :], f'{outfile_plots_path}/{cosmo.variables[j]}-{datestr}-cosmo.jpg') + # plot era original + plot_and_save_projection(input_grid[:,0], input_grid[:,1], era[i, j, 0, :], f'{output_plots_path}/{era.variables[j]}-{datestr}-era.jpg') + # plot interpolated + plot_and_save_projection(output_grid[:,0], output_grid[:,1], interpolated_data[j, 0, :], f'{output_plots_path}/{era.variables[j]}-{datestr}-era-interpolated.jpg') +def save_anemoi_time_point(i: int, ds: Dataset, data_output_path: str, ds_name: str, plots_output_path: str = None, plot_indices=[0]): + _save_datetime_file(ds[i,:,:,:], ds.variables, ds.dates[i], data_output_path) + datestr = format_date(ds.dates[i]) + if plots_output_path and i in plot_indices: + for j,var in enumerate(ds.variables): + plot_and_save_projection(ds.longitudes, ds.latitudes, ds[i, j, 0, :], f'{plots_output_path}/{var}-{datestr}-{ds_name}.jpg') def _interpolate_era5_cosmo_basic(era: Dataset, cosmo: Dataset | None, intermediate_files_path: str, threaded = True, outfile_plots_path: str =None, plot_indices=[0]): @@ -137,38 +132,47 @@ def _interpolate_era5_cosmo_basic(era: Dataset, cosmo: Dataset | None, intermedi return -def _format_date(dt64: np.datetime64) -> str: - """Makes date string from date time point, for saving files.""" - return to_datetime(dt64).strftime('%Y%m%d-%H%M') +### Main method 1: Interpolate ERA grid +def _interpolate_era5_to_grid(infile_era: str, output_grid: np.ndarray, output_path: str, plot_indices=[0]): + # read data + lats = output_grid[:,1] + lons = output_grid[:,0] + min_lat = min(lats) - ERA_MARGIN_DEGREES + max_lat = max(lats) + ERA_MARGIN_DEGREES + min_lon = min(lons) - ERA_MARGIN_DEGREES + max_lon = max(lons) + ERA_MARGIN_DEGREES + area=(max_lat, min_lon, min_lat, max_lon) + logging.info(f'projecting onto era area {area}') + era = read_anemoi_ds(infile_era, area = area) + logging.info('Successfully read input') + + # Output stats and grid + save_anemoi_stats(era, os.path.join(output_path, "info/era-stats")) + save_anemoi_latlon_grid(era, os.path.join(output_path, "info/era-lat-lon")) -def _save_datetime_file(values: np.ndarray[np.intp], variables: np.ndarray, date: np.datetime64, filepath: str): - filename = filepath + _format_date(date) - torch.save(values, filename) + # Copy the .yaml files over for recording purposes + shutil.copy(infile_era, os.path.join(output_path, "info/era.yaml")) -def save_anemoi_latlon_grid(dataset: Dataset, filename: str): - grid = np.column_stack((dataset.latitudes, dataset.longitudes)) - torch.save(grid, filename) + input_grid = np.column_stack((era.longitudes, era.latitudes)) -def save_anemoi_stats(dataset: Dataset, filename: str): - torch.save(dataset.statistics, filename) + for i in range(len(era.dates)): + interpolate_era_time_point_to_grid(i, era, input_grid, output_grid, + os.path.join(output_path, "era-interpolated"), + os.path.join(output_path, "plots"), + plot_indices) -def plot_projection(ax, longitudes: np.array, latitudes: np.array, values: np.array, cmap=None, vmin = None, vmax = None, s = None): - p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax, s=s) - ax.coastlines() - ax.gridlines(draw_labels=True) - plt.colorbar(p, orientation="horizontal") -def plot_and_save_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, projection=ccrs.PlateCarree(), cmap=None, vmin = None, vmax = None, s = None): - """Plot observed or interpolated data in a scatter plot.""" - # TODO: Refactor this somehow, it's not really generalizing well across variables. - fig = plt.figure() - fig, ax = plt.subplots(subplot_kw={"projection": projection}) - logging.info(f'plotting values to {filename}') - plot_projection(ax, longitudes, latitudes, values, cmap, vmin, vmax, s) - plt.savefig(filename) - plt.close('all') +### Part 2: Save COSMO data +def _save_cosmo_as_torch(infile_cosmo: str, outfile_data_path: str, outfile_plots_path: str = None, plot_indices=[0]): + cosmo = read_anemoi_ds(infile_cosmo) + save_anemoi_stats(cosmo, os.path.join(outfile_data_path, "info/cosmo-stats")) + save_anemoi_latlon_grid(cosmo, os.path.join(outfile_data_path, "info/cosmo-lat-lon")) + cosmo_output_path = os.path.join(outfile_data_path, "cosmo") + for i in range(len(cosmo.dates)): + save_anemoi_time_point(i, cosmo, data_output_path=cosmo_output_path, outfile_plots_path=outfile_plots_path, plot_incides=[0]) + -def interpolate_era5_cosmo_and_save(infile_era: str, infile_cosmo: str, outfile_data_path: str, threaded=True, outfile_plots_path: str = None, plot_indices=[0]): +def interpolate_era5_to_cosmo_and_save(infile_era: str, infile_cosmo: str, outfile_data_path: str, threaded=True, outfile_plots_path: str = None, plot_indices=[0]): """Read both ERA and COSMO data and perform basic interpolation. Save output into Pytorch format, and (optionally) plot ERA, COSMO, and interpolated data. @@ -176,7 +180,7 @@ def interpolate_era5_cosmo_and_save(infile_era: str, infile_cosmo: str, outfile_ infile_era: str Local file path to ERA5 data infile_cosmo: str - Local file path to COSMO2 data + Local file path to COSMO2 data. Can be a lat/lon grid or a .zarr file. outfile_data_path: str Local file path to intended output file outfile_plots_path: str (Optional) @@ -235,21 +239,40 @@ def main(): raise ValueError('Expected call interpolate_basic.py [era.yaml] [cosmo.yaml] [output directory]') infile_era = sys.argv[1] infile_cosmo = sys.argv[2] - output_directory = sys.argv[3] + output_path = sys.argv[3] + + os.makedirs(output_path, exist_ok=True) + os.makedirs(os.path.join(output_path, "info"), exist_ok=True) + os.makedirs(os.path.join(output_path, "era"), exist_ok=True) + os.makedirs(os.path.join(output_path, "cosmo"), exist_ok=True) + os.makedirs(os.path.join(output_path, "era-interpolated"), exist_ok=True) + output_plots_path = os.path.join(output_path, "plots") + os.makedirs(output_plots_path, exist_ok=True) erashortname = infile_era.split('/')[-1].split('.')[0] logging.basicConfig( - filename=os.path.join(output_directory, f'interpolate_basic-{erashortname}.log'), + filename=os.path.join(output_path, f'interpolate_basic-{erashortname}.log'), format='%(asctime)s %(levelname)-8s %(message)s', level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') logging.info(f'running {sys.argv}') - #outfile_plots_path = None - outfile_plots_path = os.path.join(output_directory, 'plots') - - interpolate_era5_cosmo_and_save(infile_era, infile_cosmo, output_directory, threaded=False, outfile_plots_path=outfile_plots_path) + #output_plots_path = None + + output_grid = None + + if infile_cosmo.endswith('yaml'): + cosmo = read_anemoi_ds(infile_cosmo) + output_grid = np.column_stack((cosmo.longitudes, cosmo.latitudes)) + else: + # This must be a lat-lon torch file. + cosmo_latlon = torch.load(infile_cosmo, weights_only=False) + lats = cosmo_latlon[:,0] + lons = cosmo_latlon[:,1] + output_grid = np.column_stack((lons, lats)) + + _interpolate_era5_to_grid(infile_era, output_grid, output_path, plot_indices=[0]) if __name__ == "__main__": main() diff --git a/src/hirad/input_data/interpolate_realch1.py b/src/hirad/input_data/interpolate_realch1.py index 7ce9992f..46c08674 100644 --- a/src/hirad/input_data/interpolate_realch1.py +++ b/src/hirad/input_data/interpolate_realch1.py @@ -77,7 +77,7 @@ def main(): max_lon = max(realch1_lon) + interpolate_basic.ERA_MARGIN_DEGREES logging.info('reading era') - era = interpolate_basic.read_era5_anemoi(era_config_file, + era = interpolate_basic.read_anemoi_ds(era_config_file, area=(max_lat, min_lon, min_lat, max_lon)) era_grid = np.column_stack((era.longitudes, era.latitudes)) realch1_grid = np.column_stack((realch1_lon, realch1_lat)) From 4c5bee5f411bd4aaf43606e7f0339c0eaa3ea5ce Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 20 Nov 2025 12:52:02 +0100 Subject: [PATCH 206/302] add balfrin scripts --- src/hirad/interpolate-batches.sh | 15 ++++++++++++--- src/hirad/interpolate.sh | 15 ++++++++++----- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/hirad/interpolate-batches.sh b/src/hirad/interpolate-batches.sh index 798e3965..62bba680 100644 --- a/src/hirad/interpolate-batches.sh +++ b/src/hirad/interpolate-batches.sh @@ -1,8 +1,17 @@ #!/bin/bash -for cfgfile in $(ls src/hirad/input_data/configs/era-all-2020*.yaml); +#clariden +#for cfgfile in $(ls src/hirad/input_data/configs/era-all-2020*.yaml); +#do +# cmd="sbatch -A a161 -t 12:00:00 -n 1 -c 1 --environment=modulus_env src/hirad/interpolate.sh ${cfgfile}" +# echo $cmd +# $cmd +#done + +# balfrin +for cfgfile in $(ls src/hirad/input_data/configs/era-all-2016*.yaml); do - cmd="sbatch -A a161 -t 12:00:00 -n 1 -c 1 --environment=modulus_env src/hirad/interpolate.sh ${cfgfile}" + cmd="sbatch -p postproc -t 12:00:00 -n 1 -c 1 src/hirad/interpolate.sh ${cfgfile}" echo $cmd $cmd -done \ No newline at end of file +done diff --git a/src/hirad/interpolate.sh b/src/hirad/interpolate.sh index 90a8d419..8e9dcd11 100755 --- a/src/hirad/interpolate.sh +++ b/src/hirad/interpolate.sh @@ -8,16 +8,21 @@ # pip install anemoi.datasets # python src/hirad/input_data/interpolate_basic.py src/hirad/input_data/era-all.yaml src/hirad/input_data/cosmo-all.yaml /capstor/scratch/cscs/mmcgloho/datasets/processed/era5-cosmo-1h-all-channels/ #" -pip install -e . --no-dependencies -pip install anemoi.datasets +#pip install -e . --no-dependencies +#pip install anemoi.datasets #pip install meteodata-lab #python src/hirad/input_data/interpolate_realch1.py \ # src/hirad/input_data/era.yaml \ # /capstor/store/mch/msopr/hirad-gen/basic-torch/era5-realch1/v0.2/info/realch1-lat-lon \ # /capstor/store/mch/msopr/hirad-gen/copernicus-datasets/tp-2023-2024.nc \ # /capstor/scratch/cscs/mmcgloho/basic-torch/era5-realch1/v1.0/ +#python src/hirad/input_data/interpolate_basic.py \ +# $1 \ +# /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/info/cosmo-lat-lon \ +# /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/ + python src/hirad/input_data/interpolate_basic.py \ -# src/hirad/input_data/era-all.yaml \ $1 \ - /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/info/cosmo-lat-lon \ - /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/ + /store_new/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-all-channels/info/cosmo-lat-lon \ + /store_new/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-all-channels/ + From b33ae25d818123dbdca5334266230ef4551bb648 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 25 Nov 2025 18:23:38 +0100 Subject: [PATCH 207/302] Add comments and make interpolate_basic a bit more generalizable. --- src/hirad/input_data/interpolate_basic.py | 286 ++++++++++++---------- 1 file changed, 150 insertions(+), 136 deletions(-) diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index 42114868..3fd7778b 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -19,19 +19,68 @@ ERA_MARGIN_DEGREES = 1.0 def read_anemoi_ds(config_file: str, start_date = None, end_date = None, area = None) -> Dataset: + """Read an Anemoi dataset from config file, given (optional) date/area parameters. + Start/end and area from config file will also be subsetted, if present, + so start_date and end_date and area parameters will be additional subsetting, + not an override. + + Parameters: + config_file: str + YAML file with anemoi recipe. + start_date, end_date: str (optional) + e.g. '2020-01-01', see anemoi open_dataset documentation. + area: tuple + (N, W, S, E) lat/lon lines to bound the area, see anemoi open_dataset. + + Returns: + Dataset + anemoi.Dataset of the dataset in question + """ with open(config_file) as cfg_file: config = yaml.safe_load(cfg_file) ds = open_dataset(config, start=start_date, end=end_date, area=area) return ds def save_anemoi_latlon_grid(dataset: Dataset, filename: str): + """Save lat/lon grid of an Anemoi dataset into a Torch file. (Note that + array will have column 0 with latitudes, and column 1 with longitutdes) + + Parameters: + dataset: anemoi.Dataset + Dataset to extract lat/lon from. + filename: str + Full file path to output to. + + Returns: None + """ grid = np.column_stack((dataset.latitudes, dataset.longitudes)) torch.save(grid, filename) def save_anemoi_stats(dataset: Dataset, filename: str): + """Save stats of an Anemoi dataset into a Torch file. (The torch file + will be a dictionary of stat to value) + + Parameters: + dataset: anemoi.Dataset + Dataset to extract stats from. + filename: str + Full file path to output to. + + Returns: None + """ torch.save(dataset.statistics, filename) -def regrid(era_for_time: np.ndarray, input_grid: np.ndarray, output_grid: np.ndarray): +def regrid(input_values_for_time: np.ndarray, input_grid: np.ndarray, output_grid: np.ndarray): + """Regrid an array of values for a given time point from an input to output grid. + + Parameters: + input_values_for_time: np.ndarray + An array of dimension (channels, ensemble, ) + filename: str + Full file path to output to. + + Returns: None + """ # shape (channel, ensemble, grid) interpolated_data = np.empty([era_for_time.shape[0], 1, output_grid.shape[0]]) for j in range(era_for_time.shape[0]): @@ -44,18 +93,26 @@ def format_date(dt64: np.datetime64) -> str: """Makes date string from date time point, for saving files.""" return to_datetime(dt64).strftime('%Y%m%d-%H%M') -def _save_datetime_file(values: np.ndarray[np.intp], variables: np.ndarray, date: np.datetime64, filepath: str): - filename = filepath + format_date(date) - torch.save(values, filename) +def _save_datetime_file(values: np.ndarray[np.intp], date: np.datetime64, filepath: str, format='torch'): + """saves interpolated values into a torch file""" + filename = os.path.join(filepath, format_date(date)) + if format == 'torch': + torch.save(values, filename) + elif format == 'numpy': + np.save(filename, values) + else: + raise NotImplementedError(f'invalid format {format}; currently only ' \ + 'output to torch or numpy') def plot_projection(ax, longitudes: np.array, latitudes: np.array, values: np.array, cmap=None, vmin = None, vmax = None, s = None): + """Plot observed or interpolated data in a scatter plot""" p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax, s=s) ax.coastlines() ax.gridlines(draw_labels=True) plt.colorbar(p, orientation="horizontal") def plot_and_save_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, projection=ccrs.PlateCarree(), cmap=None, vmin = None, vmax = None, s = None): - """Plot observed or interpolated data in a scatter plot.""" + """Plot observed or interpolated data in a scatter plot and save to file.""" # TODO: Refactor this somehow, it's not really generalizing well across variables. fig = plt.figure() fig, ax = plt.subplots(subplot_kw={"projection": projection}) @@ -64,76 +121,90 @@ def plot_and_save_projection(longitudes: np.array, latitudes: np.array, values: plt.savefig(filename) plt.close('all') -def interpolate_era_time_point_to_grid(i: int, era: Dataset, input_grid: np.ndarray, output_grid: np.ndarray, output_data_path: str, output_plots_path: str = None, plot_indices=[0]): - logging.info('interpolating time point ' + format_date(era.dates[i])) - interpolated_data = regrid(era[i,:,:,:], input_grid=input_grid, output_grid=output_grid) - logging.info(f'writing time point { format_date(era.dates[i])} to files in path {output_data_path}') - _save_datetime_file(interpolated_data, era.variables, era.dates[i], os.path.join(output_data_path)) +def interpolate_anemoi_time_point_to_grid(i: int, ds: Dataset, ds_name: str, input_grid: np.ndarray, output_grid: np.ndarray, output_data_path: str, format='torch', output_plots_path: str = None, plot_indices=[0]): + """Interpolate a given time index in a dataset from its grid (input_grid) + to an output_grid, and save the interpolated values. In certain cases, + additionally save a plot of the input and interpolated data. + + + i: int + Index of time to interpolate (0 is the first time point). This will + correspond to ds.dates[i] + ds: anemoi.Dataset + anemoi.Dataset to interpolate + ds_name: str + Name for the dataset (e.g. 'era'). This name will be used for the output + directory ('era-interpolated') and in the plot filenames. + input_grid: np.ndarray + An ndarray of shape (N,2), where N is the number of datapoints (N = X x Y). + ATTN: Longitudes are column 0 and Latitudes are column 1. + This should be equal to np.column_stack((ds.longitudes, ds.latitudes)) + output_grid: np.ndarray + Target grid to interpolate to. + An ndarray of shape (N,2), where N is the number of datapoints (N = X x Y). + ATTN: Longitudes are column 0 and Latitudes are column 1. + output_data_path: str + Path of directory for output data. + format: str (Optional) + Format of output (torch or numpy) + output_plots_path: str (Optional) + Path of directory to output plots. If None, no plots will be created. + plot_indices: Array (Optional) + Indices of time points for which to plot data + """ + logging.info('interpolating time point ' + format_date(ds.dates[i])) + interpolated_data = regrid(ds[i,:,:,:], input_grid=input_grid, output_grid=output_grid) + logging.info(f'writing time point { format_date(ds.dates[i])} to files in path {output_data_path}') + _save_datetime_file(interpolated_data, ds.dates[i], os.path.join(output_data_path), format=format) if output_plots_path and i in plot_indices: - datestr = format_date(era.dates[i]) + datestr = format_date(ds.dates[i]) logging.info(f'plotting {datestr} to {output_plots_path}') - for j in [0]: - #for j in range(len(era.variables)): - var = era.variables[j] + #for j in range(10): + for j in range(min(10, len(ds.variables))): + var = ds.variables[j] # plot era original - plot_and_save_projection(input_grid[:,0], input_grid[:,1], era[i, j, 0, :], f'{output_plots_path}/{era.variables[j]}-{datestr}-era.jpg') + plot_and_save_projection(input_grid[:,0], input_grid[:,1], ds[i, j, 0, :], f'{output_plots_path}/{ds.variables[j]}-{datestr}-{ds_name}.jpg') # plot interpolated - plot_and_save_projection(output_grid[:,0], output_grid[:,1], interpolated_data[j, 0, :], f'{output_plots_path}/{era.variables[j]}-{datestr}-era-interpolated.jpg') + plot_and_save_projection(output_grid[:,0], output_grid[:,1], interpolated_data[j, 0, :], f'{output_plots_path}/{ds.variables[j]}-{datestr}-{ds_name}-interpolated.jpg') -def save_anemoi_time_point(i: int, ds: Dataset, data_output_path: str, ds_name: str, plots_output_path: str = None, plot_indices=[0]): - _save_datetime_file(ds[i,:,:,:], ds.variables, ds.dates[i], data_output_path) +def save_anemoi_time_point(i: int, ds: Dataset, ds_name: str, data_output_path: str, plots_output_path: str = None, plot_indices=[0], format='torch'): + """Save a time point of anemoi data (either input or target) directly into a given format. + If the time point is in the """ + _save_datetime_file(ds[i,:,:,:], ds.dates[i], data_output_path, format) datestr = format_date(ds.dates[i]) if plots_output_path and i in plot_indices: for j,var in enumerate(ds.variables): plot_and_save_projection(ds.longitudes, ds.latitudes, ds[i, j, 0, :], f'{plots_output_path}/{var}-{datestr}-{ds_name}.jpg') -def _interpolate_era5_cosmo_basic(era: Dataset, cosmo: Dataset | None, intermediate_files_path: str, threaded = True, outfile_plots_path: str =None, plot_indices=[0]): - """Perform simple interpolation from ERA5 to COSMO grid for all data points in the COSMO date range. +### Main method 1: Interpolate ERA grid +def _interpolate_anemoi_to_grid(infile_anemoi: str, ds_name: str, output_grid: np.ndarray, output_path: str, format='torch', plot_indices=[0]): + """Perform basic interpolation on an input dataset in anemoi format from + its native grid to a given output grid. + Save output as intermediate datetime files in a given format (torch/numpy) + Optionally plot interpolated data. Parameters: - era: Dataset - Pre-loaded anemoi dataset for ERA - cosmo: Dataset - Pre-loaded anemoi dataset for COSMO - intermediate_files_path - If set, will save each date point to a new file. - - Returns: - np.ndarray - 4-D array of interpolated values. (date, variable, ensemble, grid-point) + infile_anemoi: str + Path to an anemoi recipe in YAML format. + ds_name: str + Name for the dataset (e.g. 'era'). This name will be used for the output + directory ('era-interpolated') and in the plot filenames. + output_grid: np.ndarray + An ndarray of shape (N,2), where N is the number of datapoints (N = X x Y). + ATTN: Longitudes are column 0 and Latitudes are column 1. + output_path: str + Path of parent directory for output. (sub-directories for plots, info, + and interpolated data will be created if they do not already exist) + format: str (Optional) + Format of output (torch or numpy) + plot_indices: Array (Optional) + Indices of time points for which to plot data """ - # Check that our date ranges do in fact line up. - if cosmo: - assert (era.start_date == cosmo.start_date and - era.end_date == cosmo.end_date and - era.frequency == cosmo.frequency and - era.shape[0] == cosmo.shape[0]), "ERA and COSMO date ranges or frequencies do not align." - input_grid = np.column_stack((era.longitudes, era.latitudes)) # stack lon-lat columns of era5 points - output_grid = None - if cosmo: - output_grid = np.column_stack((cosmo.longitudes, cosmo.latitudes)) # stack lon-lat column of cosmo points - else: - cosmo_latlon = torch.load(os.path.join(intermediate_files_path, 'info', 'cosmo-lat-lon'), weights_only=False) - output_grid = np.column_stack((cosmo_latlon[:,1], cosmo_latlon[:,0])) - - dates = range(era.shape[0]) - - if (threaded): - pool = multiprocessing.Pool() - for i in dates: - pool.apply_async(_interpolate_era5_cosmo_task, (i, era, cosmo, input_grid, output_grid, intermediate_files_path, outfile_plots_path, plot_indices)) - pool.close() - pool.join() - else: - for i in dates: - _interpolate_era5_cosmo_task(i, era, cosmo, input_grid, output_grid, intermediate_files_path, outfile_plots_path, plot_indices) - - return + os.makedirs(os.path.join(output_path, 'info'), exist_ok=True) + os.makedirs(os.path.join(output_path, f'{ds_name}-interpolated'), exist_ok=True) -### Main method 1: Interpolate ERA grid -def _interpolate_era5_to_grid(infile_era: str, output_grid: np.ndarray, output_path: str, plot_indices=[0]): # read data lats = output_grid[:,1] lons = output_grid[:,0] @@ -143,94 +214,37 @@ def _interpolate_era5_to_grid(infile_era: str, output_grid: np.ndarray, output_p max_lon = max(lons) + ERA_MARGIN_DEGREES area=(max_lat, min_lon, min_lat, max_lon) logging.info(f'projecting onto era area {area}') - era = read_anemoi_ds(infile_era, area = area) + ds = read_anemoi_ds(infile_anemoi, area = area) logging.info('Successfully read input') # Output stats and grid - save_anemoi_stats(era, os.path.join(output_path, "info/era-stats")) - save_anemoi_latlon_grid(era, os.path.join(output_path, "info/era-lat-lon")) + save_anemoi_stats(ds, os.path.join(output_path, f'info/{ds_name}-stats')) + save_anemoi_latlon_grid(ds, os.path.join(output_path, f'info/{ds_name}-lat-lon')) # Copy the .yaml files over for recording purposes - shutil.copy(infile_era, os.path.join(output_path, "info/era.yaml")) + shutil.copy(infile_anemoi, os.path.join(output_path, f'info/{ds_name}.yaml')) - input_grid = np.column_stack((era.longitudes, era.latitudes)) + input_grid = np.column_stack((ds.longitudes, ds.latitudes)) - for i in range(len(era.dates)): - interpolate_era_time_point_to_grid(i, era, input_grid, output_grid, - os.path.join(output_path, "era-interpolated"), - os.path.join(output_path, "plots"), - plot_indices) + for i in range(len(ds.dates)): + interpolate_anemoi_time_point_to_grid(i, ds, ds_name, input_grid, output_grid, + os.path.join(output_path, f'{ds_name}-interpolated'), + format=format, + output_plots_path=os.path.join(output_path, 'plots'), + plot_indices=plot_indices) ### Part 2: Save COSMO data -def _save_cosmo_as_torch(infile_cosmo: str, outfile_data_path: str, outfile_plots_path: str = None, plot_indices=[0]): - cosmo = read_anemoi_ds(infile_cosmo) - save_anemoi_stats(cosmo, os.path.join(outfile_data_path, "info/cosmo-stats")) - save_anemoi_latlon_grid(cosmo, os.path.join(outfile_data_path, "info/cosmo-lat-lon")) - cosmo_output_path = os.path.join(outfile_data_path, "cosmo") - for i in range(len(cosmo.dates)): - save_anemoi_time_point(i, cosmo, data_output_path=cosmo_output_path, outfile_plots_path=outfile_plots_path, plot_incides=[0]) - - -def interpolate_era5_to_cosmo_and_save(infile_era: str, infile_cosmo: str, outfile_data_path: str, threaded=True, outfile_plots_path: str = None, plot_indices=[0]): - """Read both ERA and COSMO data and perform basic interpolation. Save output into Pytorch format, and (optionally) plot - ERA, COSMO, and interpolated data. - - Parameters: - infile_era: str - Local file path to ERA5 data - infile_cosmo: str - Local file path to COSMO2 data. Can be a lat/lon grid or a .zarr file. - outfile_data_path: str - Local file path to intended output file - outfile_plots_path: str (Optional) - Local file path to plots. If specified, plots will be saved as "{plotfilepath_prefix}-(era|cosmo|interpolated).jpg" - - Returns: - tuple[Dataset, Dataset] - A tuple of ERA and COSMO 2m temperature data, in anemoi Dataset format, restricted to COSMO's date ranges - (optionally the COSMO area as well). - """ - os.makedirs(outfile_data_path, exist_ok=True) - os.makedirs(os.path.join(outfile_data_path, "info"), exist_ok=True) - os.makedirs(os.path.join(outfile_data_path, "era"), exist_ok=True) - os.makedirs(os.path.join(outfile_data_path, "cosmo"), exist_ok=True) - os.makedirs(os.path.join(outfile_data_path, "era-interpolated"), exist_ok=True) - - if outfile_plots_path: - os.makedirs(outfile_plots_path, exist_ok=True) - - logging.info(f'reading input according to configs {infile_era} and {infile_cosmo}') - era = None - cosmo = None - if infile_cosmo.endswith('yaml'): - era, cosmo = _read_era5_cosmo(infile_era, infile_cosmo) - save_anemoi_stats(cosmo, os.path.join(outfile_data_path, "info/cosmo-stats")) - save_anemoi_latlon_grid(cosmo, os.path.join(outfile_data_path, "info/cosmo-lat-lon")) - shutil.copy(infile_cosmo, os.path.join(outfile_data_path, "info/cosmo.yaml")) - else: - cosmo_latlon = torch.load(infile_cosmo, weights_only=False) - lats = cosmo_latlon[:,0] - lons = cosmo_latlon[:,1] - min_lat = min(lats) - ERA_MARGIN_DEGREES - max_lat = max(lats) + ERA_MARGIN_DEGREES - min_lon = min(lons) - ERA_MARGIN_DEGREES - max_lon = max(lons) + ERA_MARGIN_DEGREES - area=(max_lat, min_lon, min_lat, max_lon) - logging.info(f'projecting onto era area {area}') - era = read_era5_anemoi(infile_era, area = area) - - logging.info('Successfully read input') - - # Output stats and grid - save_anemoi_stats(era, os.path.join(outfile_data_path, "info/era-stats")) - save_anemoi_latlon_grid(era, os.path.join(outfile_data_path, "info/era-lat-lon")) - +def _save_anemoi_as_format(infile_anemoi: str, ds_name: str, outfile_data_path: str, outfile_plots_path: str = None, plot_indices=[0], format='torch'): + ds = read_anemoi_ds(infile_anemoi) # Copy the .yaml files over for recording purposes - shutil.copy(infile_era, os.path.join(outfile_data_path, "info/era.yaml")) - - # generate interpolated data - _interpolate_era5_cosmo_basic(era, cosmo, outfile_data_path, threaded=threaded, outfile_plots_path=outfile_plots_path, plot_indices=plot_indices) + shutil.copy(infile_anemoi, os.path.join(outfile_data_path, f'info/{ds_name}.yaml')) + save_anemoi_stats(ds, os.path.join(outfile_data_path, f'info/{ds_name}-stats')) + save_anemoi_latlon_grid(ds, os.path.join(outfile_data_path, f'info/{ds_name}-lat-lon')) + ds_output_path = os.path.join(outfile_data_path, ds_name) + os.makedirs(ds_output_path, exist_ok=True) + for i in range(len(ds.dates)): + save_anemoi_time_point(i, ds, ds_name, data_output_path=ds_output_path, outfile_plots_path=outfile_plots_path, plot_incides=[0], format=format) def main(): From 88a92df47be42ca196e458af85eac5a8fb076518 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 25 Nov 2025 18:44:01 +0100 Subject: [PATCH 208/302] remove ensemble dimension from processing --- src/hirad/input_data/interpolate_basic.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index 3fd7778b..2f3c71bf 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -75,16 +75,17 @@ def regrid(input_values_for_time: np.ndarray, input_grid: np.ndarray, output_gri Parameters: input_values_for_time: np.ndarray - An array of dimension (channels, ensemble, ) + An array of dimension (channels, N) (where N = X x Y) filename: str Full file path to output to. Returns: None """ - # shape (channel, ensemble, grid) - interpolated_data = np.empty([era_for_time.shape[0], 1, output_grid.shape[0]]) - for j in range(era_for_time.shape[0]): - values = np.array(era_for_time[j,0,:]) # get era grid values on the given date-time and channel + # shape (channel, grid) + assert(len(input_values_for_time.shape) == 2) + interpolated_data = np.empty([input_values_for_time.shape[0], output_grid.shape[0]]) + for j in range(input_values_for_time.shape[0]): + values = np.array(input_values_for_time[j,:]) # get era grid values on the given date-time and channel regrid = griddata(input_grid, values, output_grid, method='linear') # interpolate era5 to cosmo grid using scipy griddata linear interpolated_data[j,0,:] = regrid return interpolated_data @@ -94,7 +95,7 @@ def format_date(dt64: np.datetime64) -> str: return to_datetime(dt64).strftime('%Y%m%d-%H%M') def _save_datetime_file(values: np.ndarray[np.intp], date: np.datetime64, filepath: str, format='torch'): - """saves interpolated values into a torch file""" + """saves array of values for a given date into a torch file""" filename = os.path.join(filepath, format_date(date)) if format == 'torch': torch.save(values, filename) @@ -153,7 +154,8 @@ def interpolate_anemoi_time_point_to_grid(i: int, ds: Dataset, ds_name: str, inp Indices of time points for which to plot data """ logging.info('interpolating time point ' + format_date(ds.dates[i])) - interpolated_data = regrid(ds[i,:,:,:], input_grid=input_grid, output_grid=output_grid) + # remove ensemble (3rd) dimension + interpolated_data = regrid(ds[i,:,0,:], input_grid=input_grid, output_grid=output_grid) logging.info(f'writing time point { format_date(ds.dates[i])} to files in path {output_data_path}') _save_datetime_file(interpolated_data, ds.dates[i], os.path.join(output_data_path), format=format) if output_plots_path and i in plot_indices: @@ -170,7 +172,7 @@ def interpolate_anemoi_time_point_to_grid(i: int, ds: Dataset, ds_name: str, inp def save_anemoi_time_point(i: int, ds: Dataset, ds_name: str, data_output_path: str, plots_output_path: str = None, plot_indices=[0], format='torch'): """Save a time point of anemoi data (either input or target) directly into a given format. If the time point is in the """ - _save_datetime_file(ds[i,:,:,:], ds.dates[i], data_output_path, format) + _save_datetime_file(ds[i,:,0,:], ds.dates[i], data_output_path, format) datestr = format_date(ds.dates[i]) if plots_output_path and i in plot_indices: for j,var in enumerate(ds.variables): From 8480de4c8d2bc14e1ba36f9eb1e600bde282aca6 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 25 Nov 2025 18:58:56 +0100 Subject: [PATCH 209/302] separate out era5-cosmo specific main method into its own script --- src/hirad/input_data/interpolate_basic.py | 68 +++++----------------- src/hirad/input_data/process_era5_cosmo.py | 53 +++++++++++++++++ 2 files changed, 67 insertions(+), 54 deletions(-) create mode 100644 src/hirad/input_data/process_era5_cosmo.py diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index 2f3c71bf..22fdeae2 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -162,7 +162,7 @@ def interpolate_anemoi_time_point_to_grid(i: int, ds: Dataset, ds_name: str, inp datestr = format_date(ds.dates[i]) logging.info(f'plotting {datestr} to {output_plots_path}') #for j in range(10): - for j in range(min(10, len(ds.variables))): + for j in range(len(ds.variables)): var = ds.variables[j] # plot era original plot_and_save_projection(input_grid[:,0], input_grid[:,1], ds[i, j, 0, :], f'{output_plots_path}/{ds.variables[j]}-{datestr}-{ds_name}.jpg') @@ -179,8 +179,7 @@ def save_anemoi_time_point(i: int, ds: Dataset, ds_name: str, data_output_path: plot_and_save_projection(ds.longitudes, ds.latitudes, ds[i, j, 0, :], f'{plots_output_path}/{var}-{datestr}-{ds_name}.jpg') -### Main method 1: Interpolate ERA grid -def _interpolate_anemoi_to_grid(infile_anemoi: str, ds_name: str, output_grid: np.ndarray, output_path: str, format='torch', plot_indices=[0]): +def interpolate_anemoi_to_grid(infile_anemoi: str, ds_name: str, output_grid: np.ndarray, output_path: str, format='torch', plot_indices=[0]): """Perform basic interpolation on an input dataset in anemoi format from its native grid to a given output grid. Save output as intermediate datetime files in a given format (torch/numpy) @@ -205,6 +204,7 @@ def _interpolate_anemoi_to_grid(infile_anemoi: str, ds_name: str, output_grid: n """ os.makedirs(os.path.join(output_path, 'info'), exist_ok=True) + os.makedirs(os.path.join(output_path, 'plots'), exist_ok=True) os.makedirs(os.path.join(output_path, f'{ds_name}-interpolated'), exist_ok=True) # read data @@ -237,58 +237,18 @@ def _interpolate_anemoi_to_grid(infile_anemoi: str, ds_name: str, output_grid: n ### Part 2: Save COSMO data -def _save_anemoi_as_format(infile_anemoi: str, ds_name: str, outfile_data_path: str, outfile_plots_path: str = None, plot_indices=[0], format='torch'): +def save_anemoi_as_format(infile_anemoi: str, ds_name: str, output_path: str, plot_indices=[0], format='torch'): + os.makedirs(os.path.join(output_path, 'info'), exist_ok=True) + plots_path = os.path.join(output_path, 'plots') + os.makedirs(plots_path, exist_ok=True) + ds_output_path = os.path.join(output_path, ds_name) + os.makedirs(ds_output_path, exist_ok=True) + ds = read_anemoi_ds(infile_anemoi) # Copy the .yaml files over for recording purposes - shutil.copy(infile_anemoi, os.path.join(outfile_data_path, f'info/{ds_name}.yaml')) - save_anemoi_stats(ds, os.path.join(outfile_data_path, f'info/{ds_name}-stats')) - save_anemoi_latlon_grid(ds, os.path.join(outfile_data_path, f'info/{ds_name}-lat-lon')) - ds_output_path = os.path.join(outfile_data_path, ds_name) + shutil.copy(infile_anemoi, os.path.join(output_path, f'info/{ds_name}.yaml')) + save_anemoi_stats(ds, os.path.join(output_path, f'info/{ds_name}-stats')) + save_anemoi_latlon_grid(ds, os.path.join(output_path, f'info/{ds_name}-lat-lon')) os.makedirs(ds_output_path, exist_ok=True) for i in range(len(ds.dates)): - save_anemoi_time_point(i, ds, ds_name, data_output_path=ds_output_path, outfile_plots_path=outfile_plots_path, plot_incides=[0], format=format) - - -def main(): - # TODO: Do better arg parsing so it's not as easy to reverse era and cosmo configs. - if len(sys.argv) < 4: - raise ValueError('Expected call interpolate_basic.py [era.yaml] [cosmo.yaml] [output directory]') - infile_era = sys.argv[1] - infile_cosmo = sys.argv[2] - output_path = sys.argv[3] - - os.makedirs(output_path, exist_ok=True) - os.makedirs(os.path.join(output_path, "info"), exist_ok=True) - os.makedirs(os.path.join(output_path, "era"), exist_ok=True) - os.makedirs(os.path.join(output_path, "cosmo"), exist_ok=True) - os.makedirs(os.path.join(output_path, "era-interpolated"), exist_ok=True) - output_plots_path = os.path.join(output_path, "plots") - os.makedirs(output_plots_path, exist_ok=True) - - erashortname = infile_era.split('/')[-1].split('.')[0] - - logging.basicConfig( - filename=os.path.join(output_path, f'interpolate_basic-{erashortname}.log'), - format='%(asctime)s %(levelname)-8s %(message)s', - level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') - - logging.info(f'running {sys.argv}') - #output_plots_path = None - - output_grid = None - - if infile_cosmo.endswith('yaml'): - cosmo = read_anemoi_ds(infile_cosmo) - output_grid = np.column_stack((cosmo.longitudes, cosmo.latitudes)) - else: - # This must be a lat-lon torch file. - cosmo_latlon = torch.load(infile_cosmo, weights_only=False) - lats = cosmo_latlon[:,0] - lons = cosmo_latlon[:,1] - output_grid = np.column_stack((lons, lats)) - - _interpolate_era5_to_grid(infile_era, output_grid, output_path, plot_indices=[0]) - -if __name__ == "__main__": - main() + save_anemoi_time_point(i, ds, ds_name, data_output_path=ds_output_path, outfile_plots_path=plots_path, plot_indices=[0], format=format) diff --git a/src/hirad/input_data/process_era5_cosmo.py b/src/hirad/input_data/process_era5_cosmo.py new file mode 100644 index 00000000..3697dc84 --- /dev/null +++ b/src/hirad/input_data/process_era5_cosmo.py @@ -0,0 +1,53 @@ +import logging +import os +import sys + +import numpy as np +import torch + +import interpolate_basic + +def main(): + # TODO: Do better arg parsing so it's not as easy to reverse era and cosmo configs. + if len(sys.argv) < 4: + raise ValueError('Expected call process_era5_cosmo.py [era.yaml] [cosmo.yaml] [output directory]') + infile_era = sys.argv[1] + infile_cosmo = sys.argv[2] + output_path = sys.argv[3] + + os.makedirs(output_path, exist_ok=True) + + erashortname = infile_era.split('/')[-1].split('.')[0] + + logging.basicConfig( + filename=os.path.join(output_path, f'process-era5-cosmo-{erashortname}.log'), + format='%(asctime)s %(levelname)-8s %(message)s', + level=logging.INFO, + datefmt='%Y-%m-%d %H:%M:%S') + + logging.info(f'running {sys.argv}') + #output_plots_path = None + + output_grid = None + + if infile_cosmo.endswith('yaml'): + cosmo = interpolate_basic.read_anemoi_ds(infile_cosmo) + output_grid = np.column_stack((cosmo.longitudes, cosmo.latitudes)) + else: + # This must be a lat-lon torch file. + cosmo_latlon = torch.load(infile_cosmo, weights_only=False) + lats = cosmo_latlon[:,0] + lons = cosmo_latlon[:,1] + output_grid = np.column_stack((lons, lats)) + + # interpolate era + format = 'numpy' + plot_indices=[0] + interpolate_basic.interpolate_anemoi_to_grid(infile_era, 'era', output_grid, output_path=output_path, format=format, plot_indices=plot_indices) + # save era and cosmo input/output data in same format + interpolate_basic.save_anemoi_as_format(infile_era, 'era', output_path, plot_indices=plot_indices, format=format) + if infile_cosmo.endswith('yaml'): + interpolate_basic.save_anemoi_as_format(infile_cosmo, 'cosmo', output_path, plot_indices=plot_indices, format=format) + +if __name__ == "__main__": + main() From a45c4d785dd26dfd1dfb77afbbfeb01ebbe11f9e Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 26 Nov 2025 13:14:11 +0100 Subject: [PATCH 210/302] correct error in anemoi open dataset --- src/hirad/input_data/interpolate_basic.py | 37 +++++++++++++++++++---- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index 22fdeae2..f02ad7ad 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -8,7 +8,8 @@ from anemoi.datasets import open_dataset from anemoi.datasets.data.dataset import Dataset import cartopy.crs as ccrs -import matplotlib.pyplot as plt +import matplotlib.pyplot as plt +import netCDF4 import numpy as np from pandas import to_datetime from scipy.interpolate import griddata @@ -38,8 +39,9 @@ def read_anemoi_ds(config_file: str, start_date = None, end_date = None, area = """ with open(config_file) as cfg_file: config = yaml.safe_load(cfg_file) - ds = open_dataset(config, start=start_date, end=end_date, area=area) - return ds + if area: + return open_dataset(config, start=start_date, end=end_date, area=area) + return open_dataset(config, start=start_date, end=end_date) def save_anemoi_latlon_grid(dataset: Dataset, filename: str): """Save lat/lon grid of an Anemoi dataset into a Torch file. (Note that @@ -94,7 +96,7 @@ def format_date(dt64: np.datetime64) -> str: """Makes date string from date time point, for saving files.""" return to_datetime(dt64).strftime('%Y%m%d-%H%M') -def _save_datetime_file(values: np.ndarray[np.intp], date: np.datetime64, filepath: str, format='torch'): +def save_datetime_file(values: np.ndarray[np.intp], date: np.datetime64, filepath: str, format='torch'): """saves array of values for a given date into a torch file""" filename = os.path.join(filepath, format_date(date)) if format == 'torch': @@ -157,7 +159,7 @@ def interpolate_anemoi_time_point_to_grid(i: int, ds: Dataset, ds_name: str, inp # remove ensemble (3rd) dimension interpolated_data = regrid(ds[i,:,0,:], input_grid=input_grid, output_grid=output_grid) logging.info(f'writing time point { format_date(ds.dates[i])} to files in path {output_data_path}') - _save_datetime_file(interpolated_data, ds.dates[i], os.path.join(output_data_path), format=format) + save_datetime_file(interpolated_data, ds.dates[i], os.path.join(output_data_path), format=format) if output_plots_path and i in plot_indices: datestr = format_date(ds.dates[i]) logging.info(f'plotting {datestr} to {output_plots_path}') @@ -172,12 +174,35 @@ def interpolate_anemoi_time_point_to_grid(i: int, ds: Dataset, ds_name: str, inp def save_anemoi_time_point(i: int, ds: Dataset, ds_name: str, data_output_path: str, plots_output_path: str = None, plot_indices=[0], format='torch'): """Save a time point of anemoi data (either input or target) directly into a given format. If the time point is in the """ - _save_datetime_file(ds[i,:,0,:], ds.dates[i], data_output_path, format) + save_datetime_file(ds[i,:,0,:], ds.dates[i], data_output_path, format) datestr = format_date(ds.dates[i]) if plots_output_path and i in plot_indices: for j,var in enumerate(ds.variables): plot_and_save_projection(ds.longitudes, ds.latitudes, ds[i, j, 0, :], f'{plots_output_path}/{var}-{datestr}-{ds_name}.jpg') +def interpolate_netcdf_to_grid(infile_nc: str, ds_name: str, output_grid: np.ndarray, output_path: str, format='torch', plot_indices=[0]): + # Not yet implemented + return + # extract from yaml config + with open(infile_nc) as cfg_file: + config = yaml.safe_load(cfg_file) + config['path'] + + config['start'] + config['end'] + config['frequency'] + dates = None # date sequence + + + + # expected format is variable-startyear-endyear.nc + # extract data + # startyear= + # endyear = + # variable = + set1 = netCDF4.Dataset(infile_nc) + + def interpolate_anemoi_to_grid(infile_anemoi: str, ds_name: str, output_grid: np.ndarray, output_path: str, format='torch', plot_indices=[0]): """Perform basic interpolation on an input dataset in anemoi format from From 1c80ed899df1ed59efdbd7ed70795de06a486238 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 26 Nov 2025 13:24:55 +0100 Subject: [PATCH 211/302] wrong arg name --- src/hirad/input_data/interpolate_basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index f02ad7ad..02051764 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -276,4 +276,4 @@ def save_anemoi_as_format(infile_anemoi: str, ds_name: str, output_path: str, pl save_anemoi_latlon_grid(ds, os.path.join(output_path, f'info/{ds_name}-lat-lon')) os.makedirs(ds_output_path, exist_ok=True) for i in range(len(ds.dates)): - save_anemoi_time_point(i, ds, ds_name, data_output_path=ds_output_path, outfile_plots_path=plots_path, plot_indices=[0], format=format) + save_anemoi_time_point(i, ds, ds_name, data_output_path=ds_output_path, plots_output_path=plots_path, plot_indices=[0], format=format) From 665476976301987da61ab05dcd97002cfceac36c Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 26 Nov 2025 14:01:04 +0100 Subject: [PATCH 212/302] small fixes, script runs now --- src/hirad/input_data/interpolate_basic.py | 9 +++++---- src/hirad/input_data/process_era5_cosmo.py | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index 02051764..48b2063b 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -89,7 +89,7 @@ def regrid(input_values_for_time: np.ndarray, input_grid: np.ndarray, output_gri for j in range(input_values_for_time.shape[0]): values = np.array(input_values_for_time[j,:]) # get era grid values on the given date-time and channel regrid = griddata(input_grid, values, output_grid, method='linear') # interpolate era5 to cosmo grid using scipy griddata linear - interpolated_data[j,0,:] = regrid + interpolated_data[j,:] = regrid return interpolated_data def format_date(dt64: np.datetime64) -> str: @@ -169,7 +169,7 @@ def interpolate_anemoi_time_point_to_grid(i: int, ds: Dataset, ds_name: str, inp # plot era original plot_and_save_projection(input_grid[:,0], input_grid[:,1], ds[i, j, 0, :], f'{output_plots_path}/{ds.variables[j]}-{datestr}-{ds_name}.jpg') # plot interpolated - plot_and_save_projection(output_grid[:,0], output_grid[:,1], interpolated_data[j, 0, :], f'{output_plots_path}/{ds.variables[j]}-{datestr}-{ds_name}-interpolated.jpg') + plot_and_save_projection(output_grid[:,0], output_grid[:,1], interpolated_data[j, :], f'{output_plots_path}/{ds.variables[j]}-{datestr}-{ds_name}-interpolated.jpg') def save_anemoi_time_point(i: int, ds: Dataset, ds_name: str, data_output_path: str, plots_output_path: str = None, plot_indices=[0], format='torch'): """Save a time point of anemoi data (either input or target) directly into a given format. @@ -262,14 +262,15 @@ def interpolate_anemoi_to_grid(infile_anemoi: str, ds_name: str, output_grid: np ### Part 2: Save COSMO data -def save_anemoi_as_format(infile_anemoi: str, ds_name: str, output_path: str, plot_indices=[0], format='torch'): +def save_anemoi_as_format(infile_anemoi: str, ds_name: str, output_path: str, plot_indices=[0], format='torch', + start_date = None, end_date = None, area = None): os.makedirs(os.path.join(output_path, 'info'), exist_ok=True) plots_path = os.path.join(output_path, 'plots') os.makedirs(plots_path, exist_ok=True) ds_output_path = os.path.join(output_path, ds_name) os.makedirs(ds_output_path, exist_ok=True) - ds = read_anemoi_ds(infile_anemoi) + ds = read_anemoi_ds(infile_anemoi, start_date=start_date, end_date=end_date, area=area) # Copy the .yaml files over for recording purposes shutil.copy(infile_anemoi, os.path.join(output_path, f'info/{ds_name}.yaml')) save_anemoi_stats(ds, os.path.join(output_path, f'info/{ds_name}-stats')) diff --git a/src/hirad/input_data/process_era5_cosmo.py b/src/hirad/input_data/process_era5_cosmo.py index 3697dc84..acfec6d6 100644 --- a/src/hirad/input_data/process_era5_cosmo.py +++ b/src/hirad/input_data/process_era5_cosmo.py @@ -44,10 +44,23 @@ def main(): format = 'numpy' plot_indices=[0] interpolate_basic.interpolate_anemoi_to_grid(infile_era, 'era', output_grid, output_path=output_path, format=format, plot_indices=plot_indices) - # save era and cosmo input/output data in same format - interpolate_basic.save_anemoi_as_format(infile_era, 'era', output_path, plot_indices=plot_indices, format=format) + # save era and cosmo input/output data into same format + # Save cosmo data if infile_cosmo.endswith('yaml'): interpolate_basic.save_anemoi_as_format(infile_cosmo, 'cosmo', output_path, plot_indices=plot_indices, format=format) + # Save ERA data (subsetted) + lats = output_grid[:,1] + lons = output_grid[:,0] + min_lat = min(lats) - interpolate_basic.ERA_MARGIN_DEGREES + max_lat = max(lats) + interpolate_basic.ERA_MARGIN_DEGREES + min_lon = min(lons) - interpolate_basic.ERA_MARGIN_DEGREES + max_lon = max(lons) + interpolate_basic.ERA_MARGIN_DEGREES + area=(max_lat, min_lon, min_lat, max_lon) + logging.info(f'projecting onto era area {area}') + # skip plotting as we did it already in interpolation step + interpolate_basic.save_anemoi_as_format(infile_era, 'era', output_path, plot_indices=[], format=format, area=area) + + if __name__ == "__main__": main() From bc44d9449339b6692b13ee653de76c638ace237a Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 26 Nov 2025 20:41:20 +0100 Subject: [PATCH 213/302] add methods for netcdf interpolation (draft) --- src/hirad/input_data/interpolate_basic.py | 178 +++++++++++++++++++--- 1 file changed, 153 insertions(+), 25 deletions(-) diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index 48b2063b..0059364b 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -1,6 +1,7 @@ import datetime import logging import os +import re import shutil import sys import yaml @@ -178,31 +179,7 @@ def save_anemoi_time_point(i: int, ds: Dataset, ds_name: str, data_output_path: datestr = format_date(ds.dates[i]) if plots_output_path and i in plot_indices: for j,var in enumerate(ds.variables): - plot_and_save_projection(ds.longitudes, ds.latitudes, ds[i, j, 0, :], f'{plots_output_path}/{var}-{datestr}-{ds_name}.jpg') - -def interpolate_netcdf_to_grid(infile_nc: str, ds_name: str, output_grid: np.ndarray, output_path: str, format='torch', plot_indices=[0]): - # Not yet implemented - return - # extract from yaml config - with open(infile_nc) as cfg_file: - config = yaml.safe_load(cfg_file) - config['path'] - - config['start'] - config['end'] - config['frequency'] - dates = None # date sequence - - - - # expected format is variable-startyear-endyear.nc - # extract data - # startyear= - # endyear = - # variable = - set1 = netCDF4.Dataset(infile_nc) - - + plot_and_save_projection(ds.longitudes, ds.latitudes, ds[i, j, 0, :], f'{plots_output_path}/{var}-{datestr}-{ds_name}.jpg') def interpolate_anemoi_to_grid(infile_anemoi: str, ds_name: str, output_grid: np.ndarray, output_path: str, format='torch', plot_indices=[0]): """Perform basic interpolation on an input dataset in anemoi format from @@ -278,3 +255,154 @@ def save_anemoi_as_format(infile_anemoi: str, ds_name: str, output_path: str, pl os.makedirs(ds_output_path, exist_ok=True) for i in range(len(ds.dates)): save_anemoi_time_point(i, ds, ds_name, data_output_path=ds_output_path, plots_output_path=plots_path, plot_indices=[0], format=format) + +def load_netcdf_file(path: str, variable: str, date: datetime.datetime): + """ + Get the corresponding netCDF file for a given variable, that includes + a given date. + + Returns: tuple of (netCDF.Dataset, int) where + int is the index of where the date is within that dataset. + """ + files = os.listdir(path) + pattern = '(.*)-([0-9]+)-([0-9]+).nc' + for filename in files: + matches = re.match(pattern, filename) + if matches: + f_var = matches[1] + f_start_year = matches[2] + f_end_year = matches[3] + if f_var == variable and date.year >= f_start_year and date.year <= f_end_year: + ds = netCDF4.Dataset(os.path.join(path, filename)) + # Raises ValueError if number of instances not exactly 1, which indicates + # an implementation error somewhere. + index = np.where(ds['valid_time'][:] == np.int64(date.timestamp()))[0].item() + return ds, index + raise FileNotFoundError(f'Could not find .nc file for variable {variable} and date {date} in {path}') + +def load_netcdf_files_as_dict(path: str, variables: list, date: datetime.datetime, expected_frequency: datetime.timedelta): + """ + Get the corresponding netCDF Datasets for a given list of variables, that includes + a given date. Also checks to make sure each dataset lines up in terms of + dates and grids + + Returns: tuple of (dict[netCDF.Dataset], int) where + int is the index of where the date is within that dataset. + It should be the same for all datasets + """ + # Set up a dict that corresponds to [year][variable] with references to the + # corresponding NC dataset. + # (Note: I believe that because the NetCDF datasets are read-only, they + # will be cached so it doesn't matter for memory purposes if they are stored + # as objects or references) + curr_nc = {} + indices = {} + for var in variables: + ds, index = load_netcdf_file(input_path, var, start_date) + curr_nc[var] = ds + indices[var] = index + + # Check that all the NC datasets match up in terms of time and grid + nc_times = curr_nc[variables[0]]['valid_time'] + nc_date_index = indices[variables[0]] + grid_size = curr_nc[variables[0]][:].shape[1:] + nc_latitudes = curr_nc[variables[0]]['latitude'][:] + nc_longitudes = curr_nc[variables[0]]['longitude'][:] + for v in range(1, len(variables)): + # Check the times line up + more_times = curr_nc[variables[v]]['valid_time'] + assert np.array_equal(nc_times, more_times), 'Times between variable datasets do not line up; this is not yet supported' + assert len(more_times) == len(nc_times), 'Variable datasets appear to have different frequencies; this is not yet supported' + # Just for ease of use, we won't allow different indices. + assert nc_date_index == indices[variables[v]], 'Variable datasets do not line up with same start date' + + # Check frequecy matches the config + nc_delta = datetime.datetime.fromtimestamp(more_times[1], datetime.UTC) - datetime.datetime.fromtimestamp(more_times[0], datetime.UTC) + assert nc_delta == expected_frequency, 'Frequency of NetCDF dataset for variable {variables[v]} is not the same as requested frequency.' + + # Check the grid size and lat/lon is consistent + curr_grid_size = curr_nc[variables[v]][:].shape[1:] + assert np.array_equal(grid_size, curr_grid_size), 'NC datasets appear to have different grid sizes' + lon = curr_nc[variables[v]]['latitude'][:] + lat = curr_nc[variables[v]]['longitude'][:] + assert np.array_equal(lon, nc_longitudes), 'NC datasets appear to have different longitudes' + assert np.array_equal(lat, nc_latitudes), 'NC datasets appear to have different longitudes' + return curr_nc, nc_date_index + +def extract_netcdf_input_grid_025(nc: netCDF4.Dataset): + logging.info('extracting lat/lon') + lat = nc['latitude'][:] + lon = nc['longitude'][:] + output_lat = np.zeros(len(lat)* len(lon)) + output_lon = np.zeros(len(lat) * len(lon)) + for i in range(len(lat)): + if i % 10 == 0: + print(i) + for j in range(len(lon)): + grid_index = i * len(lon) + j + output_lat[grid_index] = lat[i] + output_lon[grid_index] = lon[j] + return np.column_stack((output_lon, output_lat)) + +def interpolate_netcdf_to_grid(infile_nc: str, ds_name: str, output_grid: np.ndarray, output_path: str, format='torch', plot_indices=[0]): + # set up output dirs + os.makedirs(os.path.join(output_path, 'info'), exist_ok=True) + os.makedirs(os.path.join(output_path, 'plots'), exist_ok=True) + os.makedirs(os.path.join(output_path, f'{ds_name}'), exist_ok=True) + os.makedirs(os.path.join(output_path, f'{ds_name}-interpolated'), exist_ok=True) + + # extract from yaml config + with open(infile_nc) as cfg_file: + config = yaml.safe_load(cfg_file) + input_path = config['path'] # string + variables = config['variables'] # array + + start_date = config['start'] # datetime type + end_date = config['end'] # datetime type + frequency = datetime.timedelta(hours=config['frequency']) + + # Set up a dict that corresponds to [year][variable] with references to the + # corresponding NC dataset. + curr_nc, nc_date_index = load_netcdf_files_as_dict(input_path, variables, start_date, frequency) + grid_size = curr_nc[variables[0]][:].shape[1:] + input_grid = extract_netcdf_input_grid_025(curr_nc[variables[extract_netcdf_input_grid_025]]) + + # Set up the input grid for interpolation purposes + + # Iterate through each date + t = start_date + t_i = 0 + while datetime.date(t) <= end_date: + # Set up an array to hold input data + input_values = np.ndarray((len(variables), grid_size[0]*grid_size[1])) + if nc_date_index >= curr_nc[variables[0]][:].shape[0]: + curr_nc, nc_date_index = load_netcdf_files_as_dict(input_path, variables, t, frequency) + # check grid size before continuing + assert np.array_equal(grid_size, curr_nc[variables[0]][:].shape[1:]), 'Grid size of NC file for date {t} do not match up' + for v in range(len(variables)): + values = curr_nc[variables[v]][:][nc_date_index,:] + input_values[v,:] = values.flatten() + # Save the input data + save_datetime_file(input_values, t, os.path.join(input_path, ds_name), format=format) + + # Regrid + interpolated_data = regrid(input_values, input_grid, output_grid) + save_datetime_file(interpolated_data, t, os.path.join(input_path, t, f'{ds_name}-interpolated'), format=format) + + # Plot + if t_i in plot_indices: + output_plots_path = os.path.join(output_path, 'plots') + datestr = format_date(t) + logging.info(f'plotting {datestr} to {output_plots_path}') + #for j in range(10): + for j in range(len(variables)): + var = variables[j] + # plot original + plot_and_save_projection(input_grid[:,0], input_grid[:,1], input_values[j,:], f'{output_plots_path}/{variables[j]}-{datestr}-{ds_name}.jpg') + # plot interpolated + plot_and_save_projection(output_grid[:,0], output_grid[:,1], interpolated_data[j, :], f'{output_plots_path}/{variables[j]}-{datestr}-{ds_name}-interpolated.jpg') + + nc_date_index = nc_date_index + 1 + t = t + frequency + t_i = t_i + 1 + \ No newline at end of file From 054bce70710ae45843c61b095078b6080615e379 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 27 Nov 2025 15:53:11 +0100 Subject: [PATCH 214/302] mostly working netcdf regridding refactor. However, there is an off-by-one error in the time parsing I am still chasing down. --- src/hirad/input_data/interpolate_basic.py | 87 ++++++++++++------- .../input_data/process_copernicus_cosmo.py | 47 ++++++++++ 2 files changed, 102 insertions(+), 32 deletions(-) create mode 100644 src/hirad/input_data/process_copernicus_cosmo.py diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index 0059364b..f157f303 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -100,6 +100,7 @@ def format_date(dt64: np.datetime64) -> str: def save_datetime_file(values: np.ndarray[np.intp], date: np.datetime64, filepath: str, format='torch'): """saves array of values for a given date into a torch file""" filename = os.path.join(filepath, format_date(date)) + logging.info(f'writing data to {filename}') if format == 'torch': torch.save(values, filename) elif format == 'numpy': @@ -256,7 +257,7 @@ def save_anemoi_as_format(infile_anemoi: str, ds_name: str, output_path: str, pl for i in range(len(ds.dates)): save_anemoi_time_point(i, ds, ds_name, data_output_path=ds_output_path, plots_output_path=plots_path, plot_indices=[0], format=format) -def load_netcdf_file(path: str, variable: str, date: datetime.datetime): +def load_netcdf_file(path: str, variable: str, index_date: datetime.datetime): """ Get the corresponding netCDF file for a given variable, that includes a given date. @@ -270,21 +271,25 @@ def load_netcdf_file(path: str, variable: str, date: datetime.datetime): matches = re.match(pattern, filename) if matches: f_var = matches[1] - f_start_year = matches[2] - f_end_year = matches[3] - if f_var == variable and date.year >= f_start_year and date.year <= f_end_year: + f_start_year = int(matches[2]) + f_end_year = int(matches[3]) + if f_var == variable and index_date.year >= f_start_year and index_date.year <= f_end_year: ds = netCDF4.Dataset(os.path.join(path, filename)) # Raises ValueError if number of instances not exactly 1, which indicates # an implementation error somewhere. - index = np.where(ds['valid_time'][:] == np.int64(date.timestamp()))[0].item() + index = np.where(ds['valid_time'][:] == np.int64(index_date.timestamp()))[0].item() + netcdf_date = ds['valid_time'][index] + logging.info(f'index {index} has datetime {datetime.datetime.fromtimestamp(netcdf_date, datetime.UTC).strftime('%Y%m%d-%H%M')}') return ds, index - raise FileNotFoundError(f'Could not find .nc file for variable {variable} and date {date} in {path}') + raise FileNotFoundError(f'Could not find .nc file for variable {variable} and date {index_date} in {path}') -def load_netcdf_files_as_dict(path: str, variables: list, date: datetime.datetime, expected_frequency: datetime.timedelta): +def load_netcdf_files_as_dict(input_path: str, variables: list, index_date: datetime.datetime, expected_frequency: datetime.timedelta, + reference_nc_dataset=None): """ Get the corresponding netCDF Datasets for a given list of variables, that includes a given date. Also checks to make sure each dataset lines up in terms of - dates and grids + dates and grids, with each other. Additionally, checks that grids match + up against another reference dataset. Returns: tuple of (dict[netCDF.Dataset], int) where int is the index of where the date is within that dataset. @@ -298,19 +303,25 @@ def load_netcdf_files_as_dict(path: str, variables: list, date: datetime.datetim curr_nc = {} indices = {} for var in variables: - ds, index = load_netcdf_file(input_path, var, start_date) + ds, index = load_netcdf_file(input_path, var, index_date) curr_nc[var] = ds indices[var] = index # Check that all the NC datasets match up in terms of time and grid nc_times = curr_nc[variables[0]]['valid_time'] nc_date_index = indices[variables[0]] - grid_size = curr_nc[variables[0]][:].shape[1:] + logging.info(type(curr_nc[variables[0]])) + grid_size = curr_nc[variables[0]][variables[0]][:].shape[1:] nc_latitudes = curr_nc[variables[0]]['latitude'][:] nc_longitudes = curr_nc[variables[0]]['longitude'][:] + if reference_nc_dataset: + reference_latitudes = curr_nc[variables[0]]['latitude'][:] + reference_longitudes = curr_nc[variables[0]]['longitude'][:] + assert np.array_equal(reference_longitudes, nc_longitudes), 'New NC datasets longitudes do not match reference dataset' + assert np.array_equal(reference_latitudes, nc_latitudes), 'New NC datasets latitudes do not match reference dataset' for v in range(1, len(variables)): # Check the times line up - more_times = curr_nc[variables[v]]['valid_time'] + more_times = curr_nc[variables[v]]['valid_time'][:] assert np.array_equal(nc_times, more_times), 'Times between variable datasets do not line up; this is not yet supported' assert len(more_times) == len(nc_times), 'Variable datasets appear to have different frequencies; this is not yet supported' # Just for ease of use, we won't allow different indices. @@ -321,10 +332,14 @@ def load_netcdf_files_as_dict(path: str, variables: list, date: datetime.datetim assert nc_delta == expected_frequency, 'Frequency of NetCDF dataset for variable {variables[v]} is not the same as requested frequency.' # Check the grid size and lat/lon is consistent - curr_grid_size = curr_nc[variables[v]][:].shape[1:] + curr_grid_size = curr_nc[variables[v]][variables[v]][:].shape[1:] assert np.array_equal(grid_size, curr_grid_size), 'NC datasets appear to have different grid sizes' - lon = curr_nc[variables[v]]['latitude'][:] - lat = curr_nc[variables[v]]['longitude'][:] + lon = curr_nc[variables[v]]['longitude'][:] + lat = curr_nc[variables[v]]['latitude'][:] + logging.info(lon) + logging.info(lon.shape) + logging.info(nc_longitudes) + logging.info(nc_longitudes.shape) assert np.array_equal(lon, nc_longitudes), 'NC datasets appear to have different longitudes' assert np.array_equal(lat, nc_latitudes), 'NC datasets appear to have different longitudes' return curr_nc, nc_date_index @@ -336,8 +351,6 @@ def extract_netcdf_input_grid_025(nc: netCDF4.Dataset): output_lat = np.zeros(len(lat)* len(lon)) output_lon = np.zeros(len(lat) * len(lon)) for i in range(len(lat)): - if i % 10 == 0: - print(i) for j in range(len(lon)): grid_index = i * len(lon) + j output_lat[grid_index] = lat[i] @@ -346,6 +359,7 @@ def extract_netcdf_input_grid_025(nc: netCDF4.Dataset): def interpolate_netcdf_to_grid(infile_nc: str, ds_name: str, output_grid: np.ndarray, output_path: str, format='torch', plot_indices=[0]): # set up output dirs + logging.info(f'setting up subdirs in {output_path}') os.makedirs(os.path.join(output_path, 'info'), exist_ok=True) os.makedirs(os.path.join(output_path, 'plots'), exist_ok=True) os.makedirs(os.path.join(output_path, f'{ds_name}'), exist_ok=True) @@ -355,39 +369,48 @@ def interpolate_netcdf_to_grid(infile_nc: str, ds_name: str, output_grid: np.nda with open(infile_nc) as cfg_file: config = yaml.safe_load(cfg_file) input_path = config['path'] # string - variables = config['variables'] # array + variables = config['channels'] # array + + start_date = datetime.datetime.combine(config['start'], datetime.time()) # datetime type + end_date = datetime.datetime.combine(config['end'], datetime.time()) # datetime type + frequency = datetime.timedelta(hours=int(config['frequency'])) - start_date = config['start'] # datetime type - end_date = config['end'] # datetime type - frequency = datetime.timedelta(hours=config['frequency']) # Set up a dict that corresponds to [year][variable] with references to the # corresponding NC dataset. curr_nc, nc_date_index = load_netcdf_files_as_dict(input_path, variables, start_date, frequency) - grid_size = curr_nc[variables[0]][:].shape[1:] - input_grid = extract_netcdf_input_grid_025(curr_nc[variables[extract_netcdf_input_grid_025]]) - - # Set up the input grid for interpolation purposes + grid_size = curr_nc[variables[0]][variables[0]][:].shape[1:] + input_grid = extract_netcdf_input_grid_025(curr_nc[variables[0]]) + # Output stats and grid + # TODO + # TODO + + # Copy the .yaml file over for recording purposes + shutil.copy(infile_nc, os.path.join(output_path, f'info/{ds_name}.yaml')) + # Iterate through each date t = start_date t_i = 0 - while datetime.date(t) <= end_date: + while t.date() <= end_date.date(): + logging.info(f'processing {t} nc date index {nc_date_index}') # Set up an array to hold input data input_values = np.ndarray((len(variables), grid_size[0]*grid_size[1])) - if nc_date_index >= curr_nc[variables[0]][:].shape[0]: - curr_nc, nc_date_index = load_netcdf_files_as_dict(input_path, variables, t, frequency) - # check grid size before continuing - assert np.array_equal(grid_size, curr_nc[variables[0]][:].shape[1:]), 'Grid size of NC file for date {t} do not match up' + if nc_date_index >= curr_nc[variables[0]][variables[0]][:].shape[0]: + logging.info(f'{t} not found in current files, loading new netcdf files') + curr_nc, nc_date_index = load_netcdf_files_as_dict(input_path, variables, t, frequency, + reference_nc_dataset=curr_nc[variables[0]]) + timestamp = curr_nc[variables[0]]['valid_time'][nc_date_index] + logging.info(f'time {t} has timestamp {timestamp}') for v in range(len(variables)): - values = curr_nc[variables[v]][:][nc_date_index,:] + values = curr_nc[variables[v]][variables[v]][:][nc_date_index,:] input_values[v,:] = values.flatten() # Save the input data - save_datetime_file(input_values, t, os.path.join(input_path, ds_name), format=format) + save_datetime_file(input_values, t, os.path.join(output_path, ds_name), format=format) # Regrid interpolated_data = regrid(input_values, input_grid, output_grid) - save_datetime_file(interpolated_data, t, os.path.join(input_path, t, f'{ds_name}-interpolated'), format=format) + save_datetime_file(interpolated_data, t, os.path.join(output_path, f'{ds_name}-interpolated'), format=format) # Plot if t_i in plot_indices: diff --git a/src/hirad/input_data/process_copernicus_cosmo.py b/src/hirad/input_data/process_copernicus_cosmo.py new file mode 100644 index 00000000..2d831f98 --- /dev/null +++ b/src/hirad/input_data/process_copernicus_cosmo.py @@ -0,0 +1,47 @@ +import logging +import os +import sys + +import numpy as np +import torch + +import interpolate_basic + +def main(): + # TODO: Do better arg parsing so it's not as easy to reverse era and cosmo configs. + if len(sys.argv) < 4: + raise ValueError('Expected call process_copenicus_cosmo.py [copernicus.yaml] [cosmo.yaml] [output directory]') + infile_copernicus = sys.argv[1] + infile_cosmo = sys.argv[2] + output_path = sys.argv[3] + + os.makedirs(output_path, exist_ok=True) + + logging.basicConfig( + filename=os.path.join(output_path, f'process-copernicus-cosmo.log'), + format='%(asctime)s %(levelname)-8s %(message)s', + level=logging.INFO, + datefmt='%Y-%m-%d %H:%M:%S') + + logging.info(f'running {sys.argv}') + + output_grid = None + + if infile_cosmo.endswith('yaml'): + cosmo = interpolate_basic.read_anemoi_ds(infile_cosmo) + output_grid = np.column_stack((cosmo.longitudes, cosmo.latitudes)) + else: + # This must be a lat-lon torch file. + cosmo_latlon = torch.load(infile_cosmo, weights_only=False) + lats = cosmo_latlon[:,0] + lons = cosmo_latlon[:,1] + output_grid = np.column_stack((lons, lats)) + + # interpolate copernicus + format = 'numpy' + plot_indices=[0,24,25] + interpolate_basic.interpolate_netcdf_to_grid(infile_copernicus, 'copernicus', output_grid, output_path=output_path, format=format, plot_indices=plot_indices) + + +if __name__ == "__main__": + main() From 66b6dba66ed0c76993280a99de6000e1594beed7 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 27 Nov 2025 18:45:11 +0100 Subject: [PATCH 215/302] fixed off-by-one error --- src/hirad/input_data/interpolate_basic.py | 21 +++--- .../process_era5_with_copernicus.py | 72 +++++++++++++++++++ 2 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 src/hirad/input_data/process_era5_with_copernicus.py diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index f157f303..3b6d70b5 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -1,4 +1,3 @@ -import datetime import logging import os import re @@ -257,7 +256,7 @@ def save_anemoi_as_format(infile_anemoi: str, ds_name: str, output_path: str, pl for i in range(len(ds.dates)): save_anemoi_time_point(i, ds, ds_name, data_output_path=ds_output_path, plots_output_path=plots_path, plot_indices=[0], format=format) -def load_netcdf_file(path: str, variable: str, index_date: datetime.datetime): +def load_netcdf_file(path: str, variable: str, index_date: np.datetime64): """ Get the corresponding netCDF file for a given variable, that includes a given date. @@ -273,17 +272,17 @@ def load_netcdf_file(path: str, variable: str, index_date: datetime.datetime): f_var = matches[1] f_start_year = int(matches[2]) f_end_year = int(matches[3]) - if f_var == variable and index_date.year >= f_start_year and index_date.year <= f_end_year: + if f_var == variable and to_datetime(index_date).year >= f_start_year and to_datetime(index_date).year <= f_end_year: ds = netCDF4.Dataset(os.path.join(path, filename)) # Raises ValueError if number of instances not exactly 1, which indicates # an implementation error somewhere. - index = np.where(ds['valid_time'][:] == np.int64(index_date.timestamp()))[0].item() + index = np.where(ds['valid_time'][:] == index_date.astype('datetime64[s]').astype('int'))[0].item() netcdf_date = ds['valid_time'][index] - logging.info(f'index {index} has datetime {datetime.datetime.fromtimestamp(netcdf_date, datetime.UTC).strftime('%Y%m%d-%H%M')}') + logging.info(f'index {index} has datetime {format_date(np.datetime64(int(netcdf_date), 's'))}') return ds, index raise FileNotFoundError(f'Could not find .nc file for variable {variable} and date {index_date} in {path}') -def load_netcdf_files_as_dict(input_path: str, variables: list, index_date: datetime.datetime, expected_frequency: datetime.timedelta, +def load_netcdf_files_as_dict(input_path: str, variables: list, index_date: np.datetime64, expected_frequency: np.timedelta64, reference_nc_dataset=None): """ Get the corresponding netCDF Datasets for a given list of variables, that includes @@ -328,7 +327,7 @@ def load_netcdf_files_as_dict(input_path: str, variables: list, index_date: date assert nc_date_index == indices[variables[v]], 'Variable datasets do not line up with same start date' # Check frequecy matches the config - nc_delta = datetime.datetime.fromtimestamp(more_times[1], datetime.UTC) - datetime.datetime.fromtimestamp(more_times[0], datetime.UTC) + nc_delta = np.datetime64(int(more_times[1]), 's') - np.datetime64(int(more_times[0]), 's') assert nc_delta == expected_frequency, 'Frequency of NetCDF dataset for variable {variables[v]} is not the same as requested frequency.' # Check the grid size and lat/lon is consistent @@ -371,9 +370,9 @@ def interpolate_netcdf_to_grid(infile_nc: str, ds_name: str, output_grid: np.nda input_path = config['path'] # string variables = config['channels'] # array - start_date = datetime.datetime.combine(config['start'], datetime.time()) # datetime type - end_date = datetime.datetime.combine(config['end'], datetime.time()) # datetime type - frequency = datetime.timedelta(hours=int(config['frequency'])) + start_date = np.datetime64(config['start']) # np.datetime64 type + end_date = np.datetime64(config['end']) # datetime type + frequency = np.timedelta64(int(config['frequency']), 'h') # Set up a dict that corresponds to [year][variable] with references to the @@ -392,7 +391,7 @@ def interpolate_netcdf_to_grid(infile_nc: str, ds_name: str, output_grid: np.nda # Iterate through each date t = start_date t_i = 0 - while t.date() <= end_date.date(): + while to_datetime(t).date() <= to_datetime(end_date).date(): logging.info(f'processing {t} nc date index {nc_date_index}') # Set up an array to hold input data input_values = np.ndarray((len(variables), grid_size[0]*grid_size[1])) diff --git a/src/hirad/input_data/process_era5_with_copernicus.py b/src/hirad/input_data/process_era5_with_copernicus.py new file mode 100644 index 00000000..cf9a162a --- /dev/null +++ b/src/hirad/input_data/process_era5_with_copernicus.py @@ -0,0 +1,72 @@ +import logging +import os +import sys + +import numpy as np +import torch +import yaml + +import interpolate_basic + +def load(filename: str): + if filename.endswith('.npy'): + return np.load(filename) + return torch.load(filename, weights_only=False) + +def save(values: np.ndarray, filename: str): + if filename.endswith('.npy'): + np.save(filename, values) + else: + torch.save(values, filename) + return + +def main(): + # TODO: Do better arg parsing so it's not as easy to reverse era and cosmo configs. + if len(sys.argv) < 4: + raise ValueError('Expected call process_copernicus_era.py [era.yaml] [copernicus.yaml] [path]') + infile_era = sys.argv[1] + infile_copernicus = sys.argv[2] + path = sys.argv[3] + + os.makedirs(os.path.join(path, 'era-copernicus-interpolated'), exist_ok=True) + + logging.basicConfig( + filename=os.path.join(path, f'process-era-copernicus.log'), + format='%(asctime)s %(levelname)-8s %(message)s', + level=logging.INFO, + datefmt='%Y-%m-%d %H:%M:%S') + + logging.info(f'running {sys.argv}') + + # Extract ERA channels from input yaml + with open(infile_era) as era_cfg_file: + era_config = yaml.safe_load(era_cfg_file) + with open(infile_copernicus) as cop_cfg_file: + copernicus_config = yaml.safe_load(cop_cfg_file) + + era_channels = era_config['select'] + copernicus_channels = copernicus_config['channels'] + mapping = {} + for c in range(len(copernicus_channels)): + if era_channels.count(copernicus_channels[c]) == 1: + e = era_channels.index(copernicus_channels[c]) + mapping[e] = c + logging.info('replacing {len(mapping)} channels in ERA data: {mapping}') + + era_dir = os.path.join(path, 'era-interpolated') + copernicus_dir = os.path.join(path, 'copernicus-interpolated') + output_dir = os.path.join(path, 'era-copernicus-interpolated') + era_files = os.listdir(era_dir) + copernicus_files = os.listdir(copernicus_dir) + intersect_files = list(set(era_files).intersection(set(copernicus_files))) + + for f in intersect_files: + era = load(os.path.join(era_dir, f)) + copernicus = load(os.path.join(copernicus_dir, f)) + assert np.array_equal(era.shape[1:], copernicus.shape[1:]), 'Era and Copernicus files appear to have different grid shapes' + for k,v in mapping.items(): + era[k,:] = copernicus[v,:] + save(era, os.path.join(output_dir, f)) + +if __name__ == "__main__": + main() From 823663cce6cbf80c2ac61ef19921cf8d0ee41d54 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 27 Nov 2025 19:06:37 +0100 Subject: [PATCH 216/302] cleanup and comments --- src/hirad/input_data/interpolate_basic.py | 41 ++++++++++++++----- .../input_data/process_copernicus_cosmo.py | 8 ++-- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index 3b6d70b5..e0dce483 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -238,9 +238,11 @@ def interpolate_anemoi_to_grid(infile_anemoi: str, ds_name: str, output_grid: np plot_indices=plot_indices) -### Part 2: Save COSMO data def save_anemoi_as_format(infile_anemoi: str, ds_name: str, output_path: str, plot_indices=[0], format='torch', - start_date = None, end_date = None, area = None): + start_date = None, end_date = None, area = None): + """ Output anemoi data into the same file structure/format as the regridded data, e.g. + to use as target data. + No regridding is performed.""" os.makedirs(os.path.join(output_path, 'info'), exist_ok=True) plots_path = os.path.join(output_path, 'plots') os.makedirs(plots_path, exist_ok=True) @@ -309,7 +311,6 @@ def load_netcdf_files_as_dict(input_path: str, variables: list, index_date: np.d # Check that all the NC datasets match up in terms of time and grid nc_times = curr_nc[variables[0]]['valid_time'] nc_date_index = indices[variables[0]] - logging.info(type(curr_nc[variables[0]])) grid_size = curr_nc[variables[0]][variables[0]][:].shape[1:] nc_latitudes = curr_nc[variables[0]]['latitude'][:] nc_longitudes = curr_nc[variables[0]]['longitude'][:] @@ -344,19 +345,36 @@ def load_netcdf_files_as_dict(input_path: str, variables: list, index_date: np.d return curr_nc, nc_date_index def extract_netcdf_input_grid_025(nc: netCDF4.Dataset): + """ + Gives reshaped lon-lat coordinates for NetCDF dataset. + For X x Y grid, shape will be (X*Y, 2) + Outputs in longitude as first column, latitude as second column, + for feeding into regridding. + """ logging.info('extracting lat/lon') - lat = nc['latitude'][:] lon = nc['longitude'][:] - output_lat = np.zeros(len(lat)* len(lon)) + lat = nc['latitude'][:] output_lon = np.zeros(len(lat) * len(lon)) + output_lat = np.zeros(len(lat)* len(lon)) for i in range(len(lat)): for j in range(len(lon)): grid_index = i * len(lon) + j - output_lat[grid_index] = lat[i] output_lon[grid_index] = lon[j] + output_lat[grid_index] = lat[i] return np.column_stack((output_lon, output_lat)) def interpolate_netcdf_to_grid(infile_nc: str, ds_name: str, output_grid: np.ndarray, output_path: str, format='torch', plot_indices=[0]): + """ + Corresponds to interpolate_anemoi_to_grid, for netCDF files. + More assumptions are made here about e.g. the filenames of the NetCDF files. + + infile_nc: str: Filepath to a .yaml file with config info + ds_name: Dataset name (e.g. 'copernicus'), for output filenames + output_grid: np.ndarray of lon/lat coordinates to regrid to + output_path: parent directory for output. Subdirectories for data and plots will be created. + format: format to output data to ('torch' or 'numpy') + plot_indices: indices of time points to plot. + """ # set up output dirs logging.info(f'setting up subdirs in {output_path}') os.makedirs(os.path.join(output_path, 'info'), exist_ok=True) @@ -381,9 +399,10 @@ def interpolate_netcdf_to_grid(infile_nc: str, ds_name: str, output_grid: np.nda grid_size = curr_nc[variables[0]][variables[0]][:].shape[1:] input_grid = extract_netcdf_input_grid_025(curr_nc[variables[0]]) - # Output stats and grid - # TODO - # TODO + # Output grid as torch file + torch.save(np.column_stack((input_grid[:,1], input_grid[:,0])), os.path.join(output_path, 'info', f'{ds_name}-lat-lon')) + + # TODO consider outputting stats, but this would require additional calculations # Copy the .yaml file over for recording purposes shutil.copy(infile_nc, os.path.join(output_path, f'info/{ds_name}.yaml')) @@ -399,8 +418,8 @@ def interpolate_netcdf_to_grid(infile_nc: str, ds_name: str, output_grid: np.nda logging.info(f'{t} not found in current files, loading new netcdf files') curr_nc, nc_date_index = load_netcdf_files_as_dict(input_path, variables, t, frequency, reference_nc_dataset=curr_nc[variables[0]]) - timestamp = curr_nc[variables[0]]['valid_time'][nc_date_index] - logging.info(f'time {t} has timestamp {timestamp}') + #timestamp = curr_nc[variables[0]]['valid_time'][nc_date_index] + #logging.info(f'time {t} has timestamp {timestamp}') for v in range(len(variables)): values = curr_nc[variables[v]][variables[v]][:][nc_date_index,:] input_values[v,:] = values.flatten() diff --git a/src/hirad/input_data/process_copernicus_cosmo.py b/src/hirad/input_data/process_copernicus_cosmo.py index 2d831f98..b9f210d3 100644 --- a/src/hirad/input_data/process_copernicus_cosmo.py +++ b/src/hirad/input_data/process_copernicus_cosmo.py @@ -10,7 +10,7 @@ def main(): # TODO: Do better arg parsing so it's not as easy to reverse era and cosmo configs. if len(sys.argv) < 4: - raise ValueError('Expected call process_copenicus_cosmo.py [copernicus.yaml] [cosmo.yaml] [output directory]') + raise ValueError('Expected call process_copernicus_cosmo.py [copernicus.yaml] [cosmo.yaml] [output directory]') infile_copernicus = sys.argv[1] infile_cosmo = sys.argv[2] output_path = sys.argv[3] @@ -39,8 +39,10 @@ def main(): # interpolate copernicus format = 'numpy' - plot_indices=[0,24,25] - interpolate_basic.interpolate_netcdf_to_grid(infile_copernicus, 'copernicus', output_grid, output_path=output_path, format=format, plot_indices=plot_indices) + plot_indices=[0] + interpolate_basic.interpolate_netcdf_to_grid(infile_copernicus, 'copernicus', + output_grid, output_path=output_path, + format=format, plot_indices=plot_indices) if __name__ == "__main__": From fbc4eb5bd0ec4cd0b3309448ea723dd0ea135cdc Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 4 Dec 2025 09:26:43 +0100 Subject: [PATCH 217/302] add logging, and small refactor of regrid_realc1 to more closely match style in interpolate_basic --- src/hirad/input_data/interpolate_basic.py | 1 + src/hirad/input_data/regrid_realch1.py | 102 ++++++++++++---------- 2 files changed, 56 insertions(+), 47 deletions(-) diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index e0dce483..5f1058d5 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -175,6 +175,7 @@ def interpolate_anemoi_time_point_to_grid(i: int, ds: Dataset, ds_name: str, inp def save_anemoi_time_point(i: int, ds: Dataset, ds_name: str, data_output_path: str, plots_output_path: str = None, plot_indices=[0], format='torch'): """Save a time point of anemoi data (either input or target) directly into a given format. If the time point is in the """ + logging.info(f'saving {ds_name} {ds.dates[i]}') save_datetime_file(ds[i,:,0,:], ds.dates[i], data_output_path, format) datestr = format_date(ds.dates[i]) if plots_output_path and i in plot_indices: diff --git a/src/hirad/input_data/regrid_realch1.py b/src/hirad/input_data/regrid_realch1.py index a4939b39..13c63a3b 100644 --- a/src/hirad/input_data/regrid_realch1.py +++ b/src/hirad/input_data/regrid_realch1.py @@ -11,7 +11,7 @@ from meteodatalab.operators import regrid import xarray as xr from meteodatalab import ogd_api -from hirad.input_data.interpolate_basic import plot_and_save_projection +from hirad.input_data import interpolate_basic import yaml import torch from pandas import to_datetime @@ -110,27 +110,40 @@ def regridded_to_numpy(regridded: xr.DataArray, trim_edge=0): # reshape to (time,channel,ensemble,grid) data = data.reshape(data.shape[1], data.shape[0], data.shape[3]*data.shape[4]) return data - -def main(): - # yml format - realch1_config_file = sys.argv[1] - output_directory = sys.argv[2] - if not os.path.exists(output_directory): - os.mkdir(output_directory) - for subdir in ['info', 'plots', 'realch1']: - if not os.path.exists(os.path.join(output_directory, subdir)): - os.mkdir(os.path.join(output_directory, subdir)) - - logging.basicConfig( - filename=os.path.join(output_directory, 'regrid_realch1.log'), - format='%(asctime)s %(levelname)-8s %(message)s', - level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') - + +def interpolate_anemoi_range_to_rotlatlon(i_start: int, i_end: int, ds: Dataset, ds_name: str, input_grid: np.ndarray, output_grid: np.ndarray, output_data_path: str, format='torch', output_plots_path: str = None, plot_indices=[0]): + torch_data = np.zeros([i_end-i_start, len(ds.variables), 1, output_grid.shape[0]]) + + xarrays = anemoi_to_xarray(ds, i_start, i_end) + for j in range(len(xarrays)): + logging.info(f'regridding {ds.variables[j]} for time {ds.dates[i_start]} to {ds.dates[i_end]}') + xarray = xarrays[j] + start = datetime.datetime.now() + regridded=regrid.icon2rotlatlon(xarray) + end = datetime.datetime.now() + logging.info(f' regridding took {end-start} seconds') + torch_data[0:i_end-i_start,j,:,:] = regridded_to_numpy(regridded, trim_edge=TRIM_EDGE) + + logging.info('saving torch data') + for k in range(torch_data.shape[0]): + interpolate_basic.save_datetime_file(torch_data[k,:], ds.dates[i_start + k], output_data_path, format=format) + if (i_start + k) in plot_indices: + for v in range(torch_data.shape[1]): + interpolate_basic.plot_and_save_projection(input_grid[:,0], input_grid[:,1], + ds[i_start+k,v,0,:], + os.path.join(output_plots_path, f'{ds.variables[v]}-iconnative.png'), + s=0.005) + interpolate_basic.plot_and_save_projection(output_grid[:,0], output_grid[:,1], + torch_data[k,v,0,:], + os.path.join(output_plots_path, f'{ds.variables[v]}-rotlatlon.png'), + s=0.005) + +def interpolate_anemoi_to_rotlatlon(infile_anemoi: str, ds_name: str, output_grid: np.ndarray, output_path: str, format='torch', plot_indices=[0]): + # Copy the realch1.yml file to the info directory - shutil.copy(realch1_config_file, os.path.join(output_directory, 'info')) + shutil.copy(infile_anemoi, os.path.join(output_path, 'info')) - with open(realch1_config_file) as realch1_file: + with open(infile_anemoi) as realch1_file: realch1_config = yaml.safe_load(realch1_file) realch1 = open_dataset(realch1_config) variables = realch1.variables @@ -143,39 +156,34 @@ def main(): # Save grid to file grid = np.column_stack((lats, lons)) - torch.save(grid, os.path.join(output_directory, 'info', 'realch1-lat-lon')) + torch.save(grid, os.path.join(output_path, 'info', 'realch1-lat-lon')) # Split regridding into batches; too many time points seems to not scale well. for i in range(0, len(realch1.dates), XARRAY_BATCH): start_index = i end_index = min(i+XARRAY_BATCH, len(realch1.dates)) - torch_data = np.zeros([end_index-start_index, len(realch1.variables), 1, len(lats)]) + interpolate_anemoi_range_to_rotlatlon(start_index, end_index, realch1, ds_name, + None, None, output_path, format, None, plot_indices) - xarrays = anemoi_to_xarray(realch1, start_index, end_index) - for j in range(len(xarrays)): - logging.info(f'regridding {variables[j]} for time {realch1.dates[start_index]} to {realch1.dates[end_index]}') - xarray = xarrays[j] - start = datetime.datetime.now() - regridded=regrid.icon2rotlatlon(xarray) - end = datetime.datetime.now() - logging.info(f' regridding took {end-start} seconds') - torch_data[0:end_index-start_index,j,:,:] = regridded_to_numpy(regridded, trim_edge=TRIM_EDGE) - - logging.info('saving torch data') - for k in range(torch_data.shape[0]): - fmtdate = to_datetime(realch1.dates[start_index + k]).strftime('%Y%m%d-%H%M') - torch.save(torch_data[k,:], os.path.join(output_directory, 'realch1', fmtdate)) - - # Output plots for each variable, for first time point - for i in range(torch_data.shape[1]): - plot_and_save_projection(realch1.longitudes, realch1.latitudes, - realch1[0,i,0,:], - os.path.join(output_directory, 'plots', f'{variables[i]}-iconnative.png'), - s=0.005) - plot_and_save_projection(lons, lats, - torch_data[0,i,0,:], - os.path.join(output_directory, 'plots', f'{variables[i]}-rotlatlon.png'), - s=0.005) + +def main(): + # yml format + realch1_config_file = sys.argv[1] + output_directory = sys.argv[2] + if not os.path.exists(output_directory): + os.mkdir(output_directory) + for subdir in ['info', 'plots', 'realch1']: + if not os.path.exists(os.path.join(output_directory, subdir)): + os.mkdir(os.path.join(output_directory, subdir)) + + logging.basicConfig( + filename=os.path.join(output_directory, 'regrid_realch1.log'), + format='%(asctime)s %(levelname)-8s %(message)s', + level=logging.INFO, + datefmt='%Y-%m-%d %H:%M:%S') + + interpolate_anemoi_to_rotlatlon(realch1_config_file, 'realch1', None, output_directory, 'numpy', [0]) + if __name__ == "__main__": From 334bd54ed79a111f497a9d1c18d5c20f1a29281f Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 4 Dec 2025 09:27:27 +0100 Subject: [PATCH 218/302] a few processing scripts --- src/hirad/input_data/process_era5_cosmo.py | 8 ++- .../process_era5_with_copernicus.py | 56 ++++++++++++++----- .../input_data/process_torch_to_numpy.py | 23 ++++++++ 3 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 src/hirad/input_data/process_torch_to_numpy.py diff --git a/src/hirad/input_data/process_era5_cosmo.py b/src/hirad/input_data/process_era5_cosmo.py index acfec6d6..be120198 100644 --- a/src/hirad/input_data/process_era5_cosmo.py +++ b/src/hirad/input_data/process_era5_cosmo.py @@ -46,8 +46,8 @@ def main(): interpolate_basic.interpolate_anemoi_to_grid(infile_era, 'era', output_grid, output_path=output_path, format=format, plot_indices=plot_indices) # save era and cosmo input/output data into same format # Save cosmo data - if infile_cosmo.endswith('yaml'): - interpolate_basic.save_anemoi_as_format(infile_cosmo, 'cosmo', output_path, plot_indices=plot_indices, format=format) + #if infile_cosmo.endswith('yaml'): + # interpolate_basic.save_anemoi_as_format(infile_cosmo, 'cosmo', output_path, plot_indices=plot_indices, format=format) # Save ERA data (subsetted) lats = output_grid[:,1] @@ -59,7 +59,9 @@ def main(): area=(max_lat, min_lon, min_lat, max_lon) logging.info(f'projecting onto era area {area}') # skip plotting as we did it already in interpolation step - interpolate_basic.save_anemoi_as_format(infile_era, 'era', output_path, plot_indices=[], format=format, area=area) + #interpolate_basic.save_anemoi_as_format(infile_era, 'era', output_path, plot_indices=plot_indices, format=format, area=area, + # start_date=cosmo.start_date, end_date=cosmo.end_date) + #interpolate_basic.save_anemoi_as_format(infile_cosmo, 'cosmo', output_path, plot_indices=plot_indices, format=format) if __name__ == "__main__": diff --git a/src/hirad/input_data/process_era5_with_copernicus.py b/src/hirad/input_data/process_era5_with_copernicus.py index cf9a162a..4bb0aec5 100644 --- a/src/hirad/input_data/process_era5_with_copernicus.py +++ b/src/hirad/input_data/process_era5_with_copernicus.py @@ -24,14 +24,17 @@ def main(): # TODO: Do better arg parsing so it's not as easy to reverse era and cosmo configs. if len(sys.argv) < 4: raise ValueError('Expected call process_copernicus_era.py [era.yaml] [copernicus.yaml] [path]') + infile_era = sys.argv[1] infile_copernicus = sys.argv[2] - path = sys.argv[3] + era_dir = sys.argv[3] + copernicus_dir = sys.argv[4] + output_path = sys.argv[5] - os.makedirs(os.path.join(path, 'era-copernicus-interpolated'), exist_ok=True) + os.makedirs(os.path.join(output_path, 'era-copernicus-interpolated'), exist_ok=True) logging.basicConfig( - filename=os.path.join(path, f'process-era-copernicus.log'), + filename=os.path.join(output_path, f'process-era-copernicus.log'), format='%(asctime)s %(levelname)-8s %(message)s', level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') @@ -53,20 +56,47 @@ def main(): mapping[e] = c logging.info('replacing {len(mapping)} channels in ERA data: {mapping}') - era_dir = os.path.join(path, 'era-interpolated') - copernicus_dir = os.path.join(path, 'copernicus-interpolated') - output_dir = os.path.join(path, 'era-copernicus-interpolated') + #era_dir = os.path.join(output_path, 'era-interpolated') + #copernicus_dir = os.path.join(path, 'copernicus-interpolated') + output_dir = os.path.join(output_path, 'era-copernicus-interpolated') era_files = os.listdir(era_dir) copernicus_files = os.listdir(copernicus_dir) - intersect_files = list(set(era_files).intersection(set(copernicus_files))) + era_files.sort() + copernicus_files.sort() + #intersect_files = list(set(era_files).intersection(set(copernicus_files))) + era_format = 'torch' + copernicus_format = 'torch' + if era_files[0].endswith('npy'): + era_format = 'numpy' + if copernicus_files[0].endswith('npy'): + copernicus_format = 'numpy' - for f in intersect_files: + #for f in era_files: + #for i in range(15000): + for i in range(10000,12000): + #for i in range(12000,15000): + #for i in range(15000,20000): + #for i in range(20000,25000): + #for i in range(25000,30000): + #for i in range(30000,35000): + #for i in range(35000,40000): + #for i in range(40000,len(era_files)): + f = era_files[i] + logging.info(f'{i} {f}') era = load(os.path.join(era_dir, f)) - copernicus = load(os.path.join(copernicus_dir, f)) - assert np.array_equal(era.shape[1:], copernicus.shape[1:]), 'Era and Copernicus files appear to have different grid shapes' - for k,v in mapping.items(): - era[k,:] = copernicus[v,:] - save(era, os.path.join(output_dir, f)) + era = era.squeeze() # get rid of extra dimension, if present + base_filename = f + if era_format == 'numpy': + base_filename = f[:-4] + c_f = base_filename + if copernicus_format == 'numpy': + c_f = base_filename + '.npy' + if c_f in copernicus_files: + copernicus = load(os.path.join(copernicus_dir, c_f)) + assert np.array_equal(era.shape[1:], copernicus.shape[1:]), f'Era and Copernicus files appear to have different grid shapes: {era.shape} vs {copernicus.shape}' + for k,v in mapping.items(): + era[k,:] = copernicus[v,:] + save(era, os.path.join(output_dir, base_filename + '.npy')) if __name__ == "__main__": main() diff --git a/src/hirad/input_data/process_torch_to_numpy.py b/src/hirad/input_data/process_torch_to_numpy.py new file mode 100644 index 00000000..e61a18ba --- /dev/null +++ b/src/hirad/input_data/process_torch_to_numpy.py @@ -0,0 +1,23 @@ + +import os +import sys + +import numpy as np +import torch + + +def main(): + input_dir = sys.argv[1] + output_dir = sys.argv[2] + in_files = os.listdir(input_dir) + in_files.sort() + for i in range(10): + f = in_files[i] + print(f) + #for f in in_files: + data = torch.load(os.path.join(input_dir, f), weights_only=False) + np.save(os.path.join(output_dir, f), data) + +if __name__ == "__main__": + main() + From ea07ee6a5a60d509d9684dec58d34331fd31fbc8 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 4 Dec 2025 09:27:59 +0100 Subject: [PATCH 219/302] start of era5->realch1 processing script --- src/hirad/input_data/process_era5_realch1.py | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/hirad/input_data/process_era5_realch1.py diff --git a/src/hirad/input_data/process_era5_realch1.py b/src/hirad/input_data/process_era5_realch1.py new file mode 100644 index 00000000..dcc14134 --- /dev/null +++ b/src/hirad/input_data/process_era5_realch1.py @@ -0,0 +1,37 @@ +import logging +import os +import sys + +import numpy as np +import torch + +import interpolate_basic + +def main(): + # TODO: Do better arg parsing so it's not as easy to reverse era and cosmo configs. + if len(sys.argv) < 4: + raise ValueError('Expected call process_era5_realch1.py [era.yaml] [cosmo.yaml] [output directory]') + infile_era = sys.argv[1] + infile_realch1 = sys.argv[2] + output_path = sys.argv[3] + + os.makedirs(output_path, exist_ok=True) + + erashortname = infile_era.split('/')[-1].split('.')[0] + + logging.basicConfig( + filename=os.path.join(output_path, f'process-era5-realch1-{erashortname}.log'), + format='%(asctime)s %(levelname)-8s %(message)s', + level=logging.INFO, + datefmt='%Y-%m-%d %H:%M:%S') + + logging.info(f'running {sys.argv}') + + format='numpy' + plot_indices=[0] + + interpolate_basic.save_anemoi_as_format(infile_realch1, 'realch1', output_path, plot_indices=plot_indices, format=format) + + +if __name__ == "__main__": + main() From f208f45062b7f175d0b811cb827126a32ae22c51 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 4 Dec 2025 11:14:03 +0100 Subject: [PATCH 220/302] add a bunch of configs --- src/hirad/fix-files.sh | 12 ++++++ .../input_data/configs/era-all-2015q4.yaml | 19 ++++++++ .../input_data/configs/era-all-2016q1.yaml | 20 +++++++++ .../input_data/configs/era-all-2016q2.yaml | 5 ++- .../input_data/configs/era-all-2016q3.yaml | 5 ++- .../input_data/configs/era-all-2016q4.yaml | 19 ++++++++ .../input_data/configs/era-all-2017q1.yaml | 20 +++++++++ .../input_data/configs/era-all-2017q2.yaml | 19 ++++++++ .../input_data/configs/era-all-2017q3.yaml | 19 ++++++++ .../input_data/configs/era-all-2017q4.yaml | 19 ++++++++ .../input_data/configs/era-all-2018q1.yaml | 20 +++++++++ .../input_data/configs/era-all-2018q2.yaml | 19 ++++++++ .../input_data/configs/era-all-2018q3.yaml | 19 ++++++++ .../input_data/configs/era-all-2018q4.yaml | 19 ++++++++ .../input_data/configs/era-all-201901.yaml | 2 +- .../input_data/configs/era-all-201902.yaml | 2 +- .../input_data/configs/era-all-201903.yaml | 2 +- .../input_data/configs/era-all-201904.yaml | 2 +- .../input_data/configs/era-all-201905.yaml | 2 +- .../input_data/configs/era-all-201906.yaml | 2 +- .../input_data/configs/era-all-201907.yaml | 2 +- .../input_data/configs/era-all-201908.yaml | 2 +- .../input_data/configs/era-all-201909.yaml | 2 +- .../input_data/configs/era-all-201910.yaml | 2 +- .../input_data/configs/era-all-201911.yaml | 2 +- .../input_data/configs/era-all-201912.yaml | 2 +- src/hirad/input_data/copernicus.yaml | 6 +++ src/hirad/input_data/cosmo-all.yaml | 8 ++-- src/hirad/interpolate-batches.sh | 20 ++++----- src/hirad/interpolate.sh | 43 ++++++++++++++++--- 30 files changed, 299 insertions(+), 36 deletions(-) create mode 100644 src/hirad/fix-files.sh create mode 100644 src/hirad/input_data/configs/era-all-2015q4.yaml create mode 100644 src/hirad/input_data/configs/era-all-2016q1.yaml create mode 100644 src/hirad/input_data/configs/era-all-2016q4.yaml create mode 100644 src/hirad/input_data/configs/era-all-2017q1.yaml create mode 100644 src/hirad/input_data/configs/era-all-2017q2.yaml create mode 100644 src/hirad/input_data/configs/era-all-2017q3.yaml create mode 100644 src/hirad/input_data/configs/era-all-2017q4.yaml create mode 100644 src/hirad/input_data/configs/era-all-2018q1.yaml create mode 100644 src/hirad/input_data/configs/era-all-2018q2.yaml create mode 100644 src/hirad/input_data/configs/era-all-2018q3.yaml create mode 100644 src/hirad/input_data/configs/era-all-2018q4.yaml create mode 100644 src/hirad/input_data/copernicus.yaml diff --git a/src/hirad/fix-files.sh b/src/hirad/fix-files.sh new file mode 100644 index 00000000..35a160a6 --- /dev/null +++ b/src/hirad/fix-files.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +#clariden +regex_pattern="^era-interpolated(.*)" +for f in $(ls /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/era-interpolated/); +do + if [[ "$f" =~ $regex_pattern ]]; then + newf=${BASH_REMATCH[1]} + echo "newf: $newf" + mv /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/era-interpolated/$f /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/era-interpolated/$newf + fi +done \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-2015q4.yaml b/src/hirad/input_data/configs/era-all-2015q4.yaml new file mode 100644 index 00000000..58242e7f --- /dev/null +++ b/src/hirad/input_data/configs/era-all-2015q4.yaml @@ -0,0 +1,19 @@ +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2015-10-01 +end: 2015-12-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-2016q1.yaml b/src/hirad/input_data/configs/era-all-2016q1.yaml new file mode 100644 index 00000000..794cec23 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-2016q1.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2016-03-01 +end: 2016-03-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-2016q2.yaml b/src/hirad/input_data/configs/era-all-2016q2.yaml index 5d2d5ca9..96aeefd7 100644 --- a/src/hirad/input_data/configs/era-all-2016q2.yaml +++ b/src/hirad/input_data/configs/era-all-2016q2.yaml @@ -1,5 +1,6 @@ -#dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' -dataset: '/scratch/mch/miccatta/ml_datasets/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', 'lsm', 'msl', 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', diff --git a/src/hirad/input_data/configs/era-all-2016q3.yaml b/src/hirad/input_data/configs/era-all-2016q3.yaml index ae3384e2..97ded368 100644 --- a/src/hirad/input_data/configs/era-all-2016q3.yaml +++ b/src/hirad/input_data/configs/era-all-2016q3.yaml @@ -1,5 +1,6 @@ -#dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' -dataset: '/scratch/mch/miccatta/ml_datasets/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', 'lsm', 'msl', 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', diff --git a/src/hirad/input_data/configs/era-all-2016q4.yaml b/src/hirad/input_data/configs/era-all-2016q4.yaml new file mode 100644 index 00000000..a3d035fd --- /dev/null +++ b/src/hirad/input_data/configs/era-all-2016q4.yaml @@ -0,0 +1,19 @@ +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2016-10-01 +end: 2016-12-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-2017q1.yaml b/src/hirad/input_data/configs/era-all-2017q1.yaml new file mode 100644 index 00000000..e39399f1 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-2017q1.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2017-01-01 +end: 2017-03-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-2017q2.yaml b/src/hirad/input_data/configs/era-all-2017q2.yaml new file mode 100644 index 00000000..8504768e --- /dev/null +++ b/src/hirad/input_data/configs/era-all-2017q2.yaml @@ -0,0 +1,19 @@ +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2017-04-01 +end: 2017-06-30 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-2017q3.yaml b/src/hirad/input_data/configs/era-all-2017q3.yaml new file mode 100644 index 00000000..97caadd5 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-2017q3.yaml @@ -0,0 +1,19 @@ +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2017-07-01 +end: 2017-09-30 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-2017q4.yaml b/src/hirad/input_data/configs/era-all-2017q4.yaml new file mode 100644 index 00000000..505c490f --- /dev/null +++ b/src/hirad/input_data/configs/era-all-2017q4.yaml @@ -0,0 +1,19 @@ +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2017-10-01 +end: 2017-12-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-2018q1.yaml b/src/hirad/input_data/configs/era-all-2018q1.yaml new file mode 100644 index 00000000..9e3ac5f8 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-2018q1.yaml @@ -0,0 +1,20 @@ + +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2018-01-01 +end: 2018-03-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-2018q2.yaml b/src/hirad/input_data/configs/era-all-2018q2.yaml new file mode 100644 index 00000000..71182629 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-2018q2.yaml @@ -0,0 +1,19 @@ +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2018-04-01 +end: 2018-06-30 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-2018q3.yaml b/src/hirad/input_data/configs/era-all-2018q3.yaml new file mode 100644 index 00000000..a951d6ba --- /dev/null +++ b/src/hirad/input_data/configs/era-all-2018q3.yaml @@ -0,0 +1,19 @@ +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2018-07-01 +end: 2018-09-30 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-2018q4.yaml b/src/hirad/input_data/configs/era-all-2018q4.yaml new file mode 100644 index 00000000..0cf76140 --- /dev/null +++ b/src/hirad/input_data/configs/era-all-2018q4.yaml @@ -0,0 +1,19 @@ +#dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +#dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', + 'lsm', 'msl', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sin_julian_day', 'sin_latitude', 'sin_local_time', 'sin_longitude', 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +# ALL ERA CHANNELS +# Intersection between ERA and COSMO excludes cp, sdor, slor, tcw +# Note: Bounding dates/area will be done in .py code. +start: 2018-10-01 +end: 2018-12-31 \ No newline at end of file diff --git a/src/hirad/input_data/configs/era-all-201901.yaml b/src/hirad/input_data/configs/era-all-201901.yaml index 748769d4..4a2f1bc9 100755 --- a/src/hirad/input_data/configs/era-all-201901.yaml +++ b/src/hirad/input_data/configs/era-all-201901.yaml @@ -1,7 +1,7 @@ #dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' #dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' -dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', 'lsm', 'msl', 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', diff --git a/src/hirad/input_data/configs/era-all-201902.yaml b/src/hirad/input_data/configs/era-all-201902.yaml index 4a7c9431..86c12bfd 100755 --- a/src/hirad/input_data/configs/era-all-201902.yaml +++ b/src/hirad/input_data/configs/era-all-201902.yaml @@ -1,7 +1,7 @@ #dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' #dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' -dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', 'lsm', 'msl', 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', diff --git a/src/hirad/input_data/configs/era-all-201903.yaml b/src/hirad/input_data/configs/era-all-201903.yaml index e96ec7e5..351a2ffc 100755 --- a/src/hirad/input_data/configs/era-all-201903.yaml +++ b/src/hirad/input_data/configs/era-all-201903.yaml @@ -1,7 +1,7 @@ #dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' #dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' -dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', 'lsm', 'msl', 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', diff --git a/src/hirad/input_data/configs/era-all-201904.yaml b/src/hirad/input_data/configs/era-all-201904.yaml index 6a17a0ac..2eac8680 100755 --- a/src/hirad/input_data/configs/era-all-201904.yaml +++ b/src/hirad/input_data/configs/era-all-201904.yaml @@ -1,7 +1,7 @@ #dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' #dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' -dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', 'lsm', 'msl', 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', diff --git a/src/hirad/input_data/configs/era-all-201905.yaml b/src/hirad/input_data/configs/era-all-201905.yaml index a5ac343c..f0fd37a8 100755 --- a/src/hirad/input_data/configs/era-all-201905.yaml +++ b/src/hirad/input_data/configs/era-all-201905.yaml @@ -1,7 +1,7 @@ #dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' #dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' -dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', 'lsm', 'msl', 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', diff --git a/src/hirad/input_data/configs/era-all-201906.yaml b/src/hirad/input_data/configs/era-all-201906.yaml index 1dc7d618..698e6898 100755 --- a/src/hirad/input_data/configs/era-all-201906.yaml +++ b/src/hirad/input_data/configs/era-all-201906.yaml @@ -1,7 +1,7 @@ #dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' #dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' -dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', 'lsm', 'msl', 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', diff --git a/src/hirad/input_data/configs/era-all-201907.yaml b/src/hirad/input_data/configs/era-all-201907.yaml index 32b7de07..8a3430fd 100755 --- a/src/hirad/input_data/configs/era-all-201907.yaml +++ b/src/hirad/input_data/configs/era-all-201907.yaml @@ -1,7 +1,7 @@ #dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' #dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' -dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', 'lsm', 'msl', 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', diff --git a/src/hirad/input_data/configs/era-all-201908.yaml b/src/hirad/input_data/configs/era-all-201908.yaml index 4803ba56..54d5b4e6 100755 --- a/src/hirad/input_data/configs/era-all-201908.yaml +++ b/src/hirad/input_data/configs/era-all-201908.yaml @@ -1,7 +1,7 @@ #dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' #dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' -dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', 'lsm', 'msl', 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', diff --git a/src/hirad/input_data/configs/era-all-201909.yaml b/src/hirad/input_data/configs/era-all-201909.yaml index 1cd54366..48e004fa 100755 --- a/src/hirad/input_data/configs/era-all-201909.yaml +++ b/src/hirad/input_data/configs/era-all-201909.yaml @@ -1,7 +1,7 @@ #dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' #dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' -dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', 'lsm', 'msl', 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', diff --git a/src/hirad/input_data/configs/era-all-201910.yaml b/src/hirad/input_data/configs/era-all-201910.yaml index 44df24e0..f4c7d7f4 100755 --- a/src/hirad/input_data/configs/era-all-201910.yaml +++ b/src/hirad/input_data/configs/era-all-201910.yaml @@ -1,7 +1,7 @@ #dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' #dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' -dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', 'lsm', 'msl', 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', diff --git a/src/hirad/input_data/configs/era-all-201911.yaml b/src/hirad/input_data/configs/era-all-201911.yaml index 0615b2e6..4e6dab71 100755 --- a/src/hirad/input_data/configs/era-all-201911.yaml +++ b/src/hirad/input_data/configs/era-all-201911.yaml @@ -1,7 +1,7 @@ #dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' #dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' -dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', 'lsm', 'msl', 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', diff --git a/src/hirad/input_data/configs/era-all-201912.yaml b/src/hirad/input_data/configs/era-all-201912.yaml index 58fb5a66..e8072aff 100755 --- a/src/hirad/input_data/configs/era-all-201912.yaml +++ b/src/hirad/input_data/configs/era-all-201912.yaml @@ -1,7 +1,7 @@ #dataset: '/store_new/mch/msopr/hirad-gen/era5-1h-new/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' #dataset: '/scratch/mch/omiralle/anemoi/aifs-ea-an-oper-0001-mars-n320-2015-2020-1h-v1-with-ERA51.zarr' -dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr/' +dataset: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_time', 'cos_longitude', 'cp', 'insolation', 'lsm', 'msl', 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', diff --git a/src/hirad/input_data/copernicus.yaml b/src/hirad/input_data/copernicus.yaml new file mode 100644 index 00000000..b557f859 --- /dev/null +++ b/src/hirad/input_data/copernicus.yaml @@ -0,0 +1,6 @@ +path: '/capstor/store/mch/msopr/hirad-gen/copernicus-datasets/' +channels: ['cp', 'tp'] +#start: 2015-10-01 +start: 2020-01-01 +end: 2020-11-30 +frequency: 1 \ No newline at end of file diff --git a/src/hirad/input_data/cosmo-all.yaml b/src/hirad/input_data/cosmo-all.yaml index ebc19dd3..d807691f 100644 --- a/src/hirad/input_data/cosmo-all.yaml +++ b/src/hirad/input_data/cosmo-all.yaml @@ -14,7 +14,7 @@ select: ['10u', '10v', '2d', '2t', 'cos_julian_day', 'cos_latitude', 'cos_local_ # ALL COSMO CHANNELS. # Intersection between ERA and COSMO excludes hsurf, tcc, tqv trim_edge: 19 # Removes boundary -start: 2016-01-01 -# start: 2015-11-29 -end: 2016-02-29 -# end: 2020-12-31 \ No newline at end of file +#start: 2016-01-01 +start: 2015-10-01 +#end: 2016-01-01 +end: 2020-12-31 \ No newline at end of file diff --git a/src/hirad/interpolate-batches.sh b/src/hirad/interpolate-batches.sh index 62bba680..e95b82df 100644 --- a/src/hirad/interpolate-batches.sh +++ b/src/hirad/interpolate-batches.sh @@ -1,17 +1,17 @@ #!/bin/bash #clariden -#for cfgfile in $(ls src/hirad/input_data/configs/era-all-2020*.yaml); -#do -# cmd="sbatch -A a161 -t 12:00:00 -n 1 -c 1 --environment=modulus_env src/hirad/interpolate.sh ${cfgfile}" -# echo $cmd -# $cmd -#done - -# balfrin -for cfgfile in $(ls src/hirad/input_data/configs/era-all-2016*.yaml); +for cfgfile in $(ls src/hirad/input_data/configs/era-all-2020*.yaml); do - cmd="sbatch -p postproc -t 12:00:00 -n 1 -c 1 src/hirad/interpolate.sh ${cfgfile}" + cmd="sbatch -A a161 -t 12:00:00 -n 1 -c 1 --begin=now+18hour --environment=modulus_env src/hirad/interpolate.sh ${cfgfile}" echo $cmd $cmd done + +# balfrin +#for cfgfile in $(ls src/hirad/input_data/configs/era-all-2016*.yaml); +#do +# cmd="sbatch -p postproc -t 12:00:00 -n 1 -c 1 src/hirad/interpolate.sh ${cfgfile}" +# echo $cmd +# $cmd +#done diff --git a/src/hirad/interpolate.sh b/src/hirad/interpolate.sh index 8e9dcd11..f8c220b7 100755 --- a/src/hirad/interpolate.sh +++ b/src/hirad/interpolate.sh @@ -8,8 +8,8 @@ # pip install anemoi.datasets # python src/hirad/input_data/interpolate_basic.py src/hirad/input_data/era-all.yaml src/hirad/input_data/cosmo-all.yaml /capstor/scratch/cscs/mmcgloho/datasets/processed/era5-cosmo-1h-all-channels/ #" -#pip install -e . --no-dependencies -#pip install anemoi.datasets +pip install -e . --no-dependencies +pip install anemoi.datasets #pip install meteodata-lab #python src/hirad/input_data/interpolate_realch1.py \ # src/hirad/input_data/era.yaml \ @@ -21,8 +21,39 @@ # /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/info/cosmo-lat-lon \ # /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/ -python src/hirad/input_data/interpolate_basic.py \ - $1 \ - /store_new/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-all-channels/info/cosmo-lat-lon \ - /store_new/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-all-channels/ +#python src/hirad/input_data/process_copernicus_cosmo.py \ +# src/hirad/input_data/copernicus.yaml \ +# src/hirad/input_data/cosmo-all.yaml \ +# /capstor/scratch/cscs/mmcgloho/basic-numpy/copernicus-cosmo-1h + +#python src/hirad/input_data/process_era5_cosmo.py \ +# src/hirad/input_data/era-all.yaml \ +# src/hirad/input_data/cosmo-all.yaml \ +# /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-cosmo-1h-all-channels/ + +#python src/hirad/input_data/process_era5_cosmo.py \ +# src/hirad/input_data/configs/era-all-202006.yaml \ +# src/hirad/input_data/cosmo-all.yaml \ +# /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-cosmo-1h-all-channels/ + +python src/hirad/input_data/process_era5_with_copernicus.py \ + src/hirad/input_data/era-all.yaml \ + src/hirad/input_data/copernicus.yaml \ + /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/era-interpolated/ \ + /capstor/scratch/cscs/mmcgloho/basic-numpy/copernicus-cosmo-1h/copernicus-interpolated/ \ + /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-cosmo-1h-all-channels/ + +#python src/hirad/input_data/process_era5_with_copernicus.py \ +# src/hirad/input_data/era-all.yaml \ +# src/hirad/input_data/copernicus.yaml \ +# /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/eram-interpolated/ \ +# /capstor/scratch/cscs/mmcgloho/basic-numpy/copernicus-cosmo-1h/copernicus-interpolated/ \ +# /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-cosmo-1h-all-channels/ + + +# balfrin +#python src/hirad/input_data/interpolate_basic.py \ +# $1 \ +# /store_new/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-all-channels/info/cosmo-lat-lon \ +# /store_new/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-all-channels/ From 526a30ad9bb477ab740d757f02a5a7b26bdd75e1 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 5 Dec 2025 13:15:35 +0100 Subject: [PATCH 221/302] fix bug in channel indexing in dataset --- src/hirad/datasets/era5_cosmo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hirad/datasets/era5_cosmo.py b/src/hirad/datasets/era5_cosmo.py index 1db87168..81718a80 100644 --- a/src/hirad/datasets/era5_cosmo.py +++ b/src/hirad/datasets/era5_cosmo.py @@ -107,12 +107,12 @@ def __init__(self, self.output_inverse_transforms = {} for transform_descriptor in transform_channels: channel, transformation = transform_descriptor.split('-') - input_channel_idx = self._era_info['select'].index(channel) if channel in self._era_info['select'] else None - output_channel_idx = self._cosmo_info['select'].index(channel) if channel in self._cosmo_info['select'] else None + input_channel_idx = input_channel_names.index(channel) if channel in input_channel_names else None + output_channel_idx = output_channel_names.index(channel) if channel in output_channel_names else None if transformation.startswith('box_cox'): lmbda_str = transformation.split('_')[-1] lmbda = float(transformation.split('_')[-1])/(10**(len(lmbda_str)-1)) - print(f"Applying Box-Cox transformation with lambda={lmbda} to channel {channel} (input idx: {input_channel_idx}, output idx: {output_channel_idx})") + print(f"Applying Box-Cox transformation with lambda={lmbda} to channel {channel} (input idx: {input_channel_idx} ({input_channel_names[input_channel_idx]}), output idx: {output_channel_idx} ({output_channel_names[output_channel_idx]}))") if input_channel_idx is not None: self.input_transforms[input_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_transform(x, lmbda) self.input_inverse_transforms[input_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_inverse_transform(x, lmbda) From b7b6bcc3d4977a73d14c9a6b65a7e2a3bb60225e Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 8 Dec 2025 10:30:45 +0100 Subject: [PATCH 222/302] add dependencies (attempt) --- ci/docker/Dockerfile.python | 8 +++++++- ci/edf/python_env.toml | 7 +++++++ src/hirad/input_data/realch1.yaml | 10 ++++++---- 3 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 ci/edf/python_env.toml diff --git a/ci/docker/Dockerfile.python b/ci/docker/Dockerfile.python index 80ea1eb1..a62a5191 100644 --- a/ci/docker/Dockerfile.python +++ b/ci/docker/Dockerfile.python @@ -23,7 +23,13 @@ RUN pip install \ numpy \ pandas \ scipy \ - torch + torch + +RUN pip install \ + meteodata-lab + +RUN pip install \ + rasterio diff --git a/ci/edf/python_env.toml b/ci/edf/python_env.toml new file mode 100644 index 00000000..c8dded80 --- /dev/null +++ b/ci/edf/python_env.toml @@ -0,0 +1,7 @@ +image = "/capstor/scratch/cscs/mmcgloho/python-ubuntu.sqsh" + +mounts = ["/capstor", "/iopsstor", "/users"] + +# The initial directory in the container. +workdir = "${PWD}" + diff --git a/src/hirad/input_data/realch1.yaml b/src/hirad/input_data/realch1.yaml index c536704b..395ae080 100644 --- a/src/hirad/input_data/realch1.yaml +++ b/src/hirad/input_data/realch1.yaml @@ -1,4 +1,6 @@ -dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.2.zarr' -select: ['TD_2M', 'U_10M', 'V_10M', 'TOT_PREC_1H'] # TOT_PREC in v0.1 -#start: 2020-01-01 -#end: 2020-01-01 +dataset: '/capstor/store/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-v1.0.zarr' +#dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.2.zarr' +select: ['2t', '10u', '10v', 'tp'] +#select: ['TD_2M', 'U_10M', 'V_10M', 'TOT_PREC_1H'] # TOT_PREC in v0.1 +start: 2020-01-01 +end: 2020-01-01 From 64b09826786749c890a2ad170a3aa28765d5894a Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 9 Dec 2025 10:38:54 +0100 Subject: [PATCH 223/302] attempted fix --- ci/docker/Dockerfile.python | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ci/docker/Dockerfile.python b/ci/docker/Dockerfile.python index a62a5191..5565b8d1 100644 --- a/ci/docker/Dockerfile.python +++ b/ci/docker/Dockerfile.python @@ -7,7 +7,7 @@ FROM ubuntu:22.04 as builder # setup RUN apt-get update && apt-get install python3-pip python3-venv -y RUN pip install --upgrade \ - pip + pip #ninja #wheel #packaging @@ -25,11 +25,16 @@ RUN pip install \ scipy \ torch -RUN pip install \ - meteodata-lab +#RUN apt-get install libgdal-dev -y # Old version 3.4.1, needs 3.5 +#RUN apt-get install gdal-bin -y # Insufficient +RUN apt-get install curl -y +RUN apt-get install sudo -y +#RUN curl -sL "https://url.geocarpentry.org/gdal-ubuntu" | bash -RUN pip install \ - rasterio + +#RUN pip install \ +# rasterio==1.3.9 \ + # meteodata-lab[regrid] From 7310bdac65a348d19c78fb3b9b9524dbad4c209d Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 11 Dec 2025 09:54:24 +0100 Subject: [PATCH 224/302] regridding dependencies and bug fixes --- ci/docker/Dockerfile.python | 49 ++++++++++++++----- .../input_data/configs/realch1-2005-2009.yaml | 8 +++ src/hirad/input_data/realch1.yaml | 6 ++- src/hirad/input_data/regrid_realch1.py | 38 ++++++++------ src/hirad/interpolate.sh | 6 ++- 5 files changed, 75 insertions(+), 32 deletions(-) create mode 100644 src/hirad/input_data/configs/realch1-2005-2009.yaml diff --git a/ci/docker/Dockerfile.python b/ci/docker/Dockerfile.python index 5565b8d1..62284af5 100644 --- a/ci/docker/Dockerfile.python +++ b/ci/docker/Dockerfile.python @@ -1,13 +1,36 @@ # Following some suggestions in https://meteoswiss.atlassian.net/wiki/spaces/APN/pages/719684202/Clariden+Alps+environment+setup -FROM ubuntu:22.04 as builder +FROM ubuntu:24.04 as builder +# 24.04 needed for GDAL >=3.5 #FROM nvcr.io/nvidia/pytorch:25.01-py3 # setup RUN apt-get update && apt-get install python3-pip python3-venv -y -RUN pip install --upgrade \ - pip +RUN apt-get install -y \ + libgdal-dev \ + libnetcdff-dev \ + ecbuild \ + curl \ + wget \ + build-essential \ + cmake \ + gfortran\ + vim + +# Install eccodes from source, because we need version 2.38 +RUN wget https://github.com/ecmwf/eccodes/archive/refs/tags/2.38.0.tar.gz &&\ + tar -xzf 2.38.0.tar.gz &&\ + mkdir eccodes-2.38.0/build +WORKDIR eccodes-2.38.0/build +RUN cmake ../../eccodes-2.38.0 &&\ + make &&\ + ctest &&\ + make install +WORKDIR ../../ + +#RUN pip install --upgrade \ +# pip #ninja #wheel #packaging @@ -16,19 +39,19 @@ RUN pip install --upgrade \ # install the rest of dependencies # TODO: Factor pydeps into a separate file(s) # TODO: Add versions for things -RUN pip install \ - anemoi-datasets \ - cartopy \ - matplotlib \ - numpy \ - pandas \ - scipy \ - torch +#RUN pip install \ +# anemoi-datasets \ +# cartopy \ +# matplotlib \ +# numpy \ +# pandas \ +# scipy \ +# torch #RUN apt-get install libgdal-dev -y # Old version 3.4.1, needs 3.5 #RUN apt-get install gdal-bin -y # Insufficient -RUN apt-get install curl -y -RUN apt-get install sudo -y +#RUN apt-get install curl -y +#RUN apt-get install sudo -y #RUN curl -sL "https://url.geocarpentry.org/gdal-ubuntu" | bash diff --git a/src/hirad/input_data/configs/realch1-2005-2009.yaml b/src/hirad/input_data/configs/realch1-2005-2009.yaml new file mode 100644 index 00000000..fc112042 --- /dev/null +++ b/src/hirad/input_data/configs/realch1-2005-2009.yaml @@ -0,0 +1,8 @@ +#dataset: '/capstor/store/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' +#dataset: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-v1.0.zarr' +dataset: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' +#dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.2.zarr' +select: ['2t', '10u', '10v', 'tp'] +#select: ['TD_2M', 'U_10M', 'V_10M', 'TOT_PREC_1H'] # TOT_PREC in v0.1 +start: 2005-01-01 +end: 2009-12-31 diff --git a/src/hirad/input_data/realch1.yaml b/src/hirad/input_data/realch1.yaml index 395ae080..c9c02a02 100644 --- a/src/hirad/input_data/realch1.yaml +++ b/src/hirad/input_data/realch1.yaml @@ -1,6 +1,8 @@ -dataset: '/capstor/store/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-v1.0.zarr' +#dataset: '/capstor/store/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' +#dataset: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-v1.0.zarr' +dataset: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' #dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.2.zarr' select: ['2t', '10u', '10v', 'tp'] #select: ['TD_2M', 'U_10M', 'V_10M', 'TOT_PREC_1H'] # TOT_PREC in v0.1 start: 2020-01-01 -end: 2020-01-01 +end: 2020-01-10 diff --git a/src/hirad/input_data/regrid_realch1.py b/src/hirad/input_data/regrid_realch1.py index 13c63a3b..c7b95a4b 100644 --- a/src/hirad/input_data/regrid_realch1.py +++ b/src/hirad/input_data/regrid_realch1.py @@ -116,7 +116,7 @@ def interpolate_anemoi_range_to_rotlatlon(i_start: int, i_end: int, ds: Dataset, xarrays = anemoi_to_xarray(ds, i_start, i_end) for j in range(len(xarrays)): - logging.info(f'regridding {ds.variables[j]} for time {ds.dates[i_start]} to {ds.dates[i_end]}') + logging.info(f'regridding {ds.variables[j]} for time {ds.dates[i_start]} to {ds.dates[i_end-1]}') xarray = xarrays[j] start = datetime.datetime.now() regridded=regrid.icon2rotlatlon(xarray) @@ -124,19 +124,21 @@ def interpolate_anemoi_range_to_rotlatlon(i_start: int, i_end: int, ds: Dataset, logging.info(f' regridding took {end-start} seconds') torch_data[0:i_end-i_start,j,:,:] = regridded_to_numpy(regridded, trim_edge=TRIM_EDGE) - logging.info('saving torch data') - for k in range(torch_data.shape[0]): - interpolate_basic.save_datetime_file(torch_data[k,:], ds.dates[i_start + k], output_data_path, format=format) - if (i_start + k) in plot_indices: - for v in range(torch_data.shape[1]): - interpolate_basic.plot_and_save_projection(input_grid[:,0], input_grid[:,1], - ds[i_start+k,v,0,:], - os.path.join(output_plots_path, f'{ds.variables[v]}-iconnative.png'), - s=0.005) - interpolate_basic.plot_and_save_projection(output_grid[:,0], output_grid[:,1], - torch_data[k,v,0,:], - os.path.join(output_plots_path, f'{ds.variables[v]}-rotlatlon.png'), - s=0.005) + logging.info('saving torch data') + for k in range(torch_data.shape[0]): + interpolate_basic.save_datetime_file(torch_data[k,:], ds.dates[i_start + k], output_data_path, format=format) + if (i_start + k) in plot_indices: + logging.info(f'plotting {i_start+k}') + datestr = interpolate_basic.format_date(ds.dates[i_start+k]) + for v in range(torch_data.shape[1]): + interpolate_basic.plot_and_save_projection(input_grid[:,0], input_grid[:,1], + ds[i_start+k,v,0,:], + os.path.join(output_plots_path, f'{datestr}-{ds.variables[v]}-iconnative.png'), + s=0.005) + interpolate_basic.plot_and_save_projection(output_grid[:,0], output_grid[:,1], + torch_data[k,v,0,:], + os.path.join(output_plots_path, f'{datestr}-{ds.variables[v]}-rotlatlon.png'), + s=0.005) def interpolate_anemoi_to_rotlatlon(infile_anemoi: str, ds_name: str, output_grid: np.ndarray, output_path: str, format='torch', plot_indices=[0]): @@ -147,23 +149,29 @@ def interpolate_anemoi_to_rotlatlon(infile_anemoi: str, ds_name: str, output_gri realch1_config = yaml.safe_load(realch1_file) realch1 = open_dataset(realch1_config) variables = realch1.variables + input_grid = np.column_stack((realch1.longitudes, realch1.latitudes)) # Get the lat/lon info by regridding one variable xarrays = anemoi_to_xarray(realch1, 0, 1) regridded=regrid.icon2rotlatlon(xarrays[0]) logging.info('getting geo coords') lats, lons = get_geo_coords(regridded, trim_edge=TRIM_EDGE) + output_grid=np.column_stack((lons, lats)) # Save grid to file grid = np.column_stack((lats, lons)) torch.save(grid, os.path.join(output_path, 'info', 'realch1-lat-lon')) + output_data_path = os.path.join(output_path, ds_name) + output_plots_path = os.path.join(output_path, 'plots') + # Split regridding into batches; too many time points seems to not scale well. for i in range(0, len(realch1.dates), XARRAY_BATCH): start_index = i end_index = min(i+XARRAY_BATCH, len(realch1.dates)) + logging.info(f'start={start_index} end={end_index}') interpolate_anemoi_range_to_rotlatlon(start_index, end_index, realch1, ds_name, - None, None, output_path, format, None, plot_indices) + input_grid, output_grid, output_data_path, format, output_plots_path, plot_indices) def main(): diff --git a/src/hirad/interpolate.sh b/src/hirad/interpolate.sh index f8c220b7..8dff46dc 100755 --- a/src/hirad/interpolate.sh +++ b/src/hirad/interpolate.sh @@ -2,14 +2,16 @@ #SBATCH --time=12:00:00 +source /users/mmcgloho/interpolate-env-ssp/bin/activate +python src/hirad/input_data/regrid_realch1.py src/hirad/input_data/configs/realch1-2005-2009.yaml /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/ #srun -A a161 -t 12:00:00 --environment=modulus_env bash -c " # pip install -e . --no-dependencies # pip install anemoi.datasets # python src/hirad/input_data/interpolate_basic.py src/hirad/input_data/era-all.yaml src/hirad/input_data/cosmo-all.yaml /capstor/scratch/cscs/mmcgloho/datasets/processed/era5-cosmo-1h-all-channels/ #" -pip install -e . --no-dependencies -pip install anemoi.datasets +#pip install -e . --no-dependencies +#pip install anemoi.datasets #pip install meteodata-lab #python src/hirad/input_data/interpolate_realch1.py \ # src/hirad/input_data/era.yaml \ From fb7bc9e5817388b5b87612b24bc341a958bcaaa2 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Tue, 16 Dec 2025 16:49:13 +0100 Subject: [PATCH 225/302] stash some stuff --- src/hirad/input_data/check-fileload.py | 19 +++++++++++++++++++ .../input_data/configs/realch1-2009-2011.yaml | 8 ++++++++ .../input_data/configs/realch1-2012-2014.yaml | 8 ++++++++ .../input_data/configs/realch1-2015-2017.yaml | 8 ++++++++ .../input_data/configs/realch1-2018-2020.yaml | 8 ++++++++ .../input_data/configs/realch1-2021-2024.yaml | 8 ++++++++ .../input_data/process_torch_to_numpy.py | 14 +++++++++----- src/hirad/interpolate-reprocess.sh | 10 ++++++++++ src/hirad/interpolate.sh | 3 ++- src/hirad/rsync.sh | 6 ++++++ 10 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 src/hirad/input_data/check-fileload.py create mode 100644 src/hirad/input_data/configs/realch1-2009-2011.yaml create mode 100644 src/hirad/input_data/configs/realch1-2012-2014.yaml create mode 100644 src/hirad/input_data/configs/realch1-2015-2017.yaml create mode 100644 src/hirad/input_data/configs/realch1-2018-2020.yaml create mode 100644 src/hirad/input_data/configs/realch1-2021-2024.yaml create mode 100644 src/hirad/interpolate-reprocess.sh create mode 100644 src/hirad/rsync.sh diff --git a/src/hirad/input_data/check-fileload.py b/src/hirad/input_data/check-fileload.py new file mode 100644 index 00000000..ff78b70d --- /dev/null +++ b/src/hirad/input_data/check-fileload.py @@ -0,0 +1,19 @@ +import os +import sys +import numpy as np + +dir = sys.argv[1] +files = os.listdir(dir) +data = np.load(dir + files[0]) +shape = data.shape +for i in range(len(files)): + if i % 10000 == 0: + print(i) + try: + data = np.load(dir + files[i]) + except: + print(f'{files[i]} does not load') + if data.shape != shape: + print(f'{files[i]} has shape {data.shape}') + + \ No newline at end of file diff --git a/src/hirad/input_data/configs/realch1-2009-2011.yaml b/src/hirad/input_data/configs/realch1-2009-2011.yaml new file mode 100644 index 00000000..4e59a48f --- /dev/null +++ b/src/hirad/input_data/configs/realch1-2009-2011.yaml @@ -0,0 +1,8 @@ +#dataset: '/capstor/store/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' +#dataset: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-v1.0.zarr' +dataset: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' +#dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.2.zarr' +select: ['2t', '10u', '10v', 'tp'] +#select: ['TD_2M', 'U_10M', 'V_10M', 'TOT_PREC_1H'] # TOT_PREC in v0.1 +start: 2009-01-01 +end: 2011-12-31 diff --git a/src/hirad/input_data/configs/realch1-2012-2014.yaml b/src/hirad/input_data/configs/realch1-2012-2014.yaml new file mode 100644 index 00000000..f7da639d --- /dev/null +++ b/src/hirad/input_data/configs/realch1-2012-2014.yaml @@ -0,0 +1,8 @@ +#dataset: '/capstor/store/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' +#dataset: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-v1.0.zarr' +dataset: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' +#dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.2.zarr' +select: ['2t', '10u', '10v', 'tp'] +#select: ['TD_2M', 'U_10M', 'V_10M', 'TOT_PREC_1H'] # TOT_PREC in v0.1 +start: 2012-01-01 +end: 2014-12-31 diff --git a/src/hirad/input_data/configs/realch1-2015-2017.yaml b/src/hirad/input_data/configs/realch1-2015-2017.yaml new file mode 100644 index 00000000..cc7885cf --- /dev/null +++ b/src/hirad/input_data/configs/realch1-2015-2017.yaml @@ -0,0 +1,8 @@ +#dataset: '/capstor/store/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' +#dataset: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-v1.0.zarr' +dataset: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' +#dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.2.zarr' +select: ['2t', '10u', '10v', 'tp'] +#select: ['TD_2M', 'U_10M', 'V_10M', 'TOT_PREC_1H'] # TOT_PREC in v0.1 +start: 2015-01-01 +end: 2017-12-31 diff --git a/src/hirad/input_data/configs/realch1-2018-2020.yaml b/src/hirad/input_data/configs/realch1-2018-2020.yaml new file mode 100644 index 00000000..13c967d8 --- /dev/null +++ b/src/hirad/input_data/configs/realch1-2018-2020.yaml @@ -0,0 +1,8 @@ +#dataset: '/capstor/store/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' +#dataset: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-v1.0.zarr' +dataset: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' +#dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.2.zarr' +select: ['2t', '10u', '10v', 'tp'] +#select: ['TD_2M', 'U_10M', 'V_10M', 'TOT_PREC_1H'] # TOT_PREC in v0.1 +start: 2018-01-01 +end: 2020-12-31 diff --git a/src/hirad/input_data/configs/realch1-2021-2024.yaml b/src/hirad/input_data/configs/realch1-2021-2024.yaml new file mode 100644 index 00000000..4788a998 --- /dev/null +++ b/src/hirad/input_data/configs/realch1-2021-2024.yaml @@ -0,0 +1,8 @@ +#dataset: '/capstor/store/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' +#dataset: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-v1.0.zarr' +dataset: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' +#dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.2.zarr' +select: ['2t', '10u', '10v', 'tp'] +#select: ['TD_2M', 'U_10M', 'V_10M', 'TOT_PREC_1H'] # TOT_PREC in v0.1 +start: 2021-01-01 +end: 2024-12-31 diff --git a/src/hirad/input_data/process_torch_to_numpy.py b/src/hirad/input_data/process_torch_to_numpy.py index e61a18ba..3a83a64a 100644 --- a/src/hirad/input_data/process_torch_to_numpy.py +++ b/src/hirad/input_data/process_torch_to_numpy.py @@ -10,13 +10,17 @@ def main(): input_dir = sys.argv[1] output_dir = sys.argv[2] in_files = os.listdir(input_dir) + out_files = os.listdir(output_dir) in_files.sort() - for i in range(10): + for i in range(len(in_files)): + if i % 1000 == 0: + print(i) f = in_files[i] - print(f) - #for f in in_files: - data = torch.load(os.path.join(input_dir, f), weights_only=False) - np.save(os.path.join(output_dir, f), data) + #for f in in_files + if not (f + '.npy') in out_files: + print(f) + data = torch.load(os.path.join(input_dir, f), weights_only=False) + np.save(os.path.join(output_dir, f), data) if __name__ == "__main__": main() diff --git a/src/hirad/interpolate-reprocess.sh b/src/hirad/interpolate-reprocess.sh new file mode 100644 index 00000000..054e7397 --- /dev/null +++ b/src/hirad/interpolate-reprocess.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +#SBATCH --time=12:00:00 + +echo 'activating env' +source /users/mmcgloho/interpolate-env-ssp/bin/activate +echo 'running' +#python src/hirad/input_data/process_torch_to_numpy.py /store_new/mch/msopr/hirad-gen/basic-torch/era5-realch1/v1.0/era-copernicus-interpolated/ /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/era-copernicus-interpolated/ +#python src/hirad/input_data/process_torch_to_numpy.py /capstor/scratch/cscs/mmcgloho/basic-torch/era5-realch1/v1.0/era-copernicus-interpolated/ /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/era-copernicus-interpolated/ +python src/hirad/input_data/check-fileload.py /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/realch1/ \ No newline at end of file diff --git a/src/hirad/interpolate.sh b/src/hirad/interpolate.sh index 8dff46dc..4016e9ac 100755 --- a/src/hirad/interpolate.sh +++ b/src/hirad/interpolate.sh @@ -3,7 +3,8 @@ #SBATCH --time=12:00:00 source /users/mmcgloho/interpolate-env-ssp/bin/activate -python src/hirad/input_data/regrid_realch1.py src/hirad/input_data/configs/realch1-2005-2009.yaml /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/ +python src/hirad/input_data/regrid_realch1.py src/hirad/input_data/configs/realch1-2021-2024.yaml /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/ + #srun -A a161 -t 12:00:00 --environment=modulus_env bash -c " # pip install -e . --no-dependencies diff --git a/src/hirad/rsync.sh b/src/hirad/rsync.sh new file mode 100644 index 00000000..a5828794 --- /dev/null +++ b/src/hirad/rsync.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +#SBATCH --time=12:00:00 +#SBATCH --partition=xfer + +rsync -av /iopsstor/scratch/cscs/mmcgloho/basic-numpy /capstor/store/cscs/pasc/c38/basic-numpy \ No newline at end of file From 37aa3fafa7e3dd2e4b02fa1bd8d0605b47365f1a Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 17 Dec 2025 14:24:06 +0100 Subject: [PATCH 226/302] add static and stats calc to ralch1 --- src/hirad/input_data/configs/realch1-static.yaml | 8 ++++++++ src/hirad/input_data/regrid_realch1.py | 3 +++ src/hirad/interpolate.sh | 14 +++++++------- 3 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 src/hirad/input_data/configs/realch1-static.yaml diff --git a/src/hirad/input_data/configs/realch1-static.yaml b/src/hirad/input_data/configs/realch1-static.yaml new file mode 100644 index 00000000..55f42368 --- /dev/null +++ b/src/hirad/input_data/configs/realch1-static.yaml @@ -0,0 +1,8 @@ + +dataset: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' +#dataset: '/scratch/mch/fzanetta/data/anemoi/datasets/mch-realch1-fdb-1km-2020-2020-1h-pl13-v0.2.zarr' +select: ['z'] +#select: ['TD_2M', 'U_10M', 'V_10M', 'TOT_PREC_1H'] # TOT_PREC in v0.1 +start: 2021-01-01 +end: 2021-01-01 +# date is irrelevant as we only need one time point diff --git a/src/hirad/input_data/regrid_realch1.py b/src/hirad/input_data/regrid_realch1.py index c7b95a4b..b1d873de 100644 --- a/src/hirad/input_data/regrid_realch1.py +++ b/src/hirad/input_data/regrid_realch1.py @@ -162,6 +162,9 @@ def interpolate_anemoi_to_rotlatlon(infile_anemoi: str, ds_name: str, output_gri grid = np.column_stack((lats, lons)) torch.save(grid, os.path.join(output_path, 'info', 'realch1-lat-lon')) + # Save stats + interpolate_basic.save_anemoi_stats(realch1, os.path.join(output_path, f'info/{ds_name}-stats')) + output_data_path = os.path.join(output_path, ds_name) output_plots_path = os.path.join(output_path, 'plots') diff --git a/src/hirad/interpolate.sh b/src/hirad/interpolate.sh index 4016e9ac..9210c5d2 100755 --- a/src/hirad/interpolate.sh +++ b/src/hirad/interpolate.sh @@ -3,7 +3,7 @@ #SBATCH --time=12:00:00 source /users/mmcgloho/interpolate-env-ssp/bin/activate -python src/hirad/input_data/regrid_realch1.py src/hirad/input_data/configs/realch1-2021-2024.yaml /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/ +python src/hirad/input_data/regrid_realch1.py src/hirad/input_data/configs/realch1-static.yaml /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/static #srun -A a161 -t 12:00:00 --environment=modulus_env bash -c " @@ -39,12 +39,12 @@ python src/hirad/input_data/regrid_realch1.py src/hirad/input_data/configs/realc # src/hirad/input_data/cosmo-all.yaml \ # /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-cosmo-1h-all-channels/ -python src/hirad/input_data/process_era5_with_copernicus.py \ - src/hirad/input_data/era-all.yaml \ - src/hirad/input_data/copernicus.yaml \ - /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/era-interpolated/ \ - /capstor/scratch/cscs/mmcgloho/basic-numpy/copernicus-cosmo-1h/copernicus-interpolated/ \ - /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-cosmo-1h-all-channels/ +#python src/hirad/input_data/process_era5_with_copernicus.py \ + # src/hirad/input_data/era-all.yaml \ + # src/hirad/input_data/copernicus.yaml \ + # /capstor/scratch/cscs/mmcgloho/basic-torch/era5-cosmo-1h-all-channels/era-interpolated/ \ + # /capstor/scratch/cscs/mmcgloho/basic-numpy/copernicus-cosmo-1h/copernicus-interpolated/ \ + # /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-cosmo-1h-all-channels/ #python src/hirad/input_data/process_era5_with_copernicus.py \ # src/hirad/input_data/era-all.yaml \ From 836f223189ac16184c8c95248c74bb07d2ccf170 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 17 Dec 2025 14:34:00 +0100 Subject: [PATCH 227/302] reorg files --- src/hirad/input_data/{ => configs}/copernicus.yaml | 0 src/hirad/input_data/{ => configs}/cosmo-all.yaml | 0 src/hirad/input_data/{ => configs}/cosmo-static.yaml | 0 src/hirad/input_data/{ => configs}/cosmo.yaml | 0 src/hirad/input_data/{ => configs}/era-all.yaml | 0 src/hirad/input_data/{ => configs}/era.yaml | 0 src/hirad/input_data/{ => configs}/realch1-all.yaml | 0 src/hirad/input_data/{ => configs}/realch1.yaml | 0 src/hirad/input_data/{ => scripts}/copernicus-tp.sh | 0 src/hirad/input_data/{ => scripts}/copyanemoi.sh | 0 src/hirad/{ => input_data/scripts}/fix-files.sh | 0 src/hirad/{ => input_data/scripts}/interpolate-batches.sh | 0 src/hirad/{ => input_data/scripts}/interpolate-reprocess.sh | 0 src/hirad/{ => input_data/scripts}/interpolate.sh | 0 src/hirad/input_data/{ => scripts}/regrid_copernicus_tp.sh | 0 src/hirad/{ => input_data/scripts}/rsync.sh | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename src/hirad/input_data/{ => configs}/copernicus.yaml (100%) rename src/hirad/input_data/{ => configs}/cosmo-all.yaml (100%) rename src/hirad/input_data/{ => configs}/cosmo-static.yaml (100%) rename src/hirad/input_data/{ => configs}/cosmo.yaml (100%) rename src/hirad/input_data/{ => configs}/era-all.yaml (100%) rename src/hirad/input_data/{ => configs}/era.yaml (100%) rename src/hirad/input_data/{ => configs}/realch1-all.yaml (100%) rename src/hirad/input_data/{ => configs}/realch1.yaml (100%) rename src/hirad/input_data/{ => scripts}/copernicus-tp.sh (100%) rename src/hirad/input_data/{ => scripts}/copyanemoi.sh (100%) rename src/hirad/{ => input_data/scripts}/fix-files.sh (100%) rename src/hirad/{ => input_data/scripts}/interpolate-batches.sh (100%) rename src/hirad/{ => input_data/scripts}/interpolate-reprocess.sh (100%) rename src/hirad/{ => input_data/scripts}/interpolate.sh (100%) rename src/hirad/input_data/{ => scripts}/regrid_copernicus_tp.sh (100%) rename src/hirad/{ => input_data/scripts}/rsync.sh (100%) diff --git a/src/hirad/input_data/copernicus.yaml b/src/hirad/input_data/configs/copernicus.yaml similarity index 100% rename from src/hirad/input_data/copernicus.yaml rename to src/hirad/input_data/configs/copernicus.yaml diff --git a/src/hirad/input_data/cosmo-all.yaml b/src/hirad/input_data/configs/cosmo-all.yaml similarity index 100% rename from src/hirad/input_data/cosmo-all.yaml rename to src/hirad/input_data/configs/cosmo-all.yaml diff --git a/src/hirad/input_data/cosmo-static.yaml b/src/hirad/input_data/configs/cosmo-static.yaml similarity index 100% rename from src/hirad/input_data/cosmo-static.yaml rename to src/hirad/input_data/configs/cosmo-static.yaml diff --git a/src/hirad/input_data/cosmo.yaml b/src/hirad/input_data/configs/cosmo.yaml similarity index 100% rename from src/hirad/input_data/cosmo.yaml rename to src/hirad/input_data/configs/cosmo.yaml diff --git a/src/hirad/input_data/era-all.yaml b/src/hirad/input_data/configs/era-all.yaml similarity index 100% rename from src/hirad/input_data/era-all.yaml rename to src/hirad/input_data/configs/era-all.yaml diff --git a/src/hirad/input_data/era.yaml b/src/hirad/input_data/configs/era.yaml similarity index 100% rename from src/hirad/input_data/era.yaml rename to src/hirad/input_data/configs/era.yaml diff --git a/src/hirad/input_data/realch1-all.yaml b/src/hirad/input_data/configs/realch1-all.yaml similarity index 100% rename from src/hirad/input_data/realch1-all.yaml rename to src/hirad/input_data/configs/realch1-all.yaml diff --git a/src/hirad/input_data/realch1.yaml b/src/hirad/input_data/configs/realch1.yaml similarity index 100% rename from src/hirad/input_data/realch1.yaml rename to src/hirad/input_data/configs/realch1.yaml diff --git a/src/hirad/input_data/copernicus-tp.sh b/src/hirad/input_data/scripts/copernicus-tp.sh similarity index 100% rename from src/hirad/input_data/copernicus-tp.sh rename to src/hirad/input_data/scripts/copernicus-tp.sh diff --git a/src/hirad/input_data/copyanemoi.sh b/src/hirad/input_data/scripts/copyanemoi.sh similarity index 100% rename from src/hirad/input_data/copyanemoi.sh rename to src/hirad/input_data/scripts/copyanemoi.sh diff --git a/src/hirad/fix-files.sh b/src/hirad/input_data/scripts/fix-files.sh similarity index 100% rename from src/hirad/fix-files.sh rename to src/hirad/input_data/scripts/fix-files.sh diff --git a/src/hirad/interpolate-batches.sh b/src/hirad/input_data/scripts/interpolate-batches.sh similarity index 100% rename from src/hirad/interpolate-batches.sh rename to src/hirad/input_data/scripts/interpolate-batches.sh diff --git a/src/hirad/interpolate-reprocess.sh b/src/hirad/input_data/scripts/interpolate-reprocess.sh similarity index 100% rename from src/hirad/interpolate-reprocess.sh rename to src/hirad/input_data/scripts/interpolate-reprocess.sh diff --git a/src/hirad/interpolate.sh b/src/hirad/input_data/scripts/interpolate.sh similarity index 100% rename from src/hirad/interpolate.sh rename to src/hirad/input_data/scripts/interpolate.sh diff --git a/src/hirad/input_data/regrid_copernicus_tp.sh b/src/hirad/input_data/scripts/regrid_copernicus_tp.sh similarity index 100% rename from src/hirad/input_data/regrid_copernicus_tp.sh rename to src/hirad/input_data/scripts/regrid_copernicus_tp.sh diff --git a/src/hirad/rsync.sh b/src/hirad/input_data/scripts/rsync.sh similarity index 100% rename from src/hirad/rsync.sh rename to src/hirad/input_data/scripts/rsync.sh From 6f1b0edd76834a6c6bac4a3b6075ce4501b00cbf Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 19 Dec 2025 15:42:32 +0100 Subject: [PATCH 228/302] adapt snapshots to be able to select channels to plot --- src/hirad/eval/snapshots.py | 129 ++++++++++++++++++++++++------------ 1 file changed, 88 insertions(+), 41 deletions(-) diff --git a/src/hirad/eval/snapshots.py b/src/hirad/eval/snapshots.py index f6c7892b..15c9c203 100644 --- a/src/hirad/eval/snapshots.py +++ b/src/hirad/eval/snapshots.py @@ -129,10 +129,22 @@ def main(cfg: DictConfig) -> None: times = cfg.generation.times dataset_cfg = OmegaConf.to_container(cfg.dataset) + plot_channels = cfg.dataset.get("plot_channels", None) + if plot_channels is not None: + del dataset_cfg["plot_channels"] + plot_channels = [ChannelMetadata(name) if len(name.split('_'))==1 else ChannelMetadata(name.split('_')[0],name.split('_')[1]) for name in plot_channels] has_lead_time = cfg.generation.get("has_lead_time", False) dataset, sampler = get_dataset_and_sampler_inference( dataset_cfg=dataset_cfg, times=times, has_lead_time=has_lead_time ) + input_channels = dataset.input_channels() + output_channels = dataset.output_channels() + + input_channel_indices = [] + output_channel_indices = [] + for channel in plot_channels or []: + input_channel_indices.append(input_channels.index(channel) if channel in input_channels else -1) + output_channel_indices.append(output_channels.index(channel) if channel in output_channels else -1) output_path = getattr(cfg.generation.io, "output_path", "./outputs") files = FileRepository(output_path) @@ -146,91 +158,110 @@ def main(cfg: DictConfig) -> None: except: mean_pred = None - input_channels = dataset.input_channels() - output_channels = dataset.output_channels() - output_to_input_channel_map = map_output_to_input_channels(output_channels, input_channels) + # output_to_input_channel_map = map_output_to_input_channels(output_channels, input_channels) - for idx, channel in enumerate(output_channels): - input_channel_idx = output_to_input_channel_map[idx] - plot_title = f"{format_time_str(curr_time)}: {getattr(channel, 'title', channel.name)}" + for idx, channel in enumerate(plot_channels): + # input_channel_idx = output_to_input_channel_map[idx] + input_channel_idx = input_channel_indices[idx] + output_channel_idx = output_channel_indices[idx] + + # TODO Implement that it plots just output or just input channel if the other is missing + if input_channel_idx == -1 or output_channel_idx == -1: + logger.warning(f"Channel {channel.name} not found in input or output channels. Skipping.") + continue + + plot_title = f"{format_time_str(curr_time)}: {getattr(channel, 'title', channel.name if channel.level == '' else f'{channel.name}_{channel.level}')}" vmin, vmax = calculate_bounds( - target[idx,:,:], - prediction[:,idx,:,:], - baseline[input_channel_idx,:,:], - mean_pred[idx,:,:] if mean_pred is not None else None + target[output_channel_idx,:,:], + prediction[:,output_channel_idx,:,:] if prediction.ndim>3 else prediction[idx,:,:], + baseline[input_channel_idx,:,:] if not channel.name == "tp" else None, + mean_pred[output_channel_idx,:,:] if mean_pred is not None else None ) metadata = ChannelMeta.get(channel, vmin=vmin, vmax=vmax) if channel.name == "tp": save_field( - "target", target[idx, :, :], metadata, files, channel, curr_time, + "target", target[output_channel_idx, :, :], metadata, files, channel, curr_time, plot_func=plot_map_precipitation, title=plot_title ) save_field( "baseline", baseline[input_channel_idx, :, :], metadata, files, channel, curr_time, plot_func=plot_map_precipitation, title=plot_title ) - if prediction.shape[0] > 1: + if prediction.ndim>3: for member_idx in range(prediction.shape[0]): save_field( - "prediction", prediction[member_idx, idx, :, :], metadata, files, channel, curr_time, + "prediction", prediction[member_idx, output_channel_idx, :, :], metadata, files, channel, curr_time, member=member_idx, plot_func=plot_map_precipitation, title=plot_title ) else: save_field( - "prediction", prediction[0, idx, :, :], metadata, files, channel, curr_time, + "prediction", prediction[output_channel_idx, :, :], metadata, files, channel, curr_time, plot_func=plot_map_precipitation, title=plot_title ) if mean_pred is not None: save_field( - "mean-prediction", mean_pred[idx, :, :], metadata, files, channel, curr_time, + "mean-prediction", mean_pred[output_channel_idx, :, :], metadata, files, channel, curr_time, plot_func=plot_map_precipitation, title=plot_title ) continue # Plot target and baseline and regression prediction if available - save_field("target", target[idx, :, :], metadata, files, channel, curr_time, title=plot_title) + save_field("target", target[output_channel_idx, :, :], metadata, files, channel, curr_time, title=plot_title) save_field("baseline", baseline[input_channel_idx, :, :], metadata, files, channel, curr_time, title=plot_title) if mean_pred is not None: - save_field("mean-prediction", mean_pred[idx, :, :], metadata, files, channel, curr_time, title=plot_title) + save_field("mean-prediction", mean_pred[output_channel_idx, :, :], metadata, files, channel, curr_time, title=plot_title) # Baseline MAE and ME - _, baseline_mae = compute_mae(baseline[input_channel_idx, :, :], target[idx, :, :]) - baseline_me = (baseline[input_channel_idx, :, :] - target[idx, :, :]) + _, baseline_mae = compute_mae(baseline[input_channel_idx, :, :], target[output_channel_idx, :, :]) + baseline_me = (baseline[input_channel_idx, :, :] - target[output_channel_idx, :, :]) save_field("baseline", baseline_mae.reshape(baseline[input_channel_idx, :, :].shape), metadata, files, channel, curr_time, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title) save_field("baseline", baseline_me, metadata, files, channel, curr_time, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title) # Regression prediction MAE and ME if mean_pred is not None: - _, mean_mae = compute_mae(mean_pred[idx, :, :], target[idx, :, :]) - mean_me = (mean_pred[idx, :, :] - target[idx, :, :]) - save_field("mean-prediction", mean_mae.reshape(mean_pred[idx, :, :].shape), metadata, files, channel, curr_time, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title) + _, mean_mae = compute_mae(mean_pred[idx, :, :], target[output_channel_idx, :, :]) + mean_me = (mean_pred[output_channel_idx, :, :] - target[output_channel_idx, :, :]) + save_field("mean-prediction", mean_mae.reshape(mean_pred[output_channel_idx, :, :].shape), metadata, files, channel, curr_time, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title) save_field("mean-prediction", mean_me, metadata, files, channel, curr_time, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title) # Ensemble predictions - for member_idx in range(prediction.shape[0]): - member = prediction[member_idx, idx, :, :] - save_field("prediction", member, metadata, files, channel, curr_time, member=member_idx, title=plot_title) - _, prediction_mae = compute_mae(member, target[idx, :, :]) - save_field("prediction", prediction_mae.reshape(member.shape), metadata, files, channel, curr_time, member=member_idx, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title) - prediction_me = (member - target[idx, :, :]) - save_field("prediction", prediction_me, metadata, files, channel, curr_time, member=member_idx, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title) + if prediction.ndim > 3: + for member_idx in range(prediction.shape[0]): + member = prediction[member_idx, output_channel_idx, :, :] + save_field("prediction", member, metadata, files, channel, curr_time, member=member_idx, title=plot_title) + _, prediction_mae = compute_mae(member, target[output_channel_idx, :, :]) + save_field("prediction", prediction_mae.reshape(member.shape), metadata, files, channel, curr_time, member=member_idx, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title) + prediction_me = (member - target[output_channel_idx, :, :]) + save_field("prediction", prediction_me, metadata, files, channel, curr_time, member=member_idx, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title) + else: + member = prediction[output_channel_idx, :, :] + save_field("prediction", member, metadata, files, channel, curr_time, title=plot_title) + _, prediction_mae = compute_mae(member, target[output_channel_idx, :, :]) + save_field("prediction", prediction_mae.reshape(member.shape), metadata, files, channel, curr_time, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title) + prediction_me = (member - target[output_channel_idx, :, :]) + save_field("prediction", prediction_me, metadata, files, channel, curr_time, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title) # Plot Windspeed and direction wind_channels = {ch.name: idx for idx, ch in enumerate(output_channels) if ch.name in ("10u", "10v")} + wind_channels_input = {ch.name: idx for idx, ch in enumerate(input_channels) if ch.name in ("10u", "10v")} if "10u" in wind_channels and "10v" in wind_channels: idx_10u = wind_channels["10u"] idx_10v = wind_channels["10v"] - input_idx_10u = output_to_input_channel_map[idx_10u] - input_idx_10v = output_to_input_channel_map[idx_10v] + input_idx_10u = wind_channels_input["10u"] + input_idx_10v = wind_channels_input["10v"] # Compute windspeed and direction for target, baseline, prediction and mean prediction target_wind_speed = np.hypot(target[idx_10u, :, :], target[idx_10v, :, :]) target_wind_dir = wind_direction(target[idx_10u, :, :], target[idx_10v, :, :]) baseline_wind_speed = np.hypot(baseline[input_idx_10u, :, :], baseline[input_idx_10v, :, :]) baseline_wind_dir = wind_direction(baseline[input_idx_10u, :, :], baseline[input_idx_10v, :, :]) - prediction_wind_speed = np.hypot(prediction[:, idx_10u, :, :], prediction[:, idx_10v, :, :]) - prediction_wind_dir = wind_direction(prediction[:, idx_10u, :, :], prediction[:, idx_10v, :, :]) + if prediction.ndim > 3: + prediction_wind_speed = np.hypot(prediction[:, idx_10u, :, :], prediction[:, idx_10v, :, :]) + prediction_wind_dir = wind_direction(prediction[:, idx_10u, :, :], prediction[:, idx_10v, :, :]) + else: + prediction_wind_speed = np.hypot(prediction[idx_10u, :, :], prediction[idx_10v, :, :]) + prediction_wind_dir = wind_direction(prediction[idx_10u, :, :], prediction[idx_10v, :, :]) if mean_pred is not None: mean_wind_speed = np.hypot(mean_pred[idx_10u, :, :], mean_pred[idx_10v, :, :]) mean_wind_dir = wind_direction(mean_pred[idx_10u, :, :], mean_pred[idx_10v, :, :]) @@ -254,11 +285,19 @@ def main(cfg: DictConfig) -> None: custom_path=files.wind_file("FF10m", curr_time, "FF10m-baseline"), plot_func=plot_map, title=plot_title_speed ) - for member_idx in range(prediction.shape[0]): + if prediction.ndim > 3: + for member_idx in range(prediction.shape[0]): + save_field( + "FF10m-prediction", prediction_wind_speed[member_idx], wind_meta, files, None, curr_time, + member=member_idx, cmap="viridis", vmin=0, vmax=10, extend='max', + custom_path=files.wind_file("FF10m", curr_time, "FF10m-prediction", member_idx), + plot_func=plot_map, title=plot_title_speed + ) + else: save_field( - "FF10m-prediction", prediction_wind_speed[member_idx], wind_meta, files, None, curr_time, - member=member_idx, cmap="viridis", vmin=0, vmax=10, extend='max', - custom_path=files.wind_file("FF10m", curr_time, "FF10m-prediction", member_idx), + "FF10m-prediction", prediction_wind_speed, wind_meta, files, None, curr_time, + cmap="viridis", vmin=0, vmax=10, extend='max', + custom_path=files.wind_file("FF10m", curr_time, "FF10m-prediction"), plot_func=plot_map, title=plot_title_speed ) if mean_pred is not None: @@ -282,11 +321,19 @@ def main(cfg: DictConfig) -> None: custom_path=files.wind_file("DD10m", curr_time, "DD10m-baseline"), plot_func=plot_map, title=plot_title_dir ) - for member_idx in range(prediction.shape[0]): + if prediction.ndim > 3: + for member_idx in range(prediction.shape[0]): + save_field( + "DD10m-prediction", prediction_wind_dir[member_idx], dir_meta, files, None, curr_time, + member=member_idx, cmap="twilight", vmin=0, vmax=360, + custom_path=files.wind_file("DD10m", curr_time, "DD10m-prediction", member_idx), + plot_func=plot_map, title=plot_title_dir + ) + else: save_field( - "DD10m-prediction", prediction_wind_dir[member_idx], dir_meta, files, None, curr_time, - member=member_idx, cmap="twilight", vmin=0, vmax=360, - custom_path=files.wind_file("DD10m", curr_time, "DD10m-prediction", member_idx), + "DD10m-prediction", prediction_wind_dir, dir_meta, files, None, curr_time, + cmap="twilight", vmin=0, vmax=360, + custom_path=files.wind_file("DD10m", curr_time, "DD10m-prediction"), plot_func=plot_map, title=plot_title_dir ) if mean_pred is not None: From 42eed9036c92229f113ee12ec6a90cfea38b3c9c Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 19 Dec 2025 15:45:31 +0100 Subject: [PATCH 229/302] fix bug in snapshots --- src/hirad/eval/snapshots.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/hirad/eval/snapshots.py b/src/hirad/eval/snapshots.py index 15c9c203..649730b0 100644 --- a/src/hirad/eval/snapshots.py +++ b/src/hirad/eval/snapshots.py @@ -9,7 +9,7 @@ import torch from omegaconf import DictConfig, OmegaConf -from hirad.datasets import get_dataset_and_sampler_inference +from hirad.datasets import get_dataset_and_sampler_inference, get_channels_from_strings from hirad.distributed import DistributedManager from hirad.eval import compute_mae, plot_map from hirad.eval.plotting import plot_map_precipitation, wind_direction @@ -129,14 +129,18 @@ def main(cfg: DictConfig) -> None: times = cfg.generation.times dataset_cfg = OmegaConf.to_container(cfg.dataset) - plot_channels = cfg.dataset.get("plot_channels", None) - if plot_channels is not None: - del dataset_cfg["plot_channels"] - plot_channels = [ChannelMetadata(name) if len(name.split('_'))==1 else ChannelMetadata(name.split('_')[0],name.split('_')[1]) for name in plot_channels] + has_lead_time = cfg.generation.get("has_lead_time", False) dataset, sampler = get_dataset_and_sampler_inference( dataset_cfg=dataset_cfg, times=times, has_lead_time=has_lead_time ) + + plot_channels = cfg.get("plot_channels", None) + if plot_channels is not None: + plot_channels = get_channels_from_strings(plot_channels) + else: + plot_channels = dataset.output_channels() + input_channels = dataset.input_channels() output_channels = dataset.output_channels() From a8e3345f4c3a45cee80dd821ffc0a59657cd6c29 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 19 Dec 2025 15:57:03 +0100 Subject: [PATCH 230/302] update dataset for realch1 - Add new dataset class for era-real dataset pair - Update era_cosmo dataset class for npy file format - Some small performance changes to era_cosmo dataset - Add helper functions for transforming between channel string names and ChannelMetadata --- src/hirad/datasets/__init__.py | 3 +- src/hirad/datasets/base.py | 14 ++ src/hirad/datasets/dataset.py | 2 + src/hirad/datasets/era5_cosmo.py | 32 +++- src/hirad/datasets/era5_real.py | 309 +++++++++++++++++++++++++++++++ 5 files changed, 351 insertions(+), 9 deletions(-) create mode 100644 src/hirad/datasets/era5_real.py diff --git a/src/hirad/datasets/__init__.py b/src/hirad/datasets/__init__.py index 2fb3d5dc..dcfc8f7a 100644 --- a/src/hirad/datasets/__init__.py +++ b/src/hirad/datasets/__init__.py @@ -1,3 +1,4 @@ from .dataset import init_train_valid_datasets_from_config, init_dataset_from_config, get_dataset_and_sampler_inference from .era5_cosmo import ERA5_COSMO -from .base import DownscalingDataset +from .era5_real import ERA5_REAL +from .base import DownscalingDataset, ChannelMetadata, get_channels_from_strings, get_strings_from_channels diff --git a/src/hirad/datasets/base.py b/src/hirad/datasets/base.py index 22b00d25..1fa57c1e 100644 --- a/src/hirad/datasets/base.py +++ b/src/hirad/datasets/base.py @@ -31,6 +31,20 @@ class ChannelMetadata: auxiliary: bool = False +def get_channels_from_strings(channel_strings: List[str] | str) -> List[ChannelMetadata] | ChannelMetadata: + """Convert list of channel strings to ChannelMetadata objects.""" + if isinstance(channel_strings, str): + return ChannelMetadata(channel_strings) if len(channel_strings.split('_'))==1 else ChannelMetadata(channel_strings.split('_')[0],channel_strings.split('_')[1]) + else: + return [ChannelMetadata(name) if len(name.split('_'))==1 else ChannelMetadata(name.split('_')[0],name.split('_')[1]) for name in channel_strings] + +def get_strings_from_channels(channels: List[ChannelMetadata] | ChannelMetadata) -> List[str] | str: + """Convert list of ChannelMetadata objects to channel strings.""" + if isinstance(channels, ChannelMetadata): + return channels.name if not channels.level else f"{channels.name}_{channels.level}" + else: + return [ch.name if not ch.level else f"{ch.name}_{ch.level}" for ch in channels] + class DownscalingDataset(torch.utils.data.Dataset, ABC): """An abstract class that defines the interface for downscaling datasets.""" diff --git a/src/hirad/datasets/dataset.py b/src/hirad/datasets/dataset.py index 924951e6..118cff9e 100644 --- a/src/hirad/datasets/dataset.py +++ b/src/hirad/datasets/dataset.py @@ -22,12 +22,14 @@ from hirad.distributed import DistributedManager from .era5_cosmo import ERA5_COSMO +from .era5_real import ERA5_REAL from .base import DownscalingDataset # this maps all known dataset types to the corresponding init function known_datasets = { "era5_cosmo": ERA5_COSMO, + "era5_real": ERA5_REAL, } diff --git a/src/hirad/datasets/era5_cosmo.py b/src/hirad/datasets/era5_cosmo.py index 81718a80..c7acf28e 100644 --- a/src/hirad/datasets/era5_cosmo.py +++ b/src/hirad/datasets/era5_cosmo.py @@ -12,7 +12,8 @@ logger = PythonLogger(__name__) -DATASET_ORIG_PATH = '/capstor/store/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-linear-interpolation-full' +# DATASET_ORIG_PATH = '/capstor/store/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-linear-interpolation-full' +DATASET_ORIG_PATH = "/iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-cosmo-1h-all-channels/train/" class ERA5_COSMO(DownscalingDataset): def __init__(self, @@ -22,14 +23,16 @@ def __init__(self, static_channel_names: List[str] = [], transform_channels: List[str] = [], n_month_hour_channels: int = None, + input_dir_name: str = 'era-interpolated', + output_dir_name: str = 'cosmo', ): super().__init__() #TODO switch hanbdling paths to Path rather than pure strings self._n_month_hour_channels = n_month_hour_channels self._dataset_path = dataset_path - self._era5_path = os.path.join(dataset_path, 'era-interpolated') - self._cosmo_path = os.path.join(dataset_path, 'cosmo') + self._era5_path = os.path.join(dataset_path, input_dir_name) + self._cosmo_path = os.path.join(dataset_path, output_dir_name) self._info_path = os.path.join(DATASET_ORIG_PATH, 'info') # self._static_path = '/capstor/store/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-linear-interpolation-full/static'# os.path.join(dataset_path, 'static') self._static_path = os.path.join(DATASET_ORIG_PATH, 'static')# os.path.join(dataset_path, 'static') @@ -135,27 +138,40 @@ def __getitem__(self, idx): # orig_shape = [350,542] #TODO currently padding to be divisible by 16 orig_shape = self.image_shape() try: - era5_data = torch.load(os.path.join(self._era5_path,self._file_list[idx]), weights_only=False)[self._era_indeces] + # start = time.perf_counter() + # era5_data = torch.load(os.path.join(self._era5_path,self._file_list[idx].split('.')[0]), weights_only=False)[self._era_indeces] + era5_data = np.load(os.path.join(self._era5_path,self._file_list[idx]), mmap_mode='r')[self._era_indeces] + # end = time.perf_counter() + # logger.info(f"Reading time era: {end - start:.6f} seconds") except: logger.error(f"Error loading file {os.path.join(self._era5_path,self._file_list[idx])}") raise + # start = time.perf_counter() era5_data = np.flip(era5_data \ .squeeze() \ .reshape(-1,*orig_shape), 1) era5_data = np.concatenate((era5_data, self.static_data), axis=0) if self.static_data is not None else era5_data era5_data = self.normalize_input(era5_data) - + # end = time.perf_counter() + # logger.info(f"Preprocess time era: {end - start:.6f} seconds") try: - cosmo_data = torch.load(os.path.join(self._cosmo_path,self._file_list[idx]), weights_only=False)[self._cosmo_indeces] + # start = time.perf_counter() + # cosmo_data = torch.load(os.path.join(self._cosmo_path,self._file_list[idx]), weights_only=False)[self._cosmo_indeces] + cosmo_data = np.load(os.path.join(self._cosmo_path,self._file_list[idx]), mmap_mode='r')[self._cosmo_indeces] + # end = time.perf_counter() + # logger.info(f"Reading time cosmo: {end - start:.6f} seconds") except: logger.error(f"Error loading file {os.path.join(self._cosmo_path,self._file_list[idx])}") raise + # start = time.perf_counter() cosmo_data = np.flip(cosmo_data\ .squeeze() \ .reshape(-1,*orig_shape), 1) cosmo_data = self.normalize_output(cosmo_data) + # end = time.perf_counter() + # logger.info(f"Preprocess time cosmo: {end - start:.6f} seconds") if self._n_month_hour_channels is not None and self._n_month_hour_channels>0: # extract month and hour from filename @@ -167,8 +183,8 @@ def __getitem__(self, idx): time_grid = self.make_time_grids(hour, month) era5_data = np.concatenate((era5_data, time_grid), axis=0) - return torch.tensor(cosmo_data),\ - torch.tensor(era5_data) + return torch.from_numpy(cosmo_data),\ + torch.from_numpy(era5_data) def __len__(self): return len(self._file_list) diff --git a/src/hirad/datasets/era5_real.py b/src/hirad/datasets/era5_real.py new file mode 100644 index 00000000..bd8265ee --- /dev/null +++ b/src/hirad/datasets/era5_real.py @@ -0,0 +1,309 @@ +from .base import DownscalingDataset, ChannelMetadata +import os +import numpy as np +import torch +from typing import List, Tuple +import yaml +import torch.nn.functional as F +import time +# import zarr + +from hirad.utils.console import PythonLogger + +logger = PythonLogger(__name__) + +DATASET_ORIG_PATH = '/iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset' + +ERA5_TO_REAL_CHANNEL_MAP = { + '2t': 'TD_2M', + '10u': 'U_10M', + '10v': 'V_10M', + 'tp': 'TOT_PREC_1H' +} + +class ERA5_REAL(DownscalingDataset): + def __init__(self, + dataset_path: str, + input_channel_names: List[str] = [], + output_channel_names: List[str] = [], + static_channel_names: List[str] = [], + transform_channels: List[str] = [], + n_month_hour_channels: int = 0, + input_dir_name: str = 'era-copernicus-interpolated', + output_dir_name: str = 'realch1', + ): + super().__init__() + + #TODO switch hanbdling paths to Path rather than pure strings + self._n_month_hour_channels = n_month_hour_channels + self._dataset_path = dataset_path + self._era5_path = os.path.join(dataset_path, input_dir_name) + self._real_path = os.path.join(dataset_path, output_dir_name) + self._info_path = os.path.join(DATASET_ORIG_PATH, 'info') + # self._static_path = '/capstor/store/mch/msopr/hirad-gen/basic-torch/era5-real-1h-linear-interpolation-full/static'# os.path.join(dataset_path, 'static') + self._static_path = os.path.join(DATASET_ORIG_PATH, 'static')# os.path.join(dataset_path, 'static') + # self._zarr_path = os.path.join(dataset_path, 'dataset.zarr') + + # load file list (each file is one date-time state) + self._file_list = sorted(os.listdir(self._real_path)) + + # open zarr store + # self._zarr_store = zarr.open(self._zarr_path, mode='r') + # self.era5 = self._zarr_store['era5'] + # self.real = self._zarr_store['real'] + + # Load static info and channel names + if static_channel_names: + with open(os.path.join(self._static_path, output_dir_name+'-static.yaml'), 'r') as file: + self._static_info = yaml.safe_load(file) + self._static_indeces = [self._static_info['select'].index(name) for name in static_channel_names] + self._static_channels = [ChannelMetadata(name) if len(name.split('_'))==1 + else ChannelMetadata(name.split('_')[0],name.split('_')[1]) + for name in self._static_info['select'] if name in static_channel_names] + static_data = np.load(os.path.join(self._static_path, output_dir_name+'-static.npy'))[self._static_indeces] + orig_shape = self.image_shape() + self.static_data = np.flip(static_data \ + .squeeze() \ + .reshape(-1,*orig_shape), + 1) + self.static_mean = self.static_data.mean(axis=(1,2)) + self.static_std = self.static_data.std(axis=(1,2)) + else: + self.static_data = None + + # Load real info and channel names + with open(os.path.join(self._info_path, output_dir_name+'.yaml'), 'r') as file: + self._real_info = yaml.safe_load(file) + if output_channel_names: + self._real_indeces = [self._real_info['select'].index(name) for name in output_channel_names] + else: + self._real_indeces = list(range(len(self._real_info['select']))) + output_channel_names = self._real_info['select'] + self._real_channels = [ChannelMetadata(name) if len(name.split('_'))==1 + else ChannelMetadata(name.split('_')[0],name.split('_')[1]) + for name in self._real_info['select'] if name in output_channel_names] + + # Load era5 info and channel names + with open(os.path.join(self._info_path,'era.yaml'), 'r') as file: + self._era_info = yaml.safe_load(file) + if input_channel_names: + self._era_indeces = [self._era_info['select'].index(name) for name in input_channel_names] + else: + self._era_indeces = list(range(len(self._era_info['select']))) + input_channel_names = self._era_info['select'] + self._era_channels = [ChannelMetadata(name) if len(name.split('_'))==1 + else ChannelMetadata(name.split('_')[0],name.split('_')[1]) + for name in self._era_info['select'] if name in input_channel_names] + + # Load stats for normalizing channels of input and output + + era_stats = torch.load(os.path.join(self._info_path,'era-stats'), weights_only=False) + self.input_mean = era_stats['mean'][self._era_indeces] + self.input_std = era_stats['stdev'][self._era_indeces] + + real_stats = torch.load(os.path.join(self._info_path,'realch1-stats'), weights_only=False) + self.output_mean = real_stats['mean'][self._real_indeces] + self.output_std = real_stats['stdev'][self._real_indeces] + + if self.static_data is not None: + self.input_mean = np.concatenate((self.input_mean, self.static_mean), axis=0) + self.input_std = np.concatenate((self.input_std, self.static_std), axis=0) + + # FEATURE: load the mean and std values for transformed channels and update the normalization statistics + + self.input_transforms = {} + self.input_inverse_transforms = {} + self.output_transforms = {} + self.output_inverse_transforms = {} + for transform_descriptor in transform_channels: + channel, transformation = transform_descriptor.split('-') + input_channel_idx = self._era_info['select'].index(channel) if channel in self._era_info['select'] else None + # output_channel_idx = self._real_info['select'].index(ERA5_TO_REAL_CHANNEL_MAP[channel]) if ERA5_TO_REAL_CHANNEL_MAP[channel] in self._real_info['select'] else None + output_channel_idx = self._real_info['select'].index(channel) if channel in self._real_info['select'] else None + if transformation.startswith('box_cox'): + lmbda_str = transformation.split('_')[-1] + lmbda = float(transformation.split('_')[-1])/(10**(len(lmbda_str)-1)) + print(f"Applying Box-Cox transformation with lambda={lmbda} to channel {channel} (input idx: {input_channel_idx}, output idx: {output_channel_idx})") + if input_channel_idx is not None: + self.input_transforms[input_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_transform(x, lmbda) + self.input_inverse_transforms[input_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_inverse_transform(x, lmbda) + self.input_mean[input_channel_idx] = torch.load(os.path.join(self._info_path,f"era5-{transform_descriptor}-mean"), weights_only=False) + self.input_std[input_channel_idx] = torch.load(os.path.join(self._info_path,f"era5-{transform_descriptor}-std"), weights_only=False) + if output_channel_idx is not None: + self.output_transforms[output_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_transform(x, lmbda) + self.output_inverse_transforms[output_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_inverse_transform(x, lmbda) + self.output_mean[output_channel_idx] = torch.load(os.path.join(self._info_path,f"realch1-{transform_descriptor}-mean"), weights_only=False) + self.output_std[output_channel_idx] = torch.load(os.path.join(self._info_path,f"realch1-{transform_descriptor}-std"), weights_only=False) + else: + raise ValueError(f"Transformation: {transformation} for channel {channel} not implemented.") + + def __getitem__(self, idx): + """Get real and era5 interpolated to real grid""" + # get data point + # squeeze the ensemble dimesnsion + # reshape to image_shape + # flip so that it starts in top-left corner (by default it is bottom left) + # orig_shape = [350,542] #TODO currently padding to be divisible by 16 + orig_shape = self.image_shape() + try: + era5_data = np.load(os.path.join(self._era5_path,self._file_list[idx]), mmap_mode='r')[self._era_indeces] + except: + logger.error(f"Error loading file {os.path.join(self._era5_path,self._file_list[idx])}") + raise + era5_data = np.flip(era5_data \ + .squeeze() \ + .reshape(-1,*orig_shape), + 1) + era5_data = np.concatenate((era5_data, self.static_data), axis=0) if self.static_data is not None else era5_data + era5_data = self.normalize_input(era5_data) + + try: + real_data = np.load(os.path.join(self._real_path,self._file_list[idx]), mmap_mode='r')[self._real_indeces] + except: + logger.error(f"Error loading file {os.path.join(self._real_path,self._file_list[idx])}") + raise + real_data = np.flip(real_data\ + .squeeze() \ + .reshape(-1,*orig_shape), + 1) + real_data = self.normalize_output(real_data) + + if self._n_month_hour_channels is not None and self._n_month_hour_channels>0: + # extract month and hour from filename + filename = self._file_list[idx] + date_str, hour_str = filename.split('-') + month = int(date_str[4:6]) + hour = int(hour_str[0:2]) + + time_grid = self.make_time_grids(hour, month) + era5_data = np.concatenate((era5_data, time_grid), axis=0) + + return torch.tensor(real_data),\ + torch.tensor(era5_data) + + def __len__(self): + return len(self._file_list) + + + def longitude(self) -> np.ndarray: + """Get longitude values from the dataset.""" + lat_lon = torch.load(os.path.join(self._info_path,output_dir_name+'-lat-lon'), weights_only=False) + return lat_lon[:,1] + + + def latitude(self) -> np.ndarray: + """Get latitude values from the dataset.""" + lat_lon = torch.load(os.path.join(self._info_path,output_dir_name+'-lat-lon'), weights_only=False) + return lat_lon[:,0] + + + def input_channels(self) -> List[ChannelMetadata]: + """Metadata for the input channels. A list of ChannelMetadata, one for each channel""" + channels = self._era_channels + self._static_channels if self.static_data is not None else self._era_channels + if self._n_month_hour_channels is not None and self._n_month_hour_channels>0: + for i in range(self._n_month_hour_channels): + channels.append(ChannelMetadata("hour-enc",f"{i}")) + for i in range(self._n_month_hour_channels): + channels.append(ChannelMetadata("month-enc",f"{i}")) + return channels + + def output_channels(self) -> List[ChannelMetadata]: + """Metadata for the output channels. A list of ChannelMetadata, one for each channel""" + return self._real_channels + + + def time(self) -> List: + """Get time values from the dataset.""" + #TODO Choose the time format and convert to that, currently it's a string from a filename + return [file.split('.')[0] for file in self._file_list] + + + def image_shape(self) -> Tuple[int, int]: + """Get the (height, width) of the data (same for input and output).""" + #TODO load from info, I hardcode it for now (real from anemoi-datasets minus trim-edge=20) + return 704,1088 + + + def normalize_input(self, x: np.ndarray) -> np.ndarray: + """Convert input from physical units to normalized data.""" + for channel_idx, transform in self.input_transforms.items(): + x[channel_idx,::] = transform(x[channel_idx,::]) + return (x - self.input_mean.reshape((self.input_mean.shape[0],1,1))) \ + / self.input_std.reshape((self.input_std.shape[0],1,1)) + + + def denormalize_input(self, x: np.ndarray) -> np.ndarray: + """Convert input from normalized data to physical units.""" + if self._n_month_hour_channels is not None and self._n_month_hour_channels>0: + x = x[:,:-2*self._n_month_hour_channels,:,:] + x = x * self.input_std.reshape((self.input_std.shape[0],1,1)) \ + + self.input_mean.reshape((self.input_mean.shape[0],1,1)) + for channel_idx, inverse_transform in self.input_inverse_transforms.items(): + x[:,channel_idx,::] = inverse_transform(x[:,channel_idx,::]) + return x + + + def normalize_output(self, x: np.ndarray) -> np.ndarray: + """Convert output from physical units to normalized data.""" + for channel_idx, transform in self.output_transforms.items(): + x[channel_idx,::] = transform(x[channel_idx,::]) + return (x - self.output_mean.reshape((self.output_mean.shape[0],1,1))) \ + / self.output_std.reshape((self.output_std.shape[0],1,1)) + + + def denormalize_output(self, x: np.ndarray) -> np.ndarray: + """Convert output from normalized data to physical units.""" + x = x * self.output_std.reshape((self.output_std.shape[0],1,1)) \ + + self.output_mean.reshape((self.output_mean.shape[0],1,1)) + for channel_idx, inverse_transform in self.output_inverse_transforms.items(): + x[:,channel_idx,::] = inverse_transform(x[:,channel_idx,::]) + return x + + def box_cox_transform(self, channel_array: np.ndarray, lmbda: float) -> np.ndarray: + """Apply Box-Cox transformation to the data.""" + channel_array = np.clip(channel_array, 0, None) + return (np.power(channel_array, lmbda) - 1) / lmbda + + def box_cox_inverse_transform(self, channel_array: np.ndarray, lmbda: float) -> np.ndarray: + """Apply inverse Box-Cox transformation to the data.""" + channel_array = np.clip(channel_array, -1/lmbda, None) + return np.power((lmbda * channel_array) + 1, 1 / lmbda) + + def make_time_grids(self, hour, month): + """ + Create multi-frequency cyclic sin/cos feature grids for hour and month. + + Parameters + ---------- + hour : int + Hour of day, 0-23 + month : int + Month of year, 1-12 + + Returns + ------- + grid : np.ndarray, shape (C, H, W) + Channels = [sin(k*hour), cos(k*hour), sin(k*month), cos(k*month) for each k frequency] + """ + H, W = self.image_shape() + hour_freqs = np.arange(1, self._n_month_hour_channels//2 + 1) + month_freqs = np.arange(1, self._n_month_hour_channels//2 + 1) + + channels = [] + + # --- hour encodings --- + for k in hour_freqs: + angle = 2 * np.pi * k * (hour % 24) / 24.0 + channels.append(np.sin(angle)) + channels.append(np.cos(angle)) + + # --- month encodings --- + for k in month_freqs: + angle = 2 * np.pi * k * ((month - 1) % 12) / 12.0 + channels.append(np.sin(angle)) + channels.append(np.cos(angle)) + + channels = np.array(channels, dtype=np.float32) + grid = np.tile(channels[:, None, None], (1, H, W)) # (C, H, W) + + return grid \ No newline at end of file From f1b678a02c9262a8554ac3771278a497fa0cba6f Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 19 Dec 2025 16:27:11 +0100 Subject: [PATCH 231/302] update plotting to use new channel metadata to string functions --- src/hirad/eval/plotting.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index e83a625c..b28a125a 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -9,6 +9,7 @@ from matplotlib.colors import BoundaryNorm, ListedColormap from pathlib import Path from datetime import datetime +from hirad.datasets import get_channels_from_strings, get_strings_from_channels # COSMO‑2 GRID: TODO: Add to dataset config @@ -40,8 +41,8 @@ def get_channel_indices(dataset, channels=None): tp_out = indices['output']['tp'] tp_in = indices['input'].get('tp', tp_out) # Fallback to output index if not in input """ - out_ch = {c.name: i for i, c in enumerate(dataset.output_channels())} - in_ch = {c.name: i for i, c in enumerate(dataset.input_channels())} + out_ch = {get_strings_from_channels(c): i for i, c in enumerate(dataset.output_channels())} + in_ch = {get_strings_from_channels(c): i for i, c in enumerate(dataset.input_channels())} if channels is None: return {'input': in_ch, 'output': out_ch} From 44b33acb5a0da7a31c2c35716864bf9d7cc460d1 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 23 Dec 2025 12:20:13 +0100 Subject: [PATCH 232/302] add script to make video from snapshots --- src/hirad/eval/video_from_snapshots.py | 248 +++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 src/hirad/eval/video_from_snapshots.py diff --git a/src/hirad/eval/video_from_snapshots.py b/src/hirad/eval/video_from_snapshots.py new file mode 100644 index 00000000..c1562653 --- /dev/null +++ b/src/hirad/eval/video_from_snapshots.py @@ -0,0 +1,248 @@ +import cv2 +import numpy as np +from PIL import Image +import os +import re +from datetime import datetime +import glob +from pathlib import Path +from tqdm import tqdm +import subprocess +import shutil + +def parse_filename(filename): + """Parse filename to extract date, hour, type, and member""" + # Pattern for prediction files: YYYYMMDD-HH-tp-prediction_MM.png + pred_pattern = r'(\d{8})-(\d+)-tp-prediction_(\d+)\.png' + # Pattern for target/mean files: YYYYMMDD-HH-tp-(target|mean-prediction).png + other_pattern = r'(\d{8})-(\d+)-tp-(target|mean-prediction)\.png' + + pred_match = re.match(pred_pattern, filename) + if pred_match: + date, hour, member = pred_match.groups() + return date, hour, 'prediction', int(member) + + other_match = re.match(other_pattern, filename) + if other_match: + date, hour, img_type = other_match.groups() + return date, hour, img_type, None + + return None + +def get_sorted_timestamps(image_folder): + """Get all unique timestamps sorted chronologically""" + timestamps = set() + for filename in os.listdir(image_folder): + parsed = parse_filename(filename) + if parsed: + date, hour, _, _ = parsed + timestamps.add((date, hour)) + + return sorted(list(timestamps)) + +def load_and_resize_image(filepath, target_size): + """Load image and resize to target size, converting RGBA to RGB""" + if not os.path.exists(filepath): + # Create blank image if file doesn't exist + return np.zeros((*target_size[::-1], 3), dtype=np.uint8) + + img = Image.open(filepath) + + # Convert RGBA to RGB if needed + if img.mode == 'RGBA': + # Create white background + background = Image.new('RGB', img.size, (255, 255, 255)) + background.paste(img, mask=img.split()[-1]) # Use alpha channel as mask + img = background + elif img.mode != 'RGB': + img = img.convert('RGB') + + img = img.resize(target_size, Image.Resampling.LANCZOS) + return np.array(img) + +def create_grid_layout(images, grid_shape): + """Create a grid layout from list of images""" + rows, cols = grid_shape + if len(images) != rows * cols: + # Pad with blank images if needed + while len(images) < rows * cols: + images.append(np.zeros_like(images[0])) + + # Arrange images in grid + image_rows = [] + for i in range(rows): + row_images = images[i*cols:(i+1)*cols] + image_rows.append(np.hstack(row_images)) + + return np.vstack(image_rows) + + +def create_video_from_existing_images(image_folder, output_path, layout_type="all_members", fps=12): + """Create video directly from existing images without creating intermediate frames""" + timestamps = get_sorted_timestamps(image_folder) + + if not timestamps: + print("No valid images found!") + return + + # Get image size + sample_file = next(Path(image_folder).glob("*.png")) + sample_img = Image.open(sample_file) + img_width, img_height = sample_img.size + if layout_type == "all_members": + img_width //= 8 + img_height //= 8 + + print(f"Sample image size: {img_width} x {img_height}") + + # Create one test frame to get EXACT dimensions + if layout_type == "all_members": + # Load real images for the first timestamp to get exact dimensions + first_date, first_hour = timestamps[0] + + # Load 16 prediction images + pred_images = [] + for member in range(16): + filename = f"{first_date}-{first_hour}-tp-prediction_{member}.png" + filepath = Path(image_folder) / filename + img = load_and_resize_image(str(filepath), (img_width, img_height)) + pred_images.append(img) + + pred_grid = create_grid_layout(pred_images, (4, 4)) + + # Load target and mean and baseline + target_file = f"{first_date}-{first_hour}-tp-target.png" + target_img = load_and_resize_image(str(Path(image_folder) / target_file), (img_width, img_height)) + + mean_file = f"{first_date}-{first_hour}-tp-mean-prediction.png" + mean_img = load_and_resize_image(str(Path(image_folder) / mean_file), (img_width, img_height)) + + baseline_file = f"{first_date}-{first_hour}-tp-baseline.png" + baseline_img = load_and_resize_image(str(Path(image_folder) / baseline_file), (img_width, img_height)) + + bottom_row = np.hstack([baseline_img, mean_img, target_img]) + + # Pad if needed + pad_width = pred_grid.shape[1] - bottom_row.shape[1] + if pad_width > 0: + padding = np.zeros((bottom_row.shape[0], pad_width, 3), dtype=np.uint8) + bottom_row = np.hstack([bottom_row, padding]) + + test_frame = np.vstack([pred_grid, bottom_row]) + frame_height, frame_width = test_frame.shape[:2] # numpy: (height, width) + + else: # single_member + frame_width = 4 * img_width + frame_height = img_height + + # print(f"Frame dimensions (H x W): {frame_height} x {frame_width}") + + # Initialize VideoWriter with CORRECT dimension order (width, height) + fourcc = cv2.VideoWriter_fourcc(*'MJPG') + out = cv2.VideoWriter(output_path, fourcc, fps, (frame_width, frame_height)) # OpenCV: (width, height) + + if not out.isOpened(): + print("Failed to initialize VideoWriter!") + return False + + # print(f"VideoWriter initialized with (W x H): {frame_width} x {frame_height}") + + # Create video + for date, hour in tqdm(timestamps, desc=f"Creating {layout_type} video"): + + if layout_type == "all_members": + # Load 16 prediction images + pred_images = [] + for member in range(16): + filename = f"{date}-{hour}-tp-prediction_{member}.png" + filepath = Path(image_folder) / filename + img = load_and_resize_image(str(filepath), (img_width, img_height)) + pred_images.append(img) + + pred_grid = create_grid_layout(pred_images, (4, 4)) + + # Load target and mean + target_file = f"{date}-{hour}-tp-target.png" + target_img = load_and_resize_image(str(Path(image_folder) / target_file), (img_width, img_height)) + + mean_file = f"{date}-{hour}-tp-mean-prediction.png" + mean_img = load_and_resize_image(str(Path(image_folder) / mean_file), (img_width, img_height)) + + baseline_file = f"{date}-{hour}-tp-baseline.png" + baseline_img = load_and_resize_image(str(Path(image_folder) / baseline_file), (img_width, img_height)) + + bottom_row = np.hstack([baseline_img, mean_img, target_img]) + + # Pad if needed (using same logic as test frame) + pad_width = pred_grid.shape[1] - bottom_row.shape[1] + if pad_width > 0: + padding = np.zeros((bottom_row.shape[0], pad_width, 3), dtype=np.uint8) + bottom_row = np.hstack([bottom_row, padding]) + + frame = np.vstack([pred_grid, bottom_row]) + + else: # single_member + pred_file = f"{date}-{hour}-tp-prediction_15.png" + pred_img = load_and_resize_image(str(Path(image_folder) / pred_file), (img_width, img_height)) + + target_file = f"{date}-{hour}-tp-target.png" + target_img = load_and_resize_image(str(Path(image_folder) / target_file), (img_width, img_height)) + + mean_file = f"{date}-{hour}-tp-mean-prediction.png" + mean_img = load_and_resize_image(str(Path(image_folder) / mean_file), (img_width, img_height)) + + baseline_file = f"{date}-{hour}-tp-baseline.png" + baseline_img = load_and_resize_image(str(Path(image_folder) / baseline_file), (img_width, img_height)) + + frame = np.hstack([baseline_img, mean_img, pred_img, target_img]) + + # Verify frame dimensions EXACTLY match VideoWriter + actual_height, actual_width = frame.shape[:2] + if actual_width != frame_width or actual_height != frame_height: + print(f"ERROR: Frame size mismatch!") + print(f"Expected: {frame_width} x {frame_height}") + print(f"Got: {actual_width} x {actual_height}") + print(f"Resizing frame to match...") + # Force resize to exact dimensions + frame = cv2.resize(frame, (frame_width, frame_height)) + + # Ensure 3 channels + if frame.shape[2] != 3: + print(f"ERROR: Frame has {frame.shape[2]} channels, expected 3") + if frame.shape[2] == 4: + frame = frame[:, :, :3] # Drop alpha channel + + # print(f"Frame shape: {frame.shape}") + + # Convert RGB to BGR for OpenCV + frame_bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) + + # Write frame + success = out.write(frame_bgr) + # if not success: + # print(f"Failed to write frame for {date}-{hour}") + # break + + out.release() + print(f"Video saved: {output_path}") + return True + + +def main(): + image_folder = Path("/capstor/scratch/cscs/pstamenk/outputs/generation/generate_8_attention/tp") + + # Create both videos directly from existing images + create_video_from_existing_images( + image_folder, + image_folder / "precipitation_all_members.avi", + layout_type="all_members" + ) + + create_video_from_existing_images( + image_folder, + image_folder / "precipitation_single_member.avi", + layout_type="single_member" + ) + +if __name__ == "__main__": + main() \ No newline at end of file From aa888eb9b1c35f3057fa33f641683ef043ee2d3d Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 23 Dec 2025 12:28:07 +0100 Subject: [PATCH 233/302] add script to calculate datasets stats after transforming data - it supports only cox-box - it should be generalized and extended if needed --- .../input_data/calculate_transformed_stats.py | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/hirad/input_data/calculate_transformed_stats.py diff --git a/src/hirad/input_data/calculate_transformed_stats.py b/src/hirad/input_data/calculate_transformed_stats.py new file mode 100644 index 00000000..0e3d701e --- /dev/null +++ b/src/hirad/input_data/calculate_transformed_stats.py @@ -0,0 +1,73 @@ +import numpy as np +import yaml +import os +from tqdm import tqdm +import torch + + +def transform_channel(channel_array, channel_name="tp"): + if channel_name == "tp": + channel_array = np.clip(channel_array, 0, None) + channel_array = (np.power(channel_array,0.25)-1)/0.25 + return channel_array + +def main(): + base_path = '/iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/train' + all_files_input = os.listdir(os.path.join(base_path, 'era-copernicus-interpolated/')) + all_files_output = os.listdir(os.path.join(base_path, 'realch1/')) + input_info = yaml.safe_load(open(os.path.join(base_path, 'info', 'era.yaml'))) + output_info = yaml.safe_load(open(os.path.join(base_path, 'info', 'realch1.yaml'))) + + tp_input_index = input_info['select'].index('tp') + tp_output_index = output_info['select'].index('tp') + + print('Total precipitation input index:', tp_input_index) + print('Total precipitation output index:', tp_output_index) + + print(f"Found {len(all_files_input)} input files and {len(all_files_output)} output files.") + print("Calculating transformed mean and std...") + + # calculate mean + input_values = [] + output_values = [] + print("Calculating input mean") + for f in tqdm(all_files_input): + data = np.load(os.path.join(base_path, 'era-copernicus-interpolated', f)) + data = transform_channel(data[tp_input_index,:]) + input_values.append(np.mean(data)) + input_mean = np.mean(input_values) + print("Calculating output mean") + for f in tqdm(all_files_output): + data = np.load(os.path.join(base_path, 'realch1', f)) + data = transform_channel(data[tp_output_index,:]) + output_values.append(np.mean(data)) + output_mean = np.mean(output_values) + + torch.save(input_mean, os.path.join('./info', 'era5-tp-box_cox_025-mean')) + torch.save(output_mean, os.path.join('./info','realch1-tp-box_cox_025-mean')) + + # calculate std + input_values = [] + output_values = [] + print("Calculating input std") + for f in tqdm(all_files_input): + data = np.load(os.path.join(base_path, 'era-copernicus-interpolated', f)) + data = transform_channel(data[tp_input_index,:]) + input_values.append(np.mean((data - input_mean)**2)) + input_std = np.sqrt(np.mean(input_values)) + print("Calculating output std") + for f in tqdm(all_files_output): + data = np.load(os.path.join(base_path, 'realch1', f)) + data = transform_channel(data[tp_output_index,:]) + output_values.append(np.mean((data - output_mean)**2)) + output_std = np.sqrt(np.mean(output_values)) + + torch.save(input_std, os.path.join('./info','era5-tp-box_cox_025-std')) + torch.save(output_std, os.path.join('./info','realch1-tp-box_cox_025-std')) + + print(f"Input Mean: {input_mean}, Input Std: {input_std}") + print(f"Output Mean: {output_mean}, Output Std: {output_std}") + print("Done.") + +if __name__ == "__main__": + main() \ No newline at end of file From 858020dfb012f72cd3f3fdf3a5207681e8dd4a66 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 24 Dec 2025 12:47:12 +0100 Subject: [PATCH 234/302] enable torch compile during distributed training --- src/hirad/training/train.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 42472977..e4d9900d 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -287,8 +287,8 @@ def main(cfg: DictConfig) -> None: # Enable distributed data parallel if applicable if dist.world_size > 1: - # if use_torch_compile: - # model = torch.compile(model) + if use_torch_compile: + model = torch.compile(model) model = DistributedDataParallel( model, device_ids=[dist.local_rank], From 8a3f63e82bbbe203529b7af57cd16dddf6a41be9 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 24 Dec 2025 12:49:02 +0100 Subject: [PATCH 235/302] enable time logging of data fetching --- src/hirad/training/train.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index e4d9900d..da817bf1 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -266,6 +266,13 @@ def main(cfg: DictConfig) -> None: **model_args, ) model_args["img_in_channels"] = img_in_channels + model_args["N_grid_channels"] + + # # Print the model summary + # if dist.rank == 0: + # summary(model, input_size=[(1, img_out_channels, *img_shape), (1, img_in_channels, *img_shape), (1,1)], device=dist.device) + + # raise NotImplementedError("Check if model_args are correct when using patching - img_in_channels should include global channels and lead time channels if applicable") + model.train().requires_grad_(True).to(dist.device) @@ -410,7 +417,7 @@ def main(cfg: DictConfig) -> None: elif cfg.model.name == "lt_aware_ce_regression": loss_fn = RegressionLossCE(prob_channels=prob_channels) - # Instantiate the optimizer + # Instantiate the optimizer optimizer = torch.optim.Adam( params=model.parameters(), lr=cfg.training.hp.lr, @@ -504,9 +511,11 @@ def main(cfg: DictConfig) -> None: f"accumulation round {n_i}", color="Magenta" ): with nvtx.annotate("loading data", color="green"): + tick_read_start_time = time.time() img_clean, img_lr, *lead_time_label = next( dataset_iterator ) + tick_read_time = time.time() - tick_read_start_time if use_apex_gn: img_clean = img_clean.to( dist.device, @@ -615,17 +624,21 @@ def main(cfg: DictConfig) -> None: rank_0_only=True, ): # Print stats if we crossed the printing threshold with this batch + torch.cuda.synchronize() tick_end_time = time.time() fields = [] fields += [f"samples {cur_nimg:<9.1f}"] fields += [f"training_loss {average_loss:<7.2f}"] fields += [f"training_loss_running_mean {average_loss_running_mean:<7.2f}"] fields += [f"learning_rate {current_lr:<7.8f}"] - fields += [f"total_sec {(tick_end_time - start_time):<7.1f}"] fields += [f"sec_per_tick {(tick_end_time - tick_start_time):<7.1f}"] fields += [ f"sec_per_sample {((tick_end_time - tick_start_time) / (cur_nimg - tick_start_nimg)):<7.4f}" ] + fields += [ + f"sec_for_reading {tick_read_time:<7.4f}" + ] + fields += [f"total_sec {(tick_end_time - start_time):<7.1f}"] fields += [ f"cpu_mem_gb {(psutil.Process(os.getpid()).memory_info().rss / 2**30):<6.2f}" ] From baf645f401119ed08620cdfcb62120496fcdf448 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 24 Dec 2025 12:51:36 +0100 Subject: [PATCH 236/302] add realch1 dataset training configs --- src/hirad/calculate_stats.sh | 35 +++++++++ src/hirad/conf/dataset/era_real.yaml | 10 +++ .../conf/dataset/era_real_inference.yaml | 10 +++ src/hirad/conf/generate_era_real.yaml | 21 ++++++ src/hirad/conf/generation/era_real.yaml | 73 +++++++++++++++++++ .../conf/logging/era_real_diffusion.yaml | 8 ++ .../conf/logging/era_real_regression.yaml | 8 ++ .../model/era_real_diffusion_patched.yaml | 16 ++++ src/hirad/conf/model/era_real_regression.yaml | 10 +++ src/hirad/conf/model_size/normal_real.yaml | 27 +++++++ src/hirad/conf/snapshot_era_real.yaml | 18 +++++ .../training/era_real_diffusion_patched.yaml | 55 ++++++++++++++ .../conf/training/era_real_regression.yaml | 45 ++++++++++++ .../training_era_real_diffusion_patched.yaml | 27 +++++++ .../conf/training_era_real_regression.yaml | 28 +++++++ src/hirad/snapshots.sh | 25 ++----- 16 files changed, 398 insertions(+), 18 deletions(-) create mode 100644 src/hirad/calculate_stats.sh create mode 100644 src/hirad/conf/dataset/era_real.yaml create mode 100644 src/hirad/conf/dataset/era_real_inference.yaml create mode 100644 src/hirad/conf/generate_era_real.yaml create mode 100644 src/hirad/conf/generation/era_real.yaml create mode 100644 src/hirad/conf/logging/era_real_diffusion.yaml create mode 100644 src/hirad/conf/logging/era_real_regression.yaml create mode 100644 src/hirad/conf/model/era_real_diffusion_patched.yaml create mode 100644 src/hirad/conf/model/era_real_regression.yaml create mode 100644 src/hirad/conf/model_size/normal_real.yaml create mode 100644 src/hirad/conf/snapshot_era_real.yaml create mode 100644 src/hirad/conf/training/era_real_diffusion_patched.yaml create mode 100644 src/hirad/conf/training/era_real_regression.yaml create mode 100644 src/hirad/conf/training_era_real_diffusion_patched.yaml create mode 100644 src/hirad/conf/training_era_real_regression.yaml diff --git a/src/hirad/calculate_stats.sh b/src/hirad/calculate_stats.sh new file mode 100644 index 00000000..3e8eaf14 --- /dev/null +++ b/src/hirad/calculate_stats.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +#SBATCH --job-name="corrdiff-first-stage" + +### HARDWARE ### +#SBATCH --partition=normal +#SBATCH --nodes=1 +#SBATCH --ntasks-per-node=1 +#SBATCH --gpus-per-node=1 +#SBATCH --cpus-per-task=72 +#SBATCH --time=12:00:00 +#SBATCH --no-requeue +#SBATCH --exclusive + + +### OUTPUT ### +#SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/calculate_stats.log +#SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/calculate_stats.err + +### ENVIRONMENT #### +#SBATCH -A a161 + +# Get master node. +MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" +# Get IP for hostname. +MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" +export MASTER_ADDR +export MASTER_PORT=29500 + +export OMP_NUM_THREADS=1 + +srun --environment=./ci/edf/modulus_env.toml bash -c " + source ../hirad_env/hirad/bin/activate + python src/hirad/input_data/calculate_transformed_stats.py +" \ No newline at end of file diff --git a/src/hirad/conf/dataset/era_real.yaml b/src/hirad/conf/dataset/era_real.yaml new file mode 100644 index 00000000..95f5b02a --- /dev/null +++ b/src/hirad/conf/dataset/era_real.yaml @@ -0,0 +1,10 @@ +type: era5_real +dataset_path: /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/train +validation_path: /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/validation +# dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-all-channels +# validation_path: /iopsstor/scratch/cscs/mmcgloho/run-1_2/validation/ +input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] +output_channel_names: [2t, 10u, 10v, tp] +static_channel_names: ['z'] +transform_channels: ['tp-box_cox_025'] +n_month_hour_channels: 4 \ No newline at end of file diff --git a/src/hirad/conf/dataset/era_real_inference.yaml b/src/hirad/conf/dataset/era_real_inference.yaml new file mode 100644 index 00000000..34ea7e45 --- /dev/null +++ b/src/hirad/conf/dataset/era_real_inference.yaml @@ -0,0 +1,10 @@ +type: era5_real +# dataset_path: /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/train +dataset_path: /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/validation +# dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-all-channels +# validation_path: /iopsstor/scratch/cscs/mmcgloho/run-1_2/validation/ +input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] +output_channel_names: [2t, 10u, 10v, tp] +static_channel_names: ['z'] +transform_channels: ['tp-box_cox_025'] +n_month_hour_channels: 4 \ No newline at end of file diff --git a/src/hirad/conf/generate_era_real.yaml b/src/hirad/conf/generate_era_real.yaml new file mode 100644 index 00000000..71e24295 --- /dev/null +++ b/src/hirad/conf/generate_era_real.yaml @@ -0,0 +1,21 @@ +hydra: + job: + chdir: true + name: generate_era_real_5m + # name: july2020 + run: + dir: /capstor/scratch/cscs/pstamenk//outputs/generation/${hydra:job.name} + +# Get defaults +defaults: + - _self_ + # Dataset + - dataset/era_real_inference + + # Sampler + - sampler/stochastic + #- sampler/deterministic + + # Generation + - generation/era_real + #- generation/patched_based \ No newline at end of file diff --git a/src/hirad/conf/generation/era_real.yaml b/src/hirad/conf/generation/era_real.yaml new file mode 100644 index 00000000..9c44993e --- /dev/null +++ b/src/hirad/conf/generation/era_real.yaml @@ -0,0 +1,73 @@ +num_ensembles: 8 + # Number of ensembles to generate per input +seed_batch_size: 4 + # Size of the batched inference +inference_mode: all + # Choose between "all" (regression + diffusion), "regression" or "diffusion" + # Patch size. Patch-based sampling will be utilized if these dimensions differ from + # img_shape_x and img_shape_y + +randomize: True + # Whether to randomize the random seeds for each generation. If false, fixed seeds + # from 0 to num_ensembles-1 will be used for each time step in times/times_range. +random_seed: 2578458 + # Base random seed. This is only used when randomize is True. + # random seed will be set for numpy random module to have reproducible randomized generative process. + +# Patching parameters +patching: True +patch_shape_x: 384 +patch_shape_y: 384 +overlap_pix: 4 +# # Number of overlapping pixels between adjacent patches +boundary_pix: 2 +# # Number of boundary pixels to be cropped out. 2 is recommanded to address the boundary +# # artifact. + +hr_mean_conditioning: True +# sample_res: full + # Sampling resolution +# times_range: ['20200825-1200','20200827-1200',1] +# times_range: ['20200601-0000','20200831-2300',1] +# times_range: ['20200721-1900','20200721-2000',1] +times_range: null + # Start date, end date and time interval (in hours) for the generation +# times_range: ['20200601-0000','20200831-2300',1] + # Start date, end date and time interval (in hours) for the generation +times: + - 20210721-1900 + # - 20200721-1900 #null + # - 20200118-1800 + # - 20200721-1900 + # - 20200926-1800 + # - 20200927-0000 + # - 20200927-0600 + # - 20200927-1200 + # - 20160101-0600 + # - 20160101-1200 +has_lead_time: False + +perf: + force_fp16: False + # Whether to force fp16 precision for the model. If false, it'll use the precision + # specified upon training. + use_torch_compile: False + # whether to use torch.compile on the diffusion model + # this will make the first time stamp generation very slow due to compilation overheads + # but will significantly speed up subsequent inference runs + num_writer_workers: 8 + # number of workers to use for writing file + # To support multiple workers a threadsafe version of the netCDF library must be used + +io: + # res_ckpt_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_diffusion_8_attention + # res_ckpt_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_diffusion_all_channels + # res_ckpt_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_compression_diffusion + res_ckpt_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_diffusion_real + # Checkpoint filename for the residual predictor model + # reg_ckpt_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_regression_8_attention + # reg_ckpt_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_regression_all_channels + # reg_ckpt_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_compression_regression + reg_ckpt_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_regression_real + # Checkpoint filename for the mean predictor model + output_path: . \ No newline at end of file diff --git a/src/hirad/conf/logging/era_real_diffusion.yaml b/src/hirad/conf/logging/era_real_diffusion.yaml new file mode 100644 index 00000000..b45ef2cf --- /dev/null +++ b/src/hirad/conf/logging/era_real_diffusion.yaml @@ -0,0 +1,8 @@ +# set method to mlflow to log with mlflow +method: mlflow +experiment_name: hirad-corrdiff-diffusion-training +run_name: era-real +# change uri to remote mlflow server; if null, it is stored locally +# if uri is remote make sure to have credentials set in ~/.mlflow/credentials +uri: null +log_images: false \ No newline at end of file diff --git a/src/hirad/conf/logging/era_real_regression.yaml b/src/hirad/conf/logging/era_real_regression.yaml new file mode 100644 index 00000000..1a016667 --- /dev/null +++ b/src/hirad/conf/logging/era_real_regression.yaml @@ -0,0 +1,8 @@ +# set method to mlflow to log with mlflow +method: mlflow +experiment_name: hirad-corrdiff-regression-training +run_name: era-real-all-channels +# change uri to remote mlflow server; if null, it is stored locally +# if uri is remote make sure to have credentials set in ~/.mlflow/credentials +uri: null +log_images: false \ No newline at end of file diff --git a/src/hirad/conf/model/era_real_diffusion_patched.yaml b/src/hirad/conf/model/era_real_diffusion_patched.yaml new file mode 100644 index 00000000..2af0e5ce --- /dev/null +++ b/src/hirad/conf/model/era_real_diffusion_patched.yaml @@ -0,0 +1,16 @@ +name: patched_diffusion + # Name of the preconditioner +hr_mean_conditioning: True + # High-res mean (regression's output) as additional condition + +# Standard model parameters. +# Standard model parameters. +model_args: + gridtype: "learnable" + # Type of positional grid to use: 'sinusoidal', 'learnable', 'linear'. + # Controls how positional information is encoded. + N_grid_channels: 100 + # Number of channels for positional grid embeddings + embedding_type: "positional" + # Type of timestep embedding: 'positional' for DDPM++, 'fourier' for NCSN++, + # 'zero' for none \ No newline at end of file diff --git a/src/hirad/conf/model/era_real_regression.yaml b/src/hirad/conf/model/era_real_regression.yaml new file mode 100644 index 00000000..29b43e8f --- /dev/null +++ b/src/hirad/conf/model/era_real_regression.yaml @@ -0,0 +1,10 @@ +name: regression +hr_mean_conditioning: False + +# Default regression model parameters. Do not modify. +model_args: + "N_grid_channels": 4 + # Number of channels for positional grid embeddings + "embedding_type": "zero" + # Type of timestep embedding: 'positional' for DDPM++, 'fourier' for NCSN++, + # 'zero' for none \ No newline at end of file diff --git a/src/hirad/conf/model_size/normal_real.yaml b/src/hirad/conf/model_size/normal_real.yaml new file mode 100644 index 00000000..0aab33a4 --- /dev/null +++ b/src/hirad/conf/model_size/normal_real.yaml @@ -0,0 +1,27 @@ +# @package _global_.model + +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Normal model size (80 million parameters) should be used by default for full datasets and higher grid size. + +model_args: + # Base multiplier for the number of channels across the network. + model_channels: 128 + # Per-resolution multipliers for the number of channels. + channel_mult: [1, 2, 2, 2, 2] + # Resolutions at which self-attention layers are applied. + attn_resolutions: [44] \ No newline at end of file diff --git a/src/hirad/conf/snapshot_era_real.yaml b/src/hirad/conf/snapshot_era_real.yaml new file mode 100644 index 00000000..ca9d8f61 --- /dev/null +++ b/src/hirad/conf/snapshot_era_real.yaml @@ -0,0 +1,18 @@ +hydra: + job: + chdir: true + name: generate_era_real_5m + # name: july2020 + run: + dir: /capstor/scratch/cscs/pstamenk//outputs/generation/${hydra:job.name} + +# Get defaults +defaults: + - _self_ + # Dataset + - dataset/era_real_inference + + # Generation + - generation/era_real + +# plot_channels: ['2t', '10u', '10v', 'tp', 't_700', 'u_700', 'v_700', 'z_700', 'q_700', 'w_700'] \ No newline at end of file diff --git a/src/hirad/conf/training/era_real_diffusion_patched.yaml b/src/hirad/conf/training/era_real_diffusion_patched.yaml new file mode 100644 index 00000000..5ab5025b --- /dev/null +++ b/src/hirad/conf/training/era_real_diffusion_patched.yaml @@ -0,0 +1,55 @@ +# Hyperparameters +hp: + training_duration: 8000000 + # Training duration based on the number of processed samples + total_batch_size: "auto" + # Total batch size + batch_size_per_gpu: 6 + # Batch size per GPU + lr: 0.0002 + # Learning rate + grad_clip_threshold: 1e6 + # no gradient clipping for defualt non-patch-based training + lr_decay: 0.7 + # LR decay rate + lr_rampup: 1000000 + # Rampup for learning rate, in number of samples + lr_decay_rate: 1e6 + # Learning rate decay threshold in number of samples, applied every lr_decay_rate samples. + patch_shape_x: 384 + patch_shape_y: 384 + # Patch size. Patch training is used if these dimensions differ from + # img_shape_x and img_shape_y. + patch_num: 2 + # Number of patches from a single sample. Total number of patches is + # patch_num * batch_size_global. + max_patch_per_gpu: 300 + # Maximum number of pataches a gpu can hold + +# Performance +perf: + fp_optimizations: amp-bf16 + # Floating point mode, one of ["fp32", "fp16", "amp-fp16", "amp-bf16"] + # "amp-{fp16,bf16}" activates Automatic Mixed Precision (AMP) with {float16,bfloat16} + dataloader_workers: 10 + # DataLoader worker processes + songunet_checkpoint_level: 0 # 0 means no checkpointing + # Gradient checkpointing level, value is number of layers to checkpoint + use_apex_gn: True + torch_compile: True + profile_mode: False +# I/O +io: + regression_checkpoint_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_regression_real + # Where to load the regression checkpoint + print_progress_freq: 2000 + # How often to print progress + save_checkpoint_freq: 250000 + # How often to save the checkpoints, measured in number of processed samples + visualization_freq: 250000 + # how often to visualize network outputs + validation_freq: 50000 + # how often to record the validation loss, measured in number of processed samples + validation_steps: 45 + # how many loss evaluations are used to compute the validation loss per checkpoint + checkpoint_dir: . \ No newline at end of file diff --git a/src/hirad/conf/training/era_real_regression.yaml b/src/hirad/conf/training/era_real_regression.yaml new file mode 100644 index 00000000..08cd6292 --- /dev/null +++ b/src/hirad/conf/training/era_real_regression.yaml @@ -0,0 +1,45 @@ +# Hyperparameters +hp: + training_duration: 3000000 + # Training duration based on the number of processed samples + total_batch_size: "auto" + # Total batch size -- based 8 per GPU -- 2 nodes is 2x8x4 -- see sbatch vars for how many gpus. diffusion need to point to the rgression. + batch_size_per_gpu: 4 + # Batch size per GPU + lr: 0.0002 + # Learning rate + grad_clip_threshold: null + # no gradient clipping for defualt non-patch-based training + lr_decay: 1 + # LR decay rate + lr_rampup: 0 + # Rampup for learning rate, in number of samples + lr_decay_rate: 5e5 + # Learning rate decay threshold in number of samples, applied every lr_decay_rate samples. + +# Performance +perf: + fp_optimizations: amp-bf16 + # Floating point mode, one of ["fp32", "fp16", "amp-fp16", "amp-bf16"] + # "amp-{fp16,bf16}" activates Automatic Mixed Precision (AMP) with {float16,bfloat16} + dataloader_workers: 8 + # DataLoader worker processes + songunet_checkpoint_level: 0 # 0 means no checkpointing + # Gradient checkpointing level, value is number of layers to checkpoint + use_apex_gn: True + torch_compile: True + profile_mode: False + +# I/O +io: + print_progress_freq: 1000 + # How often to print progress + save_checkpoint_freq: 100000 + # How often to save the checkpoints, measured in number of processed samples + visualization_freq: 50000 + # how often to visualize network output + validation_freq: 40000 + # how often to record the validation loss, measured in number of processed samples + validation_steps: 55 + # how many loss evaluations are used to compute the validation loss per checkpoint + checkpoint_dir: . \ No newline at end of file diff --git a/src/hirad/conf/training_era_real_diffusion_patched.yaml b/src/hirad/conf/training_era_real_diffusion_patched.yaml new file mode 100644 index 00000000..a22831a3 --- /dev/null +++ b/src/hirad/conf/training_era_real_diffusion_patched.yaml @@ -0,0 +1,27 @@ +hydra: + job: + chdir: true + name: era_real_diffusion + run: + dir: /capstor/scratch/cscs/pstamenk/outputs/training/${hydra:job.name} + +# Get defaults +defaults: + - _self_ + + # Dataset + - dataset/era_real + + # Model + - model/era_real_diffusion_patched + + - model_size/normal_real + + # Training + - training/era_real_diffusion_patched + + # Inference visualization + # - generation/era_cosmo_training_patched + + # Logging + - logging/era_real_diffusion \ No newline at end of file diff --git a/src/hirad/conf/training_era_real_regression.yaml b/src/hirad/conf/training_era_real_regression.yaml new file mode 100644 index 00000000..d2f538a9 --- /dev/null +++ b/src/hirad/conf/training_era_real_regression.yaml @@ -0,0 +1,28 @@ +hydra: + job: + chdir: true + name: era_real_regression + run: + dir: /capstor/scratch/cscs/pstamenk/outputs/training/${hydra:job.name} + + +# Get defaults +defaults: + - _self_ + + # Dataset + - dataset/era_real + + # Model + - model/era_real_regression + + - model_size/normal_real + + # Training + - training/era_real_regression + + # Inference visualization + # - generation/era_cosmo_training + + # Logging + - logging/era_real_regression diff --git a/src/hirad/snapshots.sh b/src/hirad/snapshots.sh index 16a12de4..a8ebba50 100644 --- a/src/hirad/snapshots.sh +++ b/src/hirad/snapshots.sh @@ -5,15 +5,15 @@ ### HARDWARE ### #SBATCH --partition=normal #SBATCH --nodes=1 -#SBATCH --ntasks-per-node=2 -#SBATCH --gpus-per-node=2 +#SBATCH --ntasks-per-node=1 +#SBATCH --gpus-per-node=1 #SBATCH --cpus-per-task=72 -#SBATCH --time=05:00:00 +#SBATCH --time=00:30:00 #SBATCH --no-requeue #SBATCH --exclusive ### OUTPUT ### -#SBATCH --output=./logs/plots_precip.log +#SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/snapshot.log ### ENVIRONMENT #### #SBATCH -A a161 @@ -30,21 +30,10 @@ export MASTER_ADDR export MASTER_PORT=29500 echo "Master port: $MASTER_PORT" -# Get number of physical cores using Python -# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") -# # Use SLURM_NTASKS (number of processes to be launched by torchrun) -# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} -# # Compute threads per process -# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) -# export OMP_NUM_THREADS=$OMP_THREADS -export OMP_NUM_THREADS=72 -# echo "Physical cores: $PHYSICAL_CORES" -# echo "Local processes: $LOCAL_PROCS" -# echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" +export OMP_NUM_THREADS=1 srun --environment=./ci/edf/modulus_env.toml bash -c " - pip install -e . --no-dependencies - - python src/hirad/eval/snapshots.py --config-name=generate_era_cosmo.yaml + source ../hirad_env/hirad/bin/activate + python src/hirad/eval/snapshots.py --config-name=snapshot_era_real.yaml " \ No newline at end of file From a7f16711217f49ec7a894b0805459086c8b7a3f0 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 5 Jan 2026 12:43:27 +0100 Subject: [PATCH 237/302] Refactor evaluation scripts for improved configuration handling and usability across different target datasets - Updated evaluation scripts to use argparse for configuration file input that contains eval realted information. - Updated evaluation scripts to use saved configuration files from generation folder for simplicity. - Refactored dataset initialization to utilize known_datasets to avoid initializing distributed environment and dataloader. - Enhanced error handling for missing configuration parameters and paths. - Modified plotting functions to accept grid configuration parameters to be able to use with different target datasets/grids. - Adjusted evaluation shell scripts (eval_precip.sh, eval_wind.sh, snapshots.sh) to reflect changes in configuration file paths and removed unnecessary distributed initialization code. --- src/hirad/conf/eval_real.yaml | 25 ++ src/hirad/datasets/__init__.py | 2 +- src/hirad/eval/__init__.py | 2 +- .../diurnal_cycle_precip_mean_wet-hour.py | 92 ++++--- src/hirad/eval/diurnal_cycle_precip_p99.py | 73 ++++-- src/hirad/eval/diurnal_cycle_temp_wind.py | 74 ++++-- src/hirad/eval/hist.py | 75 ++++-- src/hirad/eval/map_precip_stats.py | 104 +++++--- src/hirad/eval/map_wind_stats.py | 98 +++++--- src/hirad/eval/plotting.py | 56 ++++- src/hirad/eval/probability_of_exceedance.py | 81 +++++-- .../eval/probability_of_exceedance_wind.py | 71 ++++-- src/hirad/eval/snapshots.py | 228 ++++++++++-------- src/hirad/eval_precip.sh | 40 +-- src/hirad/eval_wind.sh | 36 +-- src/hirad/snapshots.sh | 19 +- 16 files changed, 694 insertions(+), 382 deletions(-) create mode 100644 src/hirad/conf/eval_real.yaml diff --git a/src/hirad/conf/eval_real.yaml b/src/hirad/conf/eval_real.yaml new file mode 100644 index 00000000..b4f87431 --- /dev/null +++ b/src/hirad/conf/eval_real.yaml @@ -0,0 +1,25 @@ +# Path to the inference output directory +inference_output_dir: '/capstor/scratch/cscs/pstamenk/outputs/generation/generate_era_real_8m' +results_dir_name: 'evaluation_maps' + +# Constants for evaluation depend on dataset specifics and should be modified accordingly + +conv_factor_hourly: 1000 # Convert precip of ERA5 from meters to mm/h +conv_factor: 24000 # Convert the precip of ERA5 from meters/h to mm/day +wet_threshold: 0.1 # Threshold for wet-hour in mm/h +log_interval: 24 # Log progress every N timesteps +land_sea_mask_path: '/users/pstamenk/HiRAD-Gen/lsm_real.npy' + +# Constants describing the grid for plotting +lat_start: -4.42 +lat_end: 3.36 +lat_step: 0.01 +lon_start: -6.82 +lon_end: 4.80 +lon_step: 0.01 +relax_zone: 19 +height: 704 +width: 1088 + +# List of channels to evaluate/plot - comment out if all channels are to be used +# plot_channels: ['2t', '10u', '10v', 'tp', 't_700', 'u_700', 'v_700', 'z_700', 'q_700', 'w_700'] \ No newline at end of file diff --git a/src/hirad/datasets/__init__.py b/src/hirad/datasets/__init__.py index dcfc8f7a..46e8bd4a 100644 --- a/src/hirad/datasets/__init__.py +++ b/src/hirad/datasets/__init__.py @@ -1,4 +1,4 @@ -from .dataset import init_train_valid_datasets_from_config, init_dataset_from_config, get_dataset_and_sampler_inference +from .dataset import init_train_valid_datasets_from_config, init_dataset_from_config, get_dataset_and_sampler_inference, known_datasets from .era5_cosmo import ERA5_COSMO from .era5_real import ERA5_REAL from .base import DownscalingDataset, ChannelMetadata, get_channels_from_strings, get_strings_from_channels diff --git a/src/hirad/eval/__init__.py b/src/hirad/eval/__init__.py index 16eff0ae..c3894a66 100644 --- a/src/hirad/eval/__init__.py +++ b/src/hirad/eval/__init__.py @@ -1,2 +1,2 @@ from .metrics import absolute_error, compute_mae, average_power_spectrum, crps -from .plotting import plot_map, plot_error_projection, plot_power_spectra, plot_scores_vs_t +from .plotting import plot_map, plot_error_projection, plot_power_spectra, plot_scores_vs_t, GridConfig diff --git a/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py b/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py index 03e7d702..cb1f0c00 100644 --- a/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py +++ b/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py @@ -1,4 +1,6 @@ import logging +import argparse +import yaml from datetime import datetime from pathlib import Path @@ -7,12 +9,10 @@ import numpy as np import torch import xarray as xr -from omegaconf import DictConfig, OmegaConf -from hirad.datasets import get_dataset_and_sampler_inference -from hirad.distributed import DistributedManager +from hirad.datasets import get_channels_from_strings, get_strings_from_channels, known_datasets from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, CONV_FACTOR, WET_THRESHOLD, LOG_INTERVAL, concat_and_group_diurnal +from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, concat_and_group_diurnal def save_plot(hour, means, stds, labels, ylabel, title, out_path): hrs = np.concatenate([hour.values, [24]]) @@ -35,25 +35,50 @@ def save_plot(hour, means, stds, labels, ylabel, title, out_path): plt.savefig(out_path) plt.close() -@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") -def main(cfg: DictConfig): +def main(cfg: dict): # Setup logging - DistributedManager.initialize() logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) + generation_dir = cfg.get("inference_output_dir", None) + if generation_dir is None: + logger.error("No inference_output_dir specified in config.") + return + + if not Path(generation_dir).exists() or not Path(generation_dir).is_dir(): + logger.error(f"Inference output directory {generation_dir} does not exist or is not a directory.") + return + + generation_config_path = Path(generation_dir) / ".hydra" / "config.yaml" + if not generation_config_path.exists(): + logger.error(f"Generation config file {generation_config_path} does not exist.") + return + + with open(generation_config_path, "r") as f: + gen_cfg = yaml.safe_load(f) + logger.info("Starting computations for diurnal cycle of precipitation amount and wet-hours") - times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + if cfg.get("times_range", None): + times = get_time_from_range(cfg.get("times_range"), time_format="%Y%m%d-%H%M") + elif cfg.get("times", None): + times = cfg.get("times") + elif gen_cfg.get("generation").get("times_range", None): + times = get_time_from_range(gen_cfg.get("generation").get("times_range"), time_format="%Y%m%d-%H%M") + elif gen_cfg.get("generation").get("times", None): + times = gen_cfg.get("generation").get("times") + else: + logger.error("No times or times_range specified in config or generation config.") + return datetimes = [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times] logger.info(f"Loaded {len(times)} timesteps to process") - ds_cfg = OmegaConf.to_container(cfg.dataset) - dataset, _ = get_dataset_and_sampler_inference( - ds_cfg, times, cfg.generation.get('has_lead_time', False) - ) + dataset_cfg = gen_cfg.get("dataset") + dataset_type = dataset_cfg.pop("type") + dataset = known_datasets[dataset_type](**dataset_cfg) + logger.info("Dataset initialized") # Location of the output from inference - out_root = Path(cfg.generation.io.output_path or './outputs') + out_root = Path(generation_dir) # Find channel indices indices = get_channel_indices(dataset) @@ -62,7 +87,7 @@ def main(cfg: DictConfig): logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") # Land-sea mask - land_mask = load_land_sea_mask() + land_mask = load_land_sea_mask(cfg.get("land_sea_mask_path"), cfg.get("height"), cfg.get("width")) # Prepare lists to collect DataArrays target_precip, baseline_precip, pred_precip, mean_pred_precip = [], [], [], [] @@ -71,11 +96,11 @@ def main(cfg: DictConfig): # Collect data for idx, ts in enumerate(times, 1): dt = datetimes[idx-1] - target = torch.load(out_root/ts/f"{ts}-target", weights_only=False)[tp_out] * CONV_FACTOR - baseline = torch.load(out_root/ts/f"{ts}-baseline", weights_only=False)[tp_in] * CONV_FACTOR # / 6. # 6 because 1h -> accumulation period is 6h in hourly ERA5 dataset - preds = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False)[:, tp_out, :, :] * CONV_FACTOR + target = torch.load(out_root/ts/f"{ts}-target", weights_only=False)[tp_out] * cfg.get("conv_factor") + baseline = torch.load(out_root/ts/f"{ts}-baseline", weights_only=False)[tp_in] * cfg.get("conv_factor") + preds = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False)[:, tp_out, :, :] * cfg.get("conv_factor") try: - mean_pred = torch.load(out_root/ts/f"{ts}-regression-prediction", weights_only=False)[tp_out] * CONV_FACTOR + mean_pred = torch.load(out_root/ts/f"{ts}-regression-prediction", weights_only=False)[tp_out] * cfg.get("conv_factor") except: mean_pred = None @@ -100,14 +125,14 @@ def main(cfg: DictConfig): if mean_pred is not None: mean_pred_precip.append(da_mean_pred.mean(dim=("lat","lon")).assign_coords(time=dt)) - # Wet-hour fraction, i.e., freq(precip) > WET_THRESHOLD - target_wet.append(((da_target / 24 > WET_THRESHOLD).mean().assign_coords(time=dt))) - baseline_wet.append(((da_baseline / 24 > WET_THRESHOLD).mean().assign_coords(time=dt))) - pred_wet.append(((da_preds / 24> WET_THRESHOLD).mean(dim=("lat","lon")).assign_coords(time=dt))) + # Wet-hour fraction, i.e., freq(precip) > wet_threshold + target_wet.append(((da_target / 24 > cfg.get("wet_threshold")).mean().assign_coords(time=dt))) + baseline_wet.append(((da_baseline / 24 > cfg.get("wet_threshold")).mean().assign_coords(time=dt))) + pred_wet.append(((da_preds / 24> cfg.get("wet_threshold")).mean(dim=("lat","lon")).assign_coords(time=dt))) if mean_pred is not None: - mean_pred_wet.append(((da_mean_pred / 24 > WET_THRESHOLD).mean().assign_coords(time=dt))) + mean_pred_wet.append(((da_mean_pred / 24 > cfg.get("wet_threshold")).mean().assign_coords(time=dt))) - if idx % LOG_INTERVAL == 0 or idx == len(times): + if idx % cfg.get("log_interval") == 0 or idx == len(times): logger.info(f"Processed {idx}/{len(times)} timesteps ({ts})") # Compute diurnal means and stds @@ -124,26 +149,35 @@ def main(cfg: DictConfig): wet_mean_pred_mean, _ = concat_and_group_diurnal(mean_pred_wet, scale=100.0) # Generate plots + output_path = out_root / cfg.get("results_dir_name", "evaluation_maps") + output_path.mkdir(parents=True, exist_ok=True) save_plot( amount_target_mean.hour, [amount_target_mean, amount_baseline_mean, amount_pred_mean, amount_mean_pred_mean] if mean_pred_precip else [amount_target_mean, amount_baseline_mean, amount_pred_mean], [None, None, amount_pred_std, None] if mean_pred_precip else [None, None, amount_pred_std], - ['COSMO-2 Analysis','ERA5','CorrDiff ± Std(Members)', 'Regression Prediction'] if mean_pred_precip else ['COSMO-2 Analysis','ERA5','CorrDiff ± Std(Members)'], + ['Target','Input','CorrDiff ± Std(Members)', 'Regression Prediction'] if mean_pred_precip else ['Target','Input','CorrDiff ± Std(Members)'], 'Precipitation (mm/day)', 'Diurnal Cycle of Precip Amount', - out_root / 'diurnal_cycle_precip_amount.png' + output_path / 'diurnal_cycle_precip_amount.png' ) save_plot( wet_target_mean.hour, [wet_target_mean, wet_baseline_mean, wet_pred_mean, wet_mean_pred_mean] if mean_pred_wet else [wet_target_mean, wet_baseline_mean, wet_pred_mean], [None, None, wet_pred_std, None] if mean_pred_wet else [None, None, wet_pred_std], - ['COSMO-2 Analysis','ERA5','CorrDiff ± Std(Members)', 'Regression Prediction'] if mean_pred_wet else ['COSMO-2 Analysis','ERA5','CorrDiff ± Std(Members)'], + ['Target','Input','CorrDiff ± Std(Members)', 'Regression Prediction'] if mean_pred_wet else ['Target','Input','CorrDiff ± Std(Members)'], 'Wet-Hour Fraction [%]', 'Diurnal Cycle of Wet-Hours (>0.1 mm/h)', - out_root / 'diurnal_cycle_precip_wethours.png' + output_path / 'diurnal_cycle_precip_wethours.png' ) logger.info("Plots saved.") if __name__ == '__main__': - main() + parser = argparse.ArgumentParser() + parser.add_argument("--config-name", help="Path to YAML config file for evaluation.") + args = parser.parse_args() + + with open(args.config_name, "r") as f: + cfg = yaml.safe_load(f) + + main(cfg) \ No newline at end of file diff --git a/src/hirad/eval/diurnal_cycle_precip_p99.py b/src/hirad/eval/diurnal_cycle_precip_p99.py index 9c54eb7a..abf31464 100644 --- a/src/hirad/eval/diurnal_cycle_precip_p99.py +++ b/src/hirad/eval/diurnal_cycle_precip_p99.py @@ -6,6 +6,8 @@ period is long, this can still be a lot of data and thus an OOM error can occur. """ import logging +import argparse +import yaml from datetime import datetime from pathlib import Path @@ -13,13 +15,11 @@ import matplotlib.pyplot as plt import numpy as np import torch -from omegaconf import DictConfig, OmegaConf import xarray as xr -from hirad.datasets import get_dataset_and_sampler_inference -from hirad.distributed import DistributedManager +from hirad.datasets import get_channels_from_strings, get_strings_from_channels, known_datasets from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, CONV_FACTOR +from hirad.eval.plotting import get_channel_indices, load_land_sea_mask def save_plot(hours, lines, labels, ylabel, title, out_path): @@ -46,26 +46,50 @@ def save_plot(hours, lines, labels, ylabel, title, out_path): plt.close() -@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") -def main(cfg: DictConfig): +def main(cfg: dict): # Setup logging - DistributedManager.initialize() logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) + generation_dir = cfg.get("inference_output_dir", None) + if generation_dir is None: + logger.error("No inference_output_dir specified in config.") + return + + if not Path(generation_dir).exists() or not Path(generation_dir).is_dir(): + logger.error(f"Inference output directory {generation_dir} does not exist or is not a directory.") + return + + generation_config_path = Path(generation_dir) / ".hydra" / "config.yaml" + if not generation_config_path.exists(): + logger.error(f"Generation config file {generation_config_path} does not exist.") + return + + with open(generation_config_path, "r") as f: + gen_cfg = yaml.safe_load(f) + logger.info("Starting computation for diurnal cycle of 99th-percentile of precipitation") - times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + if cfg.get("times_range", None): + times = get_time_from_range(cfg.get("times_range"), time_format="%Y%m%d-%H%M") + elif cfg.get("times", None): + times = cfg.get("times") + elif gen_cfg.get("generation").get("times_range", None): + times = get_time_from_range(gen_cfg.get("generation").get("times_range"), time_format="%Y%m%d-%H%M") + elif gen_cfg.get("generation").get("times", None): + times = gen_cfg.get("generation").get("times") + else: + logger.error("No times or times_range specified in config or generation config.") + return logger.info(f"Loaded {len(times)} timesteps to process") # Initialize dataset - ds_cfg = OmegaConf.to_container(cfg.dataset) - dataset, _ = get_dataset_and_sampler_inference( - ds_cfg, times, cfg.generation.get('has_lead_time', False) - ) - logger.info("Dataset and sampler initialized") + dataset_cfg = gen_cfg.get("dataset") + dataset_type = dataset_cfg.pop("type") + dataset = known_datasets[dataset_type](**dataset_cfg) + logger.info("Dataset initialized") # Output root - out_root = Path(cfg.generation.io.output_path or './outputs') + out_root = Path(generation_dir) # Find channel indices indices = get_channel_indices(dataset) @@ -74,7 +98,7 @@ def main(cfg: DictConfig): logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") # Land-sea mask - land_mask = load_land_sea_mask() + land_mask = load_land_sea_mask(cfg.get("land_sea_mask_path"), cfg.get("height"), cfg.get("width")) # Storage for diurnal cycles pct99_mean = {} @@ -87,7 +111,7 @@ def main(cfg: DictConfig): data_list = [] try: for ts in times: - data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode in ['target','regression-prediction'] else tp_in] * CONV_FACTOR * land_mask + data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode in ['target','regression-prediction'] else tp_in] * cfg.get("conv_factor") * land_mask data_list.append(data) except: logger.error(f"Error loading data for mode {mode}. Skipping.") @@ -114,7 +138,7 @@ def main(cfg: DictConfig): # Load all prediction data at once into xarray pred_data_list = [] for ts in times: - preds = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) * CONV_FACTOR # [n_members, n_channels, lat, lon] + preds = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) * cfg.get("conv_factor") # [n_members, n_channels, lat, lon] # Extract precipitation channel and convert to xarray for proper broadcasting tp_data = preds[:, tp_out] # [n_members, lat, lon] tp_da = xr.DataArray(tp_data, dims=['member', 'lat', 'lon']) @@ -153,8 +177,10 @@ def cycle_fn(x): pct99_lines.append(cycle_fn(pct99_mean['regression-prediction'])) # Plot combined diurnal 99th-percentile cycle - labels = ['COSMO-2 Analysis', 'ERA5', 'CorrDiff 99th Pct ± Std', 'Regression Prediction'] if 'regression-prediction' in pct99_mean else ['COSMO-2 Analysis', 'ERA5', 'CorrDiff 99th Pct ± Std'] - fn = out_root/'diurnal_cycle_precip_99th_percentile.png' + labels = ['Target', 'Input', 'CorrDiff 99th Pct ± Std', 'Regression Prediction'] if 'regression-prediction' in pct99_mean else ['Target', 'Input', 'CorrDiff 99th Pct ± Std'] + output_path = out_root / cfg.get("results_dir_name", "evaluation_maps") + output_path.mkdir(parents=True, exist_ok=True) + fn = output_path / 'diurnal_cycle_precip_99th_percentile.png' save_plot( hrs_c, pct99_lines, @@ -166,4 +192,11 @@ def cycle_fn(x): logger.info(f"Combined plot saved: {fn}") if __name__ == '__main__': - main() + parser = argparse.ArgumentParser() + parser.add_argument("--config-name", help="Path to YAML config file for evaluation.") + args = parser.parse_args() + + with open(args.config_name, "r") as f: + cfg = yaml.safe_load(f) + + main(cfg) \ No newline at end of file diff --git a/src/hirad/eval/diurnal_cycle_temp_wind.py b/src/hirad/eval/diurnal_cycle_temp_wind.py index 5b4c36f4..1f7470d5 100644 --- a/src/hirad/eval/diurnal_cycle_temp_wind.py +++ b/src/hirad/eval/diurnal_cycle_temp_wind.py @@ -1,4 +1,6 @@ import logging +import argparse +import yaml from datetime import datetime from pathlib import Path @@ -7,31 +9,54 @@ import numpy as np import torch import xarray as xr -from omegaconf import DictConfig, OmegaConf -from hirad.datasets import get_dataset_and_sampler_inference -from hirad.distributed import DistributedManager +from hirad.datasets import get_channels_from_strings, get_strings_from_channels, known_datasets from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, LOG_INTERVAL, concat_and_group_diurnal +from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, concat_and_group_diurnal -@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") -def main(cfg: DictConfig): +def main(cfg: dict): # Initialize - DistributedManager.initialize() logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) + generation_dir = cfg.get("inference_output_dir", None) + if generation_dir is None: + logger.error("No inference_output_dir specified in config.") + return + + if not Path(generation_dir).exists() or not Path(generation_dir).is_dir(): + logger.error(f"Inference output directory {generation_dir} does not exist or is not a directory.") + return + + generation_config_path = Path(generation_dir) / ".hydra" / "config.yaml" + if not generation_config_path.exists(): + logger.error(f"Generation config file {generation_config_path} does not exist.") + return + + with open(generation_config_path, "r") as f: + gen_cfg = yaml.safe_load(f) + # Load times logger.info("Starting computation for diurnal cycles of 2m temperature and windspeed") - times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + if cfg.get("times_range", None): + times = get_time_from_range(cfg.get("times_range"), time_format="%Y%m%d-%H%M") + elif cfg.get("times", None): + times = cfg.get("times") + elif gen_cfg.get("generation").get("times_range", None): + times = get_time_from_range(gen_cfg.get("generation").get("times_range"), time_format="%Y%m%d-%H%M") + elif gen_cfg.get("generation").get("times", None): + times = gen_cfg.get("generation").get("times") + else: + logger.error("No times or times_range specified in config or generation config.") + return datetimes = [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times] logger.info(f"Loaded {len(times)} timesteps to process") # Dataset - ds_cfg = OmegaConf.to_container(cfg.dataset) - dataset, _ = get_dataset_and_sampler_inference( - ds_cfg, times, cfg.generation.get('has_lead_time', False) - ) + dataset_cfg = gen_cfg.get("dataset") + dataset_type = dataset_cfg.pop("type") + dataset = known_datasets[dataset_type](**dataset_cfg) + logger.info("Dataset initialized") # Indices for channels indices = get_channel_indices(dataset) @@ -49,12 +74,12 @@ def main(cfg: DictConfig): v_in = in_ch.get('10v', v_out) # Output path - out_root = Path(cfg.generation.io.output_path or './outputs') + out_root = Path(generation_dir) def load(ts, fn): return torch.load(out_root/ts/fn, weights_only=False) # Land-sea mask - land_mask = load_land_sea_mask() + land_mask = load_land_sea_mask(cfg.get("land_sea_mask_path"), cfg.get("height"), cfg.get("width")) # Prepare lists to collect DataArrays target_temp, baseline_temp, pred_temp, mean_pred_temp = [], [], [], [] @@ -102,7 +127,7 @@ def mean_over_land(data, dims, coords, time_coord): mean_pred_wind.append(mean_over_land( np.hypot(regression_pred[u_out], regression_pred[v_out]), ("lat","lon"), land_mask.coords, dt)) - if idx % LOG_INTERVAL == 0 or idx == len(times): + if idx % cfg.get("log_interval") == 0 or idx == len(times): logger.info(f"Processed {idx}/{len(times)} timesteps ({ts})") # Compute diurnal means and stds @@ -140,9 +165,11 @@ def save_plot(hour, means, stds, labels, ylabel, title, out_path): plt.close() data = [temp_target_mean, temp_baseline_mean, temp_pred_mean, temp_mean_pred_mean] if mean_pred_temp else [temp_target_mean, temp_baseline_mean, temp_pred_mean] - labels = ['COSMO-2 Analysis', 'ERA5', 'CorrDiff ± Std(Members)', 'Regression Prediction'] if mean_pred_temp else ['COSMO-2 Analysis', 'ERA5', 'CorrDiff ± Std(Members)'] + labels = ['Target', 'Input', 'CorrDiff ± Std(Members)', 'Regression Prediction'] if mean_pred_temp else ['Target', 'Input', 'CorrDiff ± Std(Members)'] stds = [None, None, temp_pred_std, None] if mean_pred_temp else [None, None, temp_pred_std] + output_path = out_root / cfg.get("results_dir_name", "evaluation_maps") + output_path.mkdir(parents=True, exist_ok=True) # Generate plots save_plot( temp_target_mean.hour, @@ -151,11 +178,11 @@ def save_plot(hour, means, stds, labels, ylabel, title, out_path): labels, '2m Temperature [°C]', 'Diurnal Cycle of 2m Temperature', - out_root / 'diurnal_cycle_2t.png' + output_path / 'diurnal_cycle_2t.png' ) data = [wind_target_mean, wind_baseline_mean, wind_pred_mean, wind_mean_pred_mean] if mean_pred_wind else [wind_target_mean, wind_baseline_mean, wind_pred_mean] - labels = ['COSMO-2 Analysis', 'ERA5', 'CorrDiff ± Std(Members)', 'Regression Prediction'] if mean_pred_wind else ['COSMO-2 Analysis', 'ERA5', 'CorrDiff ± Std(Members)'] + labels = ['Target', 'Input', 'CorrDiff ± Std(Members)', 'Regression Prediction'] if mean_pred_wind else ['Target', 'Input', 'CorrDiff ± Std(Members)'] stds = [None, None, wind_pred_std, None] if mean_pred_wind else [None, None, wind_pred_std] save_plot( @@ -165,10 +192,17 @@ def save_plot(hour, means, stds, labels, ylabel, title, out_path): labels, 'Windspeed [m/s]', 'Diurnal Cycle of Windspeed', - out_root / 'diurnal_cycle_windspeed.png' + output_path / 'diurnal_cycle_windspeed.png' ) logger.info("Plots saved.") if __name__ == '__main__': - main() + parser = argparse.ArgumentParser() + parser.add_argument("--config-name", help="Path to YAML config file for evaluation.") + args = parser.parse_args() + + with open(args.config_name, "r") as f: + cfg = yaml.safe_load(f) + + main(cfg) \ No newline at end of file diff --git a/src/hirad/eval/hist.py b/src/hirad/eval/hist.py index 78d7395b..947d557a 100644 --- a/src/hirad/eval/hist.py +++ b/src/hirad/eval/hist.py @@ -5,19 +5,19 @@ over land. """ import logging +import argparse +import yaml from pathlib import Path import hydra import matplotlib.pyplot as plt import numpy as np import torch -from omegaconf import DictConfig, OmegaConf import xarray as xr -from hirad.datasets import get_dataset_and_sampler_inference -from hirad.distributed import DistributedManager +from hirad.datasets import get_channels_from_strings, get_strings_from_channels, known_datasets from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, CONV_FACTOR_HOURLY, LOG_INTERVAL +from hirad.eval.plotting import get_channel_indices, load_land_sea_mask def save_distribution_plot(hist_data_dict, bin_edges, labels, colors, title, ylabel, out_path, percentiles_data=None): @@ -100,26 +100,50 @@ def save_distribution_plot(hist_data_dict, bin_edges, labels, colors, title, yla plt.close() -@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") -def main(cfg: DictConfig): +def main(cfg: dict): # Setup logging - DistributedManager.initialize() logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) + generation_dir = cfg.get("inference_output_dir", None) + if generation_dir is None: + logger.error("No inference_output_dir specified in config.") + return + + if not Path(generation_dir).exists() or not Path(generation_dir).is_dir(): + logger.error(f"Inference output directory {generation_dir} does not exist or is not a directory.") + return + + generation_config_path = Path(generation_dir) / ".hydra" / "config.yaml" + if not generation_config_path.exists(): + logger.error(f"Generation config file {generation_config_path} does not exist.") + return + + with open(generation_config_path, "r") as f: + gen_cfg = yaml.safe_load(f) + logger.info("Starting computation for domain-mean precipitation distribution over land") - times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + if cfg.get("times_range", None): + times = get_time_from_range(cfg.get("times_range"), time_format="%Y%m%d-%H%M") + elif cfg.get("times", None): + times = cfg.get("times") + elif gen_cfg.get("generation").get("times_range", None): + times = get_time_from_range(gen_cfg.get("generation").get("times_range"), time_format="%Y%m%d-%H%M") + elif gen_cfg.get("generation").get("times", None): + times = gen_cfg.get("generation").get("times") + else: + logger.error("No times or times_range specified in config or generation config.") + return logger.info(f"Loaded {len(times)} timesteps to process") # Initialize dataset - ds_cfg = OmegaConf.to_container(cfg.dataset) - dataset, _ = get_dataset_and_sampler_inference( - ds_cfg, times, cfg.generation.get('has_lead_time', False) - ) + dataset_cfg = gen_cfg.get("dataset") + dataset_type = dataset_cfg.pop("type") + dataset = known_datasets[dataset_type](**dataset_cfg) logger.info("Dataset and sampler initialized") # Output root - out_root = Path(cfg.generation.io.output_path or './outputs') + out_root = Path(generation_dir) # Find channel indices indices = get_channel_indices(dataset) @@ -128,7 +152,7 @@ def main(cfg: DictConfig): logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") # Land-sea mask - land_mask = load_land_sea_mask() + land_mask = load_land_sea_mask(cfg.get("land_sea_mask_path"), cfg.get("height"), cfg.get("width")) # Define histogram bins bins = np.logspace(-1, 3.3, 200) # Log-spaced bins for precipitation @@ -147,10 +171,10 @@ def main(cfg: DictConfig): try: for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: + if i % cfg.get("log_interval") == 0: logger.info(f"Processing timestep {i+1}/{len(times)}") - data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode in ['target', 'regression-prediction'] else tp_in] * CONV_FACTOR_HOURLY * land_mask + data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode in ['target', 'regression-prediction'] else tp_in] * cfg.get("conv_factor_hourly") * land_mask # Apply scaling factor for baseline # if mode == 'baseline': @@ -179,10 +203,10 @@ def main(cfg: DictConfig): all_member_values = [] for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: + if i % cfg.get("log_interval") == 0: logger.info(f"Processing timestep {i+1}/{len(times)}") - preds = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) * CONV_FACTOR_HOURLY # [n_members, n_channels, lat, lon] + preds = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) * cfg.get("conv_factor_hourly") # [n_members, n_channels, lat, lon] if n_members is None: n_members = preds.shape[0] @@ -233,10 +257,12 @@ def main(cfg: DictConfig): } # Create distribution plots - labels = ['COSMO-2 Analysis', 'ERA5', 'Regression Prediction', 'CorrDiff Ensemble'] if 'regression-prediction' in hist_data else ['COSMO-2 Analysis', 'ERA5', 'CorrDiff Ensemble'] + labels = ['Target', 'Input', 'Regression Prediction', 'CorrDiff Ensemble'] if 'regression-prediction' in hist_data else ['Target', 'Input', 'CorrDiff Ensemble'] colors = ['blue', 'orange', 'red', 'green'] if 'regression-prediction' in hist_data else ['blue', 'orange', 'green'] - fn = out_root / 'precipitation_distribution_over_land.png' + output_path = out_root / cfg.get("results_dir_name", "evaluation_maps") + output_path.mkdir(parents=True, exist_ok=True) + fn = output_path / 'precipitation_distribution_over_land.png' save_distribution_plot( hist_data, bins, @@ -251,4 +277,11 @@ def main(cfg: DictConfig): if __name__ == '__main__': - main() \ No newline at end of file + parser = argparse.ArgumentParser() + parser.add_argument("--config-name", help="Path to YAML config file for evaluation.") + args = parser.parse_args() + + with open(args.config_name, "r") as f: + cfg = yaml.safe_load(f) + + main(cfg) \ No newline at end of file diff --git a/src/hirad/eval/map_precip_stats.py b/src/hirad/eval/map_precip_stats.py index 257cfcda..eba2fd17 100644 --- a/src/hirad/eval/map_precip_stats.py +++ b/src/hirad/eval/map_precip_stats.py @@ -1,19 +1,18 @@ import logging +import argparse +import yaml from datetime import datetime from pathlib import Path import hydra import numpy as np import torch -from omegaconf import DictConfig, OmegaConf import xarray as xr -from hirad.datasets import get_dataset_and_sampler_inference -from hirad.distributed import DistributedManager +from hirad.datasets import get_channels_from_strings, get_strings_from_channels, known_datasets from hirad.utils.function_utils import get_time_from_range from hirad.eval.plotting import ( - plot_map_precipitation, plot_map, get_channel_indices, - CONV_FACTOR, LOG_INTERVAL, WET_THRESHOLD + plot_map_precipitation, plot_map, get_channel_indices, GridConfig ) @@ -51,54 +50,90 @@ def apply_statistic(data, stat_type, stat_param): daily = data.resample(time="1D").sum("time") return consecutive_spell(daily >= 1.0) if stat_type == 'weth_freq': - return (data / 24 > WET_THRESHOLD).mean(dim='time') * 100 + return (data / 24 > cfg.get("wet_threshold")).mean(dim='time') * 100 raise ValueError(f"Unsupported statistic type: {stat_type}") -def plot_stat_map(data, filename, stat_config, label): +def plot_stat_map(data, filename, stat_config, label, grid_cfg): """Plot a single statistic map with appropriate styling.""" if stat_config['type'] == 'weth_freq': plot_map( data, filename, title=f'{label}: {stat_config["title_stat"]} (%)', - label='Wet-Hour Frequency [%]', vmin=0, vmax=30, cmap='PuBu', extend='max' + label='Wet-Hour Frequency [%]', vmin=0, vmax=30, cmap='PuBu', extend='max', grid_cfg=grid_cfg ) elif stat_config['type'] == 'cdd': plot_map( data, filename, title=f'{label}: {stat_config["title_stat"]}', - label='Days', vmin=0, vmax=60, cmap='viridis', extend='max' + label='Days', vmin=0, vmax=60, cmap='viridis', extend='max', grid_cfg=grid_cfg ) elif stat_config['type'] == 'cwd': plot_map( data, filename, title=f'{label}: {stat_config["title_stat"]}', - label='Days', vmin=0, vmax=20, cmap='viridis', extend='max' + label='Days', vmin=0, vmax=20, cmap='viridis', extend='max', grid_cfg=grid_cfg ) else: plot_map_precipitation( data, filename, title=f'{label}: {stat_config["title_stat"]} Precipitation', - threshold=stat_config['threshold'], rfac=1.0 + threshold=stat_config['threshold'], rfac=1.0, grid_cfg=grid_cfg ) -@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") -def main(cfg: DictConfig): +def main(cfg: dict): # Setup and config - DistributedManager.initialize() logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) + grid_cfg = GridConfig( + lat = np.arange(cfg.get("lat_start"), cfg.get("lat_end") + cfg.get("lat_step"), cfg.get("lat_step")), + lon = np.arange(cfg.get("lon_start"), cfg.get("lon_end") + cfg.get("lon_step"), cfg.get("lon_step")), + height = cfg.get("height"), + width = cfg.get("width"), + relax_zone = cfg.get("relax_zone") + ) + + generation_dir = cfg.get("inference_output_dir", None) + if generation_dir is None: + logger.error("No inference_output_dir specified in config.") + return + + if not Path(generation_dir).exists() or not Path(generation_dir).is_dir(): + logger.error(f"Inference output directory {generation_dir} does not exist or is not a directory.") + return + + generation_config_path = Path(generation_dir) / ".hydra" / "config.yaml" + if not generation_config_path.exists(): + logger.error(f"Generation config file {generation_config_path} does not exist.") + return + + with open(generation_config_path, "r") as f: + gen_cfg = yaml.safe_load(f) + logger.info("Starting precipitation statistics generation") - times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + if cfg.get("times_range", None): + times = get_time_from_range(cfg.get("times_range"), time_format="%Y%m%d-%H%M") + elif cfg.get("times", None): + times = cfg.get("times") + elif gen_cfg.get("generation").get("times_range", None): + times = get_time_from_range(gen_cfg.get("generation").get("times_range"), time_format="%Y%m%d-%H%M") + elif gen_cfg.get("generation").get("times", None): + times = gen_cfg.get("generation").get("times") + else: + logger.error("No times or times_range specified in config or generation config.") + return logger.info(f"Processing {len(times)} timesteps") - ds_cfg = OmegaConf.to_container(cfg.dataset) - dataset, _ = get_dataset_and_sampler_inference( - ds_cfg, times, cfg.generation.get('has_lead_time', False) - ) - out_root = Path(cfg.generation.io.output_path or './outputs') + dataset_cfg = gen_cfg.get("dataset") + dataset_type = dataset_cfg.pop("type") + dataset = known_datasets[dataset_type](**dataset_cfg) + logger.info("Dataset initialized") + + out_root = Path(generation_dir) + output_path = out_root / cfg.get("results_dir_name", "evaluation_maps") + output_path.mkdir(parents=True, exist_ok=True) indices = get_channel_indices(dataset) tp_out = indices['output']['tp'] tp_in = indices['input'].get('tp', tp_out) @@ -128,8 +163,8 @@ def main(cfg: DictConfig): # Target and baseline modes basic_modes = { - 'target': (tp_out, 'COSMO-2 Analysis'), - 'baseline': (tp_in, 'ERA5'), + 'target': (tp_out, 'Target'), + 'baseline': (tp_in, 'Input'), 'regression-prediction': (tp_out, 'Regression Prediction') } logger.info(f"Generating {len(stat_configs)} statistics for {len(basic_modes)} basic modes + predictions") @@ -140,9 +175,9 @@ def main(cfg: DictConfig): data_list = [] try: for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: + if i % cfg.get("log_interval") == 0: logger.info(f"Loading {mode} timestep {i+1}/{len(times)}: {ts}") - data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False) * CONV_FACTOR + data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False) * cfg.get("conv_factor") data_list.append(data[tp_channel]) except: logger.warning(f"{mode} not available, skipping") @@ -158,9 +193,9 @@ def main(cfg: DictConfig): for stat_config in stat_configs: logger.info(f"Computing {stat_config['title_stat']} for {mode}...") result = apply_statistic(mode_data, stat_config['type'], stat_config['param']) - map_output_dir = out_root / f"maps_{stat_config['stat_name']}" + map_output_dir = output_path / f"maps_{stat_config['stat_name']}" map_output_dir.mkdir(parents=True, exist_ok=True) - plot_stat_map(result.values, str(map_output_dir / f'{mode}_{stat_config["stat_name"]}'), stat_config, label) + plot_stat_map(result.values, str(map_output_dir / f'{mode}_{stat_config["stat_name"]}'), stat_config, label, grid_cfg) # Predictions mode: process each member separately to save memory logger.info("Processing predictions mode...") @@ -173,9 +208,9 @@ def main(cfg: DictConfig): # Load all timesteps for this member data_list = [] for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: + if i % cfg.get("log_interval") == 0: logger.info(f"Loading prediction member {member_idx} timestep {i+1}/{len(times)}: {ts}") - pred_data = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) * CONV_FACTOR + pred_data = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) * cfg.get("conv_factor") data_list.append(pred_data[member_idx, tp_out]) member_data = xr.DataArray( np.stack(data_list, axis=0), @@ -189,14 +224,21 @@ def main(cfg: DictConfig): member_result = apply_statistic(member_data, stat_config['type'], stat_config['param']) # Create map - map_output_dir = out_root / f"maps_{stat_config['stat_name']}" + map_output_dir = output_path / f"maps_{stat_config['stat_name']}" map_output_dir.mkdir(parents=True, exist_ok=True) member_filename = str(map_output_dir / f'prediction_member_{member_idx:02d}_{stat_config["stat_name"]}') member_label = f'CorrDiff Member {member_idx+1}' - plot_stat_map(member_result.values, member_filename, stat_config, member_label) + plot_stat_map(member_result.values, member_filename, stat_config, member_label, grid_cfg) logger.info("All precipitation statistics maps generated successfully") if __name__ == '__main__': - main() + parser = argparse.ArgumentParser() + parser.add_argument("--config-name", help="Path to YAML config file for evaluation.") + args = parser.parse_args() + + with open(args.config_name, "r") as f: + cfg = yaml.safe_load(f) + + main(cfg) \ No newline at end of file diff --git a/src/hirad/eval/map_wind_stats.py b/src/hirad/eval/map_wind_stats.py index ac632243..32435b34 100644 --- a/src/hirad/eval/map_wind_stats.py +++ b/src/hirad/eval/map_wind_stats.py @@ -1,15 +1,15 @@ import logging +import argparse +import yaml from pathlib import Path import hydra import numpy as np import torch -from omegaconf import DictConfig, OmegaConf -from hirad.datasets import get_dataset_and_sampler_inference -from hirad.distributed import DistributedManager +from hirad.datasets import get_channels_from_strings, get_strings_from_channels, known_datasets from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import plot_map, get_channel_indices, LOG_INTERVAL +from hirad.eval.plotting import plot_map, get_channel_indices, GridConfig def compute_wind_speed(u, v): @@ -121,73 +121,107 @@ def apply_wind_statistic_streaming(times, out_root, mode, u_channel, v_channel, return accumulator / count -def plot_wind_stat_map(data, filename, stat_config, label): +def plot_wind_stat_map(data, filename, stat_config, label, grid_cfg): """Plot wind statistic map.""" if stat_config['type'] == 'mean_speed': plot_map( data, filename, title=f'{label}: {stat_config["title_stat"]}', - label='Wind Speed [m/s]', vmin=0, vmax=10, cmap='inferno', extend='max' + label='Wind Speed [m/s]', vmin=0, vmax=10, cmap='inferno', extend='max', grid_cfg=grid_cfg ) elif stat_config['type'] == 'max_speed': plot_map( data, filename, title=f'{label}: {stat_config["title_stat"]}', - label='Wind Speed [m/s]', vmin=0, vmax=30, cmap='inferno', extend='max' + label='Wind Speed [m/s]', vmin=0, vmax=30, cmap='inferno', extend='max', grid_cfg=grid_cfg ) elif stat_config['type'] == 'wind_power': plot_map( data, filename, title=f'{label}: {stat_config["title_stat"]}', - label='Wind Power Density [m³/s³]', vmin=0, vmax=1000, cmap='plasma', extend='max' + label='Wind Power Density [m³/s³]', vmin=0, vmax=1000, cmap='plasma', extend='max', grid_cfg=grid_cfg ) elif stat_config['type'] in ['calm_freq', 'light_breeze_freq', 'moderate_breeze_freq', 'strong_breeze_freq', 'gale_freq']: plot_map( data, filename, title=f'{label}: {stat_config["title_stat"]}', - label='Frequency [%]', vmin=0, vmax=80, cmap='GnBu', extend='max' + label='Frequency [%]', vmin=0, vmax=80, cmap='GnBu', extend='max', grid_cfg=grid_cfg ) elif stat_config['type'] == 'prevailing_direction': plot_map( data, filename, title=f'{label}: {stat_config["title_stat"]}', - label='Direction [degrees from N]', vmin=0, vmax=360, cmap='twilight', extend='neither' + label='Direction [degrees from N]', vmin=0, vmax=360, cmap='twilight', extend='neither', grid_cfg=grid_cfg ) elif stat_config['type'] == 'direction_variability': plot_map( data, filename, title=f'{label}: {stat_config["title_stat"]}', - label='Circular Std Dev [degrees]', vmin=20, vmax=140, cmap='viridis', extend='max' + label='Circular Std Dev [degrees]', vmin=20, vmax=140, cmap='viridis', extend='max', grid_cfg=grid_cfg ) elif stat_config['type'] in ['mean_u', 'mean_v']: plot_map( data, filename, title=f'{label}: {stat_config["title_stat"]}', - label='Wind Component [m/s]', vmin=-5, vmax=5, cmap='RdBu_r', extend='both' + label='Wind Component [m/s]', vmin=-5, vmax=5, cmap='RdBu_r', extend='both', grid_cfg=grid_cfg ) else: plot_map( data, filename, title=f'{label}: {stat_config["title_stat"]}', - label='Value', vmin=None, vmax=None, cmap='viridis', extend='neither' + label='Value', vmin=None, vmax=None, cmap='viridis', extend='neither', grid_cfg=grid_cfg ) -@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") -def main(cfg: DictConfig): - DistributedManager.initialize() +def main(cfg: dict): logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) + grid_cfg = GridConfig( + lat = np.arange(cfg.get("lat_start"), cfg.get("lat_end") + cfg.get("lat_step"), cfg.get("lat_step")), + lon = np.arange(cfg.get("lon_start"), cfg.get("lon_end") + cfg.get("lon_step"), cfg.get("lon_step")), + height = cfg.get("height"), + width = cfg.get("width"), + relax_zone = cfg.get("relax_zone") + ) + + generation_dir = cfg.get("inference_output_dir", None) + if generation_dir is None: + logger.error("No inference_output_dir specified in config.") + return + + if not Path(generation_dir).exists() or not Path(generation_dir).is_dir(): + logger.error(f"Inference output directory {generation_dir} does not exist or is not a directory.") + return + + generation_config_path = Path(generation_dir) / ".hydra" / "config.yaml" + if not generation_config_path.exists(): + logger.error(f"Generation config file {generation_config_path} does not exist.") + return + + with open(generation_config_path, "r") as f: + gen_cfg = yaml.safe_load(f) + logger.info("Starting wind statistics generation") - times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + if cfg.get("times_range", None): + times = get_time_from_range(cfg.get("times_range"), time_format="%Y%m%d-%H%M") + elif cfg.get("times", None): + times = cfg.get("times") + elif gen_cfg.get("generation").get("times_range", None): + times = get_time_from_range(gen_cfg.get("generation").get("times_range"), time_format="%Y%m%d-%H%M") + elif gen_cfg.get("generation").get("times", None): + times = gen_cfg.get("generation").get("times") + else: + logger.error("No times or times_range specified in config or generation config.") + return logger.info(f"Processing {len(times)} timesteps") - ds_cfg = OmegaConf.to_container(cfg.dataset) - dataset, _ = get_dataset_and_sampler_inference( - ds_cfg, times, cfg.generation.get('has_lead_time', False) - ) - out_root = Path(cfg.generation.io.output_path or './outputs') + dataset_cfg = gen_cfg.get("dataset") + dataset_type = dataset_cfg.pop("type") + dataset = known_datasets[dataset_type](**dataset_cfg) + out_root = Path(generation_dir) + output_path = out_root / cfg.get("results_dir_name", "evaluation_maps") + output_path.mkdir(parents=True, exist_ok=True) indices = get_channel_indices(dataset) u10_out = indices['output'].get('10u') @@ -283,13 +317,14 @@ def main(cfg: DictConfig): stat_config['type'], stat_config.get('param') ) - map_output_dir = out_root / f"maps_wind_{stat_config['stat_name']}" + map_output_dir = output_path / f"maps_wind_{stat_config['stat_name']}" map_output_dir.mkdir(parents=True, exist_ok=True) plot_wind_stat_map( result, str(map_output_dir / f'{mode}_{stat_config["stat_name"]}'), stat_config, - label + label, + grid_cfg ) del result except Exception as e: @@ -323,7 +358,7 @@ def load_member_data(ts): sin_acc = cos_acc = speed_acc = None for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: + if i % cfg.get("log_interval") == 0: logger.info(f"Loading prediction member {member_idx} timestep {i+1}/{len(times)}: {ts}") u, v = load_member_data(ts) @@ -413,10 +448,10 @@ def load_member_data(ts): else: member_result = accumulator / count - map_output_dir = out_root / f"maps_wind_{stat_config['stat_name']}" + map_output_dir = output_path / f"maps_wind_{stat_config['stat_name']}" map_output_dir.mkdir(parents=True, exist_ok=True) member_filename = str(map_output_dir / f'prediction_member_{member_idx:02d}_{stat_config["stat_name"]}') - plot_wind_stat_map(member_result, member_filename, stat_config, f'CorrDiff Member {member_idx+1}') + plot_wind_stat_map(member_result, member_filename, stat_config, f'CorrDiff Member {member_idx+1}', grid_cfg) del member_result except Exception as e: @@ -430,4 +465,11 @@ def load_member_data(ts): if __name__ == '__main__': - main() + parser = argparse.ArgumentParser() + parser.add_argument("--config-name", help="Path to YAML config file for evaluation.") + args = parser.parse_args() + + with open(args.config_name, "r") as f: + cfg = yaml.safe_load(f) + + main(cfg) \ No newline at end of file diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index b28a125a..731fc1c4 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -1,4 +1,5 @@ import logging +from dataclasses import dataclass import cartopy.crs as ccrs import cartopy.feature as cfeature @@ -12,10 +13,22 @@ from hirad.datasets import get_channels_from_strings, get_strings_from_channels -# COSMO‑2 GRID: TODO: Add to dataset config -LAT = np.arange(-4.42, 3.36 + 0.02, 0.02) -LON = np.arange(-6.82, 4.80 + 0.02, 0.02) -RELAX_ZONE = 19 # Number of points dropped on each side (relaxation zone) + +@dataclass +class GridConfig: + lat: np.ndarray + lon: np.ndarray + height: int + width: int + relax_zone: int + +DEFAULT_GRID_CONFIG = GridConfig( + lat=np.arange(-4.42, 3.36 + 0.02, 0.02), + lon=np.arange(-6.82, 4.80 + 0.02, 0.02), + height=352, + width=544, + relax_zone=19 +) # Constants for data processing CONV_FACTOR_HOURLY = 1000 # Convert precip of ERA5 from meters to mm/h @@ -53,13 +66,13 @@ def get_channel_indices(dataset, channels=None): return {'input': filtered_in, 'output': filtered_out} -def load_land_sea_mask(path=LAND_SEA_MASK_PATH): +def load_land_sea_mask(path=LAND_SEA_MASK_PATH, height=352, width=544): """Load and retrun a land-sea mask as xarray DataArray.""" - lsm_data = np.load(path).reshape(352, 544) + lsm_data = np.load(path).reshape(height, width) return xr.DataArray( np.where(lsm_data >= 0.5, 1.0, np.nan), dims=['lat', 'lon'], - coords={"lat": np.arange(352), "lon": np.arange(544)} + coords={"lat": np.arange(height), "lon": np.arange(width)} ) def concat_and_group_diurnal(list_of_da, is_member=False, scale=1.0): @@ -84,12 +97,30 @@ def plot_map(values: np.array, cmap=None, extend='neither', norm=None, - ticks=None): + ticks=None, + grid_cfg = DEFAULT_GRID_CONFIG, + patch_idx=0, + patch_size=None + ): """Plot observed or interpolated data in a scatter plot.""" logging.info(f'Creating map: {filename}') - latitudes = LAT[RELAX_ZONE : RELAX_ZONE + 352] - longitudes = LON[RELAX_ZONE : RELAX_ZONE + 544] + if patch_size is None: + patch_size = (grid_cfg.height, grid_cfg.width) + # TODO: implement properly plotting of pathces for patched diffusion inference inspection + # n_col_stacked = math.ceil(704/patch_size[0]) + # last_start = (n_col_stacked-1) * patch_size[0] + # # print(n_col_stacked) + # latitudes_start = last_start-(patch_idx%n_col_stacked)*patch_size[0] + # # print(latitudes_start) + # longitudes_start = (patch_idx//n_col_stacked)*patch_size[1] + # # print(longitudes_start) + # lat = LAT if lat is None else lat + # lon = LON if lon is None else lon + # latitudes = lat[RELAX_ZONE : RELAX_ZONE + patch_size[0]] #LAT[RELAX_ZONE+latitudes_start:RELAX_ZONE+latitudes_start+patch_size[0]] #LAT[RELAX_ZONE : RELAX_ZONE + 352] + # longitudes = lon[RELAX_ZONE : RELAX_ZONE + patch_size[1]] #LON[RELAX_ZONE+longitudes_start:RELAX_ZONE+longitudes_start+patch_size[1]] #LON[RELAX_ZONE : RELAX_ZONE + 544] + latitudes = grid_cfg.lat[grid_cfg.relax_zone : grid_cfg.relax_zone + grid_cfg.height] + longitudes = grid_cfg.lon[grid_cfg.relax_zone : grid_cfg.relax_zone + grid_cfg.width] lon2d, lat2d = np.meshgrid(longitudes, latitudes) fig, ax = plt.subplots( @@ -127,7 +158,7 @@ def plot_map(values: np.array, fig.savefig(f"{filename}.png", dpi=300, bbox_inches="tight") plt.close(fig) -def plot_map_precipitation(values, filename, title='', threshold=0.1, rfac=1000.0): +def plot_map_precipitation(values, filename, title='', threshold=0.01, rfac=1000.0, grid_cfg=DEFAULT_GRID_CONFIG): """Plot precipitation data with specific colormap and thresholds.""" # Scale and mask values below threshold values = rfac * values # m/h --> mm/h @@ -150,7 +181,8 @@ def plot_map_precipitation(values, filename, title='', threshold=0.1, rfac=1000. ticks=bounds, title=title, label='mm/h', - extend='max' + extend='max', + grid_cfg=grid_cfg, ) def wind_direction(u, v): diff --git a/src/hirad/eval/probability_of_exceedance.py b/src/hirad/eval/probability_of_exceedance.py index f56b5518..ca4a273a 100644 --- a/src/hirad/eval/probability_of_exceedance.py +++ b/src/hirad/eval/probability_of_exceedance.py @@ -5,19 +5,19 @@ (probability of exceeding x mm/h) over land). """ import logging +import argparse +import yaml from pathlib import Path import hydra import matplotlib.pyplot as plt import numpy as np import torch -from omegaconf import DictConfig, OmegaConf import xarray as xr -from hirad.datasets import get_dataset_and_sampler_inference -from hirad.distributed import DistributedManager +from hirad.datasets import get_channels_from_strings, get_strings_from_channels, known_datasets from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import get_channel_indices, load_land_sea_mask, CONV_FACTOR_HOURLY, LOG_INTERVAL +from hirad.eval.plotting import get_channel_indices, load_land_sea_mask def save_exceedance_plot(exceedance_data_dict, thresholds, labels, colors, title, ylabel, out_path, percentiles_data=None): @@ -97,26 +97,50 @@ def save_exceedance_plot(exceedance_data_dict, thresholds, labels, colors, title plt.close() -@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") -def main(cfg: DictConfig): +def main(cfg: dict): # Setup logging - DistributedManager.initialize() logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) + generation_dir = cfg.get("inference_output_dir", None) + if generation_dir is None: + logger.error("No inference_output_dir specified in config.") + return + + if not Path(generation_dir).exists() or not Path(generation_dir).is_dir(): + logger.error(f"Inference output directory {generation_dir} does not exist or is not a directory.") + return + + generation_config_path = Path(generation_dir) / ".hydra" / "config.yaml" + if not generation_config_path.exists(): + logger.error(f"Generation config file {generation_config_path} does not exist.") + return + + with open(generation_config_path, "r") as f: + gen_cfg = yaml.safe_load(f) + logger.info("Starting computation for probability of exceedance over land") - times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + if cfg.get("times_range", None): + times = get_time_from_range(cfg.get("times_range"), time_format="%Y%m%d-%H%M") + elif cfg.get("times", None): + times = cfg.get("times") + elif gen_cfg.get("generation").get("times_range", None): + times = get_time_from_range(gen_cfg.get("generation").get("times_range"), time_format="%Y%m%d-%H%M") + elif gen_cfg.get("generation").get("times", None): + times = gen_cfg.get("generation").get("times") + else: + logger.error("No times or times_range specified in config or generation config.") + return logger.info(f"Loaded {len(times)} timesteps to process") # Initialize dataset - ds_cfg = OmegaConf.to_container(cfg.dataset) - dataset, _ = get_dataset_and_sampler_inference( - ds_cfg, times, cfg.generation.get('has_lead_time', False) - ) - logger.info("Dataset and sampler initialized") + dataset_cfg = gen_cfg.get("dataset") + dataset_type = dataset_cfg.pop("type") + dataset = known_datasets[dataset_type](**dataset_cfg) + logger.info("Dataset initialized") # Output root - out_root = Path(cfg.generation.io.output_path or './outputs') + out_root = Path(generation_dir) # Find channel indices indices = get_channel_indices(dataset) @@ -125,7 +149,7 @@ def main(cfg: DictConfig): logger.info(f"TP channel indices - output: {tp_out}, input: {tp_in}") # Land-sea mask - land_mask = load_land_sea_mask() + land_mask = load_land_sea_mask(cfg.get("land_sea_mask_path"), cfg.get("height"), cfg.get("width")) # Define thresholds for exceedance calculation thresholds = np.logspace(-2, 2.1, 200) # From 0.01 to 100 mm/h @@ -142,14 +166,10 @@ def main(cfg: DictConfig): try: for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: + if i % cfg.get("log_interval") == 0: logger.info(f"Processing timestep {i+1}/{len(times)}") - data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode in ['target','regression-prediction'] else tp_in] * CONV_FACTOR_HOURLY * land_mask - - # Apply scaling factor for baseline - # if mode == 'baseline': - # data = data / 6.0 + data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode in ['target','regression-prediction'] else tp_in] * cfg.get("conv_factor_hourly") * land_mask land_values = data.values[~np.isnan(data.values)] all_values.extend(land_values) @@ -175,10 +195,10 @@ def main(cfg: DictConfig): all_member_values = [] for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: + if i % cfg.get("log_interval") == 0: logger.info(f"Processing timestep {i+1}/{len(times)}") - preds = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) * CONV_FACTOR_HOURLY # [n_members, n_channels, lat, lon] + preds = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) * cfg.get("conv_factor_hourly") # [n_members, n_channels, lat, lon] if n_members is None: n_members = preds.shape[0] @@ -226,10 +246,12 @@ def main(cfg: DictConfig): } # Create exceedance plots - labels = ['COSMO-2 Analysis', 'ERA5', 'Regression Prediction', 'CorrDiff Ensemble'] if 'regression-prediction' in exceedance_data else ['COSMO-2 Analysis', 'ERA5', 'CorrDiff Ensemble'] + labels = ['Target', 'Input', 'Regression Prediction', 'CorrDiff Ensemble'] if 'regression-prediction' in exceedance_data else ['Target', 'Input', 'CorrDiff Ensemble'] colors = ['blue', 'orange', 'red', 'green'] if 'regression-prediction' in exceedance_data else ['blue', 'orange', 'green'] - fn = out_root / 'precipitation_exceedance_over_land.png' + output_path = out_root / cfg.get("results_dir_name", "evaluation_maps") + output_path.mkdir(parents=True, exist_ok=True) + fn = output_path / 'precipitation_exceedance_over_land.png' save_exceedance_plot( exceedance_data, thresholds, @@ -244,4 +266,11 @@ def main(cfg: DictConfig): if __name__ == '__main__': - main() + parser = argparse.ArgumentParser() + parser.add_argument("--config-name", help="Path to YAML config file for evaluation.") + args = parser.parse_args() + + with open(args.config_name, "r") as f: + cfg = yaml.safe_load(f) + + main(cfg) diff --git a/src/hirad/eval/probability_of_exceedance_wind.py b/src/hirad/eval/probability_of_exceedance_wind.py index 6fe3ff70..d9f9070f 100644 --- a/src/hirad/eval/probability_of_exceedance_wind.py +++ b/src/hirad/eval/probability_of_exceedance_wind.py @@ -1,18 +1,18 @@ """Probability of exceedance for wind speed and components.""" import logging +import argparse +import yaml from pathlib import Path import hydra import matplotlib.pyplot as plt import numpy as np import torch -from omegaconf import DictConfig, OmegaConf import xarray as xr -from hirad.datasets import get_dataset_and_sampler_inference -from hirad.distributed import DistributedManager +from hirad.datasets import get_channels_from_strings, get_strings_from_channels, known_datasets from hirad.utils.function_utils import get_time_from_range -from hirad.eval.plotting import get_channel_indices, LOG_INTERVAL +from hirad.eval.plotting import get_channel_indices def compute_wind_speed(u, v): @@ -121,26 +121,50 @@ def save_exceedance_plot(exceedance_data_dict, thresholds, labels, colors, title plt.close() -@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") -def main(cfg: DictConfig): +def main(cfg: dict): # Setup logging - DistributedManager.initialize() logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) + generation_dir = cfg.get("inference_output_dir", None) + if generation_dir is None: + logger.error("No inference_output_dir specified in config.") + return + + if not Path(generation_dir).exists() or not Path(generation_dir).is_dir(): + logger.error(f"Inference output directory {generation_dir} does not exist or is not a directory.") + return + + generation_config_path = Path(generation_dir) / ".hydra" / "config.yaml" + if not generation_config_path.exists(): + logger.error(f"Generation config file {generation_config_path} does not exist.") + return + + with open(generation_config_path, "r") as f: + gen_cfg = yaml.safe_load(f) + logger.info("Starting computation for probability of exceedance for wind speed") - times = get_time_from_range(cfg.generation.times_range, "%Y%m%d-%H%M") + if cfg.get("times_range", None): + times = get_time_from_range(cfg.get("times_range"), time_format="%Y%m%d-%H%M") + elif cfg.get("times", None): + times = cfg.get("times") + elif gen_cfg.get("generation").get("times_range", None): + times = get_time_from_range(gen_cfg.get("generation").get("times_range"), time_format="%Y%m%d-%H%M") + elif gen_cfg.get("generation").get("times", None): + times = gen_cfg.get("generation").get("times") + else: + logger.error("No times or times_range specified in config or generation config.") + return logger.info(f"Loaded {len(times)} timesteps to process") # Initialize dataset - ds_cfg = OmegaConf.to_container(cfg.dataset) - dataset, _ = get_dataset_and_sampler_inference( - ds_cfg, times, cfg.generation.get('has_lead_time', False) - ) - logger.info("Dataset and sampler initialized") + dataset_cfg = gen_cfg.get("dataset") + dataset_type = dataset_cfg.pop("type") + dataset = known_datasets[dataset_type](**dataset_cfg) + logger.info("Dataset initialized") # Output root - out_root = Path(cfg.generation.io.output_path or './outputs') + out_root = Path(generation_dir) # Find channel indices for wind components indices = get_channel_indices(dataset) @@ -180,7 +204,7 @@ def main(cfg: DictConfig): try: for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: + if i % cfg.get("log_interval") == 0: logger.info(f"Processing timestep {i+1}/{len(times)}") data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False) @@ -233,7 +257,7 @@ def main(cfg: DictConfig): member_samples = {'speed': [], 'u': [], 'v': []} for i, ts in enumerate(times): - if i % LOG_INTERVAL == 0: + if i % cfg.get("log_interval") == 0: logger.info(f"Processing timestep {i+1}/{len(times)}") preds = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) # [n_members, n_channels, lat, lon] @@ -312,7 +336,7 @@ def main(cfg: DictConfig): ) # Create exceedance plots - labels = ['COSMO-2 Analysis', 'ERA5', 'Regression Prediction', 'CorrDiff Ensemble'] if 'regression-prediction' in exceedance_data['speed'] else ['COSMO-2 Analysis', 'ERA5', 'CorrDiff Ensemble'] + labels = ['Target', 'Input', 'Regression Prediction', 'CorrDiff Ensemble'] if 'regression-prediction' in exceedance_data['speed'] else ['Target', 'Input', 'CorrDiff Ensemble'] colors = ['blue', 'orange', 'red', 'green'] if 'regression-prediction' in exceedance_data['speed'] else ['blue', 'orange', 'green'] # Define plot configurations @@ -325,8 +349,10 @@ def main(cfg: DictConfig): 'All-hour 10v Component [m/s] (Pooled Data)'), ] + output_path = out_root / cfg.get("results_dir_name", "evaluation_maps") + output_path.mkdir(parents=True, exist_ok=True) for filename, var, title, ylabel in plot_configs: - fn = out_root / filename + fn = output_path / filename save_exceedance_plot( exceedance_data[var], thresholds, @@ -341,4 +367,11 @@ def main(cfg: DictConfig): if __name__ == '__main__': - main() + parser = argparse.ArgumentParser() + parser.add_argument("--config-name", help="Path to YAML config file for evaluation.") + args = parser.parse_args() + + with open(args.config_name, "r") as f: + cfg = yaml.safe_load(f) + + main(cfg) diff --git a/src/hirad/eval/snapshots.py b/src/hirad/eval/snapshots.py index 649730b0..25f30da6 100644 --- a/src/hirad/eval/snapshots.py +++ b/src/hirad/eval/snapshots.py @@ -1,5 +1,7 @@ """Generates maps of precipitation, temperature, and wind components/speed/direction.""" import logging +import argparse +import yaml from dataclasses import dataclass, field, replace from datetime import datetime from pathlib import Path @@ -7,12 +9,10 @@ import hydra import numpy as np import torch -from omegaconf import DictConfig, OmegaConf -from hirad.datasets import get_dataset_and_sampler_inference, get_channels_from_strings -from hirad.distributed import DistributedManager +from hirad.datasets import get_channels_from_strings, get_strings_from_channels, known_datasets from hirad.eval import compute_mae, plot_map -from hirad.eval.plotting import plot_map_precipitation, wind_direction +from hirad.eval.plotting import plot_map_precipitation, wind_direction, GridConfig, DEFAULT_GRID_CONFIG from hirad.utils.function_utils import get_time_from_range from hirad.utils.inference_utils import calculate_bounds @@ -29,7 +29,7 @@ class ChannelMeta: vmin: float = None vmax: float = None extend: str = "both" - precip_kwargs: dict = field(default_factory=lambda: {"threshold": 0.1, "rfac": 1000.0}) + precip_kwargs: dict = field(default_factory=lambda: {"threshold": 0.01, "rfac": 1000.0}) @classmethod def get(cls, ch_or_name: "ChannelMeta | str | None", *, vmin=None, vmax=None) -> "ChannelMeta": @@ -40,7 +40,7 @@ def get(cls, ch_or_name: "ChannelMeta | str | None", *, vmin=None, vmax=None) -> return base CHANNELS = { - "tp": ChannelMeta(name="tp", cmap=None, unit="mm/h", extend="max", precip_kwargs={"threshold": 0.1, "rfac": 1000.0}), + "tp": ChannelMeta(name="tp", cmap=None, unit="mm/h", extend="max", precip_kwargs={"threshold": 0.01, "rfac": 1000.0}), "2t": ChannelMeta(name="2t", cmap="RdYlBu_r", me_cmap="RdBu", unit="K", err_vmin=-4.5, err_vmax=4.5), "10u": ChannelMeta(name="10u", cmap="BrBG", me_cmap="BrBG", unit="m/s", err_vmin=-10, err_vmax=10, vmin=-10, vmax=10), "10v": ChannelMeta(name="10v", cmap="BrBG", me_cmap="BrBG", unit="m/s", err_vmin=-10, err_vmax=10, vmin=-10, vmax=10), @@ -82,20 +82,12 @@ def wind_file(self, wind_type, curr_time, suffix, member_idx=None): fname = self._make_fname(curr_time, wind_type, suffix, member_idx) return self._ensure_dir(wind_type) / fname -def map_output_to_input_channels(output_channels, input_channels): - """ - Maps output channels to input channels based on their names. - """ - return { - j: next((k for k, input_channel in enumerate(input_channels) if input_channel.name == output_channel.name), -1) - for j, output_channel in enumerate(output_channels) - } -def save_field(name, data, meta, files, channel, t, member=None, kind=None, cmap=None, vmin=None, vmax=None, custom_path=None, plot_func=None, title=None, **plot_kwargs): +def save_field(name, data, meta, output_files, channel, t, member=None, kind=None, cmap=None, vmin=None, vmax=None, custom_path=None, plot_func=None, title=None, grid_cfg=DEFAULT_GRID_CONFIG, **plot_kwargs): """Save a field by plotting it with the appropriate function and parameters. Handles precipitation specially and supports custom paths.""" # Determine output path suffix = f"{name}-{kind}" if kind else f"{name}" - out_path = custom_path or files.output_file(channel, t, suffix, member) + out_path = custom_path or output_files.output_file(channel, t, suffix, member) # Choose plotting function plot = plot_func or plot_map @@ -103,46 +95,77 @@ def save_field(name, data, meta, files, channel, t, member=None, kind=None, cmap # Case dependent plot parameters title = title or meta.name extend = 'max' if kind == 'mae' else meta.extend - common = {'title': title, 'norm': meta.norm, 'extend': extend, **plot_kwargs} + plot_map_args = {'title': title, 'norm': meta.norm, 'extend': extend, **plot_kwargs} + common_args = {'grid_cfg': grid_cfg} # Precipitation case if plot.__name__ == 'plot_map_precipitation': precip_args = {**meta.precip_kwargs, **{k: plot_kwargs[k] for k in meta.precip_kwargs if k in plot_kwargs}} - plot(data, out_path, title=title, **precip_args) + plot(data, out_path, title=title, **precip_args, **common_args) else: - plot(data, out_path, vmin=vmin if vmin is not None else meta.vmin, vmax=vmax if vmax is not None else meta.vmax, cmap=cmap or meta.cmap, label=meta.unit, **common) + plot(data, out_path, vmin=vmin if vmin is not None else meta.vmin, vmax=vmax if vmax is not None else meta.vmax, cmap=cmap or meta.cmap, label=meta.unit, **plot_map_args, **common_args) -@hydra.main(version_base="1.2", config_path="../conf", config_name="config_generate") -def main(cfg: DictConfig) -> None: - - # Initialize distributed manager - DistributedManager.initialize() - +def main(cfg: dict) -> None: # Initialize logger logging.basicConfig(level=logging.INFO) logger = logging.getLogger("plot_maps") - if cfg.generation.times_range: - times = get_time_from_range(cfg.generation.times_range, time_format="%Y%m%d-%H%M") + grid_cfg = GridConfig( + lat = np.arange(cfg.get("lat_start"), cfg.get("lat_end") + cfg.get("lat_step"), cfg.get("lat_step")), + lon = np.arange(cfg.get("lon_start"), cfg.get("lon_end") + cfg.get("lon_step"), cfg.get("lon_step")), + height = cfg.get("height"), + width = cfg.get("width"), + relax_zone = cfg.get("relax_zone") + ) + + generation_dir = cfg.get("inference_output_dir", None) + if generation_dir is None: + logger.error("No inference_output_dir specified in config.") + return + + if not Path(generation_dir).exists() or not Path(generation_dir).is_dir(): + logger.error(f"Inference output directory {generation_dir} does not exist or is not a directory.") + return + + generation_config_path = Path(generation_dir) / ".hydra" / "config.yaml" + if not generation_config_path.exists(): + logger.error(f"Generation config file {generation_config_path} does not exist.") + return + + with open(generation_config_path, "r") as f: + gen_cfg = yaml.safe_load(f) + + if cfg.get("times_range", None): + times = get_time_from_range(cfg.get("times_range"), time_format="%Y%m%d-%H%M") + elif cfg.get("times", None): + times = cfg.get("times") + elif gen_cfg.get("generation").get("times_range", None): + times = get_time_from_range(gen_cfg.get("generation").get("times_range"), time_format="%Y%m%d-%H%M") + elif gen_cfg.get("generation").get("times", None): + times = gen_cfg.get("generation").get("times") else: - times = cfg.generation.times + logger.error("No times or times_range specified in config or generation config.") + return - dataset_cfg = OmegaConf.to_container(cfg.dataset) - - has_lead_time = cfg.generation.get("has_lead_time", False) - dataset, sampler = get_dataset_and_sampler_inference( - dataset_cfg=dataset_cfg, times=times, has_lead_time=has_lead_time - ) + dataset_cfg = gen_cfg.get("dataset") + input_channels = get_channels_from_strings(dataset_cfg.get("input_channel_names", [])) + output_channels = get_channels_from_strings(dataset_cfg.get("output_channels_names", [])) + if not input_channels or not output_channels: + dataset_type = dataset_cfg.pop("type") + dataset = known_datasets[dataset_type](**dataset_cfg) + input_channels = dataset.input_channels() + output_channels = dataset.output_channels() plot_channels = cfg.get("plot_channels", None) if plot_channels is not None: plot_channels = get_channels_from_strings(plot_channels) else: - plot_channels = dataset.output_channels() + plot_channels = output_channels - input_channels = dataset.input_channels() - output_channels = dataset.output_channels() + logger.info(get_strings_from_channels(plot_channels)) + logger.info(get_strings_from_channels(input_channels)) + logger.info(get_strings_from_channels(output_channels)) input_channel_indices = [] output_channel_indices = [] @@ -150,20 +173,20 @@ def main(cfg: DictConfig) -> None: input_channel_indices.append(input_channels.index(channel) if channel in input_channels else -1) output_channel_indices.append(output_channels.index(channel) if channel in output_channels else -1) - output_path = getattr(cfg.generation.io, "output_path", "./outputs") - files = FileRepository(output_path) + output_path = Path(generation_dir) / cfg.get("results_dir_name", "evaluation_maps") + output_path.mkdir(parents=True, exist_ok=True) + input_files = FileRepository(generation_dir) + output_files = FileRepository(output_path) for curr_time in times: - prediction = files.load(curr_time, f'{curr_time}-predictions') - baseline = files.load(curr_time, f'{curr_time}-baseline') - target = files.load(curr_time, f'{curr_time}-target') + prediction = input_files.load(curr_time, f'{curr_time}-predictions') + baseline = input_files.load(curr_time, f'{curr_time}-baseline') + target = input_files.load(curr_time, f'{curr_time}-target') try: - mean_pred = files.load(curr_time, f'{curr_time}-regression-prediction') + mean_pred = input_files.load(curr_time, f'{curr_time}-regression-prediction') except: mean_pred = None - # output_to_input_channel_map = map_output_to_input_channels(output_channels, input_channels) - for idx, channel in enumerate(plot_channels): # input_channel_idx = output_to_input_channel_map[idx] input_channel_idx = input_channel_indices[idx] @@ -185,66 +208,66 @@ def main(cfg: DictConfig) -> None: if channel.name == "tp": save_field( - "target", target[output_channel_idx, :, :], metadata, files, channel, curr_time, - plot_func=plot_map_precipitation, title=plot_title + "target", target[output_channel_idx, :, :], metadata, output_files, channel, curr_time, + plot_func=plot_map_precipitation, title=plot_title, grid_cfg=grid_cfg ) save_field( - "baseline", baseline[input_channel_idx, :, :], metadata, files, channel, curr_time, - plot_func=plot_map_precipitation, title=plot_title + "baseline", baseline[input_channel_idx, :, :], metadata, output_files, channel, curr_time, + plot_func=plot_map_precipitation, title=plot_title, grid_cfg=grid_cfg ) if prediction.ndim>3: for member_idx in range(prediction.shape[0]): save_field( - "prediction", prediction[member_idx, output_channel_idx, :, :], metadata, files, channel, curr_time, - member=member_idx, plot_func=plot_map_precipitation, title=plot_title + "prediction", prediction[member_idx, output_channel_idx, :, :], metadata, output_files, channel, curr_time, + member=member_idx, plot_func=plot_map_precipitation, title=plot_title, grid_cfg=grid_cfg ) else: save_field( - "prediction", prediction[output_channel_idx, :, :], metadata, files, channel, curr_time, - plot_func=plot_map_precipitation, title=plot_title + "prediction", prediction[output_channel_idx, :, :], metadata, output_files, channel, curr_time, + plot_func=plot_map_precipitation, title=plot_title, grid_cfg=grid_cfg ) if mean_pred is not None: save_field( - "mean-prediction", mean_pred[output_channel_idx, :, :], metadata, files, channel, curr_time, - plot_func=plot_map_precipitation, title=plot_title + "mean-prediction", mean_pred[output_channel_idx, :, :], metadata, output_files, channel, curr_time, + plot_func=plot_map_precipitation, title=plot_title, grid_cfg=grid_cfg ) continue # Plot target and baseline and regression prediction if available - save_field("target", target[output_channel_idx, :, :], metadata, files, channel, curr_time, title=plot_title) - save_field("baseline", baseline[input_channel_idx, :, :], metadata, files, channel, curr_time, title=plot_title) + save_field("target", target[output_channel_idx, :, :], metadata, output_files, channel, curr_time, title=plot_title, grid_cfg=grid_cfg) + save_field("baseline", baseline[input_channel_idx, :, :], metadata, output_files, channel, curr_time, title=plot_title, grid_cfg=grid_cfg) if mean_pred is not None: - save_field("mean-prediction", mean_pred[output_channel_idx, :, :], metadata, files, channel, curr_time, title=plot_title) + save_field("mean-prediction", mean_pred[output_channel_idx, :, :], metadata, output_files, channel, curr_time, title=plot_title, grid_cfg=grid_cfg) # Baseline MAE and ME _, baseline_mae = compute_mae(baseline[input_channel_idx, :, :], target[output_channel_idx, :, :]) baseline_me = (baseline[input_channel_idx, :, :] - target[output_channel_idx, :, :]) - save_field("baseline", baseline_mae.reshape(baseline[input_channel_idx, :, :].shape), metadata, files, channel, curr_time, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title) - save_field("baseline", baseline_me, metadata, files, channel, curr_time, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title) + save_field("baseline", baseline_mae.reshape(baseline[input_channel_idx, :, :].shape), metadata, output_files, channel, curr_time, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title, grid_cfg=grid_cfg) + save_field("baseline", baseline_me, metadata, output_files, channel, curr_time, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title, grid_cfg=grid_cfg) # Regression prediction MAE and ME if mean_pred is not None: _, mean_mae = compute_mae(mean_pred[idx, :, :], target[output_channel_idx, :, :]) mean_me = (mean_pred[output_channel_idx, :, :] - target[output_channel_idx, :, :]) - save_field("mean-prediction", mean_mae.reshape(mean_pred[output_channel_idx, :, :].shape), metadata, files, channel, curr_time, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title) - save_field("mean-prediction", mean_me, metadata, files, channel, curr_time, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title) + save_field("mean-prediction", mean_mae.reshape(mean_pred[output_channel_idx, :, :].shape), metadata, output_files, channel, curr_time, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title, grid_cfg=grid_cfg) + save_field("mean-prediction", mean_me, metadata, output_files, channel, curr_time, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title, grid_cfg=grid_cfg) # Ensemble predictions if prediction.ndim > 3: for member_idx in range(prediction.shape[0]): member = prediction[member_idx, output_channel_idx, :, :] - save_field("prediction", member, metadata, files, channel, curr_time, member=member_idx, title=plot_title) + save_field("prediction", member, metadata, output_files, channel, curr_time, member=member_idx, title=plot_title, grid_cfg=grid_cfg) _, prediction_mae = compute_mae(member, target[output_channel_idx, :, :]) - save_field("prediction", prediction_mae.reshape(member.shape), metadata, files, channel, curr_time, member=member_idx, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title) + save_field("prediction", prediction_mae.reshape(member.shape), metadata, output_files, channel, curr_time, member=member_idx, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title, grid_cfg=grid_cfg) prediction_me = (member - target[output_channel_idx, :, :]) - save_field("prediction", prediction_me, metadata, files, channel, curr_time, member=member_idx, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title) + save_field("prediction", prediction_me, metadata, output_files, channel, curr_time, member=member_idx, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title, grid_cfg=grid_cfg) else: member = prediction[output_channel_idx, :, :] - save_field("prediction", member, metadata, files, channel, curr_time, title=plot_title) + save_field("prediction", member, metadata, output_files, channel, curr_time, title=plot_title, grid_cfg=grid_cfg) _, prediction_mae = compute_mae(member, target[output_channel_idx, :, :]) - save_field("prediction", prediction_mae.reshape(member.shape), metadata, files, channel, curr_time, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title) + save_field("prediction", prediction_mae.reshape(member.shape), metadata, output_files, channel, curr_time, kind="mae", cmap=metadata.cmap if channel.name not in ("10u", "10v", "2t") else 'viridis', vmin=0, vmax=metadata.err_vmax, title=plot_title, grid_cfg=grid_cfg) prediction_me = (member - target[output_channel_idx, :, :]) - save_field("prediction", prediction_me, metadata, files, channel, curr_time, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title) + save_field("prediction", prediction_me, metadata, output_files, channel, curr_time, kind="me", cmap=metadata.me_cmap, vmin=metadata.err_vmin, vmax=metadata.err_vmax, title=plot_title, grid_cfg=grid_cfg) # Plot Windspeed and direction wind_channels = {ch.name: idx for idx, ch in enumerate(output_channels) if ch.name in ("10u", "10v")} @@ -278,77 +301,84 @@ def main(cfg: DictConfig) -> None: # Save windspeed plots save_field( - "FF10m-target", target_wind_speed, wind_meta, files, None, curr_time, + "FF10m-target", target_wind_speed, wind_meta, output_files, None, curr_time, cmap="viridis", vmin=0, vmax=10, extend='max', - custom_path=files.wind_file("FF10m", curr_time, "FF10m-target"), - plot_func=plot_map, title=plot_title_speed + custom_path=output_files.wind_file("FF10m", curr_time, "FF10m-target"), + plot_func=plot_map, title=plot_title_speed, grid_cfg=grid_cfg ) save_field( - "FF10m-baseline", baseline_wind_speed, wind_meta, files, None, curr_time, + "FF10m-baseline", baseline_wind_speed, wind_meta, output_files, None, curr_time, cmap="viridis", vmin=0, vmax=10, extend='max', - custom_path=files.wind_file("FF10m", curr_time, "FF10m-baseline"), - plot_func=plot_map, title=plot_title_speed + custom_path=output_files.wind_file("FF10m", curr_time, "FF10m-baseline"), + plot_func=plot_map, title=plot_title_speed, grid_cfg=grid_cfg ) if prediction.ndim > 3: for member_idx in range(prediction.shape[0]): save_field( - "FF10m-prediction", prediction_wind_speed[member_idx], wind_meta, files, None, curr_time, + "FF10m-prediction", prediction_wind_speed[member_idx], wind_meta, output_files, None, curr_time, member=member_idx, cmap="viridis", vmin=0, vmax=10, extend='max', - custom_path=files.wind_file("FF10m", curr_time, "FF10m-prediction", member_idx), - plot_func=plot_map, title=plot_title_speed + custom_path=output_files.wind_file("FF10m", curr_time, "FF10m-prediction", member_idx), + plot_func=plot_map, title=plot_title_speed, grid_cfg=grid_cfg ) else: save_field( - "FF10m-prediction", prediction_wind_speed, wind_meta, files, None, curr_time, + "FF10m-prediction", prediction_wind_speed, wind_meta, output_files, None, curr_time, cmap="viridis", vmin=0, vmax=10, extend='max', - custom_path=files.wind_file("FF10m", curr_time, "FF10m-prediction"), - plot_func=plot_map, title=plot_title_speed + custom_path=output_files.wind_file("FF10m", curr_time, "FF10m-prediction"), + plot_func=plot_map, title=plot_title_speed, grid_cfg=grid_cfg ) if mean_pred is not None: save_field( - "FF10m-mean-prediction", mean_wind_speed, wind_meta, files, None, curr_time, + "FF10m-mean-prediction", mean_wind_speed, wind_meta, output_files, None, curr_time, cmap="viridis", vmin=0, vmax=10, extend='max', - custom_path=files.wind_file("FF10m", curr_time, "FF10m-mean-prediction"), - plot_func=plot_map, title=plot_title_speed + custom_path=output_files.wind_file("FF10m", curr_time, "FF10m-mean-prediction"), + plot_func=plot_map, title=plot_title_speed, grid_cfg=grid_cfg ) # Save wind direction plots save_field( - "DD10m-target", target_wind_dir, dir_meta, files, None, curr_time, + "DD10m-target", target_wind_dir, dir_meta, output_files, None, curr_time, cmap="twilight", vmin=0, vmax=360, - custom_path=files.wind_file("DD10m", curr_time, "DD10m-target"), - plot_func=plot_map, title=plot_title_dir + custom_path=output_files.wind_file("DD10m", curr_time, "DD10m-target"), + plot_func=plot_map, title=plot_title_dir, grid_cfg=grid_cfg ) save_field( - "DD10m-baseline", baseline_wind_dir, dir_meta, files, None, curr_time, + "DD10m-baseline", baseline_wind_dir, dir_meta, output_files, None, curr_time, cmap="twilight", vmin=0, vmax=360, - custom_path=files.wind_file("DD10m", curr_time, "DD10m-baseline"), - plot_func=plot_map, title=plot_title_dir + custom_path=output_files.wind_file("DD10m", curr_time, "DD10m-baseline"), + plot_func=plot_map, title=plot_title_dir, grid_cfg=grid_cfg ) if prediction.ndim > 3: for member_idx in range(prediction.shape[0]): save_field( - "DD10m-prediction", prediction_wind_dir[member_idx], dir_meta, files, None, curr_time, + "DD10m-prediction", prediction_wind_dir[member_idx], dir_meta, output_files, None, curr_time, member=member_idx, cmap="twilight", vmin=0, vmax=360, - custom_path=files.wind_file("DD10m", curr_time, "DD10m-prediction", member_idx), - plot_func=plot_map, title=plot_title_dir + custom_path=output_files.wind_file("DD10m", curr_time, "DD10m-prediction", member_idx), + plot_func=plot_map, title=plot_title_dir, grid_cfg=grid_cfg ) else: save_field( - "DD10m-prediction", prediction_wind_dir, dir_meta, files, None, curr_time, + "DD10m-prediction", prediction_wind_dir, dir_meta, output_files, None, curr_time, cmap="twilight", vmin=0, vmax=360, - custom_path=files.wind_file("DD10m", curr_time, "DD10m-prediction"), - plot_func=plot_map, title=plot_title_dir + custom_path=output_files.wind_file("DD10m", curr_time, "DD10m-prediction"), + plot_func=plot_map, title=plot_title_dir, grid_cfg=grid_cfg ) if mean_pred is not None: save_field( - "DD10m-mean-prediction", mean_wind_dir, dir_meta, files, None, curr_time, + "DD10m-mean-prediction", mean_wind_dir, dir_meta, output_files, None, curr_time, cmap="twilight", vmin=0, vmax=360, - custom_path=files.wind_file("DD10m", curr_time, "DD10m-mean-prediction"), - plot_func=plot_map, title=plot_title_dir + custom_path=output_files.wind_file("DD10m", curr_time, "DD10m-mean-prediction"), + plot_func=plot_map, title=plot_title_dir, grid_cfg=grid_cfg ) logger.info("Image loading and plotting completed.") if __name__ == "__main__": - main() + parser = argparse.ArgumentParser() + parser.add_argument("--config-name", help="Path to YAML config file for evaluation.") + args = parser.parse_args() + + with open(args.config_name, "r") as f: + cfg = yaml.safe_load(f) + + main(cfg) diff --git a/src/hirad/eval_precip.sh b/src/hirad/eval_precip.sh index ec522d9d..6d1acbd5 100644 --- a/src/hirad/eval_precip.sh +++ b/src/hirad/eval_precip.sh @@ -5,8 +5,8 @@ ### HARDWARE ### #SBATCH --partition=normal #SBATCH --nodes=1 -#SBATCH --ntasks-per-node=2 -#SBATCH --gpus-per-node=2 +#SBATCH --ntasks-per-node=1 +#SBATCH --gpus-per-node=1 #SBATCH --cpus-per-task=72 #SBATCH --time=05:00:00 #SBATCH --no-requeue @@ -18,42 +18,20 @@ ### ENVIRONMENT #### #SBATCH -A a161 -# Choose method to initialize dist in pythorch -export DISTRIBUTED_INITIALIZATION_METHOD=SLURM - -MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" -echo "Master node : $MASTER_ADDR" -# Get IP for hostname. -MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" -echo "Master address : $MASTER_ADDR" -export MASTER_ADDR -export MASTER_PORT=29500 -echo "Master port: $MASTER_PORT" - -# Get number of physical cores using Python -# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") -# # Use SLURM_NTASKS (number of processes to be launched by torchrun) -# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} -# # Compute threads per process -# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) -# export OMP_NUM_THREADS=$OMP_THREADS -export OMP_NUM_THREADS=72 -# echo "Physical cores: $PHYSICAL_CORES" -# echo "Local processes: $LOCAL_PROCS" -# echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" - +### CONFIG ### +CONFIG_NAME="src/hirad/conf/eval_real.yaml" srun --environment=./ci/edf/modulus_env.toml bash -c " pip install -e . --no-dependencies # Diurnal cycle - python src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py --config-name=generate_era_cosmo.yaml - python src/hirad/eval/diurnal_cycle_precip_p99.py --config-name=generate_era_cosmo.yaml + # python src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py --config-name=${CONFIG_NAME} + # python src/hirad/eval/diurnal_cycle_precip_p99.py --config-name=${CONFIG_NAME} # Histograms - python src/hirad/eval/hist.py --config-name=generate_era_cosmo.yaml - python src/hirad/eval/probability_of_exceedance.py --config-name=generate_era_cosmo.yaml + # python src/hirad/eval/hist.py --config-name=${CONFIG_NAME} + # python src/hirad/eval/probability_of_exceedance.py --config-name=${CONFIG_NAME} # Maps - python src/hirad/eval/map_precip_stats.py --config-name=generate_era_cosmo.yaml + # python src/hirad/eval/map_precip_stats.py --config-name=${CONFIG_NAME} " \ No newline at end of file diff --git a/src/hirad/eval_wind.sh b/src/hirad/eval_wind.sh index df113e87..6981d1b7 100644 --- a/src/hirad/eval_wind.sh +++ b/src/hirad/eval_wind.sh @@ -5,8 +5,8 @@ ### HARDWARE ### #SBATCH --partition=normal #SBATCH --nodes=1 -#SBATCH --ntasks-per-node=2 -#SBATCH --gpus-per-node=2 +#SBATCH --ntasks-per-node=1 +#SBATCH --gpus-per-node=1 #SBATCH --cpus-per-task=72 #SBATCH --time=12:00:00 #SBATCH --no-requeue @@ -18,36 +18,18 @@ ### ENVIRONMENT #### #SBATCH -A a161 -# Choose method to initialize dist in pythorch -export DISTRIBUTED_INITIALIZATION_METHOD=SLURM - -MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" -echo "Master node : $MASTER_ADDR" -# Get IP for hostname. -MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" -echo "Master address : $MASTER_ADDR" -export MASTER_ADDR -export MASTER_PORT=29500 -echo "Master port: $MASTER_PORT" - -# Get number of physical cores using Python -# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") -# # Use SLURM_NTASKS (number of processes to be launched by torchrun) -# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} -# # Compute threads per process -# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) -# export OMP_NUM_THREADS=$OMP_THREADS -export OMP_NUM_THREADS=72 +### CONFIG ### +CONFIG_NAME="src/hirad/conf/eval_real.yaml" srun --environment=./ci/edf/modulus_env.toml bash -c " pip install -e . --no-dependencies # Diurnal cycle - python src/hirad/eval/diurnal_cycle_temp_wind.py --config-name=generate_era_cosmo.yaml - - # Maps - python src/hirad/eval/map_wind_stats.py --config-name=generate_era_cosmo.yaml + # python src/hirad/eval/diurnal_cycle_temp_wind.py --config-name=${CONFIG_NAME} # Probability of exceedance - python src/hirad/eval/probability_of_exceedance_wind.py --config-name=generate_era_cosmo.yaml + # python src/hirad/eval/probability_of_exceedance_wind.py --config-name=${CONFIG_NAME} + + # Maps + # python src/hirad/eval/map_wind_stats.py --config-name=${CONFIG_NAME} " \ No newline at end of file diff --git a/src/hirad/snapshots.sh b/src/hirad/snapshots.sh index a8ebba50..398b5b4d 100644 --- a/src/hirad/snapshots.sh +++ b/src/hirad/snapshots.sh @@ -1,6 +1,6 @@ #!/bin/bash -#SBATCH --job-name="eval_precip" +#SBATCH --job-name="snapshots" ### HARDWARE ### #SBATCH --partition=normal @@ -18,22 +18,7 @@ ### ENVIRONMENT #### #SBATCH -A a161 -# Choose method to initialize dist in pythorch -export DISTRIBUTED_INITIALIZATION_METHOD=SLURM - -MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)" -echo "Master node : $MASTER_ADDR" -# Get IP for hostname. -MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" -echo "Master address : $MASTER_ADDR" -export MASTER_ADDR -export MASTER_PORT=29500 -echo "Master port: $MASTER_PORT" - -export OMP_NUM_THREADS=1 - - srun --environment=./ci/edf/modulus_env.toml bash -c " source ../hirad_env/hirad/bin/activate - python src/hirad/eval/snapshots.py --config-name=snapshot_era_real.yaml + python src/hirad/eval/snapshots.py --config-name=src/hirad/conf/eval_real.yaml " \ No newline at end of file From bbb8deafd4d2af9c89eb3a280f82a4286e7022a9 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 5 Jan 2026 16:45:17 +0100 Subject: [PATCH 238/302] changed config files --- src/hirad/conf/dataset/era_cosmo.yaml | 5 +-- src/hirad/conf/generate_era_cosmo.yaml | 2 +- src/hirad/conf/generate_era_real.yaml | 4 +-- src/hirad/conf/generation/era_cosmo.yaml | 32 ++++++++++--------- src/hirad/conf/generation/era_real.yaml | 23 +++---------- src/hirad/conf/sampler/stochastic.yaml | 4 +-- .../conf/training/era_cosmo_diffusion.yaml | 20 ++++++------ .../training/era_cosmo_diffusion_patched.yaml | 18 ++++++----- .../conf/training/era_cosmo_regression.yaml | 6 ++-- .../conf/training_era_cosmo_diffusion.yaml | 7 ++-- .../training_era_cosmo_diffusion_patched.yaml | 5 +-- .../conf/training_era_cosmo_regression.yaml | 8 ++--- src/hirad/eval.sh | 12 +------ src/hirad/eval_precip.sh | 2 +- src/hirad/generate.sh | 19 +++-------- src/hirad/train_diffusion.sh | 20 ++++-------- src/hirad/train_regression.sh | 24 +++++--------- 17 files changed, 78 insertions(+), 133 deletions(-) diff --git a/src/hirad/conf/dataset/era_cosmo.yaml b/src/hirad/conf/dataset/era_cosmo.yaml index fc81714d..f4033b60 100644 --- a/src/hirad/conf/dataset/era_cosmo.yaml +++ b/src/hirad/conf/dataset/era_cosmo.yaml @@ -1,9 +1,10 @@ type: era5_cosmo -dataset_path: /iopsstor/scratch/cscs/mmcgloho/run-1_2/train/ -# dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-all-channels validation_path: /iopsstor/scratch/cscs/mmcgloho/run-1_2/validation/ +# dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-all-channels input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] output_channel_names: [2t, 10u, 10v, tp] +input_dir_name: era-copernicus-interpolated +output_dir_name: cosmo static_channel_names: ['hsurf'] transform_channels: ['tp-box_cox_025'] n_month_hour_channels: 4 \ No newline at end of file diff --git a/src/hirad/conf/generate_era_cosmo.yaml b/src/hirad/conf/generate_era_cosmo.yaml index d448b583..9ee78661 100644 --- a/src/hirad/conf/generate_era_cosmo.yaml +++ b/src/hirad/conf/generate_era_cosmo.yaml @@ -1,7 +1,7 @@ hydra: job: chdir: true - name: diffusion_era5_cosmo_7500000_test + name: generation_era_cosmo_results run: dir: ./outputs/generation/${hydra:job.name} diff --git a/src/hirad/conf/generate_era_real.yaml b/src/hirad/conf/generate_era_real.yaml index 71e24295..27e46217 100644 --- a/src/hirad/conf/generate_era_real.yaml +++ b/src/hirad/conf/generate_era_real.yaml @@ -1,10 +1,10 @@ hydra: job: chdir: true - name: generate_era_real_5m + name: generation_era_real_results # name: july2020 run: - dir: /capstor/scratch/cscs/pstamenk//outputs/generation/${hydra:job.name} + dir: ./outputs/generation/${hydra:job.name} # Get defaults defaults: diff --git a/src/hirad/conf/generation/era_cosmo.yaml b/src/hirad/conf/generation/era_cosmo.yaml index a5302ff1..3c19a0a9 100644 --- a/src/hirad/conf/generation/era_cosmo.yaml +++ b/src/hirad/conf/generation/era_cosmo.yaml @@ -6,26 +6,30 @@ inference_mode: all # Choose between "all" (regression + diffusion), "regression" or "diffusion" # Patch size. Patch-based sampling will be utilized if these dimensions differ from # img_shape_x and img_shape_y + randomize: True # Whether to randomize the random seeds for each generation. If false, fixed seeds # from 0 to num_ensembles-1 will be used for each time step in times/times_range. -random_seed: 129 +random_seed: 2578458 # Base random seed. This is only used when randomize is True. # random seed will be set for numpy random module to have reproducible randomized generative process. - # Number of overlapping pixels between adjacent patches -# boundary_pixels: 0 - # Number of boundary pixels to be cropped out. 2 is recommanded to address the boundary - # artifact. +# Patching parameters patching: False +# patch_shape_x: 128 +# patch_shape_y: 128 +# overlap_pix: 4 +# # Number of overlapping pixels between adjacent patches +# boundary_pix: 2 +# # Number of boundary pixels to be cropped out. 2 is recommanded to address the boundary +# # artifact. + hr_mean_conditioning: True # sample_res: full # Sampling resolution -times_range: ['20200101-0000','20200102-0000',1] +times_range: ['20200601-0000','20200831-2300',1] + # Start date, end date and time interval (in hours) for the generation times: null -# - 20200926-1800 - #- 20160101-0600 - # - 20160101-1200 has_lead_time: False perf: @@ -41,10 +45,8 @@ perf: # To support multiple workers a threadsafe version of the netCDF library must be used io: - # res_ckpt_path: /capstor/scratch/cscs/pstamenk/outputs/diffusion_full/checkpoints_diffusion - res_ckpt_path: /capstor/scratch/cscs/pstamenk/outputs/training/diffusion_era5_cosmo/checkpoints_diffusion - # res_ckpt_path: /capstor/scratch/cscs/pstamenk/outputs/training/diffusion_era5_cosmo_cmp_fix_2/checkpoints_diffusion - # Checkpoint filename for the diffusion model - reg_ckpt_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_era5_cosmo/checkpoints_regression - # Checkpoint filename for the mean predictor model + res_ckpt_path: /path/to/diffusion/model/weights + # Checkpoint filename for the residual predictor model + reg_ckpt_path: /oath/to/regression/model/weigths + # Checkpoint filename for the mean predictor model output_path: . \ No newline at end of file diff --git a/src/hirad/conf/generation/era_real.yaml b/src/hirad/conf/generation/era_real.yaml index 9c44993e..94411e59 100644 --- a/src/hirad/conf/generation/era_real.yaml +++ b/src/hirad/conf/generation/era_real.yaml @@ -25,26 +25,11 @@ boundary_pix: 2 # # artifact. hr_mean_conditioning: True -# sample_res: full - # Sampling resolution -# times_range: ['20200825-1200','20200827-1200',1] -# times_range: ['20200601-0000','20200831-2300',1] -# times_range: ['20200721-1900','20200721-2000',1] -times_range: null - # Start date, end date and time interval (in hours) for the generation -# times_range: ['20200601-0000','20200831-2300',1] + +times_range: ['20211015-0000','20211015-2300',1] # Start date, end date and time interval (in hours) for the generation -times: - - 20210721-1900 - # - 20200721-1900 #null - # - 20200118-1800 - # - 20200721-1900 - # - 20200926-1800 - # - 20200927-0000 - # - 20200927-0600 - # - 20200927-1200 - # - 20160101-0600 - # - 20160101-1200 + +times: null has_lead_time: False perf: diff --git a/src/hirad/conf/sampler/stochastic.yaml b/src/hirad/conf/sampler/stochastic.yaml index 808270c9..2f3cccbe 100644 --- a/src/hirad/conf/sampler/stochastic.yaml +++ b/src/hirad/conf/sampler/stochastic.yaml @@ -1,5 +1,3 @@ # Stochastic sampler is slower, but should give better results than deterministic sampler. -type: stochastic -# boundary_pix: 2 # set for patched diffusion -# overlap_pix: 4 # set for patched diffusion \ No newline at end of file +type: stochastic \ No newline at end of file diff --git a/src/hirad/conf/training/era_cosmo_diffusion.yaml b/src/hirad/conf/training/era_cosmo_diffusion.yaml index f673e57d..41c3cc0b 100644 --- a/src/hirad/conf/training/era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training/era_cosmo_diffusion.yaml @@ -1,20 +1,20 @@ # Hyperparameters hp: - training_duration: 3500000 + training_duration: 8000000 # Training duration based on the number of processed samples total_batch_size: "auto" # Total batch size - batch_size_per_gpu: 20 + batch_size_per_gpu: 6 # Batch size per GPU lr: 0.0002 # Learning rate - grad_clip_threshold: null + grad_clip_threshold: 1e6 # no gradient clipping for defualt non-patch-based training - lr_decay: 1 + lr_decay: 0.7 # LR decay rate - lr_rampup: 0 + lr_rampup: 1e6 # Rampup for learning rate, in number of samples - lr_decay_rate: 5e5 + lr_decay_rate: 2e6 # Learning rate decay threshold in number of samples, applied every lr_decay_rate samples. # Performance @@ -22,7 +22,7 @@ perf: fp_optimizations: amp-bf16 # Floating point mode, one of ["fp32", "fp16", "amp-fp16", "amp-bf16"] # "amp-{fp16,bf16}" activates Automatic Mixed Precision (AMP) with {float16,bfloat16} - dataloader_workers: 10 + dataloader_workers: 8 # DataLoader worker processes songunet_checkpoint_level: 0 # 0 means no checkpointing # Gradient checkpointing level, value is number of layers to checkpoint @@ -32,16 +32,16 @@ perf: # I/O io: # regression_checkpoint_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_era5_cosmo/checkpoints_regression - regression_checkpoint_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_era5_cosmo_perf_apex_gn/checkpoints_regression + regression_checkpoint_path: /path/to/regression/checkpoint # Where to load the regression checkpoint print_progress_freq: 2000 # How often to print progress - save_checkpoint_freq: 250000 + save_checkpoint_freq: 200000 # How often to save the checkpoints, measured in number of processed samples visualization_freq: 250000 # how often to visualize network outputs validation_freq: 50000 # how often to record the validation loss, measured in number of processed samples - validation_steps: 30 + validation_steps: 90 # how many loss evaluations are used to compute the validation loss per checkpoint checkpoint_dir: . \ No newline at end of file diff --git a/src/hirad/conf/training/era_cosmo_diffusion_patched.yaml b/src/hirad/conf/training/era_cosmo_diffusion_patched.yaml index 679afd65..11874924 100644 --- a/src/hirad/conf/training/era_cosmo_diffusion_patched.yaml +++ b/src/hirad/conf/training/era_cosmo_diffusion_patched.yaml @@ -1,6 +1,6 @@ # Hyperparameters hp: - training_duration: 1000000 + training_duration: 8000000 # Training duration based on the number of processed samples total_batch_size: "auto" # Total batch size @@ -20,10 +20,10 @@ hp: patch_shape_y: 128 # Patch size. Patch training is used if these dimensions differ from # img_shape_x and img_shape_y. - patch_num: 7 + patch_num: 15 # Number of patches from a single sample. Total number of patches is # patch_num * batch_size_global. - max_patch_per_gpu: 100 + max_patch_per_gpu: 300 # Maximum number of pataches a gpu can hold # Performance @@ -35,19 +35,21 @@ perf: # DataLoader worker processes songunet_checkpoint_level: 0 # 0 means no checkpointing # Gradient checkpointing level, value is number of layers to checkpoint - + use_apex_gn: True + torch_compile: True + profile_mode: False # I/O io: - regression_checkpoint_path: /capstor/scratch/cscs/pstamenk/outputs/training/regression_era5_cosmo/checkpoints_regression + regression_checkpoint_path: /path/to/regression/checkpoint # Where to load the regression checkpoint - print_progress_freq: 1000 + print_progress_freq: 2000 # How often to print progress - save_checkpoint_freq: 500000 + save_checkpoint_freq: 250000 # How often to save the checkpoints, measured in number of processed samples visualization_freq: 250000 # how often to visualize network outputs validation_freq: 50000 # how often to record the validation loss, measured in number of processed samples - validation_steps: 100 + validation_steps: 50 # how many loss evaluations are used to compute the validation loss per checkpoint checkpoint_dir: . \ No newline at end of file diff --git a/src/hirad/conf/training/era_cosmo_regression.yaml b/src/hirad/conf/training/era_cosmo_regression.yaml index 7d35d3da..c8423b73 100644 --- a/src/hirad/conf/training/era_cosmo_regression.yaml +++ b/src/hirad/conf/training/era_cosmo_regression.yaml @@ -22,7 +22,7 @@ perf: fp_optimizations: amp-bf16 # Floating point mode, one of ["fp32", "fp16", "amp-fp16", "amp-bf16"] # "amp-{fp16,bf16}" activates Automatic Mixed Precision (AMP) with {float16,bfloat16} - dataloader_workers: 10 + dataloader_workers: 8 # DataLoader worker processes songunet_checkpoint_level: 0 # 0 means no checkpointing # Gradient checkpointing level, value is number of layers to checkpoint @@ -38,8 +38,8 @@ io: # How often to save the checkpoints, measured in number of processed samples visualization_freq: 50000 # how often to visualize network output - validation_freq: 20000 + validation_freq: 40000 # how often to record the validation loss, measured in number of processed samples - validation_steps: 30 + validation_steps: 55 # how many loss evaluations are used to compute the validation loss per checkpoint checkpoint_dir: . \ No newline at end of file diff --git a/src/hirad/conf/training_era_cosmo_diffusion.yaml b/src/hirad/conf/training_era_cosmo_diffusion.yaml index 83629462..ef00c2b5 100644 --- a/src/hirad/conf/training_era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training_era_cosmo_diffusion.yaml @@ -1,9 +1,9 @@ hydra: job: chdir: true - name: diffusion_era5_cosmo_test + name: diffusion_era_cosmo run: - dir: /capstor/scratch/cscs/pstamenk/outputs/training/${hydra:job.name} + dir: ./outputs/training/${hydra:job.name} # Get defaults defaults: @@ -20,8 +20,5 @@ defaults: # Training - training/era_cosmo_diffusion - # Inference visualization - # - generation/era_cosmo_training - # Logging - logging/era_cosmo_diffusion diff --git a/src/hirad/conf/training_era_cosmo_diffusion_patched.yaml b/src/hirad/conf/training_era_cosmo_diffusion_patched.yaml index 38baba08..d618e897 100644 --- a/src/hirad/conf/training_era_cosmo_diffusion_patched.yaml +++ b/src/hirad/conf/training_era_cosmo_diffusion_patched.yaml @@ -3,7 +3,7 @@ hydra: chdir: true name: diffusion_era5_cosmo_patched run: - dir: /capstor/scratch/cscs/pstamenk/outputs/training/${hydra:job.name} + dir: ./outputs/training/${hydra:job.name} # Get defaults defaults: @@ -20,8 +20,5 @@ defaults: # Training - training/era_cosmo_diffusion_patched - # Inference visualization - - generation/era_cosmo_training_patched - # Logging - logging/era_cosmo_diffusion \ No newline at end of file diff --git a/src/hirad/conf/training_era_cosmo_regression.yaml b/src/hirad/conf/training_era_cosmo_regression.yaml index 38912ee4..8bf93a06 100644 --- a/src/hirad/conf/training_era_cosmo_regression.yaml +++ b/src/hirad/conf/training_era_cosmo_regression.yaml @@ -1,9 +1,10 @@ hydra: job: chdir: true - name: regression_era5_cosmo_mlflow_test_x16 + name: regression_era_cosmo run: - dir: /capstor/scratch/cscs/pstamenk/outputs/training/${hydra:job.name} + dir: ./outputs/training/${hydra:job.name} + # Get defaults defaults: @@ -20,8 +21,5 @@ defaults: # Training - training/era_cosmo_regression - # Inference visualization - # - generation/era_cosmo_training - # Logging - logging/era_cosmo_regression diff --git a/src/hirad/eval.sh b/src/hirad/eval.sh index 64bb9959..39890790 100644 --- a/src/hirad/eval.sh +++ b/src/hirad/eval.sh @@ -30,17 +30,7 @@ export MASTER_ADDR export MASTER_PORT=29500 echo "Master port: $MASTER_PORT" -# Get number of physical cores using Python -# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") -# # Use SLURM_NTASKS (number of processes to be launched by torchrun) -# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} -# # Compute threads per process -# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) -# export OMP_NUM_THREADS=$OMP_THREADS -export OMP_NUM_THREADS=72 -# echo "Physical cores: $PHYSICAL_CORES" -# echo "Local processes: $LOCAL_PROCS" -# echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" +export OMP_NUM_THREADS=1 # python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml srun --environment=./ci/edf/modulus_env.toml bash -c " diff --git a/src/hirad/eval_precip.sh b/src/hirad/eval_precip.sh index 6d1acbd5..90c178fb 100644 --- a/src/hirad/eval_precip.sh +++ b/src/hirad/eval_precip.sh @@ -8,7 +8,7 @@ #SBATCH --ntasks-per-node=1 #SBATCH --gpus-per-node=1 #SBATCH --cpus-per-task=72 -#SBATCH --time=05:00:00 +#SBATCH --time=12:00:00 #SBATCH --no-requeue #SBATCH --exclusive diff --git a/src/hirad/generate.sh b/src/hirad/generate.sh index 1e3e9f5c..72e81c73 100644 --- a/src/hirad/generate.sh +++ b/src/hirad/generate.sh @@ -1,9 +1,9 @@ #!/bin/bash -#SBATCH --job-name="testrun" +#SBATCH --job-name="generate" ### HARDWARE ### -#SBATCH --partition=debug +#SBATCH --partition=normal #SBATCH --nodes=1 #SBATCH --ntasks-per-node=2 #SBATCH --gpus-per-node=2 @@ -30,19 +30,8 @@ export MASTER_ADDR export MASTER_PORT=29500 echo "Master port: $MASTER_PORT" -# Get number of physical cores using Python -# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") -# # Use SLURM_NTASKS (number of processes to be launched by torchrun) -# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} -# # Compute threads per process -# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) -# export OMP_NUM_THREADS=$OMP_THREADS -export OMP_NUM_THREADS=72 -# echo "Physical cores: $PHYSICAL_CORES" -# echo "Local processes: $LOCAL_PROCS" -# echo "Setting OMP_NUM_THREADS=$OMP_NUM_THREADS" - -# python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml +export OMP_NUM_THREADS=1 + srun --environment=./ci/edf/modulus_env.toml bash -c " pip install -e . --no-dependencies python src/hirad/inference/generate.py --config-name=generate_era_cosmo.yaml diff --git a/src/hirad/train_diffusion.sh b/src/hirad/train_diffusion.sh index 10eb13f7..6f376187 100644 --- a/src/hirad/train_diffusion.sh +++ b/src/hirad/train_diffusion.sh @@ -3,21 +3,21 @@ #SBATCH --job-name="corrdiff-second-stage" ### HARDWARE ### -#SBATCH --partition=debug -#SBATCH --nodes=2 +#SBATCH --partition=normal +#SBATCH --nodes=8 #SBATCH --ntasks-per-node=4 #SBATCH --gpus-per-node=4 #SBATCH --cpus-per-task=72 -#SBATCH --time=00:30:00 +#SBATCH --time=12:00:00 #SBATCH --no-requeue #SBATCH --exclusive ### OUTPUT ### -#SBATCH --output=/capstor/scratch/cscs/pstamenk/logs/training_diffusion_test.log -#SBATCH --error=/capstor/scratch/cscs/pstamenk/logs/training_diffusion_test.err +#SBATCH --output=./logs/train_diffusion.log +#SBATCH --error=./logs/train_diffusion.err ### ENVIRONMENT #### -#SBATCH -A a122 +#SBATCH -A a161 # Choose method to initialize dist in pythorch export DISTRIBUTED_INITIALIZATION_METHOD=SLURM @@ -29,13 +29,7 @@ MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" export MASTER_ADDR export MASTER_PORT=29500 -# Get number of physical cores using Python -# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") -# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} -# # Compute cores per process -# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) -# export OMP_NUM_THREADS=$OMP_THREADS -export OMP_NUM_THREADS=72 +export OMP_NUM_THREADS=1 # python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml srun --environment=./ci/edf/modulus_env.toml bash -c " diff --git a/src/hirad/train_regression.sh b/src/hirad/train_regression.sh index 1c88a104..03d195e7 100644 --- a/src/hirad/train_regression.sh +++ b/src/hirad/train_regression.sh @@ -3,17 +3,19 @@ #SBATCH --job-name="corrdiff-first-stage" ### HARDWARE ### -#SBATCH --partition=debug -#SBATCH --nodes=2 +#SBATCH --partition=normal +#SBATCH --nodes=8 #SBATCH --ntasks-per-node=4 #SBATCH --gpus-per-node=4 #SBATCH --cpus-per-task=72 -#SBATCH --time=00:30:00 +#SBATCH --time=12:00:00 #SBATCH --no-requeue #SBATCH --exclusive + ### OUTPUT ### -#SBATCH --output=./logs/regression_full_run.log +#SBATCH --output=./logs/train_regression.log +#SBATCH --error=./logs/train_regression.err ### ENVIRONMENT #### #SBATCH -A a161 @@ -28,18 +30,8 @@ MASTER_ADDR="$(getent ahosts "$MASTER_ADDR" | awk '{ print $1; exit }')" export MASTER_ADDR export MASTER_PORT=29500 -# Get number of physical cores using Python -# PHYSICAL_CORES=$(python -c "import psutil; print(psutil.cpu_count(logical=False))") -# LOCAL_PROCS=${SLURM_NTASKS_PER_NODE:-1} -# # Compute cores per process -# OMP_THREADS=$(( PHYSICAL_CORES / LOCAL_PROCS )) -# export OMP_NUM_THREADS=$OMP_THREADS -export OMP_NUM_THREADS=72 -# python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml -# srun bash -c " -# . ./train_env/bin/activate -# python src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml -# " +export OMP_NUM_THREADS=1 + srun --environment=./ci/edf/modulus_env.toml bash -c " pip install -e . --no-dependencies python src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml From f191b5cc4226907913da2dd44eb537d1440f40f4 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 5 Jan 2026 16:52:45 +0100 Subject: [PATCH 239/302] modify dataset configs --- src/hirad/conf/dataset/era_cosmo.yaml | 30 ++++++++++++++++--- .../conf/dataset/era_cosmo_inference.yaml | 28 ++++++++++++++--- src/hirad/conf/dataset/era_real.yaml | 2 -- .../conf/dataset/era_real_inference.yaml | 3 -- 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/hirad/conf/dataset/era_cosmo.yaml b/src/hirad/conf/dataset/era_cosmo.yaml index f4033b60..70ebb579 100644 --- a/src/hirad/conf/dataset/era_cosmo.yaml +++ b/src/hirad/conf/dataset/era_cosmo.yaml @@ -1,10 +1,32 @@ type: era5_cosmo -validation_path: /iopsstor/scratch/cscs/mmcgloho/run-1_2/validation/ -# dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-all-channels -input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] -output_channel_names: [2t, 10u, 10v, tp] +# dataset_path: /iopsstor/scratch/cscs/mmcgloho/run-1_2/train/ +# validation_path: /iopsstor/scratch/cscs/mmcgloho/run-1_2/validation/ +dataset_path: /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-cosmo-1h-all-channels/train/ +validation_path: /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-cosmo-1h-all-channels/validation/ +# input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] +# output_channel_names: [2t, 10u, 10v, tp] input_dir_name: era-copernicus-interpolated output_dir_name: cosmo +input_channel_names: ['10u', '10v', '2d', '2t', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] +output_channel_names: ['10u', '10v', '2d', '2t', + 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', + 'skt', 'sp', 'tcc', 'tp', 'tqv', + 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', + 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', + 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', + 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', + 'z', + 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +] static_channel_names: ['hsurf'] transform_channels: ['tp-box_cox_025'] n_month_hour_channels: 4 \ No newline at end of file diff --git a/src/hirad/conf/dataset/era_cosmo_inference.yaml b/src/hirad/conf/dataset/era_cosmo_inference.yaml index 71b0db15..2af121b3 100644 --- a/src/hirad/conf/dataset/era_cosmo_inference.yaml +++ b/src/hirad/conf/dataset/era_cosmo_inference.yaml @@ -1,8 +1,28 @@ type: era5_cosmo -# dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-linear-interpolation/validation -dataset_path: /iopsstor/scratch/cscs/mmcgloho/run-1_2/validation -input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] -output_channel_names: [2t, 10u, 10v, tp] +# dataset_path: /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-cosmo-1h-all-channels/validation/ +# dataset_path: /iopsstor/scratch/cscs/mmcgloho/run-1_2/validation/ +input_dir_name: era-interpolated +output_dir_name: cosmo-orig +# input_channel_names: ['10u', '10v', '2d', '2t', +# 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', +# 'sdor', 'slor', 'skt', 'sp', 'tcw', 'tp', +# 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', +# 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', +# 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', +# 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', +# 'z', +# 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +# ] +# output_channel_names: ['10u', '10v', '2d', '2t', +# 'q_100', 'q_1000', 'q_150', 'q_200', 'q_250', 'q_300', 'q_400', 'q_50', 'q_500', 'q_600', 'q_700', 'q_850', 'q_925', +# 'skt', 'sp', 'tcc', 'tp', 'tqv', +# 't_100', 't_1000', 't_150', 't_200', 't_250', 't_300', 't_400', 't_50', 't_500', 't_600', 't_700', 't_850', 't_925', +# 'u_100', 'u_1000', 'u_150', 'u_200', 'u_250', 'u_300', 'u_400', 'u_50', 'u_500', 'u_600', 'u_700', 'u_850', 'u_925', +# 'v_100', 'v_1000', 'v_150', 'v_200', 'v_250', 'v_300', 'v_400', 'v_50', 'v_500', 'v_600', 'v_700', 'v_850', 'v_925', +# 'w_100', 'w_1000', 'w_150', 'w_200', 'w_250', 'w_300', 'w_400', 'w_50', 'w_500', 'w_600', 'w_700', 'w_850', 'w_925', +# 'z', +# 'z_100', 'z_1000', 'z_150', 'z_200', 'z_250', 'z_300', 'z_400', 'z_50', 'z_500', 'z_600', 'z_700', 'z_850', 'z_925', +# ] static_channel_names: ['hsurf'] transform_channels: ['tp-box_cox_025'] n_month_hour_channels: 4 \ No newline at end of file diff --git a/src/hirad/conf/dataset/era_real.yaml b/src/hirad/conf/dataset/era_real.yaml index 95f5b02a..ec1b9508 100644 --- a/src/hirad/conf/dataset/era_real.yaml +++ b/src/hirad/conf/dataset/era_real.yaml @@ -1,8 +1,6 @@ type: era5_real dataset_path: /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/train validation_path: /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/validation -# dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-all-channels -# validation_path: /iopsstor/scratch/cscs/mmcgloho/run-1_2/validation/ input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] output_channel_names: [2t, 10u, 10v, tp] static_channel_names: ['z'] diff --git a/src/hirad/conf/dataset/era_real_inference.yaml b/src/hirad/conf/dataset/era_real_inference.yaml index 34ea7e45..847d6046 100644 --- a/src/hirad/conf/dataset/era_real_inference.yaml +++ b/src/hirad/conf/dataset/era_real_inference.yaml @@ -1,8 +1,5 @@ type: era5_real -# dataset_path: /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/train dataset_path: /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-realch1/v1.0-channel-subset/validation -# dataset_path: /iopsstor/scratch/cscs/pstamenk/datasets/basic-torch/era5-cosmo-1h-all-channels -# validation_path: /iopsstor/scratch/cscs/mmcgloho/run-1_2/validation/ input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] output_channel_names: [2t, 10u, 10v, tp] static_channel_names: ['z'] From 21f6234431929b1f05fbed2383c88b5353fba6d1 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 5 Jan 2026 17:02:43 +0100 Subject: [PATCH 240/302] clean up configs further --- .../conf/dataset/era_cosmo_inference.yaml | 2 +- src/hirad/conf/eval_real.yaml | 4 ++-- src/hirad/conf/generate_era_cosmo.yaml | 3 +-- src/hirad/conf/generate_era_cosmo_test.yaml | 3 +-- src/hirad/conf/generate_era_real.yaml | 4 +--- src/hirad/conf/generation/era_real.yaml | 10 ++------- .../conf/logging/era_real_regression.yaml | 2 +- src/hirad/conf/plot_maps.yaml | 21 ------------------- src/hirad/conf/snapshot_era_real.yaml | 18 ---------------- .../training/era_real_diffusion_patched.yaml | 2 +- .../training_era_real_diffusion_patched.yaml | 7 ++----- .../conf/training_era_real_regression.yaml | 7 ++----- 12 files changed, 14 insertions(+), 69 deletions(-) delete mode 100644 src/hirad/conf/plot_maps.yaml delete mode 100644 src/hirad/conf/snapshot_era_real.yaml diff --git a/src/hirad/conf/dataset/era_cosmo_inference.yaml b/src/hirad/conf/dataset/era_cosmo_inference.yaml index 2af121b3..9fabeffd 100644 --- a/src/hirad/conf/dataset/era_cosmo_inference.yaml +++ b/src/hirad/conf/dataset/era_cosmo_inference.yaml @@ -1,6 +1,6 @@ type: era5_cosmo # dataset_path: /iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-cosmo-1h-all-channels/validation/ -# dataset_path: /iopsstor/scratch/cscs/mmcgloho/run-1_2/validation/ +dataset_path: /iopsstor/scratch/cscs/mmcgloho/run-1_2/validation/ input_dir_name: era-interpolated output_dir_name: cosmo-orig # input_channel_names: ['10u', '10v', '2d', '2t', diff --git a/src/hirad/conf/eval_real.yaml b/src/hirad/conf/eval_real.yaml index b4f87431..15f61db0 100644 --- a/src/hirad/conf/eval_real.yaml +++ b/src/hirad/conf/eval_real.yaml @@ -1,5 +1,5 @@ # Path to the inference output directory -inference_output_dir: '/capstor/scratch/cscs/pstamenk/outputs/generation/generate_era_real_8m' +inference_output_dir: '/path/to/generation/results/directory' results_dir_name: 'evaluation_maps' # Constants for evaluation depend on dataset specifics and should be modified accordingly @@ -8,7 +8,7 @@ conv_factor_hourly: 1000 # Convert precip of ERA5 from meters to mm/h conv_factor: 24000 # Convert the precip of ERA5 from meters/h to mm/day wet_threshold: 0.1 # Threshold for wet-hour in mm/h log_interval: 24 # Log progress every N timesteps -land_sea_mask_path: '/users/pstamenk/HiRAD-Gen/lsm_real.npy' +land_sea_mask_path: '/path/to/land_sea_mask.npy' # Path to land-sea mask numpy file # Constants describing the grid for plotting lat_start: -4.42 diff --git a/src/hirad/conf/generate_era_cosmo.yaml b/src/hirad/conf/generate_era_cosmo.yaml index 9ee78661..8c9b23fb 100644 --- a/src/hirad/conf/generate_era_cosmo.yaml +++ b/src/hirad/conf/generate_era_cosmo.yaml @@ -16,5 +16,4 @@ defaults: #- sampler/deterministic # Generation - - generation/era_cosmo - #- generation/patched_based \ No newline at end of file + - generation/era_cosmo \ No newline at end of file diff --git a/src/hirad/conf/generate_era_cosmo_test.yaml b/src/hirad/conf/generate_era_cosmo_test.yaml index bb838902..e0f53a60 100644 --- a/src/hirad/conf/generate_era_cosmo_test.yaml +++ b/src/hirad/conf/generate_era_cosmo_test.yaml @@ -16,5 +16,4 @@ defaults: #- sampler/deterministic # Generation - - generation/era_cosmo_test - #- generation/patched_based \ No newline at end of file + - generation/era_cosmo_test \ No newline at end of file diff --git a/src/hirad/conf/generate_era_real.yaml b/src/hirad/conf/generate_era_real.yaml index 27e46217..0a4c0467 100644 --- a/src/hirad/conf/generate_era_real.yaml +++ b/src/hirad/conf/generate_era_real.yaml @@ -2,7 +2,6 @@ hydra: job: chdir: true name: generation_era_real_results - # name: july2020 run: dir: ./outputs/generation/${hydra:job.name} @@ -17,5 +16,4 @@ defaults: #- sampler/deterministic # Generation - - generation/era_real - #- generation/patched_based \ No newline at end of file + - generation/era_real \ No newline at end of file diff --git a/src/hirad/conf/generation/era_real.yaml b/src/hirad/conf/generation/era_real.yaml index 94411e59..e6e35ec1 100644 --- a/src/hirad/conf/generation/era_real.yaml +++ b/src/hirad/conf/generation/era_real.yaml @@ -45,14 +45,8 @@ perf: # To support multiple workers a threadsafe version of the netCDF library must be used io: - # res_ckpt_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_diffusion_8_attention - # res_ckpt_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_diffusion_all_channels - # res_ckpt_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_compression_diffusion - res_ckpt_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_diffusion_real + res_ckpt_path: /path/to/diffusion/checkpoint # Checkpoint filename for the residual predictor model - # reg_ckpt_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_regression_8_attention - # reg_ckpt_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_regression_all_channels - # reg_ckpt_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_compression_regression - reg_ckpt_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_regression_real + reg_ckpt_path: /path/to/regression/checkpoint # Checkpoint filename for the mean predictor model output_path: . \ No newline at end of file diff --git a/src/hirad/conf/logging/era_real_regression.yaml b/src/hirad/conf/logging/era_real_regression.yaml index 1a016667..1b731019 100644 --- a/src/hirad/conf/logging/era_real_regression.yaml +++ b/src/hirad/conf/logging/era_real_regression.yaml @@ -1,7 +1,7 @@ # set method to mlflow to log with mlflow method: mlflow experiment_name: hirad-corrdiff-regression-training -run_name: era-real-all-channels +run_name: era-real # change uri to remote mlflow server; if null, it is stored locally # if uri is remote make sure to have credentials set in ~/.mlflow/credentials uri: null diff --git a/src/hirad/conf/plot_maps.yaml b/src/hirad/conf/plot_maps.yaml deleted file mode 100644 index 07ecb854..00000000 --- a/src/hirad/conf/plot_maps.yaml +++ /dev/null @@ -1,21 +0,0 @@ -hydra: - job: - chdir: true - name: plot_maps - run: - dir: . - -# Get defaults -defaults: - - _self_ - # Dataset - - dataset/era_cosmo_inference - -results_dir: /capstor/scratch/cscs/pstamenk/outputs/generation/test_period_advanced_stats_2 - -time_steps: null # If null, will use all time steps in the results directory - -output_dir: ${results_dir}/plots_boxcox_masked_limits # Directory to save the plots - -plot_box_precipitation: False # Whether to plot box precipitation maps, otherwise plot with box-cox transform -tp_threshold: 0.0002 # Threshold in m/h for masking precipitation values \ No newline at end of file diff --git a/src/hirad/conf/snapshot_era_real.yaml b/src/hirad/conf/snapshot_era_real.yaml deleted file mode 100644 index ca9d8f61..00000000 --- a/src/hirad/conf/snapshot_era_real.yaml +++ /dev/null @@ -1,18 +0,0 @@ -hydra: - job: - chdir: true - name: generate_era_real_5m - # name: july2020 - run: - dir: /capstor/scratch/cscs/pstamenk//outputs/generation/${hydra:job.name} - -# Get defaults -defaults: - - _self_ - # Dataset - - dataset/era_real_inference - - # Generation - - generation/era_real - -# plot_channels: ['2t', '10u', '10v', 'tp', 't_700', 'u_700', 'v_700', 'z_700', 'q_700', 'w_700'] \ No newline at end of file diff --git a/src/hirad/conf/training/era_real_diffusion_patched.yaml b/src/hirad/conf/training/era_real_diffusion_patched.yaml index 5ab5025b..2f31f5ca 100644 --- a/src/hirad/conf/training/era_real_diffusion_patched.yaml +++ b/src/hirad/conf/training/era_real_diffusion_patched.yaml @@ -40,7 +40,7 @@ perf: profile_mode: False # I/O io: - regression_checkpoint_path: /capstor/scratch/cscs/pstamenk/checkpoints/checkpoint_regression_real + regression_checkpoint_path: /path/to/regression/checkpoint # Where to load the regression checkpoint print_progress_freq: 2000 # How often to print progress diff --git a/src/hirad/conf/training_era_real_diffusion_patched.yaml b/src/hirad/conf/training_era_real_diffusion_patched.yaml index a22831a3..908feccf 100644 --- a/src/hirad/conf/training_era_real_diffusion_patched.yaml +++ b/src/hirad/conf/training_era_real_diffusion_patched.yaml @@ -1,9 +1,9 @@ hydra: job: chdir: true - name: era_real_diffusion + name: diffusion_era_real run: - dir: /capstor/scratch/cscs/pstamenk/outputs/training/${hydra:job.name} + dir: ./outputs/training/${hydra:job.name} # Get defaults defaults: @@ -20,8 +20,5 @@ defaults: # Training - training/era_real_diffusion_patched - # Inference visualization - # - generation/era_cosmo_training_patched - # Logging - logging/era_real_diffusion \ No newline at end of file diff --git a/src/hirad/conf/training_era_real_regression.yaml b/src/hirad/conf/training_era_real_regression.yaml index d2f538a9..8dab37e3 100644 --- a/src/hirad/conf/training_era_real_regression.yaml +++ b/src/hirad/conf/training_era_real_regression.yaml @@ -1,9 +1,9 @@ hydra: job: chdir: true - name: era_real_regression + name: regression_era_real run: - dir: /capstor/scratch/cscs/pstamenk/outputs/training/${hydra:job.name} + dir: ./outputs/training/${hydra:job.name} # Get defaults @@ -21,8 +21,5 @@ defaults: # Training - training/era_real_regression - # Inference visualization - # - generation/era_cosmo_training - # Logging - logging/era_real_regression From 9a706f0c02e861cfb3900a219fd31518c1718bfa Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 6 Jan 2026 14:43:58 +0100 Subject: [PATCH 241/302] add config for cosmo model evaluation --- src/hirad/conf/eval_cosmo.yaml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/hirad/conf/eval_cosmo.yaml diff --git a/src/hirad/conf/eval_cosmo.yaml b/src/hirad/conf/eval_cosmo.yaml new file mode 100644 index 00000000..08c55f2d --- /dev/null +++ b/src/hirad/conf/eval_cosmo.yaml @@ -0,0 +1,25 @@ +# Path to the inference output directory +inference_output_dir: '/path/to/generation/results/directory' +results_dir_name: 'evaluation_maps' + +# Constants for evaluation depend on dataset specifics and should be modified accordingly + +conv_factor_hourly: 1000 # Convert precip of ERA5 from meters to mm/h +conv_factor: 24000 # Convert the precip of ERA5 from meters/h to mm/day +wet_threshold: 0.1 # Threshold for wet-hour in mm/h +log_interval: 24 # Log progress every N timesteps +land_sea_mask_path: '/capstor/store/mch/msopr/hirad-gen/eval/lsm.npy' # Path to land-sea mask numpy file + +# Constants describing the grid for plotting +lat_start: -4.42 +lat_end: 3.36 +lat_step: 0.02 +lon_start: -6.82 +lon_end: 4.80 +lon_step: 0.02 +relax_zone: 19 +height: 352 +width: 544 + +# List of channels to evaluate/plot - comment out if all channels are to be used +# plot_channels: ['2t', '10u', '10v', 'tp', 't_700', 'u_700', 'v_700', 'z_700', 'q_700', 'w_700'] \ No newline at end of file From 3300354789963a723849544cc473b6bc30ced6fd Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 5 Mar 2026 16:56:15 +0100 Subject: [PATCH 242/302] update base image to the one Fabian gave us that has the correct entrypoint --- ci/docker/Dockerfile.ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/docker/Dockerfile.ci b/ci/docker/Dockerfile.ci index 80925d3f..9a4fb641 100644 --- a/ci/docker/Dockerfile.ci +++ b/ci/docker/Dockerfile.ci @@ -1,4 +1,4 @@ -FROM nvcr.io/nvidia/physicsnemo/physicsnemo:25.06 +FROM jfrog.svc.cscs.ch/docker-group-csstaff/alps-images/ngc-physicsnemo:25.11-alps4-dev # setup RUN apt-get update && apt-get install python3-pip python3-venv -y From 7f93f07c1aa42c9cb3b892325e1a98e1b6fcf69e Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 5 Mar 2026 16:57:13 +0100 Subject: [PATCH 243/302] update base image to the CSCS one with correct entrypoint --- ci/docker/Dockerfile.ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/docker/Dockerfile.ci b/ci/docker/Dockerfile.ci index 80925d3f..9a4fb641 100644 --- a/ci/docker/Dockerfile.ci +++ b/ci/docker/Dockerfile.ci @@ -1,4 +1,4 @@ -FROM nvcr.io/nvidia/physicsnemo/physicsnemo:25.06 +FROM jfrog.svc.cscs.ch/docker-group-csstaff/alps-images/ngc-physicsnemo:25.11-alps4-dev # setup RUN apt-get update && apt-get install python3-pip python3-venv -y From a3d62163cafe0b21c329014128a9be7ced1a0eb1 Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Thu, 5 Mar 2026 17:31:40 +0100 Subject: [PATCH 244/302] add varialbes so runner can conform to launch constraints listed at https://docs.cscs.ch/software/alps-extended-images/ --- ci/cscs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ci/cscs.yml b/ci/cscs.yml index c3ac15c6..0c2f5c43 100644 --- a/ci/cscs.yml +++ b/ci/cscs.yml @@ -23,6 +23,10 @@ test_job: # - pip install -e . --no-dependencies # - python src/hirad/training/train.py --config-name=training_era_cosmo_regression_test.yaml variables: + USE_MPI: NO + SLURM_MPI_TYPE: pmix + SLURM_NETWORK: disable_rdzv_get + PMIX_MCA_psec: native SLURM_JOB_NUM_NODES: 2 SLURM_NTASKS: 2 #SLURM_ACCOUNT: a161 From 6a24532c7a4da4ecffc204454706c6f2f243c0f5 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 14 Jan 2026 15:49:20 +0100 Subject: [PATCH 245/302] add class for irregular grid interpolation --- src/hirad/utils/dataset_utils.py | 146 +++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 src/hirad/utils/dataset_utils.py diff --git a/src/hirad/utils/dataset_utils.py b/src/hirad/utils/dataset_utils.py new file mode 100644 index 00000000..9f52a580 --- /dev/null +++ b/src/hirad/utils/dataset_utils.py @@ -0,0 +1,146 @@ +import numpy as np +from scipy.spatial import Delaunay +from typing import Optional + + +class GridData(): + """ + Performs interpolation from irregular points to target grid using Delaunay triangulation. + + This class uses barycentric coordinate interpolation within Delaunay triangles to map + values from an original set of scattered points to a target set of points. It has same + behavior as scipy.interpolate.griddata with method='linear', but is optimized for + repeated interpolations on the same set of points. + + Attributes: + longitudes_orig (np.ndarray): Longitude coordinates of original points. + latitudes_orig (np.ndarray): Latitude coordinates of original points. + longitudes_target (np.ndarray): Longitude coordinates of target points. + latitudes_target (np.ndarray): Latitude coordinates of target points. + tri (Delaunay): Delaunay triangulation of original points. + simplex_id (np.ndarray): Simplex indices for each target point. + lambda1 (np.ndarray): Barycentric coordinate 1 for each target point. + lambda2 (np.ndarray): Barycentric coordinate 2 for each target point. + lambda3 (np.ndarray): Barycentric coordinate 3 for each target point. + """ + + def __init__( + self, + longitudes_orig: np.ndarray, + latitudes_orig: np.ndarray, + longitudes_target: np.ndarray, + latitudes_target: np.ndarray + ) -> None: + """ + Initialize the GridData interpolator. + + Args: + longitudes_orig: 1D array of longitude values for original points. + latitudes_orig: 1D array of latitude values for original points. + longitudes_target: 1D array of longitude values for target points. + latitudes_target: 1D array of latitude values for target points. + + Raises: + ValueError: If input arrays have incompatible shapes. + """ + # Validate inputs + if len(longitudes_orig) != len(latitudes_orig): + raise ValueError("Original longitude and latitude arrays must have same length") + if len(longitudes_target) != len(latitudes_target): + raise ValueError("Target longitude and latitude arrays must have same length") + + self.longitudes_orig = np.asarray(longitudes_orig) + self.latitudes_orig = np.asarray(latitudes_orig) + + self.longitudes_target = np.asarray(longitudes_target) + self.latitudes_target = np.asarray(latitudes_target) + + self._prepare_interpolation() + + def _prepare_interpolation(self): + """ + Prepare interpolation by computing Delaunay triangulation and barycentric coordinates. + + This method: + 1. Computes Delaunay triangulation of original points + 2. Finds which simplex each target point belongs to + 3. Precomputes barycentric coordinates (lambda1, lambda2, lambda3) for interpolation + """ + # Compute Delaunay triangulation + coords_orig = np.stack([self.longitudes_orig, self.latitudes_orig], axis=-1) + self.tri = Delaunay(coords_orig) + + # Find simplex indices for target points + coords_target = np.stack([self.longitudes_target, self.latitudes_target], axis=-1) + self.simplex_id = self.tri.find_simplex(coords_target) + + # Check for points outside convex hull + if np.any(self.simplex_id == -1): + n_outside = np.sum(self.simplex_id == -1) + print(f"Warning: {n_outside} target points are outside the convex hull of original points") + + # Get corner coordinates of simplices + longitudes_corners = self.longitudes_orig[self.tri.simplices] + latitudes_corners = self.latitudes_orig[self.tri.simplices] + + # Get corner coordinates for each target point's simplex + longitude_corners_per_target = longitudes_corners[self.simplex_id] + latitude_corners_per_target = latitudes_corners[self.simplex_id] + + # Extract traingle vertices + x1, y1 = longitude_corners_per_target[:, 0], latitude_corners_per_target[:, 0] + x2, y2 = longitude_corners_per_target[:, 1], latitude_corners_per_target[:, 1] + x3, y3 = longitude_corners_per_target[:, 2], latitude_corners_per_target[:, 2] + + denominator = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3) + + self.lambda1 = ((y2 - y3) * (self.longitudes_target - x3) + + (x3 - x2) * (self.latitudes_target - y3)) / denominator + + self.lambda2 = ((y3 - y1) * (self.longitudes_target - x3) + + (x1 - x3) * (self.latitudes_target - y3)) / denominator + + self.lambda3 = 1 - self.lambda1 - self.lambda2 + + def interpolate(self, values: np.ndarray, fill_value: Optional[float] = np.nan) -> np.ndarray: + """ + Interpolate values from original points to target points. + + Args: + values: Array of shape (n_samples, n_original_points) containing values + at original point locations to be interpolated. + fill_value: Value to use for target points outside the convex hull. + Defaults to np.nan. + + Returns: + Array of shape (n_samples, n_target_points) with interpolated values. + + Raises: + ValueError: If values shape is incompatible with original points. + """ + if values.shape[-1] != len(self.longitudes_orig): + raise ValueError( + f"Expected values with shape (..., {len(self.longitudes_orig)}), " + f"got shape {values.shape}" + ) + + # Find the corner values of each simplice + values_simplices = values[:,self.tri.simplices] + + # Find the simplice corner values for each target point + values_per_target_simplices = values_simplices[:,self.simplex_id] + + # Perform barycentric interpolation + out = (self.lambda1 * values_per_target_simplices[:,:,0] + + self.lambda2 * values_per_target_simplices[:,:,1] + + self.lambda3 * values_per_target_simplices[:,:,2]) + + # Handle points outside convex hull + if np.any(self.simplex_id == -1): + out[:, self.simplex_id == -1] = fill_value + + return out + + def __call__(self, values: np.ndarray, fill_value: Optional[float] = np.nan) -> np.ndarray: + """Alias for forward method to make class callable.""" + return self.interpolate(values, fill_value) \ No newline at end of file From 1e8eb1164349c9b5aafb583a350595c91ae57c43 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 14 Jan 2026 17:17:22 +0100 Subject: [PATCH 246/302] enable interpolation on gpu-s using pytorch --- src/hirad/utils/dataset_utils.py | 95 +++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 25 deletions(-) diff --git a/src/hirad/utils/dataset_utils.py b/src/hirad/utils/dataset_utils.py index 9f52a580..06539603 100644 --- a/src/hirad/utils/dataset_utils.py +++ b/src/hirad/utils/dataset_utils.py @@ -1,3 +1,4 @@ +import torch import numpy as np from scipy.spatial import Delaunay from typing import Optional @@ -17,11 +18,8 @@ class GridData(): latitudes_orig (np.ndarray): Latitude coordinates of original points. longitudes_target (np.ndarray): Longitude coordinates of target points. latitudes_target (np.ndarray): Latitude coordinates of target points. - tri (Delaunay): Delaunay triangulation of original points. - simplex_id (np.ndarray): Simplex indices for each target point. - lambda1 (np.ndarray): Barycentric coordinate 1 for each target point. - lambda2 (np.ndarray): Barycentric coordinate 2 for each target point. - lambda3 (np.ndarray): Barycentric coordinate 3 for each target point. + is_torch (bool): Whether tensors are prepared for PyTorch operations. + device (torch.device | None): Device for PyTorch tensors if applicable. """ def __init__( @@ -54,6 +52,9 @@ def __init__( self.longitudes_target = np.asarray(longitudes_target) self.latitudes_target = np.asarray(latitudes_target) + + self.is_torch = False + self.device = None self._prepare_interpolation() @@ -68,24 +69,24 @@ def _prepare_interpolation(self): """ # Compute Delaunay triangulation coords_orig = np.stack([self.longitudes_orig, self.latitudes_orig], axis=-1) - self.tri = Delaunay(coords_orig) + self._tri = Delaunay(coords_orig) # Find simplex indices for target points coords_target = np.stack([self.longitudes_target, self.latitudes_target], axis=-1) - self.simplex_id = self.tri.find_simplex(coords_target) + self._simplex_id = self._tri.find_simplex(coords_target) # Check for points outside convex hull - if np.any(self.simplex_id == -1): - n_outside = np.sum(self.simplex_id == -1) + if np.any(self._simplex_id == -1): + n_outside = np.sum(self._simplex_id == -1) print(f"Warning: {n_outside} target points are outside the convex hull of original points") # Get corner coordinates of simplices - longitudes_corners = self.longitudes_orig[self.tri.simplices] - latitudes_corners = self.latitudes_orig[self.tri.simplices] + longitudes_corners = self.longitudes_orig[self._tri.simplices] + latitudes_corners = self.latitudes_orig[self._tri.simplices] # Get corner coordinates for each target point's simplex - longitude_corners_per_target = longitudes_corners[self.simplex_id] - latitude_corners_per_target = latitudes_corners[self.simplex_id] + longitude_corners_per_target = longitudes_corners[self._simplex_id] + latitude_corners_per_target = latitudes_corners[self._simplex_id] # Extract traingle vertices x1, y1 = longitude_corners_per_target[:, 0], latitude_corners_per_target[:, 0] @@ -94,15 +95,59 @@ def _prepare_interpolation(self): denominator = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3) - self.lambda1 = ((y2 - y3) * (self.longitudes_target - x3) + + self._lambda1 = ((y2 - y3) * (self.longitudes_target - x3) + (x3 - x2) * (self.latitudes_target - y3)) / denominator - self.lambda2 = ((y3 - y1) * (self.longitudes_target - x3) + + self._lambda2 = ((y3 - y1) * (self.longitudes_target - x3) + (x1 - x3) * (self.latitudes_target - y3)) / denominator - self.lambda3 = 1 - self.lambda1 - self.lambda2 + self._lambda3 = 1 - self._lambda1 - self._lambda2 + + + def to_torch(self, device: torch.device ='cpu') -> None: + """ + Prepare barycentric coordinates and simplex indices for PyTorch operations. + + This method converts the precomputed numpy arrays to PyTorch tensors + and moves them to the specified device. + + Args: + device: The torch device to move tensors to (e.g., 'cpu' or 'cuda'). + """ + if isinstance(device, str): + device = torch.device(device) + self.device = device + + self._lambda1 = torch.from_numpy(self._lambda1).to(device) + self._lambda2 = torch.from_numpy(self._lambda2).to(device) + self._lambda3 = torch.from_numpy(self._lambda3).to(device) + + # Convert indexing arrays + self._simplex_id = torch.from_numpy(self._simplex_id).to(device) + self._tri.simplices = torch.from_numpy(self._tri.simplices).to(device) + + self.is_torch = True + + def to_numpy(self) -> None: + """ + Convert barycentric coordinates and simplex indices back to numpy arrays. + + This method converts the precomputed PyTorch tensors back to numpy arrays. + """ + if not self.is_torch: + return # Already in numpy format + + self._lambda1 = self._lambda1.cpu().numpy() + self._lambda2 = self._lambda2.cpu().numpy() + self._lambda3 = self._lambda3.cpu().numpy() + + self._simplex_id = self._simplex_id.cpu().numpy() + self._tri.simplices = self._tri.simplices.cpu().numpy() + + self.is_torch = False + self.device = None - def interpolate(self, values: np.ndarray, fill_value: Optional[float] = np.nan) -> np.ndarray: + def interpolate(self, values: np.ndarray | torch.Tensor, fill_value: Optional[float] = np.nan) -> np.ndarray: """ Interpolate values from original points to target points. @@ -125,22 +170,22 @@ def interpolate(self, values: np.ndarray, fill_value: Optional[float] = np.nan) ) # Find the corner values of each simplice - values_simplices = values[:,self.tri.simplices] + values_simplices = values[:,self._tri.simplices] # Find the simplice corner values for each target point - values_per_target_simplices = values_simplices[:,self.simplex_id] + values_per_target_simplices = values_simplices[:,self._simplex_id] # Perform barycentric interpolation - out = (self.lambda1 * values_per_target_simplices[:,:,0] + - self.lambda2 * values_per_target_simplices[:,:,1] + - self.lambda3 * values_per_target_simplices[:,:,2]) + out = (self._lambda1 * values_per_target_simplices[:,:,0] + + self._lambda2 * values_per_target_simplices[:,:,1] + + self._lambda3 * values_per_target_simplices[:,:,2]) # Handle points outside convex hull - if np.any(self.simplex_id == -1): - out[:, self.simplex_id == -1] = fill_value + if (not self.is_torch and np.any(self._simplex_id == -1)) or (self.is_torch and torch.any(self._simplex_id == -1)): + out[:, self._simplex_id == -1] = fill_value return out - def __call__(self, values: np.ndarray, fill_value: Optional[float] = np.nan) -> np.ndarray: + def __call__(self, values: np.ndarray | torch.Tensor, fill_value: Optional[float] = np.nan) -> np.ndarray: """Alias for forward method to make class callable.""" return self.interpolate(values, fill_value) \ No newline at end of file From 726cbd65b763a20423f0fe3ba827149c0c5e814b Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Mon, 19 Jan 2026 16:38:24 +0100 Subject: [PATCH 247/302] adjust netcdf interpolation to use GridData (for copernicus data processing) --- src/hirad/input_data/configs/copernicus.yaml | 6 ++--- src/hirad/input_data/interpolate_basic.py | 23 ++++++++++++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/hirad/input_data/configs/copernicus.yaml b/src/hirad/input_data/configs/copernicus.yaml index b557f859..2a9ccbf9 100644 --- a/src/hirad/input_data/configs/copernicus.yaml +++ b/src/hirad/input_data/configs/copernicus.yaml @@ -1,6 +1,6 @@ path: '/capstor/store/mch/msopr/hirad-gen/copernicus-datasets/' -channels: ['cp', 'tp'] -#start: 2015-10-01 -start: 2020-01-01 +channels: ['tp'] +start: 2015-10-01 +#start: 2020-01-01 end: 2020-11-30 frequency: 1 \ No newline at end of file diff --git a/src/hirad/input_data/interpolate_basic.py b/src/hirad/input_data/interpolate_basic.py index 5f1058d5..7ba29cbf 100644 --- a/src/hirad/input_data/interpolate_basic.py +++ b/src/hirad/input_data/interpolate_basic.py @@ -16,6 +16,8 @@ import torch import multiprocessing +from hirad.utils.dataset_utils import GridData + # Margin to use for ERA dataset (to avoid nans from interpolation at boundary) ERA_MARGIN_DEGREES = 1.0 @@ -92,6 +94,16 @@ def regrid(input_values_for_time: np.ndarray, input_grid: np.ndarray, output_gri interpolated_data[j,:] = regrid return interpolated_data +def regrid_with_interpolator(input_values_for_time: np.ndarray, interpolator: GridData): + assert(len(input_values_for_time.shape) == 2) + interpolated_data = np.empty([input_values_for_time.shape[0], interpolator.longitudes_target.shape[0]]) + for j in range(input_values_for_time.shape[0]): + values = np.array(input_values_for_time[j,:]) # get input grid values on the given date-time and channel + values = values.reshape(1, values.shape[0]) + regrid = interpolator.interpolate(values) # interpolate input to output grid using GridData method + interpolated_data[j,:] = regrid + return interpolated_data + def format_date(dt64: np.datetime64) -> str: """Makes date string from date time point, for saving files.""" return to_datetime(dt64).strftime('%Y%m%d-%H%M') @@ -400,13 +412,20 @@ def interpolate_netcdf_to_grid(infile_nc: str, ds_name: str, output_grid: np.nda grid_size = curr_nc[variables[0]][variables[0]][:].shape[1:] input_grid = extract_netcdf_input_grid_025(curr_nc[variables[0]]) - # Output grid as torch file + # Output grids as torch files torch.save(np.column_stack((input_grid[:,1], input_grid[:,0])), os.path.join(output_path, 'info', f'{ds_name}-lat-lon')) + + torch.save(np.column_stack((output_grid[:,1], output_grid[:,0])), os.path.join(output_path, 'info', f'target-lat-lon')) # TODO consider outputting stats, but this would require additional calculations # Copy the .yaml file over for recording purposes shutil.copy(infile_nc, os.path.join(output_path, f'info/{ds_name}.yaml')) + + + + # Set up the interpolator + interpolator = GridData(input_grid[:,0], input_grid[:,1], output_grid[:,0], output_grid[:,1]) # Iterate through each date t = start_date @@ -428,7 +447,7 @@ def interpolate_netcdf_to_grid(infile_nc: str, ds_name: str, output_grid: np.nda save_datetime_file(input_values, t, os.path.join(output_path, ds_name), format=format) # Regrid - interpolated_data = regrid(input_values, input_grid, output_grid) + interpolated_data = regrid_with_interpolator(input_values, interpolator) save_datetime_file(interpolated_data, t, os.path.join(output_path, f'{ds_name}-interpolated'), format=format) # Plot From 154e6cd535fe3d7ae52130934a6c713ec444144c Mon Sep 17 00:00:00 2001 From: Mary McGlohon Date: Wed, 21 Jan 2026 12:15:54 +0100 Subject: [PATCH 248/302] draft of anemoi-based data loader --- src/hirad/conf/dataset/anemoi_era_real.yaml | 13 + src/hirad/datasets/__init__.py | 1 + src/hirad/datasets/anemoi_dataset.py | 285 ++++++++++++++++++++ src/hirad/datasets/dataset.py | 3 + 4 files changed, 302 insertions(+) create mode 100644 src/hirad/conf/dataset/anemoi_era_real.yaml create mode 100644 src/hirad/datasets/anemoi_dataset.py diff --git a/src/hirad/conf/dataset/anemoi_era_real.yaml b/src/hirad/conf/dataset/anemoi_era_real.yaml new file mode 100644 index 00000000..c56bc509 --- /dev/null +++ b/src/hirad/conf/dataset/anemoi_era_real.yaml @@ -0,0 +1,13 @@ +type: anemoi_era5_real +input_dataset_path: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +corrected_tp_path: '/capstor/scratch/cscs/mmcgloho/basic-numpy/copernicus-era-1h/copernicus-interpolated/' +target_dataset_path: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' +input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] +output_channel_names: [2t, 10u, 10v, tp] +static_channel_names: ['z'] +transform_channels: ['tp-box_cox_025'] +transform_input_means: {'tp-box_cox_025': -3.815209745618941} +transform_input_stdevs: {'tp-box_cox_025': 0.22851179478814418} +transform_output_means: {'tp-box_cox_025': -3.8121187083242556} +transform_output_stdevs: {'tp-box_cox_025': 0.3345851858215482} +n_month_hour_channels: 4 \ No newline at end of file diff --git a/src/hirad/datasets/__init__.py b/src/hirad/datasets/__init__.py index 46e8bd4a..26dc6474 100644 --- a/src/hirad/datasets/__init__.py +++ b/src/hirad/datasets/__init__.py @@ -2,3 +2,4 @@ from .era5_cosmo import ERA5_COSMO from .era5_real import ERA5_REAL from .base import DownscalingDataset, ChannelMetadata, get_channels_from_strings, get_strings_from_channels +from .anemoi_dataset import AnemoiDataset, ANEMOI_ERA5_COSMO, ANEMOI_ERA5_REAL \ No newline at end of file diff --git a/src/hirad/datasets/anemoi_dataset.py b/src/hirad/datasets/anemoi_dataset.py new file mode 100644 index 00000000..77414b75 --- /dev/null +++ b/src/hirad/datasets/anemoi_dataset.py @@ -0,0 +1,285 @@ +from .base import DownscalingDataset, ChannelMetadata + +from anemoi.datasets import open_dataset +from anemoi.datasets.data.dataset import Dataset +import datetime +import os +import numpy as np +from pandas import to_datetime +import torch +from typing import List, Tuple +import yaml +import torch.nn.functional as F +import time +# import zarr + +from hirad.utils.console import PythonLogger +from hirad.utils.dataset_utils import GridData + +logger = PythonLogger(__name__) + +# Margin to use for ERA dataset (to avoid nans from interpolation at boundary) +INPUT_MARGIN_DEGREES = 0.5 + +class AnemoiDataset(DownscalingDataset): + def __init__(self, + input_anemoi_dataset_path: str, + output_anemoi_dataset_path: str, + corrected_tp_path: str, + # TODO: Consider adding train/validation start/end dates + input_channel_names: List[str] = [], + output_channel_names: List[str] = [], + static_channel_names: List[str] = [], + transform_channels: List[str] = [], + transform_input_means: dict = {}, + transform_input_stds: dict = {}, + transform_output_means: dict = {}, + transform_output_stds: dict = {}, + n_month_hour_channels: int = None, + ): + super().__init__() + + self._corrected_tp_path = corrected_tp_path + #TODO switch hanbdling paths to Path rather than pure strings + self._n_month_hour_channels = n_month_hour_channels + + self._output_dataset = open_dataset(output_anemoi_dataset_path, select=output_channel_names) + assert self._output_dataset.shape[1] == len(output_channel_names) + + # Load ERA dataset, trimming the area and limiting the dates to the target dataset + start_date = self._output_dataset.metadata()['start_date'] + end_date = self._output_dataset.metadata()['end_date'] + min_lat = min(self._output_dataset.latitudes) - INPUT_MARGIN_DEGREES + max_lat = max(self._output_dataset.latitudes) + INPUT_MARGIN_DEGREES + min_lon = max(0, min(self._output_dataset.longitudes) - INPUT_MARGIN_DEGREES) + max_lon = max(self._output_dataset.longitudes) + INPUT_MARGIN_DEGREES + area=(max_lat, min_lon, min_lat, max_lon) + + self._input_dataset = open_dataset(input_anemoi_dataset_path, select=input_channel_names, start=start_date, end=end_date, area=area) + assert self._input_dataset.shape[1] == len(input_channel_names) + + # Check that we have the same number of time points in each dataset + assert self._input_dataset.shape[0] == self._output_dataset.shape[0] + + # Load static info and channel names + if static_channel_names: + static_dataset = open_dataset(output_anemoi_dataset_path, select=static_channel_names, start=start_date, end=start_date) + assert static_dataset.shape[1] == len(static_channel_names) + # take first time point, and squeeze() to remove ensemble dimension + static_data = static_dataset[0,:,:,:].squeeze() + # Could also get these from stats, but one-time calculation is OK. + self.static_mean = static_data.mean(axis=(1,2)) + self.static_std = static_data.std(axis=(1,2)) + target_shape = static_dataset.field_shape + self.static_data_normalized = self.normalize_input(np.flip(static_data.squeeze().reshape(-1, *target_shape), 1)) + else: + self.static_data_normalized = None + + # Load target channel names + self._output_channels = [ChannelMetadata(name) if len(name.split('_'))==1 + else ChannelMetadata(name.split('_')[0],name.split('_')[1]) + for name in output_channel_names] + # Load era5 channel names + self._input_channels = [ChannelMetadata(name) if len(name.split('_'))==1 + else ChannelMetadata(name.split('_')[0],name.split('_')[1]) + for name in input_channel_names] + + # Load stats for normalizing channels of input and output + cosmo_stats = self._output_dataset.statistics + self.output_mean = cosmo_stats['mean'][:] + self.output_std = cosmo_stats['stdev'][:] + + input_stats = self._input_dataset.statistics + self.input_mean = input_stats['mean'][:] + self.input_std = input_stats['stdev'][:] + + assert len(transform_channels) == len(transform_input_means) ==\ + len(transform_input_stds) == len(transform_output_means) == \ + len(transform_output_stds) + + # FEATURE: load the mean and std values for transformed channels and update the normalization statistics + self.input_transforms = {} + self.input_inverse_transforms = {} + self.output_transforms = {} + self.output_inverse_transforms = {} + for transform_descriptor in transform_channels: + channel, transformation = transform_descriptor.split('-') + input_channel_idx = input_channel_names.index(channel) if channel in input_channel_names else None + output_channel_idx = output_channel_names.index(channel) if channel in output_channel_names else None + if transformation.startswith('box_cox'): + lmbda_str = transformation.split('_')[-1] + lmbda = float(transformation.split('_')[-1])/(10**(len(lmbda_str)-1)) + print(f"Applying Box-Cox transformation with lambda={lmbda} to channel {channel} (input idx: {input_channel_idx} ({input_channel_names[input_channel_idx]}), output idx: {output_channel_idx} ({output_channel_names[output_channel_idx]}))") + if input_channel_idx is not None: + self.input_transforms[input_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_transform(x, lmbda) + self.input_inverse_transforms[input_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_inverse_transform(x, lmbda) + self.input_mean[input_channel_idx] = transform_input_means[transform_descriptor] + self.input_std[input_channel_idx] = transform_input_stds[transform_descriptor] + if output_channel_idx is not None: + self.output_transforms[output_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_transform(x, lmbda) + self.output_inverse_transforms[output_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_inverse_transform(x, lmbda) + self.output_mean[output_channel_idx] = transform_output_means[transform_descriptor] + self.output_std[output_channel_idx] = transform_output_stds[transform_descriptor] + else: + raise ValueError(f"Transformation: {transformation} for channel {channel} not implemented.") + + # Initialize the interpolator + self.interpolator = GridData( + self._input_dataset.longitudes, + self._input_dataset.latitudes, + self._output_dataset.longitudes, + self._output_dataset.latitudes) + + + # DO NOT SUBMIT: This is not implemented yet. + # Question: Is it OK to change the signature to return 3 items? + def __getitem__(self, idx): + """Get input and target data. Transform and normalize, but do not interpolate.""" + + # Pull input, replacing the corrected tp if applicable + date_str = to_datetime(self._input_dataset.dates[idx]).strftime('%Y%m%d-%H%M') + # Don't reshape, but do squeeze ensemble dimension. + input_data = self._input_dataset[idx,:].squeeze() + # TODO: Consider generalizing this to other channels, in case we have cp. + if 'tp' in self.input_channel_names: + tp_idx = self.input_channel_names.index('tp') + corrected_tp_data = np.load(os.path.join(self._corrected_tp_path, f'{date_str}.npy')) + input_data[tp_idx,:] = corrected_tp_data + + input_data = self.normalize_input(input_data) + + # Pull target data + # squeeze the ensemble dimesnsion + # reshape to image_shape + # flip so that it starts in top-left corner (by default it is bottom left) + target_shape = self.image_shape() + target_data = self._output_dataset[idx,:] + target_data = np.flip(target_data \ + .squeeze() \ + .reshape(-1,*target_shape), + 1) + target_data = self.normalize_output(target_data) + + return torch.from_numpy(target_data),\ + torch.from_numpy(input_data) + + def get_static_data(self): + return self.static_data_normalized + + def __len__(self): + return len(self._output_dataset.dates) + + # Question: Do we need an input longitude as well? + def longitude(self) -> np.ndarray: + """Get longitude values from the target dataset.""" + return self._output_dataset.longitudes + + def latitude(self) -> np.ndarray: + """Get latitude values from the target dataset.""" + return self._output_dataset.latitudes + + def input_channels(self) -> List[ChannelMetadata]: + """Metadata for the input channels. A list of ChannelMetadata, one for each channel""" + return self._input_channels + + def output_channels(self) -> List[ChannelMetadata]: + """Metadata for the output channels. A list of ChannelMetadata, one for each channel""" + return self._output_channels + + def time(self) -> List: + """Get time values from the dataset.""" + #TODO Choose the time format and convert to that, currently it's a string from a filename + return [to_datetime(dt64).strftime('%Y%m%d-%H%M') for dt64 in self._output_dataset.dates] + + def image_shape(self) -> Tuple[int, int]: + """Get the (height, width) of the data.""" + return self._output_dataset.field_shape + + def input_shape(self) -> Tuple[int, int]: + """Get the (height, width) of the input data.""" + return self._input_dataset.field_shape + + def normalize_input(self, x: np.ndarray) -> np.ndarray: + """Convert input from physical units to normalized data.""" + for channel_idx, transform in self.input_transforms.items(): + x[channel_idx,::] = transform(x[channel_idx,::]) + return (x - self.input_mean.reshape((self.input_mean.shape[0],1,1))) \ + / self.input_std.reshape((self.input_std.shape[0],1,1)) + + + def denormalize_input(self, x: np.ndarray) -> np.ndarray: + """Convert input from normalized data to physical units.""" + x = x * self.input_std.reshape((self.input_std.shape[0],1,1)) \ + + self.input_mean.reshape((self.input_mean.shape[0],1,1)) + for channel_idx, inverse_transform in self.input_inverse_transforms.items(): + x[:,channel_idx,::] = inverse_transform(x[:,channel_idx,::]) + return x + + + def normalize_output(self, x: np.ndarray) -> np.ndarray: + """Convert output from physical units to normalized data.""" + for channel_idx, transform in self.output_transforms.items(): + x[channel_idx,::] = transform(x[channel_idx,::]) + return (x - self.output_mean.reshape((self.output_mean.shape[0],1,1))) \ + / self.output_std.reshape((self.output_std.shape[0],1,1)) + + + def denormalize_output(self, x: np.ndarray) -> np.ndarray: + """Convert output from normalized data to physical units.""" + x = x * self.output_std.reshape((self.output_std.shape[0],1,1)) \ + + self.output_mean.reshape((self.output_mean.shape[0],1,1)) + for channel_idx, inverse_transform in self.output_inverse_transforms.items(): + x[:,channel_idx,::] = inverse_transform(x[:,channel_idx,::]) + return x + + def box_cox_transform(self, channel_array: np.ndarray, lmbda: float) -> np.ndarray: + """Apply Box-Cox transformation to the data.""" + channel_array = np.clip(channel_array, 0, None) + return (np.power(channel_array, lmbda) - 1) / lmbda + + def box_cox_inverse_transform(self, channel_array: np.ndarray, lmbda: float) -> np.ndarray: + """Apply inverse Box-Cox transformation to the data.""" + channel_array = np.clip(channel_array, -1/lmbda, None) + return np.power((lmbda * channel_array) + 1, 1 / lmbda) + + def make_time_grids(self, hour, month): + """ + Create multi-frequency cyclic sin/cos feature grids for hour and month. + + Parameters + ---------- + hour : int + Hour of day, 0-23 + month : int + Month of year, 1-12 + + Returns + ------- + grid : np.ndarray, shape (C, H, W) + Channels = [sin(k*hour), cos(k*hour), sin(k*month), cos(k*month) for each k frequency] + """ + H, W = self.image_shape() + hour_freqs = np.arange(1, self._n_month_hour_channels//2 + 1) + month_freqs = np.arange(1, self._n_month_hour_channels//2 + 1) + + channels = [] + + # --- hour encodings --- + for k in hour_freqs: + angle = 2 * np.pi * k * (hour % 24) / 24.0 + channels.append(np.sin(angle)) + channels.append(np.cos(angle)) + + # --- month encodings --- + for k in month_freqs: + angle = 2 * np.pi * k * ((month - 1) % 12) / 12.0 + channels.append(np.sin(angle)) + channels.append(np.cos(angle)) + + channels = np.array(channels, dtype=np.float32) + grid = np.tile(channels[:, None, None], (1, H, W)) # (C, H, W) + + return grid + +ANEMOI_ERA5_REAL = AnemoiDataset() +ANEMOI_ERA5_COSMO = AnemoiDataset() diff --git a/src/hirad/datasets/dataset.py b/src/hirad/datasets/dataset.py index 118cff9e..e6db35ec 100644 --- a/src/hirad/datasets/dataset.py +++ b/src/hirad/datasets/dataset.py @@ -23,6 +23,7 @@ from .era5_cosmo import ERA5_COSMO from .era5_real import ERA5_REAL +from .anemoi_dataset import ANEMOI_ERA5_COSMO, ANEMOI_ERA5_REAL from .base import DownscalingDataset @@ -30,6 +31,8 @@ known_datasets = { "era5_cosmo": ERA5_COSMO, "era5_real": ERA5_REAL, + "anemoi_era5_cosmo": ANEMOI_ERA5_REAL, + "anemoi_era5_real": ANEMOI_ERA5_REAL, } From 3d09f5bc8d1ec2e1b96afe5b3a90bf77d685da18 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 26 Jan 2026 16:28:12 +0100 Subject: [PATCH 249/302] fix anemoi dataset to be comaptible with training loop --- src/hirad/conf/dataset/anemoi_era_cosmo.yaml | 16 +++ src/hirad/conf/dataset/anemoi_era_real.yaml | 10 +- src/hirad/datasets/anemoi_dataset.py | 101 ++++++++++++------- src/hirad/datasets/dataset.py | 4 +- src/hirad/training/train.py | 4 +- 5 files changed, 93 insertions(+), 42 deletions(-) create mode 100644 src/hirad/conf/dataset/anemoi_era_cosmo.yaml diff --git a/src/hirad/conf/dataset/anemoi_era_cosmo.yaml b/src/hirad/conf/dataset/anemoi_era_cosmo.yaml new file mode 100644 index 00000000..b896785c --- /dev/null +++ b/src/hirad/conf/dataset/anemoi_era_cosmo.yaml @@ -0,0 +1,16 @@ +type: anemoi_era5_cosmo +input_anemoi_dataset_path: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +corrected_tp_path: '/capstor/scratch/cscs/mmcgloho/basic-numpy/copernicus-era-1h/copernicus-interpolated/' +target_anemoi_dataset_path: '/capstor/store/mch/msopr/hirad-gen/anemoi-datasets/mch-co2-an-archive-0p02-2015-2020-1h-v3-pl13.zarr' +input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] +output_channel_names: [2t, 10u, 10v, tp] +static_channel_names: ['hsurf'] +transform_channels: ['tp-box_cox_025'] +transform_input_means: {'tp-box_cox_025': -3.82097061637088} +transform_input_stdevs: {'tp-box_cox_025': 0.2275895397699994} +transform_output_means: {'tp-box_cox_025': -3.92281} +transform_output_stdevs: {'tp-box_cox_025': 0.19150567} +n_month_hour_channels: 4 +start_date: '2015-11-29' +end_date: '2019-10-31' +trim_edge: 19 \ No newline at end of file diff --git a/src/hirad/conf/dataset/anemoi_era_real.yaml b/src/hirad/conf/dataset/anemoi_era_real.yaml index c56bc509..9d41dc07 100644 --- a/src/hirad/conf/dataset/anemoi_era_real.yaml +++ b/src/hirad/conf/dataset/anemoi_era_real.yaml @@ -1,13 +1,15 @@ type: anemoi_era5_real -input_dataset_path: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +input_anemoi_dataset_path: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' corrected_tp_path: '/capstor/scratch/cscs/mmcgloho/basic-numpy/copernicus-era-1h/copernicus-interpolated/' -target_dataset_path: '/store_new/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-ifsnames-v1.0.zarr' +target_anemoi_dataset_path: '/capstor/store/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-v1.0.zarr' input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] output_channel_names: [2t, 10u, 10v, tp] -static_channel_names: ['z'] +# static_channel_names: ['z'] transform_channels: ['tp-box_cox_025'] transform_input_means: {'tp-box_cox_025': -3.815209745618941} transform_input_stdevs: {'tp-box_cox_025': 0.22851179478814418} transform_output_means: {'tp-box_cox_025': -3.8121187083242556} transform_output_stdevs: {'tp-box_cox_025': 0.3345851858215482} -n_month_hour_channels: 4 \ No newline at end of file +n_month_hour_channels: 4 +start_date: '2005-01-02' +end_date: '2020-12-31' \ No newline at end of file diff --git a/src/hirad/datasets/anemoi_dataset.py b/src/hirad/datasets/anemoi_dataset.py index 77414b75..4770bcf9 100644 --- a/src/hirad/datasets/anemoi_dataset.py +++ b/src/hirad/datasets/anemoi_dataset.py @@ -13,42 +13,63 @@ import time # import zarr +from .constants import REAL_TO_ERA_CHANNEL_MAP, ERA_TO_REAL_CHANNEL_MAP from hirad.utils.console import PythonLogger from hirad.utils.dataset_utils import GridData + logger = PythonLogger(__name__) # Margin to use for ERA dataset (to avoid nans from interpolation at boundary) INPUT_MARGIN_DEGREES = 0.5 class AnemoiDataset(DownscalingDataset): - def __init__(self, + def __init__(self, + type: str, input_anemoi_dataset_path: str, - output_anemoi_dataset_path: str, + target_anemoi_dataset_path: str, corrected_tp_path: str, - # TODO: Consider adding train/validation start/end dates + start_date: datetime.datetime = None, + end_date: datetime.datetime = None, input_channel_names: List[str] = [], output_channel_names: List[str] = [], static_channel_names: List[str] = [], transform_channels: List[str] = [], transform_input_means: dict = {}, - transform_input_stds: dict = {}, + transform_input_stdevs: dict = {}, transform_output_means: dict = {}, - transform_output_stds: dict = {}, + transform_output_stdevs: dict = {}, n_month_hour_channels: int = None, + trim_edge: int = 0, ): super().__init__() + input_dataset = type.split('_')[1] + target_dataset = type.split('_')[2] + real_target = target_dataset == 'real' + + if input_dataset != 'era5': + raise ValueError(f"Input dataset {input_dataset} not supported for AnemoiDataset. Only 'era5' is supported.") + if target_dataset != 'cosmo' and target_dataset !='real': + raise ValueError(f"Target dataset {target_dataset} not supported for AnemoiDataset. Only 'cosmo' and 'real' are supported.") + + if real_target: + # Map output channel names from real to era5 + output_channel_names_real = [ERA_TO_REAL_CHANNEL_MAP[name] for name in output_channel_names] + self._corrected_tp_path = corrected_tp_path #TODO switch hanbdling paths to Path rather than pure strings self._n_month_hour_channels = n_month_hour_channels - - self._output_dataset = open_dataset(output_anemoi_dataset_path, select=output_channel_names) + if start_date is not None and end_date is not None: + assert start_date < end_date, "start_date must be before end_date" + self._output_dataset = open_dataset(target_anemoi_dataset_path, select=output_channel_names_real if real_target else output_channel_names, start=start_date, end=end_date, trim_edge=trim_edge) + else: + self._output_dataset = open_dataset(target_anemoi_dataset_path, select=output_channel_names_real if real_target else output_channel_names, trim_edge=trim_edge) assert self._output_dataset.shape[1] == len(output_channel_names) # Load ERA dataset, trimming the area and limiting the dates to the target dataset - start_date = self._output_dataset.metadata()['start_date'] - end_date = self._output_dataset.metadata()['end_date'] + start_date = self._output_dataset.metadata()['start_date'] if start_date is None else start_date + end_date = self._output_dataset.metadata()['end_date'] if end_date is None else end_date min_lat = min(self._output_dataset.latitudes) - INPUT_MARGIN_DEGREES max_lat = max(self._output_dataset.latitudes) + INPUT_MARGIN_DEGREES min_lon = max(0, min(self._output_dataset.longitudes) - INPUT_MARGIN_DEGREES) @@ -63,17 +84,24 @@ def __init__(self, # Load static info and channel names if static_channel_names: - static_dataset = open_dataset(output_anemoi_dataset_path, select=static_channel_names, start=start_date, end=start_date) + self._static_channel_names = [ChannelMetadata(name) if len(name.split('_'))==1 + else ChannelMetadata(name.split('_')[0],name.split('_')[1]) + for name in static_channel_names] + static_dataset = open_dataset(target_anemoi_dataset_path, select=static_channel_names, start=start_date, end=start_date) assert static_dataset.shape[1] == len(static_channel_names) # take first time point, and squeeze() to remove ensemble dimension static_data = static_dataset[0,:,:,:].squeeze() # Could also get these from stats, but one-time calculation is OK. - self.static_mean = static_data.mean(axis=(1,2)) - self.static_std = static_data.std(axis=(1,2)) + self.static_mean = static_data.mean(axis=-1, keepdims=True) + self.static_std = static_data.std(axis=-1, keepdims=True) target_shape = static_dataset.field_shape - self.static_data_normalized = self.normalize_input(np.flip(static_data.squeeze().reshape(-1, *target_shape), 1)) + self.static_data_normalized = (static_data - self.static_mean.reshape((self.static_mean.shape[0],1))) \ + / self.static_std.reshape((self.static_std.shape[0],1)) + + # self.normalize_input(np.flip(static_data.squeeze().reshape(-1, *target_shape), 1)) else: self.static_data_normalized = None + self._static_channel_names = [] # Load target channel names self._output_channels = [ChannelMetadata(name) if len(name.split('_'))==1 @@ -94,8 +122,8 @@ def __init__(self, self.input_std = input_stats['stdev'][:] assert len(transform_channels) == len(transform_input_means) ==\ - len(transform_input_stds) == len(transform_output_means) == \ - len(transform_output_stds) + len(transform_input_stdevs) == len(transform_output_means) == \ + len(transform_output_stdevs) # FEATURE: load the mean and std values for transformed channels and update the normalization statistics self.input_transforms = {} @@ -114,12 +142,12 @@ def __init__(self, self.input_transforms[input_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_transform(x, lmbda) self.input_inverse_transforms[input_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_inverse_transform(x, lmbda) self.input_mean[input_channel_idx] = transform_input_means[transform_descriptor] - self.input_std[input_channel_idx] = transform_input_stds[transform_descriptor] + self.input_std[input_channel_idx] = transform_input_stdevs[transform_descriptor] if output_channel_idx is not None: self.output_transforms[output_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_transform(x, lmbda) self.output_inverse_transforms[output_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_inverse_transform(x, lmbda) self.output_mean[output_channel_idx] = transform_output_means[transform_descriptor] - self.output_std[output_channel_idx] = transform_output_stds[transform_descriptor] + self.output_std[output_channel_idx] = transform_output_stdevs[transform_descriptor] else: raise ValueError(f"Transformation: {transformation} for channel {channel} not implemented.") @@ -139,13 +167,12 @@ def __getitem__(self, idx): # Pull input, replacing the corrected tp if applicable date_str = to_datetime(self._input_dataset.dates[idx]).strftime('%Y%m%d-%H%M') # Don't reshape, but do squeeze ensemble dimension. - input_data = self._input_dataset[idx,:].squeeze() + input_data = self._input_dataset[idx].squeeze() # TODO: Consider generalizing this to other channels, in case we have cp. - if 'tp' in self.input_channel_names: - tp_idx = self.input_channel_names.index('tp') + if ChannelMetadata('tp') in self._input_channels: + tp_idx = self._input_channels.index(ChannelMetadata('tp')) corrected_tp_data = np.load(os.path.join(self._corrected_tp_path, f'{date_str}.npy')) - input_data[tp_idx,:] = corrected_tp_data - + input_data[tp_idx,::] = corrected_tp_data input_data = self.normalize_input(input_data) # Pull target data @@ -153,7 +180,7 @@ def __getitem__(self, idx): # reshape to image_shape # flip so that it starts in top-left corner (by default it is bottom left) target_shape = self.image_shape() - target_data = self._output_dataset[idx,:] + target_data = self._output_dataset[idx] target_data = np.flip(target_data \ .squeeze() \ .reshape(-1,*target_shape), @@ -186,6 +213,10 @@ def output_channels(self) -> List[ChannelMetadata]: """Metadata for the output channels. A list of ChannelMetadata, one for each channel""" return self._output_channels + def static_channels(self) -> List[ChannelMetadata]: + """Metadata for the static channels. A list of ChannelMetadata, one for each channel""" + return self._static_channel_names + def time(self) -> List: """Get time values from the dataset.""" #TODO Choose the time format and convert to that, currently it's a string from a filename @@ -203,16 +234,16 @@ def normalize_input(self, x: np.ndarray) -> np.ndarray: """Convert input from physical units to normalized data.""" for channel_idx, transform in self.input_transforms.items(): x[channel_idx,::] = transform(x[channel_idx,::]) - return (x - self.input_mean.reshape((self.input_mean.shape[0],1,1))) \ - / self.input_std.reshape((self.input_std.shape[0],1,1)) + return (x - self.input_mean[(...,) + (None,) * (x.ndim - 1)]) \ + / self.input_std[(...,) + (None,) * (x.ndim - 1)] def denormalize_input(self, x: np.ndarray) -> np.ndarray: """Convert input from normalized data to physical units.""" - x = x * self.input_std.reshape((self.input_std.shape[0],1,1)) \ - + self.input_mean.reshape((self.input_mean.shape[0],1,1)) + x = x * self.input_std[(...,) + (None,) * (x.ndim - 1)] \ + + self.input_mean[(...,) + (None,) * (x.ndim - 1)] for channel_idx, inverse_transform in self.input_inverse_transforms.items(): - x[:,channel_idx,::] = inverse_transform(x[:,channel_idx,::]) + x[channel_idx,::] = inverse_transform(x[channel_idx,::]) return x @@ -220,16 +251,16 @@ def normalize_output(self, x: np.ndarray) -> np.ndarray: """Convert output from physical units to normalized data.""" for channel_idx, transform in self.output_transforms.items(): x[channel_idx,::] = transform(x[channel_idx,::]) - return (x - self.output_mean.reshape((self.output_mean.shape[0],1,1))) \ - / self.output_std.reshape((self.output_std.shape[0],1,1)) + return (x - self.output_mean[(...,) + (None,) * (x.ndim - 1)]) \ + / self.output_std[(...,) + (None,) * (x.ndim - 1)] def denormalize_output(self, x: np.ndarray) -> np.ndarray: """Convert output from normalized data to physical units.""" - x = x * self.output_std.reshape((self.output_std.shape[0],1,1)) \ - + self.output_mean.reshape((self.output_mean.shape[0],1,1)) + x = x * self.output_std[(...,) + (None,) * (x.ndim - 1)] \ + + self.output_mean[(...,) + (None,) * (x.ndim - 1)] for channel_idx, inverse_transform in self.output_inverse_transforms.items(): - x[:,channel_idx,::] = inverse_transform(x[:,channel_idx,::]) + x[channel_idx,::] = inverse_transform(x[channel_idx,::]) return x def box_cox_transform(self, channel_array: np.ndarray, lmbda: float) -> np.ndarray: @@ -281,5 +312,5 @@ def make_time_grids(self, hour, month): return grid -ANEMOI_ERA5_REAL = AnemoiDataset() -ANEMOI_ERA5_COSMO = AnemoiDataset() +ANEMOI_ERA5_REAL = AnemoiDataset +ANEMOI_ERA5_COSMO = AnemoiDataset diff --git a/src/hirad/datasets/dataset.py b/src/hirad/datasets/dataset.py index e6db35ec..fb504115 100644 --- a/src/hirad/datasets/dataset.py +++ b/src/hirad/datasets/dataset.py @@ -31,7 +31,7 @@ known_datasets = { "era5_cosmo": ERA5_COSMO, "era5_real": ERA5_REAL, - "anemoi_era5_cosmo": ANEMOI_ERA5_REAL, + "anemoi_era5_cosmo": ANEMOI_ERA5_COSMO, "anemoi_era5_real": ANEMOI_ERA5_REAL, } @@ -91,7 +91,7 @@ def init_dataset_from_config( sampler_start_idx: int = 0, ) -> Tuple[DownscalingDataset, Iterable]: dataset_cfg = copy.deepcopy(dataset_cfg) - dataset_type = dataset_cfg.pop("type", "era5_cosmo") + dataset_type = dataset_cfg.get("type", "era5_cosmo") if "validation_path" in dataset_cfg: del dataset_cfg['validation_path'] if "train_test_split" in dataset_cfg: diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index da817bf1..743c9d66 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -142,8 +142,9 @@ def main(cfg: DictConfig) -> None: train_test_split=train_test_split, sampler_start_idx=cur_nimg, ) + dataset.interpolator.to_torch(device=dist.device) logger0.info(f"Training on dataset with size {len(dataset)}") - logger0.info(f"Validating on dataset with size {len(validation_dataset)}") + logger0.info(f"Validating on dataset with size {len(validation_dataset) if validation_dataset else 0}") # Parse image configuration & update model args dataset_channels = len(dataset.input_channels()) @@ -516,6 +517,7 @@ def main(cfg: DictConfig) -> None: dataset_iterator ) tick_read_time = time.time() - tick_read_start_time + img_lr = dataset.interpolator(img_lr.to(dist.device)).reshape(*img_lr.shape[:-1], *img_shape) if use_apex_gn: img_clean = img_clean.to( dist.device, From f363d0fbdaffce8496d849bc211204b57b9c6eae Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Mon, 26 Jan 2026 16:29:10 +0100 Subject: [PATCH 250/302] extend custom interpolation to be able to interpolate batches of data --- src/hirad/utils/dataset_utils.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/hirad/utils/dataset_utils.py b/src/hirad/utils/dataset_utils.py index 06539603..e47c20e4 100644 --- a/src/hirad/utils/dataset_utils.py +++ b/src/hirad/utils/dataset_utils.py @@ -152,13 +152,13 @@ def interpolate(self, values: np.ndarray | torch.Tensor, fill_value: Optional[fl Interpolate values from original points to target points. Args: - values: Array of shape (n_samples, n_original_points) containing values + values: Array of shape (n_channels, n_original_points) or (batch, n_channels, n_original_points) containing values at original point locations to be interpolated. fill_value: Value to use for target points outside the convex hull. Defaults to np.nan. Returns: - Array of shape (n_samples, n_target_points) with interpolated values. + Array of shape (n_channels, n_target_points) or (batch, n_channels, n_original_points) with interpolated values. Raises: ValueError: If values shape is incompatible with original points. @@ -168,6 +168,12 @@ def interpolate(self, values: np.ndarray | torch.Tensor, fill_value: Optional[fl f"Expected values with shape (..., {len(self.longitudes_orig)}), " f"got shape {values.shape}" ) + + # Save original shape for reshaping later + orig_shape = values.shape + + # In case that there is a batch dimension, flatten it for easier indexing + values = values.reshape(-1, values.shape[-1]) # shape (batch*n_channels, n_original_points) # Find the corner values of each simplice values_simplices = values[:,self._tri.simplices] @@ -182,9 +188,9 @@ def interpolate(self, values: np.ndarray | torch.Tensor, fill_value: Optional[fl # Handle points outside convex hull if (not self.is_torch and np.any(self._simplex_id == -1)) or (self.is_torch and torch.any(self._simplex_id == -1)): - out[:, self._simplex_id == -1] = fill_value + out[::, self._simplex_id == -1] = fill_value - return out + return out.reshape(orig_shape[:-1] + (out.shape[-1],)) def __call__(self, values: np.ndarray | torch.Tensor, fill_value: Optional[float] = np.nan) -> np.ndarray: """Alias for forward method to make class callable.""" From f7fb4ff6fb336ac654444a99beab4e20c6aef7d4 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 27 Jan 2026 10:09:40 +0100 Subject: [PATCH 251/302] enable validation with anemoi dataset --- src/hirad/conf/dataset/anemoi_era_cosmo.yaml | 5 ++++- src/hirad/datasets/__init__.py | 3 ++- src/hirad/datasets/constants.py | 9 ++++++++ src/hirad/datasets/dataset.py | 22 ++++++++++++-------- src/hirad/training/train.py | 7 ++----- 5 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 src/hirad/datasets/constants.py diff --git a/src/hirad/conf/dataset/anemoi_era_cosmo.yaml b/src/hirad/conf/dataset/anemoi_era_cosmo.yaml index b896785c..dac61d14 100644 --- a/src/hirad/conf/dataset/anemoi_era_cosmo.yaml +++ b/src/hirad/conf/dataset/anemoi_era_cosmo.yaml @@ -13,4 +13,7 @@ transform_output_stdevs: {'tp-box_cox_025': 0.19150567} n_month_hour_channels: 4 start_date: '2015-11-29' end_date: '2019-10-31' -trim_edge: 19 \ No newline at end of file +trim_edge: 19 +validation: True +validation_start_date: '2019-11-01' +validation_end_date: '2020-10-28' \ No newline at end of file diff --git a/src/hirad/datasets/__init__.py b/src/hirad/datasets/__init__.py index 26dc6474..4d43aaff 100644 --- a/src/hirad/datasets/__init__.py +++ b/src/hirad/datasets/__init__.py @@ -2,4 +2,5 @@ from .era5_cosmo import ERA5_COSMO from .era5_real import ERA5_REAL from .base import DownscalingDataset, ChannelMetadata, get_channels_from_strings, get_strings_from_channels -from .anemoi_dataset import AnemoiDataset, ANEMOI_ERA5_COSMO, ANEMOI_ERA5_REAL \ No newline at end of file +from .anemoi_dataset import AnemoiDataset, ANEMOI_ERA5_COSMO, ANEMOI_ERA5_REAL +from .constants import REAL_TO_ERA_CHANNEL_MAP, ERA_TO_REAL_CHANNEL_MAP \ No newline at end of file diff --git a/src/hirad/datasets/constants.py b/src/hirad/datasets/constants.py new file mode 100644 index 00000000..aa4bd58e --- /dev/null +++ b/src/hirad/datasets/constants.py @@ -0,0 +1,9 @@ +REAL_TO_ERA_CHANNEL_MAP = { + 'T_2M': '2t', + 'U_10M': '10u', + 'V_10M': '10v', + 'TOT_PREC_1H': 'tp', +} + +ERA_TO_REAL_CHANNEL_MAP = {v: k for k, v in REAL_TO_ERA_CHANNEL_MAP.items()} + diff --git a/src/hirad/datasets/dataset.py b/src/hirad/datasets/dataset.py index fb504115..cdfa34b6 100644 --- a/src/hirad/datasets/dataset.py +++ b/src/hirad/datasets/dataset.py @@ -65,15 +65,22 @@ def init_train_valid_datasets_from_config( """ config = copy.deepcopy(dataset_cfg) - if 'validation_path' in config: - del config['validation_path'] + config.pop("validation", None) + config.pop("validation_start_date", None) + config.pop("validation_end_date", None) (dataset, dataset_iter) = init_dataset_from_config( config, dataloader_cfg, batch_size=batch_size, seed=seed, sampler_start_idx=sampler_start_idx, ) if train_test_split: valid_dataset_cfg = copy.deepcopy(dataset_cfg) - valid_dataset_cfg["dataset_path"] = valid_dataset_cfg["validation_path"] - del valid_dataset_cfg['validation_path'] + del valid_dataset_cfg['validation'] + if "validation_start_date" not in valid_dataset_cfg or "validation_end_date" not in valid_dataset_cfg: + raise ValueError("validation_start_date and validation_en_date must be specified in anemoi dataset_cfg when validation is set to True") + valid_dataset_cfg["start_date"] = valid_dataset_cfg["validation_start_date"] + valid_dataset_cfg["end_date"] = valid_dataset_cfg["validation_end_date"] + del valid_dataset_cfg['validation_start_date'] + del valid_dataset_cfg['validation_end_date'] + (valid_dataset, valid_dataset_iter) = init_dataset_from_config( valid_dataset_cfg, dataloader_cfg, batch_size=batch_size, seed=seed ) @@ -89,14 +96,11 @@ def init_dataset_from_config( batch_size: int = 1, seed: int = 0, sampler_start_idx: int = 0, + pop_type: bool = True, ) -> Tuple[DownscalingDataset, Iterable]: + dataset_cfg = copy.deepcopy(dataset_cfg) dataset_type = dataset_cfg.get("type", "era5_cosmo") - if "validation_path" in dataset_cfg: - del dataset_cfg['validation_path'] - if "train_test_split" in dataset_cfg: - # handled by init_train_valid_datasets_from_config - del dataset_cfg["train_test_split"] dataset_init_func = known_datasets[dataset_type] dataset_obj = dataset_init_func(**dataset_cfg) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 743c9d66..701e7796 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -82,10 +82,7 @@ def main(cfg: DictConfig) -> None: logger0 = RankZeroLoggingWrapper(logger, dist) # rank 0 logger dataset_cfg = OmegaConf.to_container(cfg.dataset) - if hasattr(cfg.dataset, "validation_path") and cfg.dataset.validation_path is not None: - train_test_split = True - else: - train_test_split = False + train_test_split = getattr(cfg.dataset, "validation", False) fp_optimizations = cfg.training.perf.fp_optimizations songunet_checkpoint_level = cfg.training.perf.songunet_checkpoint_level fp16 = fp_optimizations == "fp16" @@ -684,7 +681,7 @@ def main(cfg: DictConfig) -> None: img_lr_valid, *lead_time_label_valid, ) = next(validation_dataset_iterator) - + img_lr_valid = dataset.interpolator(img_lr_valid.to(dist.device)).reshape(*img_lr_valid.shape[:-1], *img_shape) if use_apex_gn: img_clean_valid = img_clean_valid.to( dist.device, From 93c5715233978f84ca1eaed75506365140d75654 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Fri, 6 Feb 2026 17:58:49 +0100 Subject: [PATCH 252/302] Take static channels and date embedding appending logic out of dataset class for better performance --- .gitignore | 1 + .../dataset/anemoi_era_cosmo_inference.yaml | 16 +++ src/hirad/datasets/anemoi_dataset.py | 98 ++++++++++++------- src/hirad/eval/snapshots.py | 2 +- src/hirad/inference/generate.py | 35 ++++++- src/hirad/inference/generator.py | 10 +- src/hirad/inference/stochastic_sampler.py | 40 ++++++++ src/hirad/losses/loss.py | 95 ++++++++++++++---- src/hirad/training/train.py | 50 ++++++++-- src/hirad/utils/inference_utils.py | 44 ++++++++- 10 files changed, 321 insertions(+), 70 deletions(-) create mode 100644 src/hirad/conf/dataset/anemoi_era_cosmo_inference.yaml diff --git a/.gitignore b/.gitignore index 1b9f90c7..8ba7696f 100644 --- a/.gitignore +++ b/.gitignore @@ -176,6 +176,7 @@ pyrightconfig.json plots/* *.npz outputs/* +logs/* # conda .conda/* diff --git a/src/hirad/conf/dataset/anemoi_era_cosmo_inference.yaml b/src/hirad/conf/dataset/anemoi_era_cosmo_inference.yaml new file mode 100644 index 00000000..1651877a --- /dev/null +++ b/src/hirad/conf/dataset/anemoi_era_cosmo_inference.yaml @@ -0,0 +1,16 @@ +type: anemoi_era5_cosmo +input_anemoi_dataset_path: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +corrected_tp_path: '/capstor/scratch/cscs/mmcgloho/basic-numpy/copernicus-era-1h/copernicus-interpolated/' +target_anemoi_dataset_path: '/capstor/store/cscs/pasc/c38/anemoi-datasets/mch-co2-an-archive-0p02-2015-2020-1h-v3-pl13.zarr' +input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] +output_channel_names: [2t, 10u, 10v, tp] +static_channel_names: ['hsurf'] +transform_channels: ['tp-box_cox_025'] +transform_input_means: {'tp-box_cox_025': -3.82097061637088} +transform_input_stdevs: {'tp-box_cox_025': 0.2275895397699994} +transform_output_means: {'tp-box_cox_025': -3.92281} +transform_output_stdevs: {'tp-box_cox_025': 0.19150567} +n_month_hour_channels: 4 +start_date: '2015-11-29' +end_date: '2020-10-28' +trim_edge: 19 \ No newline at end of file diff --git a/src/hirad/datasets/anemoi_dataset.py b/src/hirad/datasets/anemoi_dataset.py index 4770bcf9..38438905 100644 --- a/src/hirad/datasets/anemoi_dataset.py +++ b/src/hirad/datasets/anemoi_dataset.py @@ -84,10 +84,10 @@ def __init__(self, # Load static info and channel names if static_channel_names: - self._static_channel_names = [ChannelMetadata(name) if len(name.split('_'))==1 + self._static_channels = [ChannelMetadata(name) if len(name.split('_'))==1 else ChannelMetadata(name.split('_')[0],name.split('_')[1]) for name in static_channel_names] - static_dataset = open_dataset(target_anemoi_dataset_path, select=static_channel_names, start=start_date, end=start_date) + static_dataset = open_dataset(target_anemoi_dataset_path, select=static_channel_names, start=start_date, end=start_date, trim_edge=trim_edge) assert static_dataset.shape[1] == len(static_channel_names) # take first time point, and squeeze() to remove ensemble dimension static_data = static_dataset[0,:,:,:].squeeze() @@ -97,11 +97,11 @@ def __init__(self, target_shape = static_dataset.field_shape self.static_data_normalized = (static_data - self.static_mean.reshape((self.static_mean.shape[0],1))) \ / self.static_std.reshape((self.static_std.shape[0],1)) - + self.static_data_normalized = torch.from_numpy(self.static_data_normalized.reshape(-1, *target_shape)) # self.normalize_input(np.flip(static_data.squeeze().reshape(-1, *target_shape), 1)) else: self.static_data_normalized = None - self._static_channel_names = [] + self._static_channels = [] # Load target channel names self._output_channels = [ChannelMetadata(name) if len(name.split('_'))==1 @@ -188,7 +188,8 @@ def __getitem__(self, idx): target_data = self.normalize_output(target_data) return torch.from_numpy(target_data),\ - torch.from_numpy(input_data) + torch.from_numpy(input_data),\ + date_str def get_static_data(self): return self.static_data_normalized @@ -215,7 +216,7 @@ def output_channels(self) -> List[ChannelMetadata]: def static_channels(self) -> List[ChannelMetadata]: """Metadata for the static channels. A list of ChannelMetadata, one for each channel""" - return self._static_channel_names + return self._static_channels def time(self) -> List: """Get time values from the dataset.""" @@ -240,10 +241,10 @@ def normalize_input(self, x: np.ndarray) -> np.ndarray: def denormalize_input(self, x: np.ndarray) -> np.ndarray: """Convert input from normalized data to physical units.""" - x = x * self.input_std[(...,) + (None,) * (x.ndim - 1)] \ - + self.input_mean[(...,) + (None,) * (x.ndim - 1)] + x = x * self.input_std[(None,) + (...,) + (None,) * (x.ndim - 2)] \ + + self.input_mean[(None,) + (...,) + (None,) * (x.ndim - 2)] for channel_idx, inverse_transform in self.input_inverse_transforms.items(): - x[channel_idx,::] = inverse_transform(x[channel_idx,::]) + x[:,channel_idx,::] = inverse_transform(x[:,channel_idx,::]) return x @@ -257,10 +258,10 @@ def normalize_output(self, x: np.ndarray) -> np.ndarray: def denormalize_output(self, x: np.ndarray) -> np.ndarray: """Convert output from normalized data to physical units.""" - x = x * self.output_std[(...,) + (None,) * (x.ndim - 1)] \ - + self.output_mean[(...,) + (None,) * (x.ndim - 1)] + x = x * self.output_std[(None,) + (...,) + (None,) * (x.ndim - 2)] \ + + self.output_mean[(None,) + (...,) + (None,) * (x.ndim - 2)] for channel_idx, inverse_transform in self.output_inverse_transforms.items(): - x[channel_idx,::] = inverse_transform(x[channel_idx,::]) + x[:,channel_idx,::] = inverse_transform(x[:,channel_idx,::]) return x def box_cox_transform(self, channel_array: np.ndarray, lmbda: float) -> np.ndarray: @@ -273,44 +274,71 @@ def box_cox_inverse_transform(self, channel_array: np.ndarray, lmbda: float) -> channel_array = np.clip(channel_array, -1/lmbda, None) return np.power((lmbda * channel_array) + 1, 1 / lmbda) - def make_time_grids(self, hour, month): + def make_time_grids(self, dates: list[str], device: torch.device, dtype: torch.dtype = torch.float32) -> torch.Tensor: """ - Create multi-frequency cyclic sin/cos feature grids for hour and month. + Create multi-frequency cyclic sin/cos feature grids for hour and month (batched). Parameters ---------- - hour : int - Hour of day, 0-23 - month : int - Month of year, 1-12 + dates : Sequence[str] + Date strings in the format 'YYYYMMDD-HHMM', length = B Returns ------- - grid : np.ndarray, shape (C, H, W) - Channels = [sin(k*hour), cos(k*hour), sin(k*month), cos(k*month) for each k frequency] + grid : torch.Tensor, shape (B, C, H, W) + Channels = [sin(k*hour), cos(k*hour), sin(k*month), cos(k*month) for each k] """ - H, W = self.image_shape() - hour_freqs = np.arange(1, self._n_month_hour_channels//2 + 1) - month_freqs = np.arange(1, self._n_month_hour_channels//2 + 1) - channels = [] + B = len(dates) + + # --- parse month and hour --- + months = torch.tensor( + [int(d.split("-")[0][4:6]) for d in dates], + dtype=torch.float32, + device=device, + ) + hours = torch.tensor( + [int(d.split("-")[1][0:2]) for d in dates], + dtype=torch.float32, + device=device, + ) + + # normalize cyclic components + hours = (hours % 24) / 24.0 # (B,) + months = ((months - 1) % 12) / 12.0 # (B,) + + # frequencies + n_freq = self._n_month_hour_channels // 2 + freqs = torch.arange( + 1, n_freq + 1, dtype=torch.float32, device=device + ) # (K,) + + # shape helpers + hours = hours[:, None] # (B, 1) + months = months[:, None] # (B, 1) # --- hour encodings --- - for k in hour_freqs: - angle = 2 * np.pi * k * (hour % 24) / 24.0 - channels.append(np.sin(angle)) - channels.append(np.cos(angle)) + hour_angles = 2 * torch.pi * hours * freqs # (B, K) + hour_feats = torch.stack( + [torch.sin(hour_angles), torch.cos(hour_angles)], + dim=2 + ) # (B, K, 2) # --- month encodings --- - for k in month_freqs: - angle = 2 * np.pi * k * ((month - 1) % 12) / 12.0 - channels.append(np.sin(angle)) - channels.append(np.cos(angle)) + month_angles = 2 * torch.pi * months * freqs + month_feats = torch.stack( + [torch.sin(month_angles), torch.cos(month_angles)], + dim=2 + ) # (B, K, 2) - channels = np.array(channels, dtype=np.float32) - grid = np.tile(channels[:, None, None], (1, H, W)) # (C, H, W) + # concatenate and flatten channels + feats = torch.cat([hour_feats, month_feats], dim=1) # (B, 2K, 2) + feats = feats.reshape(B, -1) # (B, C) - return grid + # expand to spatial grid + # grid = feats[:, :, None, None].expand(B, feats.shape[1], H, W) + + return feats ANEMOI_ERA5_REAL = AnemoiDataset ANEMOI_ERA5_COSMO = AnemoiDataset diff --git a/src/hirad/eval/snapshots.py b/src/hirad/eval/snapshots.py index 25f30da6..8070af13 100644 --- a/src/hirad/eval/snapshots.py +++ b/src/hirad/eval/snapshots.py @@ -152,7 +152,7 @@ def main(cfg: dict) -> None: input_channels = get_channels_from_strings(dataset_cfg.get("input_channel_names", [])) output_channels = get_channels_from_strings(dataset_cfg.get("output_channels_names", [])) if not input_channels or not output_channels: - dataset_type = dataset_cfg.pop("type") + dataset_type = dataset_cfg.get("type") dataset = known_datasets[dataset_type](**dataset_cfg) input_channels = dataset.input_channels() output_channels = dataset.output_channels() diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index f90f974c..a422792c 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -41,6 +41,9 @@ def main(cfg: DictConfig) -> None: if dist.world_size > 1: torch.distributed.barrier() + use_apex_gn = cfg.generation.perf.get("use_apex_gn", False) + input_dtype = torch.float16 if cfg.generation.perf.get("force_fp16", False) and use_apex_gn else torch.float32 + # Parse the inference input times if cfg.generation.times_range and cfg.generation.times: raise ValueError("Either times_range or times must be provided, but not both") @@ -164,6 +167,7 @@ def main(cfg: DictConfig) -> None: overlap_pix=cfg.generation.overlap_pix, ) sampler_params = cfg.sampler.params if "params" in cfg.sampler else {} + sampler_params["use_apex_gn"] = use_apex_gn generator.initialize_sampler(cfg.sampler.type, **sampler_params) # generate images @@ -214,8 +218,27 @@ def elapsed_time(self, _): start = end = DummyEvent() + dataset.interpolator.to_torch(device=dist.device) + + static_channels = dataset.get_static_data() + if static_channels is not None: + static_channels = static_channels[None, ::] + if use_apex_gn: + static_channels = static_channels.to( + dist.device, + dtype=input_dtype, + non_blocking=True, + ).to(memory_format=torch.channels_last) + else: + static_channels = ( + static_channels.to(dist.device) + .to(input_dtype) + .contiguous() + ) + lead_time_label = None + times = dataset.time() - for index, (image_tar, image_lr, *lead_time_label) in enumerate( + for index, (image_tar, image_lr, *date_str) in enumerate( iter(data_loader) ): time_index += 1 @@ -232,18 +255,24 @@ def elapsed_time(self, _): lead_time_label = lead_time_label[0].to(dist.device).contiguous() else: lead_time_label = None + image_lr = dataset.interpolator(image_lr.to(dist.device)).reshape(*image_lr.shape[:-1], *image_tar.shape[-2:]).flip(-2) image_lr = ( image_lr.to(device=device) - .to(torch.float32) + .to(input_dtype) .to(memory_format=torch.channels_last) ) image_tar = image_tar.to(device=device).to(torch.float32) # image_out, image_reg = generate_fn(image_lr,lead_time_label) random_seed = cfg.generation.get("random_seed", None)+index if cfg.generation.get("randomize", False) and cfg.generation.get("random_seed", None) is not None else None # print(f"On rank {dist.rank} using base random seed: {random_seed} for time index {time_index}") + date_embedding = None + if dataset._n_month_hour_channels: + date_embedding = dataset.make_time_grids(*date_str, dist.device, dtype=input_dtype) image_out, image_reg = generator.generate( image_lr, - lead_time_label, + static_channels=static_channels, + date_embedding=date_embedding, + lead_time_label=lead_time_label, randomize=cfg.generation.get("randomize", False), random_seed=random_seed ) diff --git a/src/hirad/inference/generator.py b/src/hirad/inference/generator.py index d3c95c57..6f108e7c 100644 --- a/src/hirad/inference/generator.py +++ b/src/hirad/inference/generator.py @@ -53,7 +53,7 @@ def initialize_sampler(self, sampler_type, **sampler_args): **sampler_args ) elif sampler_type == "stochastic": - self.sampler = partial(stochastic_sampler, patching=self.patching) + self.sampler = partial(stochastic_sampler, patching=self.patching, **sampler_args) else: raise ValueError(f"Unknown sampling method {sampler_type}") @@ -65,7 +65,7 @@ def initialize_patching(self, img_shape, patch_shape, boundary_pix, overlap_pix) overlap_pix=overlap_pix, ) - def generate(self, image_lr, lead_time_label=None, randomize=False, random_seed=None): + def generate(self, image_lr, static_channels=None, date_embedding=None, lead_time_label=None, randomize=False, random_seed=None, use_apex_gn=False): with nvtx.annotate("generate_fn", color="green"): # (1, C, H, W) img_shape = image_lr.shape[-2:] @@ -82,6 +82,9 @@ def generate(self, image_lr, lead_time_label=None, randomize=False, random_seed= img_shape[1], ), # (batch_size, C, H, W) lead_time_label=lead_time_label, + static_channels=static_channels, + date_embedding=date_embedding, + use_apex_gn=use_apex_gn, ) if self.net_res: if self.hr_mean_conditioning: @@ -108,6 +111,9 @@ def generate(self, image_lr, lead_time_label=None, randomize=False, random_seed= device=image_lr.device, mean_hr=mean_hr, lead_time_label=lead_time_label, + static_channels=static_channels, + date_embedding=date_embedding, + use_apex_gn=use_apex_gn, ) if self.inference_mode == "regression": image_out = image_reg[0:1,::] diff --git a/src/hirad/inference/stochastic_sampler.py b/src/hirad/inference/stochastic_sampler.py index 24c5f7a9..5aab06b1 100644 --- a/src/hirad/inference/stochastic_sampler.py +++ b/src/hirad/inference/stochastic_sampler.py @@ -32,6 +32,8 @@ def stochastic_sampler( patching: Optional[GridPatching2D] = None, mean_hr: Optional[torch.Tensor] = None, lead_time_label: Optional[torch.Tensor] = None, + static_channels: Optional[torch.Tensor] = None, + date_embedding: Optional[torch.Tensor] = None, num_steps: int = 18, sigma_min: float = 0.002, sigma_max: float = 800, @@ -40,6 +42,7 @@ def stochastic_sampler( S_min: float = 0, S_max: float = float("inf"), S_noise: float = 1, + use_apex_gn: bool = False, ) -> torch.Tensor: """ Proposed EDM sampler (Algorithm 2) with minor changes to enable @@ -97,6 +100,10 @@ def stochastic_sampler( of `img_lr`. By default None. lead_time_label : Optional[Tensor], optional Optional lead time labels. By default None. + static_channels : Optional[Tensor], optional + Optional static channels input of shape (1, C_static, H, W). By default None. + date_embedding : Optional[Tensor], optional + Optional date embedding input of shape (B, C_date). By default None. num_steps : int Number of time steps for the sampler. By default 18. sigma_min : float @@ -114,6 +121,8 @@ def stochastic_sampler( Maximum time step for applying churn. By default float("inf"). S_noise : float Noise scaling factor applied during the churn step. By default 1. + use_apex_gn : bool + Whether Apex's fused group normalization is used. Returns ------- @@ -178,10 +187,33 @@ def stochastic_sampler( ) x_lr = torch.cat((mean_hr.expand(x_lr.shape[0], -1, -1, -1), x_lr), dim=1) + if static_channels is not None: + # Expand static channels to batch size + if static_channels.shape[-2:] != img_lr.shape[-2:]: + raise ValueError( + f"mean_hr and img_lr must have the same height and width, " + f"but found {mean_hr.shape[-2:]} vs {img_lr.shape[-2:]}." + ) + static_expanded = static_channels.expand(batch_size, -1, -1, -1) + x_lr = torch.cat((x_lr, static_expanded), dim=1) + # input and position padding + patching if patching: # print(f"Input for generator beofre patching {x_lr.shape}") # Patched conditioning [x_lr, mean_hr] + if static_channels is not None: + img_lr = torch.cat( + (img_lr, static_channels.expand(img_lr.shape[0], *static_channels.shape[1:])), + dim=1, + ) + # print(f"Shape of img_lr after static channels diffusion patching: img_lr {img_lr.shape}") + if date_embedding is not None: + date_embedding = date_embedding[:, :, None, None].expand(img_lr.shape[0], date_embedding.shape[1], *img_lr.shape[2:]) + if use_apex_gn: + date_embedding = date_embedding.to(img_lr.dtype, non_blocking=True).to(memory_format=torch.channels_last) + else: + date_embedding = date_embedding.to(img_lr.dtype, non_blocking=True).contiguous() + img_lr = torch.cat((img_lr, date_embedding), dim=1) # (batch_size * patch_num, C_in + C_out, patch_shape_y, patch_shape_x) x_lr = patching.apply(input=x_lr, additional_input=img_lr) # print(f"Input for generator after patching {x_lr.shape}") @@ -192,6 +224,14 @@ def patch_embedding_selector(emb): return patching.apply(emb[None].expand(batch_size, -1, -1, -1)) else: + if date_embedding is not None: + date_embedding = date_embedding[:, :, None, None].expand(x_lr.shape[0], date_embedding.shape[1], *x_lr.shape[2:]) + if use_apex_gn: + date_embedding = date_embedding.to(x_lr.dtype, non_blocking=True).to(memory_format=torch.channels_last) + else: + date_embedding = date_embedding.to(x_lr.dtype, non_blocking=True).contiguous() + x_lr = torch.cat((x_lr, date_embedding), dim=1) + patch_embedding_selector = None # Main sampling loop. diff --git a/src/hirad/losses/loss.py b/src/hirad/losses/loss.py index 2c2123b0..ab3ecdc7 100644 --- a/src/hirad/losses/loss.py +++ b/src/hirad/losses/loss.py @@ -377,10 +377,13 @@ def __call__( net: torch.nn.Module, img_clean: torch.Tensor, img_lr: torch.Tensor, + static_channels: Optional[torch.Tensor] = None, + date_embedding: Optional[torch.Tensor] = None, augment_pipe: Optional[ Callable[[torch.Tensor], Tuple[torch.Tensor, Optional[torch.Tensor]]] ] = None, lead_time_label: Optional[torch.Tensor] = None, + use_apex_gn: bool = False, ) -> torch.Tensor: """ Calculate and return the regression loss for @@ -409,6 +412,12 @@ def __call__( Low-resolution input images of shape (B, C_lr, H, W). Used as input to the neural network. + static_channels : torch.Tensor, optional + Static channels input of shape (C_static, H, W). + + date_embedding : torch.Tensor, optional + Date embedding input of shape (B, C_date). + augment_pipe : callable, optional An optional data augmentation function. Expected signature: @@ -441,6 +450,20 @@ def __call__( zero_input = torch.zeros_like(y, device=img_clean.device) + if static_channels is not None: + y_lr = torch.cat( + (y_lr, static_channels.expand(y_lr.shape[0], *static_channels.shape[1:])), + dim=1, + ) + + if date_embedding is not None: + date_embedding = date_embedding[:, :, None, None].expand(*date_embedding.shape[:2], *y_lr.shape[2:]) + if use_apex_gn: + date_embedding = date_embedding.to(y_lr.dtype, non_blocking=True).to(memory_format=torch.channels_last) + else: + date_embedding = date_embedding.to(y_lr.dtype, non_blocking=True).contiguous() + y_lr = torch.cat((y_lr, date_embedding), dim=1) + if lead_time_label is not None: D_yn = net( zero_input, @@ -566,12 +589,15 @@ def __call__( net: torch.nn.Module, img_clean: torch.Tensor, img_lr: torch.Tensor, + static_channels: Optional[torch.Tensor] = None, + date_embedding: Optional[torch.Tensor] = None, patching: Optional[RandomPatching2D] = None, lead_time_label: Optional[torch.Tensor] = None, augment_pipe: Optional[ Callable[[torch.Tensor], Tuple[torch.Tensor, Optional[torch.Tensor]]] ] = None, use_patch_grad_acc: bool = False, + use_apex_gn: bool = False, ) -> torch.Tensor: """ Calculate and return the loss for denoising score matching. @@ -633,6 +659,12 @@ def __call__( Used as input to the regression network and conditioning for the diffusion process. + static_channels : Optional[torch.Tensor], optional + Static channels input of shape (1, C_static, H, W), by default None. + + date_embedding : Optional[torch.Tensor], optional + Date embedding input of shape (B, C_date), by default None + patching : Optional[RandomPatching2D], optional Patching strategy for processing large images, by default None. See :class:`physicsnemo.utils.patching.RandomPatching2D` for details. @@ -657,6 +689,8 @@ def __call__( use_patch_grad_acc: bool, optional A boolean flag indicating whether to enable multi-iterations of patching accumulations for amortizing regression cost. Default False. + use_apex_gn: bool, optional + A boolean flag indicating whether apex group norm is used in the model. Returns ------- @@ -699,27 +733,25 @@ def __call__( batch_size = y.shape[0] # if using multi-iterations of patching, switch to optimized version - if use_patch_grad_acc: + if not use_patch_grad_acc or self.y_mean is None: # form residual - if self.y_mean is None: - if lead_time_label is not None: - y_mean = self.regression_net( - torch.zeros_like(y, device=img_clean.device), - y_lr_res, - lead_time_label=lead_time_label, - augment_labels=augment_labels, - ) + if static_channels is not None: + y_lr_res = torch.cat( + (y_lr_res, static_channels.expand(y_lr_res.shape[0], *static_channels.shape[1:])), + dim=1, + ) + # print(f"Shape of y_lr after static channels regression: y_lr_res {y_lr_res.shape} y_lr {y_lr.shape}") + + if date_embedding is not None: + date_embedding_reg = date_embedding[:, :, None, None].expand(*date_embedding.shape[:2], *y_lr_res.shape[2:]) + if use_apex_gn: + date_embedding_reg = date_embedding_reg.to(y_lr_res.dtype, non_blocking=True).to(memory_format=torch.channels_last) else: - y_mean = self.regression_net( - torch.zeros_like(y, device=img_clean.device), - y_lr_res, - augment_labels=augment_labels, - ) - self.y_mean = y_mean - - # if on full domain, or if using patching without multi-iterations - else: - # form residual + date_embedding_reg = date_embedding_reg.to(y_lr_res.dtype, non_blocking=True).contiguous() + y_lr_res = torch.cat((y_lr_res, date_embedding_reg), dim=1) + + # print(f"Shape of y_lr after date embedding regression: y_lr_res {y_lr_res.shape} y_lr {y_lr.shape}") + if lead_time_label is not None: y_mean = self.regression_net( torch.zeros_like(y, device=img_clean.device), @@ -741,6 +773,11 @@ def __call__( if self.hr_mean_conditioning: y_lr = torch.cat((self.y_mean, y_lr), dim=1) + if static_channels is not None: + y_lr = torch.cat( + (y_lr, static_channels.expand(y_lr.shape[0], *static_channels.shape[1:])), + dim=1, + ) # patchified training # conditioning: cat(y_mean, y_lr, input_interp, pos_embd), 4+12+100+4 # removed patch_embedding_selector due to compilation issue with dynamo. @@ -750,11 +787,31 @@ def __call__( y_patched = patching.apply(input=y) # Patched conditioning on y_lr and interp(img_lr) # (batch_size * patch_num, 2*c_in, patch_shape_y, patch_shape_x) + if static_channels is not None: + img_lr = torch.cat( + (img_lr, static_channels.expand(img_lr.shape[0], *static_channels.shape[1:])), + dim=1, + ) + # print(f"Shape of img_lr after static channels diffusion patching: img_lr {img_lr.shape}") + if date_embedding is not None: + date_embedding = date_embedding[:, :, None, None].expand(*date_embedding.shape[:2], *img_lr.shape[2:]) + if use_apex_gn: + date_embedding = date_embedding.to(img_lr.dtype, non_blocking=True).to(memory_format=torch.channels_last) + else: + date_embedding = date_embedding.to(img_lr.dtype, non_blocking=True).contiguous() + img_lr = torch.cat((img_lr, date_embedding), dim=1) y_lr_patched = patching.apply(input=y_lr, additional_input=img_lr) y = y_patched y_lr = y_lr_patched + elif date_embedding is not None: + date_embedding = date_embedding[:, :, None, None].expand(*date_embedding.shape[:2], *y_lr.shape[2:]) + if use_apex_gn: + date_embedding = date_embedding.to(y_lr.dtype, non_blocking=True).to(memory_format=torch.channels_last) + else: + date_embedding = date_embedding.to(y_lr.dtype, non_blocking=True).contiguous() + y_lr = torch.cat((y_lr, date_embedding), dim=1) # Add noise to the latent state n, sigma, weight = self.get_noise_params(y) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 701e7796..c07130ef 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -144,15 +144,18 @@ def main(cfg: DictConfig) -> None: logger0.info(f"Validating on dataset with size {len(validation_dataset) if validation_dataset else 0}") # Parse image configuration & update model args - dataset_channels = len(dataset.input_channels()) + n_month_hour_channels = 2*dataset_cfg.get("n_month_hour_channels", 0) + dataset_channels = len(dataset.input_channels()) + len(dataset.static_channels()) + n_month_hour_channels img_in_channels = dataset_channels img_shape = dataset.image_shape() img_out_channels = len(dataset.output_channels()) if cfg.model.hr_mean_conditioning: img_in_channels += img_out_channels + static_channels = dataset.get_static_data() logger0.info(f"Training on dataset with grid size {img_shape[0]}x{img_shape[1]}, {img_in_channels} input channels and {img_out_channels} output channels.") logger0.info(f"Input channels: {dataset.input_channels()}") logger0.info(f"Output channels: {dataset.output_channels()}") + logger0.info(f"Static channels: {dataset.static_channels()}") if cfg.model.name == "lt_aware_ce_regression": prob_channels = dataset.get_prob_channel_index() #TODO figure out what prob_channel are and update dataloader @@ -210,7 +213,7 @@ def main(cfg: DictConfig) -> None: logger0.info("Patch-based training disabled") # interpolate global channel if patch-based model is used if use_patching: - img_in_channels += dataset_channels + img_in_channels += len(dataset.input_channels()) + len(dataset.static_channels()) # Instantiate the model and move to device. model_args = { # default parameters for all networks @@ -485,6 +488,26 @@ def main(cfg: DictConfig) -> None: elif fp16: input_dtype = torch.float16 + # prepare static channels if there are any + if static_channels is not None: + static_channels = static_channels[None, ::] + if use_apex_gn: + static_channels = static_channels.to( + dist.device, + dtype=input_dtype, + non_blocking=True, + ).to(memory_format=torch.channels_last) + else: + static_channels = ( + static_channels.to(dist.device) + .to(input_dtype) + .contiguous() + ) + + # turn off for lead time labels for now since we are not using them + # TODO: implement lead time labels properly once we train on IFS? + lead_time_label = None + # enable profiler: with cuda_profiler(): with profiler_emit_nvtx(): @@ -510,11 +533,14 @@ def main(cfg: DictConfig) -> None: ): with nvtx.annotate("loading data", color="green"): tick_read_start_time = time.time() - img_clean, img_lr, *lead_time_label = next( + img_clean, img_lr, *date_str = next( dataset_iterator ) tick_read_time = time.time() - tick_read_start_time - img_lr = dataset.interpolator(img_lr.to(dist.device)).reshape(*img_lr.shape[:-1], *img_shape) + img_lr = dataset.interpolator(img_lr.to(dist.device)).reshape(*img_lr.shape[:-1], *img_shape).flip(-2) + date_embedding = None + if n_month_hour_channels > 0: + date_embedding = dataset.make_time_grids(*date_str, dist.device, dtype=input_dtype) if use_apex_gn: img_clean = img_clean.to( dist.device, @@ -541,7 +567,10 @@ def main(cfg: DictConfig) -> None: "net": model, "img_clean": img_clean, "img_lr": img_lr, + "static_channels": static_channels, + "date_embedding": date_embedding, "augment_pipe": None, + "use_apex_gn": use_apex_gn, } if use_patch_grad_acc is not None: loss_fn_kwargs[ @@ -675,13 +704,19 @@ def main(cfg: DictConfig) -> None: dist.rank, ): with torch.no_grad(): + # turn off lead_time_label for now since we are not using them + #TODO: implement lead time labels properly once we train on IFS? + lead_time_label_valid = None for _ in range(cfg.training.io.validation_steps): ( img_clean_valid, img_lr_valid, - *lead_time_label_valid, + *date_str, ) = next(validation_dataset_iterator) - img_lr_valid = dataset.interpolator(img_lr_valid.to(dist.device)).reshape(*img_lr_valid.shape[:-1], *img_shape) + img_lr_valid = dataset.interpolator(img_lr_valid.to(dist.device)).reshape(*img_lr_valid.shape[:-1], *img_shape).flip(-2) + date_embedding = None + if n_month_hour_channels > 0: + date_embedding = dataset.make_time_grids(*date_str, dist.device, dtype=input_dtype) if use_apex_gn: img_clean_valid = img_clean_valid.to( dist.device, @@ -710,7 +745,10 @@ def main(cfg: DictConfig) -> None: "net": model, "img_clean": img_clean_valid, "img_lr": img_lr_valid, + "static_channels": static_channels, + "date_embedding": date_embedding, "augment_pipe": None, + "use_apex_gn": use_apex_gn, } if use_patch_grad_acc is not None: loss_valid_kwargs[ diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index 0cdfd985..245b3d3d 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -38,6 +38,9 @@ def regression_step( img_lr: torch.Tensor, latents_shape: torch.Size, lead_time_label: Optional[torch.Tensor] = None, + static_channels: Optional[torch.Tensor] = None, + date_embedding: Optional[torch.Tensor] = None, + use_apex_gn: bool = False, ) -> torch.Tensor: """ Perform a regression step to produce ensemble mean prediction. @@ -59,6 +62,11 @@ def regression_step( lead_time_label : Optional[torch.Tensor], optional Lead time label tensor for lead time conditioning, with shape (1, lead_time_dims). Default is None. + static_channels : torch.Tensor, optional + Static channels input of shape (C_static, H, W). + + date_embedding : torch.Tensor, optional + Date embedding input of shape (B, C_date). Returns ------- @@ -80,6 +88,20 @@ def regression_step( f"but found {img_lr.shape[0]}." ) + if static_channels is not None: + img_lr = torch.cat( + (img_lr, static_channels.expand(img_lr.shape[0], *static_channels.shape[1:])), + dim=1, + ) + + if date_embedding is not None: + date_embedding = date_embedding[:, :, None, None].expand(*date_embedding.shape[:2], *img_lr.shape[2:]) + if use_apex_gn: + date_embedding = date_embedding.to(img_lr.dtype, non_blocking=True).to(memory_format=torch.channels_last) + else: + date_embedding = date_embedding.to(img_lr.dtype, non_blocking=True).contiguous() + img_lr = torch.cat((img_lr, date_embedding), dim=1) + # Perform regression on a single batch element with torch.inference_mode(): if lead_time_label is not None: @@ -105,6 +127,9 @@ def diffusion_step( device: torch.device, mean_hr: torch.Tensor = None, lead_time_label: torch.Tensor = None, + static_channels: Optional[torch.Tensor] = None, + date_embedding: Optional[torch.Tensor] = None, + use_apex_gn: bool = False, ) -> torch.Tensor: """ @@ -142,6 +167,12 @@ def diffusion_step( lead_time_label : torch.Tensor, optional Lead time label tensor for temporal conditioning, with shape (batch_size, lead_time_dims). Default is None. + static_channels : torch.Tensor, optional + Static channels input of shape (C_static, H, W). + date_embedding : torch.Tensor, optional + Date embedding input of shape (B, C_date). + use_apex_gn : bool, optional + Whether Apex's fused group normalization is used. Default is False. Returns ------- @@ -151,21 +182,21 @@ def diffusion_step( """ # Check img_lr dimensions match expected shape - if img_lr.shape[2:] != img_shape: + if img_lr.shape[-2:] != img_shape: raise ValueError( - f"img_lr shape {img_lr.shape[2:]} does not match expected shape img_shape {img_shape}" + f"img_lr shape {img_lr.shape[-2:]} does not match expected shape img_shape {img_shape}" ) # Check mean_hr dimensions if provided if mean_hr is not None: - if mean_hr.shape[2:] != img_shape: + if mean_hr.shape[-2:] != img_shape: raise ValueError( f"mean_hr shape {mean_hr.shape[2:]} does not match expected shape img_shape {img_shape}" ) if mean_hr.shape[0] != 1: raise ValueError(f"mean_hr must have batch size 1, got {mean_hr.shape[0]}") - img_lr = img_lr.to(memory_format=torch.channels_last) + # img_lr = img_lr.to(memory_format=torch.channels_last) # Handling of the high-res mean additional_args = {} @@ -173,6 +204,11 @@ def diffusion_step( additional_args["mean_hr"] = mean_hr if lead_time_label is not None: additional_args["lead_time_label"] = lead_time_label + if static_channels is not None: + additional_args["static_channels"] = static_channels + if date_embedding is not None: + additional_args["date_embedding"] = date_embedding + additional_args["use_apex_gn"] = use_apex_gn # Loop over batches all_images = [] From e1867fb5a18b97a1e7412e12cbc4f0665782d208 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Thu, 19 Feb 2026 14:24:33 +0100 Subject: [PATCH 253/302] Optimize wind and precip stats calculations - Consolidated `map_wind_stats.py` and `map_precip_stats.py` calculations to improve efficiency and reduce redundancy in reading same files many times. - Updated exceedance probability and histogram calculations in `hist.py`, `probability_of_exceedance.py` and `probability_of_exceedance_wind.py` to utilize histogram-based methods for percentile estimation, improving performance greatly while suffering very small precission change. - Adjusted thresholds for exceedance calculations to cover a wider range of values, accommodating higher wind speeds and precipitation rates (important for real-ch1). - Using numpy operations for efficient calculations where possible. --- .../diurnal_cycle_precip_mean_wet-hour.py | 2 +- src/hirad/eval/diurnal_cycle_precip_p99.py | 2 +- src/hirad/eval/diurnal_cycle_temp_wind.py | 2 +- src/hirad/eval/eval_utils.py | 47 ++ src/hirad/eval/hist.py | 57 +-- src/hirad/eval/map_precip_stats.py | 223 ++++---- src/hirad/eval/map_wind_stats.py | 477 ++++++++++-------- src/hirad/eval/probability_of_exceedance.py | 104 ++-- .../eval/probability_of_exceedance_wind.py | 54 +- 9 files changed, 569 insertions(+), 399 deletions(-) create mode 100644 src/hirad/eval/eval_utils.py diff --git a/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py b/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py index cb1f0c00..49679c78 100644 --- a/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py +++ b/src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py @@ -73,7 +73,7 @@ def main(cfg: dict): logger.info(f"Loaded {len(times)} timesteps to process") dataset_cfg = gen_cfg.get("dataset") - dataset_type = dataset_cfg.pop("type") + dataset_type = dataset_cfg.get("type") dataset = known_datasets[dataset_type](**dataset_cfg) logger.info("Dataset initialized") diff --git a/src/hirad/eval/diurnal_cycle_precip_p99.py b/src/hirad/eval/diurnal_cycle_precip_p99.py index abf31464..564bd729 100644 --- a/src/hirad/eval/diurnal_cycle_precip_p99.py +++ b/src/hirad/eval/diurnal_cycle_precip_p99.py @@ -84,7 +84,7 @@ def main(cfg: dict): # Initialize dataset dataset_cfg = gen_cfg.get("dataset") - dataset_type = dataset_cfg.pop("type") + dataset_type = dataset_cfg.get("type") dataset = known_datasets[dataset_type](**dataset_cfg) logger.info("Dataset initialized") diff --git a/src/hirad/eval/diurnal_cycle_temp_wind.py b/src/hirad/eval/diurnal_cycle_temp_wind.py index 1f7470d5..85c5d4e9 100644 --- a/src/hirad/eval/diurnal_cycle_temp_wind.py +++ b/src/hirad/eval/diurnal_cycle_temp_wind.py @@ -54,7 +54,7 @@ def main(cfg: dict): # Dataset dataset_cfg = gen_cfg.get("dataset") - dataset_type = dataset_cfg.pop("type") + dataset_type = dataset_cfg.get("type") dataset = known_datasets[dataset_type](**dataset_cfg) logger.info("Dataset initialized") diff --git a/src/hirad/eval/eval_utils.py b/src/hirad/eval/eval_utils.py new file mode 100644 index 00000000..81cf8131 --- /dev/null +++ b/src/hirad/eval/eval_utils.py @@ -0,0 +1,47 @@ +import numpy as np + +def percentiles_from_histogram(hist_counts, bin_edges, percentiles_dict): + """ + Estimate percentiles from a pre-computed histogram using linear interpolation + on the cumulative distribution. + + Parameters + ---------- + hist_counts : np.ndarray + Raw (unnormalized) histogram counts per bin. + bin_edges : np.ndarray + Bin edges (length = len(hist_counts) + 1). + percentiles_dict : dict + Mapping of label -> fractional percentile, e.g. {99: 0.99, 99.9: 0.999}. + + Returns + ------- + dict + Mapping of label -> estimated percentile value. + """ + cumulative = np.cumsum(hist_counts) + total = cumulative[-1] + if total == 0: + return {key: np.nan for key in percentiles_dict} + + cdf = cumulative / total # CDF at upper bin edges + + results = {} + for key, p in percentiles_dict.items(): + # Find the bin where CDF crosses p + idx = np.searchsorted(cdf, p) + if idx >= len(cdf): + # Beyond last bin — return upper edge + results[key] = bin_edges[-1] + elif idx == 0: + # Within first bin — linearly interpolate from 0 + frac = p / cdf[0] if cdf[0] > 0 else 0.0 + results[key] = bin_edges[0] + frac * (bin_edges[1] - bin_edges[0]) + else: + # Linearly interpolate within the bin + cdf_low = cdf[idx - 1] + cdf_high = cdf[idx] + frac = (p - cdf_low) / (cdf_high - cdf_low) if (cdf_high - cdf_low) > 0 else 0.0 + results[key] = bin_edges[idx] + frac * (bin_edges[idx + 1] - bin_edges[idx]) + + return results \ No newline at end of file diff --git a/src/hirad/eval/hist.py b/src/hirad/eval/hist.py index 947d557a..908c8f4b 100644 --- a/src/hirad/eval/hist.py +++ b/src/hirad/eval/hist.py @@ -18,6 +18,7 @@ from hirad.datasets import get_channels_from_strings, get_strings_from_channels, known_datasets from hirad.utils.function_utils import get_time_from_range from hirad.eval.plotting import get_channel_indices, load_land_sea_mask +from hirad.eval.eval_utils import percentiles_from_histogram def save_distribution_plot(hist_data_dict, bin_edges, labels, colors, title, ylabel, out_path, percentiles_data=None): @@ -138,7 +139,7 @@ def main(cfg: dict): # Initialize dataset dataset_cfg = gen_cfg.get("dataset") - dataset_type = dataset_cfg.pop("type") + dataset_type = dataset_cfg.get("type") dataset = known_datasets[dataset_type](**dataset_cfg) logger.info("Dataset and sampler initialized") @@ -155,11 +156,13 @@ def main(cfg: dict): land_mask = load_land_sea_mask(cfg.get("land_sea_mask_path"), cfg.get("height"), cfg.get("width")) # Define histogram bins - bins = np.logspace(-1, 3.3, 200) # Log-spaced bins for precipitation - + # bins = np.logspace(-1, 3.3, 200) # Log-spaced bins for precipitation + log_bins = np.logspace(-1, 3.3, 200) # Log-spaced bins for precipitation + bins = np.concatenate([[0], log_bins]) # Prepend 0 to capture all sub-0.1 values + # Storage for histogram data and land values hist_data = {} - all_land_values = {} + raw_hist_counts = {} # Store raw counts for percentile estimation # -- Process target and baseline -- for mode in ['target', 'baseline', 'regression-prediction']: @@ -167,7 +170,6 @@ def main(cfg: dict): hist_counts = np.zeros(len(bins) - 1) total_samples = 0 - all_values = [] try: for i, ts in enumerate(times): @@ -176,23 +178,19 @@ def main(cfg: dict): data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode in ['target', 'regression-prediction'] else tp_in] * cfg.get("conv_factor_hourly") * land_mask - # Apply scaling factor for baseline - # if mode == 'baseline': - # data = data / 6.0 - land_values = data.values[~np.isnan(data.values)] - all_values.extend(land_values) counts, _ = np.histogram(land_values, bins=bins) hist_counts += counts total_samples += len(land_values) except: logger.warning(f"{mode} not available, skipping") - continue + continue + # Store raw counts for percentile estimation + raw_hist_counts[mode] = hist_counts.copy() # Normalize to probability density bin_widths = np.diff(bins) - hist_data[mode] = hist_counts / (total_samples * bin_widths) - all_land_values[mode] = np.array(all_values) + hist_data[mode] = hist_counts[1:] / (total_samples * bin_widths[1:]) logger.info(f"Processed {total_samples} land values for {mode}") # -- Process predictions: compute histogram for each ensemble member -- @@ -200,7 +198,6 @@ def main(cfg: dict): n_members = None member_hist_data = [] - all_member_values = [] for i, ts in enumerate(times): if i % cfg.get("log_interval") == 0: @@ -212,12 +209,10 @@ def main(cfg: dict): n_members = preds.shape[0] member_hist_data = [np.zeros(len(bins) - 1) for _ in range(n_members)] member_sample_counts = [0 for _ in range(n_members)] - all_member_values = [[] for _ in range(n_members)] for member_idx in range(n_members): data = preds[member_idx, tp_out] * land_mask land_values = data.values[~np.isnan(data.values)] - all_member_values[member_idx].extend(land_values) counts, _ = np.histogram(land_values, bins=bins) member_hist_data[member_idx] += counts @@ -227,7 +222,7 @@ def main(cfg: dict): bin_widths = np.diff(bins) normalized_member_hists = [] for member_idx in range(n_members): - normalized_hist = member_hist_data[member_idx] / (member_sample_counts[member_idx] * bin_widths) + normalized_hist = member_hist_data[member_idx][1:] / (member_sample_counts[member_idx] * bin_widths[1:]) normalized_member_hists.append(normalized_hist) hist_data['predictions'] = tuple(normalized_member_hists) @@ -240,21 +235,23 @@ def main(cfg: dict): # Target and baseline percentiles for mode in ['target', 'baseline', 'regression-prediction']: - if mode in all_land_values: - data_array = xr.DataArray(all_land_values[mode]) - percentiles_data[mode] = { - key: data_array.quantile(p).item() - for key, p in percentiles.items() - } + if mode in raw_hist_counts: + cumulative = np.cumsum(raw_hist_counts[mode]) + total = cumulative[-1] + cdf = cumulative / total # CDF at upper bin edges + percentiles_data[mode] = percentiles_from_histogram( + raw_hist_counts[mode], bins, percentiles + ) # Ensemble member percentiles percentiles_data['predictions'] = {} for member_idx in range(n_members): - member_data_array = xr.DataArray(all_member_values[member_idx]) - percentiles_data['predictions'][f'member_{member_idx}'] = { - key: member_data_array.quantile(p).item() - for key, p in percentiles.items() - } + cumulative = np.cumsum(member_hist_data[member_idx]) + total = cumulative[-1] + cdf = cumulative / total # CDF at upper bin edges + percentiles_data['predictions'][f'member_{member_idx}'] = percentiles_from_histogram( + member_hist_data[member_idx], bins, percentiles + ) # Create distribution plots labels = ['Target', 'Input', 'Regression Prediction', 'CorrDiff Ensemble'] if 'regression-prediction' in hist_data else ['Target', 'Input', 'CorrDiff Ensemble'] @@ -264,8 +261,8 @@ def main(cfg: dict): output_path.mkdir(parents=True, exist_ok=True) fn = output_path / 'precipitation_distribution_over_land.png' save_distribution_plot( - hist_data, - bins, + hist_data, # Skip the first bin (0 to 0.1) for plotting + bins[1:], labels, colors, 'Domain-Mean Precip. Over Land (Pooled Data)', diff --git a/src/hirad/eval/map_precip_stats.py b/src/hirad/eval/map_precip_stats.py index eba2fd17..0fb0c2b1 100644 --- a/src/hirad/eval/map_precip_stats.py +++ b/src/hirad/eval/map_precip_stats.py @@ -4,10 +4,10 @@ from datetime import datetime from pathlib import Path -import hydra import numpy as np import torch import xarray as xr +import numba from hirad.datasets import get_channels_from_strings, get_strings_from_channels, known_datasets from hirad.utils.function_utils import get_time_from_range @@ -16,41 +16,90 @@ ) -def consecutive_spell(condition): - """Return longest consecutive spell where condition is True (per gridpoint).""" - def _spell_length(x): - x = np.asarray(x, dtype=bool) - if len(x) == 0: - return 0 - runs = np.diff(np.concatenate(([False], x, [False])).astype(int)) - starts = np.where(runs == 1)[0] - ends = np.where(runs == -1)[0] - return int(np.max(ends - starts)) if len(starts) > 0 else 0 - return xr.apply_ufunc(_spell_length, condition, input_core_dims=[['time']], vectorize=True) +@numba.njit +def _longest_spell(x): + """Longest consecutive run of True values in a 1-D boolean array.""" + best = 0 + cur = 0 + for i in range(x.shape[0]): + if x[i]: + cur += 1 + if cur > best: + best = cur + else: + cur = 0 + return best -def apply_statistic(data, stat_type, stat_param): - """Apply a statistic to the data along the time dimension.""" +@numba.njit(parallel=True) +def _consecutive_spell_2d(condition_3d): + """ + condition_3d: bool array of shape (T, H, W). + Returns int array of shape (H, W) with longest spell per grid point. + """ + T, H, W = condition_3d.shape + out = np.empty((H, W), dtype=np.int64) + for i in numba.prange(H): + for j in range(W): + out[i, j] = _longest_spell(condition_3d[:, i, j]) + return out + + +def consecutive_spell(data_np, condition_fn): + """ + data_np: numpy array (T, H, W) + condition_fn: callable that takes the array and returns bool array of same shape + """ + cond = condition_fn(data_np) + return _consecutive_spell_2d(cond) + + +def apply_statistic(data_np, times_dt, stat_type, stat_param, wet_threshold=0.1): + """ + Apply statistic on array containing time sequence of total precipitation map. + data_np: (T, H, W) float array + times_dt: list of datetime objects (length T) + Returns: (H, W) numpy array + """ if stat_type == 'mean': - return data.mean(dim='time') + return np.mean(data_np, axis=0) + if stat_type == 'quantile': - return data.quantile(stat_param, dim='time') + return np.quantile(data_np, stat_param, axis=0) + if stat_type == 'Rx1hr': - return data.max(dim='time') - if stat_type == 'Rx1day': - daily = data.resample(time="1D").sum("time") - return daily.max(dim='time') - if stat_type == 'Rx5day': - daily = data.resample(time="1D").sum("time") - return daily.rolling(time=5, center=False).sum().max(dim='time') - if stat_type == 'cdd': - daily = data.resample(time="1D").sum("time") - return consecutive_spell(daily < 1.0) - if stat_type == 'cwd': - daily = data.resample(time="1D").sum("time") - return consecutive_spell(daily >= 1.0) + return np.max(data_np, axis=0) + + # For daily aggregations, build daily sums using xarray (fast groupby) + if stat_type in ('Rx1day', 'Rx5day', 'cdd', 'cwd'): + da = xr.DataArray( + data_np, dims=['time', 'lat', 'lon'], + coords={'time': times_dt} + ) + daily = da.resample(time="1D").sum("time").values # (D, H, W) + + if stat_type == 'Rx1day': + return np.max(daily, axis=0) + + if stat_type == 'Rx5day': + # Rolling sum along time axis using a cumsum trick + D, H, W = daily.shape + if D < 5: + return np.sum(daily, axis=0) + rolling5 = np.empty((D - 4, H, W), dtype=daily.dtype) + for t in range(D - 4): + rolling5[t] = daily[t:t+5].sum(axis=0) + return np.max(rolling5, axis=0) + + if stat_type == 'cdd': + return consecutive_spell(daily, lambda x: x < 1.0) + + if stat_type == 'cwd': + return consecutive_spell(daily, lambda x: x >= 1.0) + if stat_type == 'weth_freq': - return (data / 24 > cfg.get("wet_threshold")).mean(dim='time') * 100 + return np.mean(data_np / 24.0 > wet_threshold, axis=0) * 100.0 + raise ValueError(f"Unsupported statistic type: {stat_type}") @@ -82,24 +131,28 @@ def plot_stat_map(data, filename, stat_config, label, grid_cfg): ) +def _load_predictions_all_members(filepath, conv_factor): + """Load prediction file once and return (n_members, C, H, W) tensor.""" + return torch.load(filepath, weights_only=False) * conv_factor + + def main(cfg: dict): - # Setup and config logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) grid_cfg = GridConfig( - lat = np.arange(cfg.get("lat_start"), cfg.get("lat_end") + cfg.get("lat_step"), cfg.get("lat_step")), - lon = np.arange(cfg.get("lon_start"), cfg.get("lon_end") + cfg.get("lon_step"), cfg.get("lon_step")), - height = cfg.get("height"), - width = cfg.get("width"), - relax_zone = cfg.get("relax_zone") + lat=np.arange(cfg.get("lat_start"), cfg.get("lat_end") + cfg.get("lat_step"), cfg.get("lat_step")), + lon=np.arange(cfg.get("lon_start"), cfg.get("lon_end") + cfg.get("lon_step"), cfg.get("lon_step")), + height=cfg.get("height"), + width=cfg.get("width"), + relax_zone=cfg.get("relax_zone") ) generation_dir = cfg.get("inference_output_dir", None) if generation_dir is None: logger.error("No inference_output_dir specified in config.") return - + if not Path(generation_dir).exists() or not Path(generation_dir).is_dir(): logger.error(f"Inference output directory {generation_dir} does not exist or is not a directory.") return @@ -126,8 +179,10 @@ def main(cfg: dict): return logger.info(f"Processing {len(times)} timesteps") + times_dt = [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times] + dataset_cfg = gen_cfg.get("dataset") - dataset_type = dataset_cfg.pop("type") + dataset_type = dataset_cfg.get("type") dataset = known_datasets[dataset_type](**dataset_cfg) logger.info("Dataset initialized") @@ -137,8 +192,10 @@ def main(cfg: dict): indices = get_channel_indices(dataset) tp_out = indices['output']['tp'] tp_in = indices['input'].get('tp', tp_out) + conv_factor = cfg.get("conv_factor") + log_interval = cfg.get("log_interval", 100) + wet_threshold = cfg.get("wet_threshold", 0.1) - # Statistic configuration STATISTICS_CONFIG = { 'mean': {'type': 'mean', 'threshold': 0.01, 'title': 'Mean'}, 'p99': {'type': 'quantile', 'param': 0.99, 'threshold': 0.1, 'title': '99th Percentile'}, @@ -152,84 +209,78 @@ def main(cfg: dict): 'weth_freq': {'type': 'weth_freq', 'threshold': 0.01, 'title': 'Wet-Hour Frequency'} } stat_configs = [ - { - 'stat_name': name, - 'title_stat': config['title'], - 'param': config.get('param'), - **config - } + {'stat_name': name, 'title_stat': config['title'], 'param': config.get('param'), **config} for name, config in STATISTICS_CONFIG.items() ] - # Target and baseline modes + # --- Basic modes: target, baseline, regression-prediction --- basic_modes = { 'target': (tp_out, 'Target'), 'baseline': (tp_in, 'Input'), - 'regression-prediction': (tp_out, 'Regression Prediction') + 'regression-prediction': (tp_out, 'Regression Prediction') } - logger.info(f"Generating {len(stat_configs)} statistics for {len(basic_modes)} basic modes + predictions") for mode, (tp_channel, label) in basic_modes.items(): logger.info(f"Processing mode: {mode}") - # Load all timesteps for this mode data_list = [] try: for i, ts in enumerate(times): - if i % cfg.get("log_interval") == 0: + if i % log_interval == 0: logger.info(f"Loading {mode} timestep {i+1}/{len(times)}: {ts}") - data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False) * cfg.get("conv_factor") - data_list.append(data[tp_channel]) - except: + data = torch.load(out_root / ts / f"{ts}-{mode}", weights_only=False) * conv_factor + data_list.append(data[tp_channel].numpy() if isinstance(data, torch.Tensor) else data[tp_channel]) + except Exception: logger.warning(f"{mode} not available, skipping") continue - mode_data = xr.DataArray( - np.stack(data_list, axis=0), - dims=['time', 'lat', 'lon'], - coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} - ) - # if mode == 'baseline': - # mode_data = mode_data / 6.0 - # Compute and plot all statistics for this mode + + # Stack into (T, H, W) numpy array + mode_data = np.stack(data_list, axis=0).astype(np.float64) + del data_list + for stat_config in stat_configs: logger.info(f"Computing {stat_config['title_stat']} for {mode}...") - result = apply_statistic(mode_data, stat_config['type'], stat_config['param']) + result = apply_statistic(mode_data, times_dt, stat_config['type'], stat_config['param'], wet_threshold) map_output_dir = output_path / f"maps_{stat_config['stat_name']}" map_output_dir.mkdir(parents=True, exist_ok=True) - plot_stat_map(result.values, str(map_output_dir / f'{mode}_{stat_config["stat_name"]}'), stat_config, label, grid_cfg) + plot_stat_map(result, str(map_output_dir / f'{mode}_{stat_config["stat_name"]}'), stat_config, label, grid_cfg) - # Predictions mode: process each member separately to save memory + # --- Predictions: load each file ONCE, distribute to all members --- logger.info("Processing predictions mode...") - data = torch.load(out_root/times[0]/f"{times[0]}-predictions", weights_only=False) - n_members = data.shape[0] + sample_data = torch.load(out_root / times[0] / f"{times[0]}-predictions", weights_only=False) + n_members = sample_data.shape[0] + del sample_data logger.info(f"Found {n_members} ensemble members") - + + # Pre-allocate arrays for ALL members at once: (n_members, T, H, W) + # If memory is tight, we can do this in chunks. For 16 members × 2200 × 704 × 1088 × 4 bytes ≈ 107 GB + # Instead we can do cummulative statistics on the fly without storing all members in memory (like in map_wind_stats), but this works for now. + H, W = cfg.get("height"), cfg.get("width") + member_arrays = [np.empty((len(times), H, W), dtype=np.float32) for _ in range(n_members)] + + logger.info("Loading all prediction timesteps (single pass over files)...") + for i, ts in enumerate(times): + if i % log_interval == 0: + logger.info(f"Loading predictions timestep {i+1}/{len(times)}: {ts}") + pred_data = torch.load(out_root / ts / f"{ts}-predictions", weights_only=False) * conv_factor + for m in range(n_members): + member_arrays[m][i] = (pred_data[m, tp_out].numpy() if isinstance(pred_data, torch.Tensor) + else pred_data[m, tp_out]) + del pred_data + for member_idx in range(n_members): - logger.info(f"Processing prediction member {member_idx+1}/{n_members}") - # Load all timesteps for this member - data_list = [] - for i, ts in enumerate(times): - if i % cfg.get("log_interval") == 0: - logger.info(f"Loading prediction member {member_idx} timestep {i+1}/{len(times)}: {ts}") - pred_data = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) * cfg.get("conv_factor") - data_list.append(pred_data[member_idx, tp_out]) - member_data = xr.DataArray( - np.stack(data_list, axis=0), - dims=['time', 'lat', 'lon'], - coords={'time': [datetime.strptime(ts, "%Y%m%d-%H%M") for ts in times]} - ) - - # Compute and plot all statistics for this member + logger.info(f"Computing statistics for prediction member {member_idx+1}/{n_members}") + member_data = member_arrays[member_idx].astype(np.float64) + for stat_config in stat_configs: logger.info(f"Computing {stat_config['title_stat']} for member {member_idx+1}...") - member_result = apply_statistic(member_data, stat_config['type'], stat_config['param']) - - # Create map + member_result = apply_statistic(member_data, times_dt, stat_config['type'], stat_config['param'], wet_threshold) map_output_dir = output_path / f"maps_{stat_config['stat_name']}" map_output_dir.mkdir(parents=True, exist_ok=True) member_filename = str(map_output_dir / f'prediction_member_{member_idx:02d}_{stat_config["stat_name"]}') member_label = f'CorrDiff Member {member_idx+1}' - plot_stat_map(member_result.values, member_filename, stat_config, member_label, grid_cfg) + plot_stat_map(member_result, member_filename, stat_config, member_label, grid_cfg) + del member_arrays logger.info("All precipitation statistics maps generated successfully") diff --git a/src/hirad/eval/map_wind_stats.py b/src/hirad/eval/map_wind_stats.py index 32435b34..91a649f0 100644 --- a/src/hirad/eval/map_wind_stats.py +++ b/src/hirad/eval/map_wind_stats.py @@ -26,99 +26,126 @@ def compute_wind_direction(u, v, calm_threshold=0.0): return dir_deg -def apply_wind_statistic_streaming(times, out_root, mode, u_channel, v_channel, stat_type, stat_param=None): - """Compute wind statistic by streaming through timesteps.""" - accumulator = None - count = 0 - sin_acc = cos_acc = speed_acc = None - - for ts in times: - data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False) +def apply_all_wind_statistics_streaming(times, out_root, mode, u_channel, v_channel, stat_configs, logger=None, log_interval=100): + """Compute ALL wind statistics in a single pass through timesteps.""" + accumulators = {} + counts = {} + sin_accs = {} + cos_accs = {} + speed_accs = {} + + for sc in stat_configs: + key = sc['stat_name'] + accumulators[key] = None + counts[key] = 0 + sin_accs[key] = None + cos_accs[key] = None + speed_accs[key] = None + + for i, ts in enumerate(times): + if logger and i % log_interval == 0: + logger.info(f" Streaming {mode} timestep {i+1}/{len(times)}: {ts}") + + data = torch.load(out_root / ts / f"{ts}-{mode}", weights_only=False) u = data[u_channel].cpu().numpy() if torch.is_tensor(data[u_channel]) else data[u_channel] v = data[v_channel].cpu().numpy() if torch.is_tensor(data[v_channel]) else data[v_channel] - - if stat_type == 'mean_speed': - speed = compute_wind_speed(u, v) - if accumulator is None: - accumulator = np.zeros_like(speed) - accumulator += speed - elif stat_type == 'max_speed': - speed = compute_wind_speed(u, v) - if accumulator is None: - accumulator = np.full_like(speed, -np.inf) - accumulator = np.maximum(accumulator, speed) - elif stat_type == 'wind_power': - speed = compute_wind_speed(u, v) - if accumulator is None: - accumulator = np.zeros_like(speed) - accumulator += speed**3 - elif stat_type == 'mean_u': - if accumulator is None: - accumulator = np.zeros_like(u) - accumulator += u - elif stat_type == 'mean_v': - if accumulator is None: - accumulator = np.zeros_like(v) - accumulator += v - elif stat_type in ['calm_freq', 'light_breeze_freq', 'moderate_breeze_freq', - 'strong_breeze_freq', 'gale_freq']: - speed = compute_wind_speed(u, v) - thresholds = { - 'calm_freq': 2.0, - 'light_breeze_freq': 1.6, - 'moderate_breeze_freq': 5.5, - 'strong_breeze_freq': 10.8, - 'gale_freq': 17.2 - } - threshold = thresholds[stat_type] - if accumulator is None: - accumulator = np.zeros_like(speed) - if stat_type == 'calm_freq': - accumulator += (speed < threshold).astype(float) - else: - accumulator += (speed > threshold).astype(float) - elif stat_type == 'prevailing_direction': - speed = compute_wind_speed(u, v) - direction = compute_wind_direction(u, v, calm_threshold=1.0) - rad = np.deg2rad(direction) - weighted_sin = np.sin(rad) * speed - weighted_cos = np.cos(rad) * speed - - if sin_acc is None: - sin_acc = np.zeros_like(weighted_sin) - cos_acc = np.zeros_like(weighted_cos) - speed_acc = np.zeros_like(speed) - - sin_acc += np.nan_to_num(weighted_sin, 0) - cos_acc += np.nan_to_num(weighted_cos, 0) - speed_acc += speed - elif stat_type == 'direction_variability': - direction = compute_wind_direction(u, v) - rad = np.deg2rad(direction) - - if sin_acc is None: - sin_acc = np.zeros_like(np.sin(rad)) - cos_acc = np.zeros_like(np.cos(rad)) - - sin_acc += np.sin(rad) - cos_acc += np.cos(rad) - - count += 1 - del data, u, v - - if stat_type == 'prevailing_direction': - mean_dir = np.arctan2(sin_acc / (speed_acc + 1e-10), cos_acc / (speed_acc + 1e-10)) - return np.mod(np.rad2deg(mean_dir), 360) - elif stat_type == 'direction_variability': - R = np.clip(np.hypot(sin_acc / count, cos_acc / count), 1e-10, 1.0) - return np.rad2deg(np.sqrt(-2 * np.log(R))) - elif stat_type == 'max_speed': - return accumulator - elif stat_type in ['calm_freq', 'light_breeze_freq', 'moderate_breeze_freq', - 'strong_breeze_freq', 'gale_freq']: - return (accumulator / count) * 100 - else: - return accumulator / count + del data + + # Pre-compute shared quantities once per timestep + speed = compute_wind_speed(u, v) + direction_calm = None # lazy + direction_raw = None # lazy + + for sc in stat_configs: + key = sc['stat_name'] + stype = sc['type'] + + if stype == 'mean_speed': + if accumulators[key] is None: + accumulators[key] = np.zeros_like(speed) + accumulators[key] += speed + elif stype == 'max_speed': + if accumulators[key] is None: + accumulators[key] = np.full_like(speed, -np.inf) + np.maximum(accumulators[key], speed, out=accumulators[key]) + elif stype == 'wind_power': + if accumulators[key] is None: + accumulators[key] = np.zeros_like(speed) + accumulators[key] += speed ** 3 + elif stype == 'mean_u': + if accumulators[key] is None: + accumulators[key] = np.zeros_like(u) + accumulators[key] += u + elif stype == 'mean_v': + if accumulators[key] is None: + accumulators[key] = np.zeros_like(v) + accumulators[key] += v + elif stype in ['calm_freq', 'light_breeze_freq', 'moderate_breeze_freq', + 'strong_breeze_freq', 'gale_freq']: + thresholds = { + 'calm_freq': 2.0, 'light_breeze_freq': 1.6, + 'moderate_breeze_freq': 5.5, 'strong_breeze_freq': 10.8, + 'gale_freq': 17.2 + } + threshold = thresholds[stype] + if accumulators[key] is None: + accumulators[key] = np.zeros_like(speed) + if stype == 'calm_freq': + accumulators[key] += (speed < threshold).astype(float) + else: + accumulators[key] += (speed > threshold).astype(float) + elif stype == 'prevailing_direction': + if direction_calm is None: + direction_calm = compute_wind_direction(u, v, calm_threshold=1.0) + rad = np.deg2rad(direction_calm) + weighted_sin = np.sin(rad) * speed + weighted_cos = np.cos(rad) * speed + if sin_accs[key] is None: + sin_accs[key] = np.zeros_like(weighted_sin) + cos_accs[key] = np.zeros_like(weighted_cos) + speed_accs[key] = np.zeros_like(speed) + sin_accs[key] += np.nan_to_num(weighted_sin, 0) + cos_accs[key] += np.nan_to_num(weighted_cos, 0) + speed_accs[key] += speed + elif stype == 'direction_variability': + if direction_raw is None: + direction_raw = compute_wind_direction(u, v) + rad = np.deg2rad(direction_raw) + if sin_accs[key] is None: + sin_accs[key] = np.zeros_like(speed) + cos_accs[key] = np.zeros_like(speed) + sin_accs[key] += np.sin(rad) + cos_accs[key] += np.cos(rad) + + counts[key] += 1 + + del u, v, speed, direction_calm, direction_raw + + # Finalize all statistics + results = {} + for sc in stat_configs: + key = sc['stat_name'] + stype = sc['type'] + count = counts[key] + + if stype == 'prevailing_direction': + mean_dir = np.arctan2( + sin_accs[key] / (speed_accs[key] + 1e-10), + cos_accs[key] / (speed_accs[key] + 1e-10) + ) + results[key] = np.mod(np.rad2deg(mean_dir), 360) + elif stype == 'direction_variability': + R = np.clip(np.hypot(sin_accs[key] / count, cos_accs[key] / count), 1e-10, 1.0) + results[key] = np.rad2deg(np.sqrt(-2 * np.log(R))) + elif stype == 'max_speed': + results[key] = accumulators[key] + elif stype in ['calm_freq', 'light_breeze_freq', 'moderate_breeze_freq', + 'strong_breeze_freq', 'gale_freq']: + results[key] = (accumulators[key] / count) * 100 + else: + results[key] = accumulators[key] / count + + return results def plot_wind_stat_map(data, filename, stat_config, label, grid_cfg): @@ -217,7 +244,7 @@ def main(cfg: dict): logger.info(f"Processing {len(times)} timesteps") dataset_cfg = gen_cfg.get("dataset") - dataset_type = dataset_cfg.pop("type") + dataset_type = dataset_cfg.get("type") dataset = known_datasets[dataset_type](**dataset_cfg) out_root = Path(generation_dir) output_path = out_root / cfg.get("results_dir_name", "evaluation_maps") @@ -292,12 +319,15 @@ def main(cfg: dict): ] basic_modes = { - 'target': ((u10_out, v10_out), 'COSMO-2 Analysis'), - 'baseline': ((u10_in, v10_in), 'ERA5'), + 'target': ((u10_out, v10_out), 'Target'), + 'baseline': ((u10_in, v10_in), 'Input'), 'regression-prediction': ((u10_out, v10_out), 'Regression Prediction') } + logger.info(f"Generating {len(stat_configs)} statistics for {len(basic_modes)} modes + predictions") + log_interval = cfg.get("log_interval", 100) + for mode, (wind_channels, label) in basic_modes.items(): logger.info(f"Processing mode: {mode}") u_channel, v_channel = wind_channels @@ -309,27 +339,28 @@ def main(cfg: dict): logger.warning(f"{mode} not available: {e}") continue - for stat_config in stat_configs: - logger.info(f"Computing {stat_config['title_stat']} for {mode}...") - try: - result = apply_wind_statistic_streaming( - times, out_root, mode, u_channel, v_channel, - stat_config['type'], stat_config.get('param') - ) - - map_output_dir = output_path / f"maps_wind_{stat_config['stat_name']}" + try: + results = apply_all_wind_statistics_streaming( + times, out_root, mode, u_channel, v_channel, + stat_configs, logger=logger, log_interval=log_interval + ) + + for stat_config in stat_configs: + key = stat_config['stat_name'] + map_output_dir = output_path / f"maps_wind_{key}" map_output_dir.mkdir(parents=True, exist_ok=True) plot_wind_stat_map( - result, - str(map_output_dir / f'{mode}_{stat_config["stat_name"]}'), + results[key], + str(map_output_dir / f'{mode}_{key}'), stat_config, label, grid_cfg ) - del result - except Exception as e: - logger.error(f"Failed {stat_config['title_stat']} for {mode}: {e}") - continue + del results + except Exception as e: + logger.error(f"Failed computing statistics for {mode}: {e}") + continue + logger.info("Processing predictions mode...") try: @@ -338,122 +369,140 @@ def main(cfg: dict): del data logger.info(f"Found {n_members} ensemble members") + # Initialize accumulators for all members × all statistics at once + # Each accumulator is keyed by (member_idx, stat_name) + accumulators = {} + counts = {} + sin_accs = {} + cos_accs = {} + speed_accs = {} + for member_idx in range(n_members): - logger.info(f"Processing member {member_idx+1}/{n_members}") - for stat_config in stat_configs: - logger.info(f"Computing {stat_config['title_stat']} for member {member_idx+1}...") - try: - def load_member_data(ts): - pred_data = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) - u_data = pred_data[member_idx, u10_out] - v_data = pred_data[member_idx, v10_out] - u = u_data.cpu().numpy() if torch.is_tensor(u_data) else u_data - v = v_data.cpu().numpy() if torch.is_tensor(v_data) else v_data - del pred_data - return u, v - - accumulator = None - count = 0 - sin_acc = cos_acc = speed_acc = None + key = (member_idx, stat_config['stat_name']) + accumulators[key] = None + counts[key] = 0 + sin_accs[key] = None + cos_accs[key] = None + speed_accs[key] = None + + # Single pass over timesteps — load each file once for all members + for i, ts in enumerate(times): + if i % log_interval == 0: + logger.info(f"Loading predictions timestep {i+1}/{len(times)}: {ts}") + + pred_data = torch.load(out_root/ts/f"{ts}-predictions", weights_only=False) + + for member_idx in range(n_members): + u_data = pred_data[member_idx, u10_out] + v_data = pred_data[member_idx, v10_out] + u = u_data.cpu().numpy() if torch.is_tensor(u_data) else u_data + v = v_data.cpu().numpy() if torch.is_tensor(v_data) else v_data + + # Pre-compute shared quantities once per member per timestep + speed = compute_wind_speed(u, v) + direction_calm = None + direction_raw = None + + for stat_config in stat_configs: + key = (member_idx, stat_config['stat_name']) + stype = stat_config['type'] - for i, ts in enumerate(times): - if i % cfg.get("log_interval") == 0: - logger.info(f"Loading prediction member {member_idx} timestep {i+1}/{len(times)}: {ts}") - - u, v = load_member_data(ts) - - if stat_config['type'] == 'mean_speed': - speed = compute_wind_speed(u, v) - if accumulator is None: - accumulator = np.zeros_like(speed) - accumulator += speed - elif stat_config['type'] == 'max_speed': - speed = compute_wind_speed(u, v) - if accumulator is None: - accumulator = np.full_like(speed, -np.inf) - accumulator = np.maximum(accumulator, speed) - elif stat_config['type'] == 'wind_power': - speed = compute_wind_speed(u, v) - if accumulator is None: - accumulator = np.zeros_like(speed) - accumulator += speed**3 - elif stat_config['type'] == 'mean_u': - if accumulator is None: - accumulator = np.zeros_like(u) - accumulator += u - elif stat_config['type'] == 'mean_v': - if accumulator is None: - accumulator = np.zeros_like(v) - accumulator += v - elif stat_config['type'] in ['calm_freq', 'light_breeze_freq', - 'moderate_breeze_freq', 'strong_breeze_freq', - 'gale_freq']: - speed = compute_wind_speed(u, v) - thresholds = { - 'calm_freq': 2.0, - 'light_breeze_freq': 1.6, - 'moderate_breeze_freq': 5.5, - 'strong_breeze_freq': 10.8, - 'gale_freq': 17.2 - } - threshold = thresholds[stat_config['type']] - if accumulator is None: - accumulator = np.zeros_like(speed) - if stat_config['type'] == 'calm_freq': - accumulator += (speed < threshold).astype(float) - else: - accumulator += (speed > threshold).astype(float) - elif stat_config['type'] == 'prevailing_direction': - speed = compute_wind_speed(u, v) - direction = compute_wind_direction(u, v, calm_threshold=1.0) - rad = np.deg2rad(direction) - weighted_sin = np.sin(rad) * speed - weighted_cos = np.cos(rad) * speed - - if sin_acc is None: - sin_acc = np.zeros_like(weighted_sin) - cos_acc = np.zeros_like(weighted_cos) - speed_acc = np.zeros_like(speed) - - sin_acc += np.nan_to_num(weighted_sin, 0) - cos_acc += np.nan_to_num(weighted_cos, 0) - speed_acc += speed - elif stat_config['type'] == 'direction_variability': - direction = compute_wind_direction(u, v) - rad = np.deg2rad(direction) - - if sin_acc is None: - sin_acc = np.zeros_like(np.sin(rad)) - cos_acc = np.zeros_like(np.cos(rad)) - - sin_acc += np.sin(rad) - cos_acc += np.cos(rad) - - count += 1 - del u, v + if stype == 'mean_speed': + if accumulators[key] is None: + accumulators[key] = np.zeros_like(speed) + accumulators[key] += speed + elif stype == 'max_speed': + if accumulators[key] is None: + accumulators[key] = np.full_like(speed, -np.inf) + np.maximum(accumulators[key], speed, out=accumulators[key]) + elif stype == 'wind_power': + if accumulators[key] is None: + accumulators[key] = np.zeros_like(speed) + accumulators[key] += speed ** 3 + elif stype == 'mean_u': + if accumulators[key] is None: + accumulators[key] = np.zeros_like(u) + accumulators[key] += u + elif stype == 'mean_v': + if accumulators[key] is None: + accumulators[key] = np.zeros_like(v) + accumulators[key] += v + elif stype in ['calm_freq', 'light_breeze_freq', 'moderate_breeze_freq', + 'strong_breeze_freq', 'gale_freq']: + thresholds = { + 'calm_freq': 2.0, 'light_breeze_freq': 1.6, + 'moderate_breeze_freq': 5.5, 'strong_breeze_freq': 10.8, + 'gale_freq': 17.2 + } + threshold = thresholds[stype] + if accumulators[key] is None: + accumulators[key] = np.zeros_like(speed) + if stype == 'calm_freq': + accumulators[key] += (speed < threshold).astype(float) + else: + accumulators[key] += (speed > threshold).astype(float) + elif stype == 'prevailing_direction': + if direction_calm is None: + direction_calm = compute_wind_direction(u, v, calm_threshold=1.0) + rad = np.deg2rad(direction_calm) + weighted_sin = np.sin(rad) * speed + weighted_cos = np.cos(rad) * speed + if sin_accs[key] is None: + sin_accs[key] = np.zeros_like(weighted_sin) + cos_accs[key] = np.zeros_like(weighted_cos) + speed_accs[key] = np.zeros_like(speed) + sin_accs[key] += np.nan_to_num(weighted_sin, 0) + cos_accs[key] += np.nan_to_num(weighted_cos, 0) + speed_accs[key] += speed + elif stype == 'direction_variability': + if direction_raw is None: + direction_raw = compute_wind_direction(u, v) + rad = np.deg2rad(direction_raw) + if sin_accs[key] is None: + sin_accs[key] = np.zeros_like(speed) + cos_accs[key] = np.zeros_like(speed) + sin_accs[key] += np.sin(rad) + cos_accs[key] += np.cos(rad) - if stat_config['type'] == 'prevailing_direction': - mean_dir = np.arctan2(sin_acc / (speed_acc + 1e-10), cos_acc / (speed_acc + 1e-10)) + counts[key] += 1 + + del u, v, speed, direction_calm, direction_raw + + del pred_data + + # Finalize and plot all statistics for all members + for member_idx in range(n_members): + logger.info(f"Finalizing and plotting member {member_idx+1}/{n_members}") + for stat_config in stat_configs: + stat_key = stat_config['stat_name'] + key = (member_idx, stat_key) + stype = stat_config['type'] + count = counts[key] + + try: + if stype == 'prevailing_direction': + mean_dir = np.arctan2( + sin_accs[key] / (speed_accs[key] + 1e-10), + cos_accs[key] / (speed_accs[key] + 1e-10) + ) member_result = np.mod(np.rad2deg(mean_dir), 360) - elif stat_config['type'] == 'direction_variability': - R = np.clip(np.hypot(sin_acc / count, cos_acc / count), 1e-10, 1.0) + elif stype == 'direction_variability': + R = np.clip(np.hypot(sin_accs[key] / count, cos_accs[key] / count), 1e-10, 1.0) member_result = np.rad2deg(np.sqrt(-2 * np.log(R))) - elif stat_config['type'] == 'max_speed': - member_result = accumulator - elif stat_config['type'] in ['calm_freq', 'light_breeze_freq', - 'moderate_breeze_freq', 'strong_breeze_freq', - 'gale_freq']: - member_result = (accumulator / count) * 100 + elif stype == 'max_speed': + member_result = accumulators[key] + elif stype in ['calm_freq', 'light_breeze_freq', 'moderate_breeze_freq', + 'strong_breeze_freq', 'gale_freq']: + member_result = (accumulators[key] / count) * 100 else: - member_result = accumulator / count + member_result = accumulators[key] / count - map_output_dir = output_path / f"maps_wind_{stat_config['stat_name']}" + map_output_dir = output_path / f"maps_wind_{stat_key}" map_output_dir.mkdir(parents=True, exist_ok=True) - member_filename = str(map_output_dir / f'prediction_member_{member_idx:02d}_{stat_config["stat_name"]}') + member_filename = str(map_output_dir / f'prediction_member_{member_idx:02d}_{stat_key}') plot_wind_stat_map(member_result, member_filename, stat_config, f'CorrDiff Member {member_idx+1}', grid_cfg) del member_result - except Exception as e: logger.error(f"Failed {stat_config['title_stat']} for member {member_idx+1}: {e}") continue diff --git a/src/hirad/eval/probability_of_exceedance.py b/src/hirad/eval/probability_of_exceedance.py index ca4a273a..20579afe 100644 --- a/src/hirad/eval/probability_of_exceedance.py +++ b/src/hirad/eval/probability_of_exceedance.py @@ -18,6 +18,7 @@ from hirad.datasets import get_channels_from_strings, get_strings_from_channels, known_datasets from hirad.utils.function_utils import get_time_from_range from hirad.eval.plotting import get_channel_indices, load_land_sea_mask +from hirad.eval.eval_utils import percentiles_from_histogram def save_exceedance_plot(exceedance_data_dict, thresholds, labels, colors, title, ylabel, out_path, percentiles_data=None): @@ -135,7 +136,7 @@ def main(cfg: dict): # Initialize dataset dataset_cfg = gen_cfg.get("dataset") - dataset_type = dataset_cfg.pop("type") + dataset_type = dataset_cfg.get("type") dataset = known_datasets[dataset_type](**dataset_cfg) logger.info("Dataset initialized") @@ -152,17 +153,28 @@ def main(cfg: dict): land_mask = load_land_sea_mask(cfg.get("land_sea_mask_path"), cfg.get("height"), cfg.get("width")) # Define thresholds for exceedance calculation - thresholds = np.logspace(-2, 2.1, 200) # From 0.01 to 100 mm/h - + thresholds = np.logspace(-2, 3.0, 200) # From 0.01 to 1000 mm/h + n_thresholds = len(thresholds) + + # Histogram bins for percentile estimation (fine-grained log-spaced) + hist_bins = np.concatenate([ + np.array([0.0]), + np.logspace(-2, 3.2, 5000) # From 0.01 to ~1585 mm/h + ]) + n_hist_bins = len(hist_bins) - 1 + # Storage for exceedance data and land values - exceedance_data = {} - all_land_values = {} + exceedance_counts = {} + totals = {} + hist_counts = {} # For percentile estimation # -- Process target and baseline -- for mode in ['target', 'baseline', 'regression-prediction']: logger.info(f"Processing mode: {mode}") - all_values = [] + mode_exc_counts = np.zeros(n_thresholds, dtype=np.int64) + mode_total = 0 + mode_hist = np.zeros(n_hist_bins, dtype=np.int64) try: for i, ts in enumerate(times): @@ -172,27 +184,31 @@ def main(cfg: dict): data = torch.load(out_root/ts/f"{ts}-{mode}", weights_only=False)[tp_out if mode in ['target','regression-prediction'] else tp_in] * cfg.get("conv_factor_hourly") * land_mask land_values = data.values[~np.isnan(data.values)] - all_values.extend(land_values) + n_vals = len(land_values) + mode_total += n_vals + # Update counts for exceedance calculation + mode_exc_counts += np.sum( + land_values[:, None] > thresholds[None, :], axis=0 + ) + # Update histogram counts for percentile estimation + mode_hist += np.histogram(land_values, bins=hist_bins)[0] except: logger.warning(f"{mode} data not found, skipping") continue # Compute exceedance probabilities - all_values = np.array(all_values) - exceedance_probs = [] - for threshold in thresholds: - prob_exceed = np.mean(all_values > threshold) - exceedance_probs.append(prob_exceed) - - exceedance_data[mode] = np.array(exceedance_probs) - all_land_values[mode] = all_values - logger.info(f"Processed {len(all_values)} land values for {mode}") + exceedance_counts[mode] = mode_exc_counts + totals[mode] = mode_total + hist_counts[mode] = mode_hist + logger.info(f"Processed {mode_total} land values for {mode}") # -- Process predictions: compute exceedance for each ensemble member -- logger.info("Processing predictions") n_members = None - all_member_values = [] + member_exc_counts = None + member_totals = None + member_hist = None for i, ts in enumerate(times): if i % cfg.get("log_interval") == 0: @@ -202,22 +218,32 @@ def main(cfg: dict): if n_members is None: n_members = preds.shape[0] - all_member_values = [[] for _ in range(n_members)] + member_exc_counts = [np.zeros(n_thresholds, dtype=np.int64) for _ in range(n_members)] + member_totals = [0] * n_members + member_hist = [np.zeros(n_hist_bins, dtype=np.int64) for _ in range(n_members)] for member_idx in range(n_members): data = preds[member_idx, tp_out] * land_mask land_values = data.values[~np.isnan(data.values)] - all_member_values[member_idx].extend(land_values) + n_vals = len(land_values) + member_totals[member_idx] += n_vals + member_exc_counts[member_idx] += np.sum( + land_values[:, None] > thresholds[None, :], axis=0 + ) + member_hist[member_idx] += np.histogram(land_values, bins=hist_bins)[0] - # Compute exceedance probabilities for each ensemble member + # Compute exceedance probabilities + exceedance_data = {} + for mode in ['target', 'baseline', 'regression-prediction']: + if mode in exceedance_counts and totals[mode] > 0: + exceedance_data[mode] = exceedance_counts[mode] / totals[mode] + member_exceedance_data = [] for member_idx in range(n_members): - member_values = np.array(all_member_values[member_idx]) - member_exceedance = [] - for threshold in thresholds: - prob_exceed = np.mean(member_values > threshold) - member_exceedance.append(prob_exceed) - member_exceedance_data.append(np.array(member_exceedance)) + if member_totals[member_idx] > 0: + member_exceedance_data.append( + member_exc_counts[member_idx] / member_totals[member_idx] + ) exceedance_data['predictions'] = tuple(member_exceedance_data) @@ -226,24 +252,20 @@ def main(cfg: dict): # Compute percentiles for all datasets percentiles_data = {} percentiles = {99: 0.99, 99.9: 0.999, 99.99: 0.9999} - - # Target and baseline percentiles + + # Estimating percentiles from fine-grained histograms for mode in ['target', 'baseline', 'regression-prediction']: - if mode in all_land_values: - data_array = xr.DataArray(all_land_values[mode]) - percentiles_data[mode] = { - key: data_array.quantile(p).item() - for key, p in percentiles.items() - } - - # Ensemble member percentiles + if mode in hist_counts and totals[mode] > 0: + percentiles_data[mode] = percentiles_from_histogram( + hist_counts[mode], hist_bins, percentiles + ) + percentiles_data['predictions'] = {} for member_idx in range(n_members): - member_data_array = xr.DataArray(all_member_values[member_idx]) - percentiles_data['predictions'][f'member_{member_idx}'] = { - key: member_data_array.quantile(p).item() - for key, p in percentiles.items() - } + if member_totals[member_idx] > 0: + percentiles_data['predictions'][f'member_{member_idx}'] = percentiles_from_histogram( + member_hist[member_idx], hist_bins, percentiles + ) # Create exceedance plots labels = ['Target', 'Input', 'Regression Prediction', 'CorrDiff Ensemble'] if 'regression-prediction' in exceedance_data else ['Target', 'Input', 'CorrDiff Ensemble'] diff --git a/src/hirad/eval/probability_of_exceedance_wind.py b/src/hirad/eval/probability_of_exceedance_wind.py index d9f9070f..b3ac9eb3 100644 --- a/src/hirad/eval/probability_of_exceedance_wind.py +++ b/src/hirad/eval/probability_of_exceedance_wind.py @@ -13,6 +13,7 @@ from hirad.datasets import get_channels_from_strings, get_strings_from_channels, known_datasets from hirad.utils.function_utils import get_time_from_range from hirad.eval.plotting import get_channel_indices +from hirad.eval.eval_utils import percentiles_from_histogram def compute_wind_speed(u, v): @@ -31,8 +32,7 @@ def compute_exceedance_probs(values, thresholds, use_abs=False): def update_exceedance_counts(counts, total, values, thresholds, use_abs=False): """Update exceedance counts incrementally.""" data = np.abs(values) if use_abs else values - for i, threshold in enumerate(thresholds): - counts[i] += np.sum(data > threshold) + counts += (data[:, None] > thresholds[None, :]).sum(axis=0) total += len(values) return counts, total @@ -159,7 +159,7 @@ def main(cfg: dict): # Initialize dataset dataset_cfg = gen_cfg.get("dataset") - dataset_type = dataset_cfg.pop("type") + dataset_type = dataset_cfg.get("type") dataset = known_datasets[dataset_type](**dataset_cfg) logger.info("Dataset initialized") @@ -180,17 +180,24 @@ def main(cfg: dict): logger.info(f"Wind component channel indices - output: 10u={u10_out}, 10v={v10_out}, input: 10u={u10_in}, 10v={v10_in}") # Define thresholds for exceedance calculation (same for all variables) - thresholds = np.logspace(-1, 1.5, 200) # From 0.1 to ~31.6 m/s + thresholds = np.logspace(-1, 2, 200) # From 0.1 to ~100 m/s n_thresholds = len(thresholds) + + # Histogram bins for percentile estimation (fine-grained log-spaced) + hist_bins = np.concatenate([ + np.array([0.0]), + np.logspace(-1, 2.5, 5000) # From 0.1 to ~316 m/s + ]) + n_hist_bins = len(hist_bins) - 1 # Storage for exceedance counts (incremental computation) exceedance_counts = { 'speed': {}, 'u': {}, 'v': {} } totals = {'speed': {}, 'u': {}, 'v': {}} - - # Storage for percentile computation (collect samples) - percentile_samples = {'speed': {}, 'u': {}, 'v': {}} + hist_counts = { + 'speed': {}, 'u': {}, 'v': {} + } # -- Process target and baseline -- for mode in ['target', 'baseline', 'regression-prediction']: @@ -200,7 +207,7 @@ def main(cfg: dict): for var in ['speed', 'u', 'v']: exceedance_counts[var][mode] = np.zeros(n_thresholds, dtype=np.int64) totals[var][mode] = 0 - percentile_samples[var][mode] = [] + hist_counts[var][mode] = np.zeros(n_hist_bins, dtype=np.int64) try: for i, ts in enumerate(times): @@ -237,10 +244,9 @@ def main(cfg: dict): ) # Collect samples for percentiles (subsample to save memory) - sample_rate = max(1, len(speed_vals) // 10000) # Keep ~10k samples per timestep - percentile_samples['speed'][mode].extend(speed_vals[::sample_rate]) - percentile_samples['u'][mode].extend(u_vals[::sample_rate]) - percentile_samples['v'][mode].extend(v_vals[::sample_rate]) + hist_counts['speed'][mode] += np.histogram(speed_vals, bins=hist_bins)[0] + hist_counts['u'][mode] += np.histogram(np.abs(u_vals), bins=hist_bins)[0] + hist_counts['v'][mode] += np.histogram(np.abs(v_vals), bins=hist_bins)[0] except Exception as e: logger.warning(f"{mode} data not found or error occurred, skipping: {e}") @@ -254,7 +260,7 @@ def main(cfg: dict): n_members = None member_counts = {'speed': [], 'u': [], 'v': []} member_totals = {'speed': [], 'u': [], 'v': []} - member_samples = {'speed': [], 'u': [], 'v': []} + member_hist_counts = {'speed': [], 'u': [], 'v': []} for i, ts in enumerate(times): if i % cfg.get("log_interval") == 0: @@ -267,7 +273,7 @@ def main(cfg: dict): for var in ['speed', 'u', 'v']: member_counts[var] = [np.zeros(n_thresholds, dtype=np.int64) for _ in range(n_members)] member_totals[var] = [0 for _ in range(n_members)] - member_samples[var] = [[] for _ in range(n_members)] + member_hist_counts[var] = [np.zeros(n_hist_bins, dtype=np.int64) for _ in range(n_members)] for member_idx in range(n_members): u = preds[member_idx, u10_out] @@ -291,10 +297,9 @@ def main(cfg: dict): ) # Collect samples for percentiles - sample_rate = max(1, len(speed_vals) // 10000) - member_samples['speed'][member_idx].extend(speed_vals[::sample_rate]) - member_samples['u'][member_idx].extend(u_vals[::sample_rate]) - member_samples['v'][member_idx].extend(v_vals[::sample_rate]) + member_hist_counts['speed'][member_idx] += np.histogram(speed_vals, bins=hist_bins)[0] + member_hist_counts['u'][member_idx] += np.histogram(np.abs(u_vals), bins=hist_bins)[0] + member_hist_counts['v'][member_idx] += np.histogram(np.abs(v_vals), bins=hist_bins)[0] logger.info(f"Collected {n_members} ensemble members for predictions") @@ -320,19 +325,18 @@ def main(cfg: dict): # Single datasets (target, baseline, regression-prediction) for var in ['speed', 'u', 'v']: - use_abs = (var in ['u', 'v']) for mode in ['target', 'baseline', 'regression-prediction']: - if mode in percentile_samples[var] and len(percentile_samples[var][mode]) > 0: - percentiles_data[var][mode] = compute_percentiles( - np.array(percentile_samples[var][mode]), percentiles, use_abs + if mode in hist_counts[var] and totals[var][mode] > 0: + percentiles_data[var][mode] = percentiles_from_histogram( + hist_counts[var][mode], hist_bins, percentiles ) # Ensemble members percentiles_data[var]['predictions'] = {} for member_idx in range(n_members): - if len(member_samples[var][member_idx]) > 0: - percentiles_data[var]['predictions'][f'member_{member_idx}'] = compute_percentiles( - np.array(member_samples[var][member_idx]), percentiles, use_abs + if member_totals[var][member_idx] > 0: + percentiles_data[var]['predictions'][f'member_{member_idx}'] = percentiles_from_histogram( + member_hist_counts[var][member_idx], hist_bins, percentiles ) # Create exceedance plots From e57019366d6601b53057061fda174346a8cba4aa Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Thu, 19 Feb 2026 14:25:44 +0100 Subject: [PATCH 254/302] Fix a bug in diurnal stats grouping --- src/hirad/eval/plotting.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/hirad/eval/plotting.py b/src/hirad/eval/plotting.py index 731fc1c4..67f0ef8b 100644 --- a/src/hirad/eval/plotting.py +++ b/src/hirad/eval/plotting.py @@ -77,13 +77,12 @@ def load_land_sea_mask(path=LAND_SEA_MASK_PATH, height=352, width=544): def concat_and_group_diurnal(list_of_da, is_member=False, scale=1.0): """Helper to concatenate DataArrays and compute diurnal statistics.""" - da = xr.concat(list_of_da, dim="time").groupby("time.hour") + da = xr.concat(list_of_da, dim="time") if is_member: - timmean = da.mean(dim='time') * scale - mean = timmean.mean(dim='member') - std = da.std(dim='member').mean(dim='time') * scale + mean = da.groupby("time.hour").mean(dim="time").mean(dim="member") * scale + std = da.std(dim="member").groupby("time.hour").mean(dim="time") * scale else: - mean = da.mean(dim='time') * scale + mean = da.groupby("time.hour").mean(dim="time") * scale std = None return mean, std From cf167404505ce79e64a99898de084bee5a5a8d18 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Thu, 19 Feb 2026 14:29:52 +0100 Subject: [PATCH 255/302] Update to first interpolating and then transforming and normalizing data - Switch back to previous way of transofrming and normalizing interpolated data because it gives better results in training - Effect of order of these operations on training is not clear and should be investigated --- src/hirad/datasets/anemoi_dataset.py | 81 +++++++++++++++++++++------- src/hirad/inference/generate.py | 38 ++++++------- src/hirad/inference/generator.py | 2 +- src/hirad/training/train.py | 12 ++++- src/hirad/utils/inference_utils.py | 10 ++-- 5 files changed, 96 insertions(+), 47 deletions(-) diff --git a/src/hirad/datasets/anemoi_dataset.py b/src/hirad/datasets/anemoi_dataset.py index 38438905..1bdd98b3 100644 --- a/src/hirad/datasets/anemoi_dataset.py +++ b/src/hirad/datasets/anemoi_dataset.py @@ -46,14 +46,14 @@ def __init__(self, input_dataset = type.split('_')[1] target_dataset = type.split('_')[2] - real_target = target_dataset == 'real' + self.real_target = target_dataset == 'real' if input_dataset != 'era5': raise ValueError(f"Input dataset {input_dataset} not supported for AnemoiDataset. Only 'era5' is supported.") if target_dataset != 'cosmo' and target_dataset !='real': raise ValueError(f"Target dataset {target_dataset} not supported for AnemoiDataset. Only 'cosmo' and 'real' are supported.") - if real_target: + if self.real_target: # Map output channel names from real to era5 output_channel_names_real = [ERA_TO_REAL_CHANNEL_MAP[name] for name in output_channel_names] @@ -62,9 +62,9 @@ def __init__(self, self._n_month_hour_channels = n_month_hour_channels if start_date is not None and end_date is not None: assert start_date < end_date, "start_date must be before end_date" - self._output_dataset = open_dataset(target_anemoi_dataset_path, select=output_channel_names_real if real_target else output_channel_names, start=start_date, end=end_date, trim_edge=trim_edge) + self._output_dataset = open_dataset(target_anemoi_dataset_path, select=output_channel_names_real if self.real_target else output_channel_names, start=start_date, end=end_date, trim_edge=trim_edge) else: - self._output_dataset = open_dataset(target_anemoi_dataset_path, select=output_channel_names_real if real_target else output_channel_names, trim_edge=trim_edge) + self._output_dataset = open_dataset(target_anemoi_dataset_path, select=output_channel_names_real if self.real_target else output_channel_names, trim_edge=trim_edge) assert self._output_dataset.shape[1] == len(output_channel_names) # Load ERA dataset, trimming the area and limiting the dates to the target dataset @@ -173,7 +173,7 @@ def __getitem__(self, idx): tp_idx = self._input_channels.index(ChannelMetadata('tp')) corrected_tp_data = np.load(os.path.join(self._corrected_tp_path, f'{date_str}.npy')) input_data[tp_idx,::] = corrected_tp_data - input_data = self.normalize_input(input_data) + # input_data = self.normalize_input(input_data) # Pull target data # squeeze the ensemble dimesnsion @@ -185,9 +185,9 @@ def __getitem__(self, idx): .squeeze() \ .reshape(-1,*target_shape), 1) - target_data = self.normalize_output(target_data) + # target_data = self.normalize_output(target_data) - return torch.from_numpy(target_data),\ + return torch.from_numpy(target_data.copy()),\ torch.from_numpy(input_data),\ date_str @@ -230,17 +230,46 @@ def image_shape(self) -> Tuple[int, int]: def input_shape(self) -> Tuple[int, int]: """Get the (height, width) of the input data.""" return self._input_dataset.field_shape + + def normalization_stats(self): + """Get the mean and std stats for normalizing the input and output data.""" + return {"input_mean": self.input_mean, + "input_std": self.input_std, + "output_mean": self.output_mean, + "output_std": self.output_std} + + def stats_to_torch(self, device: torch.device, dtype: torch.dtype = torch.float32): + """Convert the mean and std stats to torch tensors on the specified device.""" + self.input_mean = torch.from_numpy(self.input_mean).to(device=device, dtype=dtype) + self.input_std = torch.from_numpy(self.input_std).to(device=device, dtype=dtype) + self.output_mean = torch.from_numpy(self.output_mean).to(device=device, dtype=dtype) + self.output_std = torch.from_numpy(self.output_std).to(device=device, dtype=dtype) + + def stats_to_numpy(self): + """Convert the mean and std stats to numpy arrays.""" + self.input_mean = self.input_mean.cpu().numpy() if isinstance(self.input_mean, torch.Tensor) else self.input_mean + self.input_std = self.input_std.cpu().numpy() if isinstance(self.input_std, torch.Tensor) else self.input_std + self.output_mean = self.output_mean.cpu().numpy() if isinstance(self.output_mean, torch.Tensor) else self.output_mean + self.output_std = self.output_std.cpu().numpy() if isinstance(self.output_std, torch.Tensor) else self.output_std - def normalize_input(self, x: np.ndarray) -> np.ndarray: + def normalize_input(self, x: np.ndarray | torch.Tensor, mean: np.ndarray | torch.Tensor = None, std: np.ndarray | torch.Tensor = None) -> np.ndarray | torch.Tensor: """Convert input from physical units to normalized data.""" + if mean is None: + mean = self.input_mean + if std is None: + std = self.input_std for channel_idx, transform in self.input_transforms.items(): - x[channel_idx,::] = transform(x[channel_idx,::]) - return (x - self.input_mean[(...,) + (None,) * (x.ndim - 1)]) \ - / self.input_std[(...,) + (None,) * (x.ndim - 1)] + x[:,channel_idx,::] = transform(x[:,channel_idx,::]) + return (x - self.input_mean[(None,) + (...,) + (None,) * (x.ndim - 2)]) \ + / self.input_std[(None,) + (...,) + (None,) * (x.ndim - 2)] - def denormalize_input(self, x: np.ndarray) -> np.ndarray: + def denormalize_input(self, x: np.ndarray | torch.Tensor, mean: np.ndarray | torch.Tensor = None, std: np.ndarray | torch.Tensor = None) -> np.ndarray | torch.Tensor: """Convert input from normalized data to physical units.""" + if mean is None: + mean = self.input_mean + if std is None: + std = self.input_std x = x * self.input_std[(None,) + (...,) + (None,) * (x.ndim - 2)] \ + self.input_mean[(None,) + (...,) + (None,) * (x.ndim - 2)] for channel_idx, inverse_transform in self.input_inverse_transforms.items(): @@ -248,29 +277,43 @@ def denormalize_input(self, x: np.ndarray) -> np.ndarray: return x - def normalize_output(self, x: np.ndarray) -> np.ndarray: + def normalize_output(self, x: np.ndarray | torch.Tensor, mean: np.ndarray | torch.Tensor = None, std: np.ndarray | torch.Tensor = None) -> np.ndarray | torch.Tensor: """Convert output from physical units to normalized data.""" + if mean is None: + mean = self.output_mean + if std is None: + std = self.output_std for channel_idx, transform in self.output_transforms.items(): - x[channel_idx,::] = transform(x[channel_idx,::]) - return (x - self.output_mean[(...,) + (None,) * (x.ndim - 1)]) \ - / self.output_std[(...,) + (None,) * (x.ndim - 1)] + x[:,channel_idx,::] = transform(x[:,channel_idx,::]) + return (x - self.output_mean[(None,) + (...,) + (None,) * (x.ndim - 2)]) \ + / self.output_std[(None,) + (...,) + (None,) * (x.ndim - 2)] - def denormalize_output(self, x: np.ndarray) -> np.ndarray: + def denormalize_output(self, x: np.ndarray | torch.Tensor, mean: np.ndarray | torch.Tensor = None, std: np.ndarray | torch.Tensor = None) -> np.ndarray | torch.Tensor: """Convert output from normalized data to physical units.""" + if mean is None: + mean = self.output_mean + if std is None: + std = self.output_std x = x * self.output_std[(None,) + (...,) + (None,) * (x.ndim - 2)] \ + self.output_mean[(None,) + (...,) + (None,) * (x.ndim - 2)] for channel_idx, inverse_transform in self.output_inverse_transforms.items(): x[:,channel_idx,::] = inverse_transform(x[:,channel_idx,::]) return x - def box_cox_transform(self, channel_array: np.ndarray, lmbda: float) -> np.ndarray: + def box_cox_transform(self, channel_array: np.ndarray | torch.Tensor, lmbda: float) -> np.ndarray | torch.Tensor: """Apply Box-Cox transformation to the data.""" + if isinstance(channel_array, torch.Tensor): + channel_array = torch.clamp(channel_array, min=0) + return (torch.pow(channel_array, lmbda) - 1) / lmbda channel_array = np.clip(channel_array, 0, None) return (np.power(channel_array, lmbda) - 1) / lmbda - def box_cox_inverse_transform(self, channel_array: np.ndarray, lmbda: float) -> np.ndarray: + def box_cox_inverse_transform(self, channel_array: np.ndarray | torch.Tensor, lmbda: float) -> np.ndarray | torch.Tensor: """Apply inverse Box-Cox transformation to the data.""" + if isinstance(channel_array, torch.Tensor): + channel_array = torch.clamp(channel_array, min=-1/lmbda) + return torch.pow((lmbda * channel_array) + 1, 1 / lmbda) channel_array = np.clip(channel_array, -1/lmbda, None) return np.power((lmbda * channel_array) + 1, 1 / lmbda) diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index a422792c..729c3e3a 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -45,12 +45,14 @@ def main(cfg: DictConfig) -> None: input_dtype = torch.float16 if cfg.generation.perf.get("force_fp16", False) and use_apex_gn else torch.float32 # Parse the inference input times - if cfg.generation.times_range and cfg.generation.times: + if cfg.generation.get("times_range", None) and cfg.generation.get("times", None): raise ValueError("Either times_range or times must be provided, but not both") - if cfg.generation.times_range: + if cfg.generation.get("times_range", None): times = get_time_from_range(cfg.generation.times_range, time_format="%Y%m%d-%H%M") #TODO check what time formats we are using and adapt - else: + elif cfg.generation.get("times", None): times = cfg.generation.times + else: + raise ValueError("Either times_range or times must be provided") # Create dataset object dataset_cfg = OmegaConf.to_container(cfg.dataset) @@ -61,6 +63,7 @@ def main(cfg: DictConfig) -> None: dataset, sampler = get_dataset_and_sampler_inference( dataset_cfg=dataset_cfg, times=times, has_lead_time=has_lead_time ) + dataset.stats_to_torch(device=dist.device, dtype=input_dtype) img_shape = dataset.image_shape() img_out_channels = len(dataset.output_channels()) @@ -222,7 +225,7 @@ def elapsed_time(self, _): static_channels = dataset.get_static_data() if static_channels is not None: - static_channels = static_channels[None, ::] + static_channels = static_channels[None, ::].flip(-2) if use_apex_gn: static_channels = static_channels.to( dist.device, @@ -255,16 +258,10 @@ def elapsed_time(self, _): lead_time_label = lead_time_label[0].to(dist.device).contiguous() else: lead_time_label = None - image_lr = dataset.interpolator(image_lr.to(dist.device)).reshape(*image_lr.shape[:-1], *image_tar.shape[-2:]).flip(-2) - image_lr = ( - image_lr.to(device=device) - .to(input_dtype) - .to(memory_format=torch.channels_last) - ) - image_tar = image_tar.to(device=device).to(torch.float32) - # image_out, image_reg = generate_fn(image_lr,lead_time_label) + image_lr = dataset.interpolator(image_lr.to(dist.device, dtype=input_dtype)).reshape(*image_lr.shape[:-1], *image_tar.shape[-2:]).flip(-2) + image_lr = dataset.normalize_input(image_lr) + image_lr = image_lr.to(memory_format=torch.channels_last) random_seed = cfg.generation.get("random_seed", None)+index if cfg.generation.get("randomize", False) and cfg.generation.get("random_seed", None) is not None else None - # print(f"On rank {dist.rank} using base random seed: {random_seed} for time index {time_index}") date_embedding = None if dataset._n_month_hour_channels: date_embedding = dataset.make_time_grids(*date_str, dist.device, dtype=input_dtype) @@ -280,17 +277,22 @@ def elapsed_time(self, _): if dist.rank == 0: batch_size = image_out.shape[0] # write out data in a seperate thread so we don't hold up inferencing - + image_tar = image_tar[0].squeeze().flip(-2).cpu().numpy() + prediction_ensemble = dataset.denormalize_output(image_out).squeeze().flip(-2).cpu().numpy() + baseline = dataset.denormalize_input(image_lr)[0].squeeze().flip(-2).cpu().numpy() + if image_reg is not None: + mean_pred = dataset.denormalize_output(image_reg)[0].squeeze().flip(-2).cpu().numpy() + writer_threads.append( writer_executor.submit( save_results_as_torch, savedir, times[sampler[time_index]], dataset, - image_out.cpu().numpy(), - image_tar.cpu().numpy(), - image_lr.cpu().numpy(), - image_reg.cpu().numpy() if image_reg is not None else None, + prediction_ensemble, + image_tar, + baseline, + mean_pred if image_reg is not None else None, ) ) end.record() diff --git a/src/hirad/inference/generator.py b/src/hirad/inference/generator.py index 6f108e7c..0fddc2f8 100644 --- a/src/hirad/inference/generator.py +++ b/src/hirad/inference/generator.py @@ -106,7 +106,7 @@ def generate(self, image_lr, static_channels=None, date_embedding=None, lead_tim rank_batches=self.rank_batches, img_lr=image_lr.expand( self.batch_size, -1, -1, -1 - ).to(memory_format=torch.channels_last), #.to(memory_format=torch.channels_last), + ).to(memory_format=torch.channels_last), rank=self.dist.rank, device=image_lr.device, mean_hr=mean_hr, diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index c07130ef..f4f45ef8 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -488,9 +488,13 @@ def main(cfg: DictConfig) -> None: elif fp16: input_dtype = torch.float16 + # convert dataset stats to torch tensors on the correct device for later use in loss normalization and denormalization + dataset.stats_to_torch(device=dist.device, dtype=input_dtype) + normalization_stats = dataset.normalization_stats() + # prepare static channels if there are any if static_channels is not None: - static_channels = static_channels[None, ::] + static_channels = static_channels[None, ::].flip(-2) if use_apex_gn: static_channels = static_channels.to( dist.device, @@ -537,7 +541,9 @@ def main(cfg: DictConfig) -> None: dataset_iterator ) tick_read_time = time.time() - tick_read_start_time - img_lr = dataset.interpolator(img_lr.to(dist.device)).reshape(*img_lr.shape[:-1], *img_shape).flip(-2) + img_lr = dataset.interpolator(img_lr.to(dist.device, dtype=input_dtype)).reshape(*img_lr.shape[:-1], *img_shape).flip(-2) + img_lr = dataset.normalize_input(img_lr) + img_clean = dataset.normalize_output(img_clean.to(dist.device, dtype=input_dtype)) date_embedding = None if n_month_hour_channels > 0: date_embedding = dataset.make_time_grids(*date_str, dist.device, dtype=input_dtype) @@ -714,6 +720,8 @@ def main(cfg: DictConfig) -> None: *date_str, ) = next(validation_dataset_iterator) img_lr_valid = dataset.interpolator(img_lr_valid.to(dist.device)).reshape(*img_lr_valid.shape[:-1], *img_shape).flip(-2) + img_lr_valid = dataset.normalize_input(img_lr_valid) + img_clean_valid = dataset.normalize_output(img_clean_valid.to(dist.device, dtype=input_dtype)) date_embedding = None if n_month_hour_channels > 0: date_embedding = dataset.make_time_grids(*date_str, dist.device, dtype=input_dtype) diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index 245b3d3d..ddc4594b 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -245,15 +245,11 @@ def diffusion_step( def save_results_as_torch(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): os.makedirs(output_path, exist_ok=True) - target = np.flip(dataset.denormalize_output(image_hr)[0,::].squeeze(),1) - prediction_ensemble = np.flip(dataset.denormalize_output(image_pred).squeeze(),-2) - baseline = np.flip(dataset.denormalize_input(image_lr)[0,::].squeeze(),1) if mean_pred is not None: - mean_pred = np.flip(dataset.denormalize_output(mean_pred)[0,::].squeeze(),1) torch.save(mean_pred, os.path.join(output_path, f'{time_step}-regression-prediction')) - torch.save(target, os.path.join(output_path, f'{time_step}-target')) - torch.save(prediction_ensemble, os.path.join(output_path, f'{time_step}-predictions')) - torch.save(baseline, os.path.join(output_path, f'{time_step}-baseline')) + torch.save(image_hr, os.path.join(output_path, f'{time_step}-target')) + torch.save(image_pred, os.path.join(output_path, f'{time_step}-predictions')) + torch.save(image_lr, os.path.join(output_path, f'{time_step}-baseline')) @DeprecationWarning def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): From 12e606084f6055382a1774300695b4af4262fd41 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Thu, 12 Mar 2026 11:29:09 +0100 Subject: [PATCH 256/302] clean up --- src/hirad/datasets/constants.py | 2 +- src/hirad/utils/capture.py | 513 ----------------------------- src/hirad/utils/checkpoint.py | 6 +- src/hirad/utils/function_utils.py | 10 +- src/hirad/utils/generate_utils.py | 22 -- src/hirad/utils/inference_utils.py | 138 +------- src/hirad/utils/patching.py | 8 +- src/hirad/utils/train_helpers.py | 25 +- 8 files changed, 50 insertions(+), 674 deletions(-) delete mode 100644 src/hirad/utils/capture.py delete mode 100644 src/hirad/utils/generate_utils.py diff --git a/src/hirad/datasets/constants.py b/src/hirad/datasets/constants.py index aa4bd58e..2ebe4d68 100644 --- a/src/hirad/datasets/constants.py +++ b/src/hirad/datasets/constants.py @@ -2,7 +2,7 @@ 'T_2M': '2t', 'U_10M': '10u', 'V_10M': '10v', - 'TOT_PREC_1H': 'tp', + 'TOT_PREC_1H': 'tp' } ERA_TO_REAL_CHANNEL_MAP = {v: k for k, v in REAL_TO_ERA_CHANNEL_MAP.items()} diff --git a/src/hirad/utils/capture.py b/src/hirad/utils/capture.py deleted file mode 100644 index 9c38d5aa..00000000 --- a/src/hirad/utils/capture.py +++ /dev/null @@ -1,513 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. -# SPDX-FileCopyrightText: All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import functools -import logging -import os -import time -from contextlib import nullcontext -from logging import Logger -from typing import Any, Callable, Dict, NewType, Optional, Union - -import torch - -from hirad.distributed import DistributedManager - -float16 = NewType("float16", torch.float16) -bfloat16 = NewType("bfloat16", torch.bfloat16) -optim = NewType("optim", torch.optim) - - -class _StaticCapture(object): - """Base class for StaticCapture decorator. - - This class should not be used, rather StaticCaptureTraining and StaticCaptureEvaluate - should be used instead for training and evaluation functions. - """ - - # Grad scaler and checkpoint class variables use for checkpoint saving and loading - # Since an instance of Static capture does not exist for checkpoint functions - # one must use class functions to access state dicts - _amp_scalers = {} - _amp_scaler_checkpoints = {} - _logger = logging.getLogger("capture") - - def __new__(cls, *args, **kwargs): - obj = super(_StaticCapture, cls).__new__(cls) - obj.amp_scalers = cls._amp_scalers - obj.amp_scaler_checkpoints = cls._amp_scaler_checkpoints - obj.logger = cls._logger - return obj - - def __init__( - self, - model: "physicsnemo.Module", - optim: Optional[optim] = None, - logger: Optional[Logger] = None, - use_graphs: bool = True, - use_autocast: bool = True, - use_gradscaler: bool = True, - compile: bool = False, - cuda_graph_warmup: int = 11, - amp_type: Union[float16, bfloat16] = torch.float16, - gradient_clip_norm: Optional[float] = None, - label: Optional[str] = None, - ): - self.logger = logger if logger else self.logger - # Checkpoint label (used for gradscaler) - self.label = label if label else f"scaler_{len(self.amp_scalers.keys())}" - - # DDP fix - if not isinstance(model, physicsnemo.models.Module) and hasattr( - model, "module" - ): - model = model.module - - if not isinstance(model, physicsnemo.models.Module): - self.logger.error("Model not a PhysicsNeMo Module!") - raise ValueError("Model not a PhysicsNeMo Module!") - if compile: - model = torch.compile(model) - - self.model = model - - self.optim = optim - self.eval = False - self.no_grad = False - self.gradient_clip_norm = gradient_clip_norm - - # Set up toggles for optimizations - if not (amp_type == torch.float16 or amp_type == torch.bfloat16): - raise ValueError("AMP type must be torch.float16 or torch.bfloat16") - # CUDA device - if "cuda" in str(self.model.device): - # CUDA graphs - if use_graphs and not self.model.meta.cuda_graphs: - self.logger.warning( - f"Model {model.meta.name} does not support CUDA graphs, turning off" - ) - use_graphs = False - self.cuda_graphs_enabled = use_graphs - - # AMP GPU - if not self.model.meta.amp_gpu: - self.logger.warning( - f"Model {model.meta.name} does not support AMP on GPUs, turning off" - ) - use_autocast = False - use_gradscaler = False - self.use_gradscaler = use_gradscaler - self.use_autocast = use_autocast - - self.amp_device = "cuda" - # Check if bfloat16 is suppored on the GPU - if amp_type == torch.bfloat16 and not torch.cuda.is_bf16_supported(): - self.logger.warning( - "Current CUDA device does not support bfloat16, falling back to float16" - ) - amp_type = torch.float16 - self.amp_dtype = amp_type - # Gradient Scaler - scaler_enabled = self.use_gradscaler and amp_type == torch.float16 - self.scaler = self._init_amp_scaler(scaler_enabled, self.logger) - - self.replay_stream = torch.cuda.Stream(self.model.device) - # CPU device - else: - self.cuda_graphs_enabled = False - # AMP CPU - if use_autocast and not self.model.meta.amp_cpu: - self.logger.warning( - f"Model {model.meta.name} does not support AMP on CPUs, turning off" - ) - use_autocast = False - - self.use_autocast = use_autocast - self.amp_device = "cpu" - # Only float16 is supported on CPUs - # https://pytorch.org/docs/stable/amp.html#cpu-op-specific-behavior - if amp_type == torch.float16 and use_autocast: - self.logger.warning( - "torch.float16 not supported for CPU AMP, switching to torch.bfloat16" - ) - amp_type = torch.bfloat16 - self.amp_dtype = torch.bfloat16 - # Gradient Scaler (not enabled) - self.scaler = self._init_amp_scaler(False, self.logger) - self.replay_stream = None - - if self.cuda_graphs_enabled: - self.graph = torch.cuda.CUDAGraph() - - self.output = None - self.iteration = 0 - self.cuda_graph_warmup = cuda_graph_warmup # Default for DDP = 11 - - def __call__(self, fn: Callable) -> Callable: - self.function = fn - - @functools.wraps(fn) - def decorated(*args: Any, **kwds: Any) -> Any: - """Training step decorator function""" - - with torch.no_grad() if self.no_grad else nullcontext(): - if self.cuda_graphs_enabled: - self._cuda_graph_forward(*args, **kwds) - else: - self._zero_grads() - self.output = self._amp_forward(*args, **kwds) - - if not self.eval: - # Update model parameters - self.scaler.step(self.optim) - self.scaler.update() - - return self.output - - return decorated - - def _cuda_graph_forward(self, *args: Any, **kwargs: Any) -> Any: - """Forward training step with CUDA graphs - - Returns - ------- - Any - Output of neural network forward - """ - # Graph warm up - if self.iteration < self.cuda_graph_warmup: - self.replay_stream.wait_stream(torch.cuda.current_stream()) - self._zero_grads() - with torch.cuda.stream(self.replay_stream): - output = self._amp_forward(*args, **kwargs) - self.output = output.detach() - torch.cuda.current_stream().wait_stream(self.replay_stream) - # CUDA Graphs - else: - # Graph record - if self.iteration == self.cuda_graph_warmup: - self.logger.warning(f"Recording graph of '{self.function.__name__}'") - self._zero_grads() - torch.cuda.synchronize() - if DistributedManager().distributed: - torch.distributed.barrier() - # TODO: temporary workaround till this issue is fixed: - # https://github.com/pytorch/pytorch/pull/104487#issuecomment-1638665876 - delay = os.environ.get("PHYSICSNEMO_CUDA_GRAPH_CAPTURE_DELAY", "10") - time.sleep(int(delay)) - with torch.cuda.graph(self.graph): - output = self._amp_forward(*args, **kwargs) - self.output = output.detach() - # Graph replay - self.graph.replay() - - self.iteration += 1 - return self.output - - def _zero_grads(self): - """Zero gradients - - Default to `set_to_none` since this will in general have lower memory - footprint, and can modestly improve performance. - - Note - ---- - Zeroing gradients can potentially cause an invalid CUDA memory access in another - graph. However if your graph involves gradients, you much set your gradients to none. - If there is already a graph recorded that includes these gradients, this will error. - Use the `NoGrad` version of capture to avoid this issue for inferencers / validators. - """ - # Skip zeroing if no grad is being used - if self.no_grad: - return - - try: - self.optim.zero_grad(set_to_none=True) - except Exception: - if self.optim: - self.optim.zero_grad() - # For apex optim support and eval mode (need to reset model grads) - self.model.zero_grad(set_to_none=True) - - def _amp_forward(self, *args, **kwargs) -> Any: - """Compute loss and gradients (if training) with AMP - - Returns - ------- - Any - Output of neural network forward - """ - with torch.autocast( - self.amp_device, enabled=self.use_autocast, dtype=self.amp_dtype - ): - output = self.function(*args, **kwargs) - - if not self.eval: - # In training mode output should be the loss - self.scaler.scale(output).backward() - if self.gradient_clip_norm is not None: - self.scaler.unscale_(self.optim) - torch.nn.utils.clip_grad_norm_( - self.model.parameters(), self.gradient_clip_norm - ) - - return output - - def _init_amp_scaler( - self, scaler_enabled: bool, logger: Logger - ) -> torch.cuda.amp.GradScaler: - # Create gradient scaler - scaler = torch.cuda.amp.GradScaler(enabled=scaler_enabled) - # Store scaler in class variable - self.amp_scalers[self.label] = scaler - logging.debug(f"Created gradient scaler {self.label}") - - # If our checkpoint dictionary has weights for this scaler lets load - if self.label in self.amp_scaler_checkpoints: - try: - scaler.load_state_dict(self.amp_scaler_checkpoints[self.label]) - del self.amp_scaler_checkpoints[self.label] - self.logger.info(f"Loaded grad scaler state dictionary {self.label}.") - except Exception as e: - self.logger.error( - f"Failed to load grad scaler {self.label} state dict from saved " - + "checkpoints. Did you switch the ordering of declared static captures?" - ) - raise ValueError(e) - return scaler - - @classmethod - def state_dict(cls) -> Dict[str, Any]: - """Class method for accsessing the StaticCapture state dictionary. - Use this in a training checkpoint function. - - Returns - ------- - Dict[str, Any] - Dictionary of states to save for file - """ - scaler_states = {} - for key, value in cls._amp_scalers.items(): - scaler_states[key] = value.state_dict() - - return scaler_states - - @classmethod - def load_state_dict(cls, state_dict: Dict[str, Any]) -> None: - """Class method for loading a StaticCapture state dictionary. - Use this in a training checkpoint function. - - Returns - ------- - Dict[str, Any] - Dictionary of states to save for file - """ - for key, value in state_dict.items(): - # If scaler has been created already load the weights - if key in cls._amp_scalers: - try: - cls._amp_scalers[key].load_state_dict(value) - cls._logger.info(f"Loaded grad scaler state dictionary {key}.") - except Exception as e: - cls._logger.error( - f"Failed to load grad scaler state dict with id {key}." - + " Something went wrong!" - ) - raise ValueError(e) - # Otherwise store in checkpoints for later use - else: - cls._amp_scaler_checkpoints[key] = value - - @classmethod - def reset_state(cls): - cls._amp_scalers = {} - cls._amp_scaler_checkpoints = {} - - -class StaticCaptureTraining(_StaticCapture): - """A performance optimization decorator for PyTorch training functions. - - This class should be initialized as a decorator on a function that computes the - forward pass of the neural network and loss function. The user should only call the - defind training step function. This will apply optimizations including: AMP and - Cuda Graphs. - - Parameters - ---------- - model : physicsnemo.models.Module - PhysicsNeMo Model - optim : torch.optim - Optimizer - logger : Optional[Logger], optional - PhysicsNeMo Launch Logger, by default None - use_graphs : bool, optional - Toggle CUDA graphs if supported by model, by default True - use_amp : bool, optional - Toggle AMP if supported by mode, by default True - cuda_graph_warmup : int, optional - Number of warmup steps for cuda graphs, by default 11 - amp_type : Union[float16, bfloat16], optional - Auto casting type for AMP, by default torch.float16 - gradient_clip_norm : Optional[float], optional - Threshold for gradient clipping - label : Optional[str], optional - Static capture checkpoint label, by default None - - Raises - ------ - ValueError - If the model provided is not a physicsnemo.models.Module. I.e. has no meta data. - - Example - ------- - >>> # Create model - >>> model = physicsnemo.models.mlp.FullyConnected(2, 64, 2) - >>> input = torch.rand(8, 2) - >>> output = torch.rand(8, 2) - >>> # Create optimizer - >>> optim = torch.optim.Adam(model.parameters(), lr=0.001) - >>> # Create training step function with optimization wrapper - >>> @StaticCaptureTraining(model=model, optim=optim) - ... def training_step(model, invar, outvar): - ... predvar = model(invar) - ... loss = torch.sum(torch.pow(predvar - outvar, 2)) - ... return loss - ... - >>> # Sample training loop - >>> for i in range(3): - ... loss = training_step(model, input, output) - ... - - Note - ---- - Static captures must be checkpointed when training using the `state_dict()` if AMP - is being used with gradient scaler. By default, this requires static captures to be - instantiated in the same order as when they were checkpointed. The label parameter - can be used to relax/circumvent this ordering requirement. - - Note - ---- - Capturing multiple cuda graphs in a single program can lead to potential invalid CUDA - memory access errors on some systems. Prioritize capturing training graphs when this - occurs. - """ - - def __init__( - self, - model: "physicsnemo.Module", - optim: torch.optim, - logger: Optional[Logger] = None, - use_graphs: bool = True, - use_amp: bool = True, - compile: bool = False, - cuda_graph_warmup: int = 11, - amp_type: Union[float16, bfloat16] = torch.float16, - gradient_clip_norm: Optional[float] = None, - label: Optional[str] = None, - ): - super().__init__( - model, - optim, - logger, - use_graphs, - use_amp, - use_amp, - compile, - cuda_graph_warmup, - amp_type, - gradient_clip_norm, - label, - ) - - -class StaticCaptureEvaluateNoGrad(_StaticCapture): - - """An performance optimization decorator for PyTorch no grad evaluation. - - This class should be initialized as a decorator on a function that computes run the - forward pass of the model that does not require gradient calculations. This is the - recommended method to use for inference and validation methods. - - Parameters - ---------- - model : physicsnemo.models.Module - PhysicsNeMo Model - logger : Optional[Logger], optional - PhysicsNeMo Launch Logger, by default None - use_graphs : bool, optional - Toggle CUDA graphs if supported by model, by default True - use_amp : bool, optional - Toggle AMP if supported by mode, by default True - cuda_graph_warmup : int, optional - Number of warmup steps for cuda graphs, by default 11 - amp_type : Union[float16, bfloat16], optional - Auto casting type for AMP, by default torch.float16 - label : Optional[str], optional - Static capture checkpoint label, by default None - - Raises - ------ - ValueError - If the model provided is not a physicsnemo.models.Module. I.e. has no meta data. - - Example - ------- - >>> # Create model - >>> model = physicsnemo.models.mlp.FullyConnected(2, 64, 2) - >>> input = torch.rand(8, 2) - >>> # Create evaluate function with optimization wrapper - >>> @StaticCaptureEvaluateNoGrad(model=model) - ... def eval_step(model, invar): - ... predvar = model(invar) - ... return predvar - ... - >>> output = eval_step(model, input) - >>> output.size() - torch.Size([8, 2]) - - Note - ---- - Capturing multiple cuda graphs in a single program can lead to potential invalid CUDA - memory access errors on some systems. Prioritize capturing training graphs when this - occurs. - """ - - def __init__( - self, - model: "physicsnemo.Module", - logger: Optional[Logger] = None, - use_graphs: bool = True, - use_amp: bool = True, - compile: bool = False, - cuda_graph_warmup: int = 11, - amp_type: Union[float16, bfloat16] = torch.float16, - label: Optional[str] = None, - ): - super().__init__( - model, - None, - logger, - use_graphs, - use_amp, - compile, - False, - cuda_graph_warmup, - amp_type, - None, - label, - ) - self.eval = True # No optimizer/scaler calls - self.no_grad = True # No grad context and no grad zeroing diff --git a/src/hirad/utils/checkpoint.py b/src/hirad/utils/checkpoint.py index 28e65472..5bd48313 100644 --- a/src/hirad/utils/checkpoint.py +++ b/src/hirad/utils/checkpoint.py @@ -175,11 +175,15 @@ def save_checkpoint( # == Saving model checkpoint == if model is not None: + # Strip out optimization wrapper if exists before stripping DDP + if isinstance(model, torch._dynamo.eval_frame.OptimizedModule): + model = model._orig_mod + if hasattr(model, "module"): # Strip out DDP layer model = model.module - # Strip out optimization wrapper if exists + # Strip out optimization wrapper if exists after stripping DDP if isinstance(model, torch._dynamo.eval_frame.OptimizedModule): model = model._orig_mod diff --git a/src/hirad/utils/function_utils.py b/src/hirad/utils/function_utils.py index 65347829..13fce1e4 100644 --- a/src/hirad/utils/function_utils.py +++ b/src/hirad/utils/function_utils.py @@ -163,7 +163,7 @@ def __init__( self.seed = seed self.window_size = window_size self.start_idx = start_idx - + def __iter__(self) -> Iterator[int]: order = np.arange(len(self.dataset)) rnd = None @@ -173,12 +173,14 @@ def __iter__(self) -> Iterator[int]: rnd.shuffle(order) window = int(np.rint(order.size * self.window_size)) - idx = self.start_idx + idx = self.start_idx % order.size while True: i = idx % order.size if idx % self.num_replicas == self.rank: yield order[i] - if window >= 2: - j = (i - rnd.randint(window)) % order.size + if window >= 2 and i>0: + window_size = min(i+1, window) + j = (i - rnd.randint(window_size)) % order.size order[i], order[j] = order[j], order[i] idx += 1 + idx = idx % order.size diff --git a/src/hirad/utils/generate_utils.py b/src/hirad/utils/generate_utils.py deleted file mode 100644 index 43f83b63..00000000 --- a/src/hirad/utils/generate_utils.py +++ /dev/null @@ -1,22 +0,0 @@ -import datetime -from hirad.datasets import init_dataset_from_config -from .function_utils import convert_datetime_to_cftime - - -def get_dataset_and_sampler(dataset_cfg, times, has_lead_time=False): - """ - Get a dataset and sampler for generation. - """ - (dataset, _) = init_dataset_from_config(dataset_cfg, batch_size=1) - # if has_lead_time: - # plot_times = times - # else: - # plot_times = [ - # datetime.datetime.strptime(time, "%Y-%m-%dT%H:%M:%S") - # for time in times - # ] - all_times = dataset.time() - time_indices = [all_times.index(t) for t in times] - sampler = time_indices - - return dataset, sampler \ No newline at end of file diff --git a/src/hirad/utils/inference_utils.py b/src/hirad/utils/inference_utils.py index ddc4594b..c9bf1dd2 100644 --- a/src/hirad/utils/inference_utils.py +++ b/src/hirad/utils/inference_utils.py @@ -22,11 +22,8 @@ import numpy as np import torch import tqdm -from matplotlib import pyplot as plt -import cartopy.crs as ccrs from .function_utils import StackedRandomGenerator -from hirad.eval import compute_mae, average_power_spectrum, plot_error_projection, plot_power_spectra, crps ############################################################################ # CorrDiff Generation Utilities # @@ -196,6 +193,9 @@ def diffusion_step( if mean_hr.shape[0] != 1: raise ValueError(f"mean_hr must have batch size 1, got {mean_hr.shape[0]}") + if len(rank_batches) == 0: + raise ValueError("rank_batches is empty, at least one batch of seeds is required") + # img_lr = img_lr.to(memory_format=torch.channels_last) # Handling of the high-res mean @@ -217,6 +217,10 @@ def diffusion_step( batch_size = len(batch_seeds) if batch_size == 0: continue + if batch_size != img_lr.shape[0]: + raise ValueError( + f"Batch size {batch_size} does not match img_lr batch size {img_lr.shape[0]}" + ) # Initialize random generator, and generate latents rnd = StackedRandomGenerator(device, batch_seeds) @@ -239,11 +243,11 @@ def diffusion_step( ############################################################################ -# Visualization Utilities # +# Saving and Visualization Utilities # ############################################################################ -def save_results_as_torch(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): +def save_results_as_torch(output_path, time_step, image_pred, image_hr, image_lr, mean_pred): os.makedirs(output_path, exist_ok=True) if mean_pred is not None: torch.save(mean_pred, os.path.join(output_path, f'{time_step}-regression-prediction')) @@ -251,132 +255,12 @@ def save_results_as_torch(output_path, time_step, dataset, image_pred, image_hr, torch.save(image_pred, os.path.join(output_path, f'{time_step}-predictions')) torch.save(image_lr, os.path.join(output_path, f'{time_step}-baseline')) -@DeprecationWarning -def save_images(output_path, time_step, dataset, image_pred, image_hr, image_lr, mean_pred): - - os.makedirs(output_path, exist_ok=True) - - longitudes = dataset.longitude() - latitudes = dataset.latitude() - input_channels = dataset.input_channels() - output_channels = dataset.output_channels() - - target = np.flip(dataset.denormalize_output(image_hr[0,::].squeeze()),1) #.reshape(len(output_channels),-1) - prediction = np.flip(dataset.denormalize_output(image_pred),-2) #.reshape(len(output_channels),-1) - baseline = np.flip(dataset.denormalize_input(image_lr[0,::].squeeze()),1)# .reshape(len(input_channels),-1) - if mean_pred is not None: - mean_pred = np.flip(dataset.denormalize_output(mean_pred[0,::].squeeze()),1) #.reshape(len(output_channels),-1) - - # Plot power spectra - freqs = {} - power = {} - for idx, channel in enumerate(output_channels): - channel_dir = channel.name + "_" + channel.level if channel.level else channel.name - output_path_channel = os.path.join(output_path, channel_dir) - if not os.path.exists(output_path_channel): - os.makedirs(output_path_channel) - input_channel_idx = input_channels.index(channel) - - if channel.name=="tp": - target[idx,::] = transform_channel(target[idx,:,:]) - prediction[:,idx,::] = transform_channel(prediction[:,idx,:,:]) - baseline[input_channel_idx,:,:] = transform_channel(baseline[input_channel_idx,::]) - if mean_pred is not None: - mean_pred[idx,::] = transform_channel(mean_pred[idx,::]) - - if mean_pred is not None: - vmin, vmax = calculate_bounds(target[idx,:,:], - prediction[:,idx,:,:], - baseline[input_channel_idx,:,:], - mean_pred[idx,:,:]) - else: - vmin, vmax = calculate_bounds(target[idx,:,:], - prediction[:,idx,:,:], - baseline[input_channel_idx,:,:]) - _plot_projection(longitudes, latitudes, target[idx,:,:], - os.path.join(output_path_channel, f'{time_step}-{channel.name}-target.jpg'), - vmin=vmin, vmax=vmax) - if prediction.shape[0] > 1: - for member_idx in range(prediction.shape[0]): - _plot_projection(longitudes, latitudes, prediction[member_idx,idx,:,:], - os.path.join(output_path_channel, f'{time_step}-{channel.name}-prediction_{member_idx}.jpg'), - vmin=vmin, vmax=vmax) - else: - _plot_projection(longitudes, latitudes, - prediction[0,idx,:,:], os.path.join(output_path_channel, f'{time_step}-{channel.name}-prediction.jpg'), - vmin=vmin, vmax=vmax) - _plot_projection(longitudes, latitudes, baseline[input_channel_idx,:,:], - os.path.join(output_path_channel, f'{time_step}-{channel.name}-input.jpg'), - vmin=vmin, vmax=vmax) - if mean_pred is not None: - _plot_projection(longitudes, latitudes, mean_pred[idx,:,:], - os.path.join(output_path_channel, f'{time_step}-{channel.name}-mean_prediction.jpg'), - vmin=vmin, vmax=vmax) - - _, baseline_errors = compute_mae(baseline[input_channel_idx,:,:], target[idx,:,:]) - plot_error_projection(baseline_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path_channel, f'{time_step}-{channel.name}-baseline-error.jpg')) - if prediction.shape[0] > 1: - for member_idx in range(prediction.shape[0]): - _, prediction_errors = compute_mae(prediction[member_idx,idx,:,:], target[idx,:,:]) - plot_error_projection(prediction_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path_channel, f'{time_step}-{channel.name}-prediction_{member_idx}-error.jpg')) - else: - _, prediction_errors = compute_mae(prediction[0,idx,:,:], target[idx,:,:]) - plot_error_projection(prediction_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path_channel, f'{time_step}-{channel.name}-prediction-error.jpg')) - if mean_pred is not None: - _, mean_prediction_errors = compute_mae(mean_pred[idx,:,:], target[idx,:,:]) - plot_error_projection(mean_prediction_errors.reshape(-1), latitudes, longitudes, os.path.join(output_path_channel, f'{time_step}-{channel.name}-mean-prediction-error.jpg')) - - b_freq, b_power = average_power_spectrum(baseline[input_channel_idx,:,:].squeeze(), 2.0) - freqs['baseline'] = b_freq - power['baseline'] = b_power - #plotting.plot_power_spectrum(b_freq, b_power, target_channels[t_c], os.path.join('plots/spectra/baseline2dt', target_channels[t_c] + '-all_dates')) - t_freq, t_power = average_power_spectrum(target[idx,:,:].squeeze(), 2.0) - freqs['target'] = t_freq - power['target'] = t_power - p_freq, p_power = average_power_spectrum(prediction[-1,idx,:,:].squeeze(), 2.0) - freqs['prediction'] = p_freq - power['prediction'] = p_power - if mean_pred is not None: - mp_freq, mp_power = average_power_spectrum(mean_pred[idx,:,:].squeeze(), 2.0) - freqs['mean_prediction'] = mp_freq - power['mean_prediction'] = mp_power - plot_power_spectra(freqs, power, channel.name, os.path.join(output_path_channel, f'{time_step}-{channel.name}-spectra.jpg')) - -def transform_channel(channel_array, channel_name="tp"): - # precip_array = np.clip(precip_array, 0, None) - # precip_array = np.where(precip_array == 0, 1e-6, precip_array) - # epsilon = 1e-2 - # precip_array = precip_array + epsilon - # precip_array = np.log10(precip_array) - # log_min, log_max = precip_array.min(), precip_array.max() - # precip_array = (precip_array-log_min)/(log_max-log_min) - if channel_name == "tp": - channel_array = np.clip(channel_array, 0, None) - channel_array = (np.power(channel_array,0.25)-1)/0.25 - elif channel_name == "2t": - channel_array = channel_array - 273.15 - # precip_array = np.sqrt(precip_array) - return channel_array - -@DeprecationWarning -def _plot_projection(longitudes: np.array, latitudes: np.array, values: np.array, filename: str, cmap=None, vmin = None, vmax = None): - - """Plot observed or interpolated data in a scatter plot.""" - # TODO: Refactor this somehow, it's not really generalizing well across variables. - fig = plt.figure() - fig, ax = plt.subplots(subplot_kw={"projection": ccrs.PlateCarree()}) - p = ax.scatter(x=longitudes, y=latitudes, c=values, cmap=cmap, vmin=vmin, vmax=vmax) - ax.coastlines() - ax.gridlines(draw_labels=True) - plt.colorbar(p, orientation="horizontal") - plt.savefig(filename) - plt.close('all') def calculate_bounds(*arrays: np.ndarray) -> tuple[float]: """Calculate consistent bounds across all arrays""" valid_arrays = [arr for arr in arrays if arr is not None] if not valid_arrays: - return 0, 1 + return None, None # hanndle if there are masked arrays with invalid values (e.g. NaNs) all_values = [] @@ -391,7 +275,7 @@ def calculate_bounds(*arrays: np.ndarray) -> tuple[float]: all_values.append(arr) if not all_values: - return 0, 1 + return None, None vmin = min(all_values) vmax = max(all_values) diff --git a/src/hirad/utils/patching.py b/src/hirad/utils/patching.py index bd537af8..cc9a072c 100644 --- a/src/hirad/utils/patching.py +++ b/src/hirad/utils/patching.py @@ -755,5 +755,11 @@ def image_fuse( ..., pad[2] : pad[2] + img_shape_y, pad[0] : pad[0] + img_shape_x ] + x_no_padding = x_no_padding / overlap_count_no_padding + + #TODO: do we want to introduce this and will it break existing checkpoints + # if input.dtype in [torch.int32, torch.int64]: + # x_no_padding = x_no_padding.round().view(input.dtype) + # Normalize by overlap count - return x_no_padding / overlap_count_no_padding + return x_no_padding diff --git a/src/hirad/utils/train_helpers.py b/src/hirad/utils/train_helpers.py index fd6e8cc4..d8abc570 100644 --- a/src/hirad/utils/train_helpers.py +++ b/src/hirad/utils/train_helpers.py @@ -104,6 +104,21 @@ def handle_and_clip_gradients(model, grad_clip_threshold=None): torch.nn.utils.clip_grad_norm_(model.parameters(), grad_clip_threshold) +def check_model_health(model, step, logger): + for name, param in model.named_parameters(): + # Check weights + if not torch.isfinite(param).all(): + logger.warning(f"!!! Weights in {name} went NaN at step {step}") + return False + + # Check gradients + if param.grad is not None: + if not torch.isfinite(param.grad).all(): + logger.warning(f"!!! Gradients in {name} went NaN at step {step}") + return False + return True + + def is_time_for_periodic_task( cur_nimg, freq, done, batch_size, rank, rank_0_only=False ): @@ -116,15 +131,15 @@ def is_time_for_periodic_task( return cur_nimg % freq < batch_size -def init_mlflow(cfg: DictConfig, dist: DistributedManager) -> None: +def init_mlflow(cfg: DictConfig, dist: DistributedManager, write_dir: str=".") -> None: if dist.rank==0: print("Started activating initial mlflow run") if cfg.logging.uri is not None: mlflow.set_tracking_uri(cfg.logging.uri) mlflow.set_experiment(experiment_name=cfg.logging.experiment_name) run_id = None - if os.path.isfile('run_id.txt'): - with open('run_id.txt','r') as f: + if os.path.isfile(os.path.join(write_dir, 'run_id.txt')): + with open(os.path.join(write_dir, 'run_id.txt'),'r') as f: run_id = f.read() if dist.world_size<=4: mlflow.system_metrics.set_system_metrics_node_id("node-0") @@ -134,7 +149,7 @@ def init_mlflow(cfg: DictConfig, dist: DistributedManager) -> None: mlflow.start_run(run_name=cfg.logging.run_name, log_system_metrics=False if dist.world_size>4 else True) if run_id is None: run = mlflow.active_run() - with open("run_id.txt", 'w') as f: + with open(os.path.join(write_dir, "run_id.txt"), 'w') as f: f.write(run.info.run_id) # log environment info if run is not continuing from previous checkpoint mlflow.log_params(flatten_dict(OmegaConf.to_object(cfg))) @@ -155,6 +170,6 @@ def init_mlflow(cfg: DictConfig, dist: DistributedManager) -> None: if dist.rank!=1 else "node-0") mlflow.set_experiment(experiment_name=cfg.logging.experiment_name) - with open("run_id.txt", 'r') as f: + with open(os.path.join(write_dir, "run_id.txt"), 'r') as f: run_id = f.read() mlflow.start_run(run_id=run_id, log_system_metrics=True) From 6aaef652d677deeb03959632faae7df33d143e6e Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Thu, 12 Mar 2026 11:34:25 +0100 Subject: [PATCH 257/302] Add anemoi datasest without copernicus tp data - keep anemoi with copernicus tp in anemoi_dataset_copernicus_tp.py - adapt anemoi_dataset for usage with real anemoi target which needs regridding to rotlatlon - adapt training and generation to include real regriding - remove visualization during traininig in train.py / deprecated --- src/hirad/datasets/__init__.py | 1 + src/hirad/datasets/anemoi_dataset.py | 82 ++-- .../datasets/anemoi_dataset_copernicus_tp.py | 410 ++++++++++++++++++ src/hirad/datasets/dataset.py | 3 + src/hirad/inference/generate.py | 17 +- src/hirad/training/train.py | 167 ++----- src/hirad/utils/dataset_utils.py | 42 ++ 7 files changed, 549 insertions(+), 173 deletions(-) create mode 100644 src/hirad/datasets/anemoi_dataset_copernicus_tp.py diff --git a/src/hirad/datasets/__init__.py b/src/hirad/datasets/__init__.py index 4d43aaff..6f96a844 100644 --- a/src/hirad/datasets/__init__.py +++ b/src/hirad/datasets/__init__.py @@ -3,4 +3,5 @@ from .era5_real import ERA5_REAL from .base import DownscalingDataset, ChannelMetadata, get_channels_from_strings, get_strings_from_channels from .anemoi_dataset import AnemoiDataset, ANEMOI_ERA5_COSMO, ANEMOI_ERA5_REAL +from .anemoi_dataset_copernicus_tp import AnemoiDatasetCopernicus, ANEMOI_ERA5COPERNICUSTP_REAL, ANEMOI_ERA5COPERNICUSTP_COSMO from .constants import REAL_TO_ERA_CHANNEL_MAP, ERA_TO_REAL_CHANNEL_MAP \ No newline at end of file diff --git a/src/hirad/datasets/anemoi_dataset.py b/src/hirad/datasets/anemoi_dataset.py index 1bdd98b3..77ded7c4 100644 --- a/src/hirad/datasets/anemoi_dataset.py +++ b/src/hirad/datasets/anemoi_dataset.py @@ -11,11 +11,12 @@ import yaml import torch.nn.functional as F import time +from pathlib import Path # import zarr from .constants import REAL_TO_ERA_CHANNEL_MAP, ERA_TO_REAL_CHANNEL_MAP from hirad.utils.console import PythonLogger -from hirad.utils.dataset_utils import GridData +from hirad.utils.dataset_utils import GridData, regrid_icon_to_rotlatlon logger = PythonLogger(__name__) @@ -28,7 +29,6 @@ def __init__(self, type: str, input_anemoi_dataset_path: str, target_anemoi_dataset_path: str, - corrected_tp_path: str, start_date: datetime.datetime = None, end_date: datetime.datetime = None, input_channel_names: List[str] = [], @@ -44,9 +44,10 @@ def __init__(self, ): super().__init__() - input_dataset = type.split('_')[1] - target_dataset = type.split('_')[2] + input_dataset = type.split('_')[-2] + target_dataset = type.split('_')[-1] self.real_target = target_dataset == 'real' + self.trim_edge = trim_edge if input_dataset != 'era5': raise ValueError(f"Input dataset {input_dataset} not supported for AnemoiDataset. Only 'era5' is supported.") @@ -56,24 +57,31 @@ def __init__(self, if self.real_target: # Map output channel names from real to era5 output_channel_names_real = [ERA_TO_REAL_CHANNEL_MAP[name] for name in output_channel_names] + self.lat_lon_real = torch.load("/capstor/store/cscs/pasc/c38/real_grid_info/realch1-lat-lon", weights_only=False) + self.regrid_indices_real = torch.from_numpy(np.load("/capstor/store/cscs/pasc/c38/real_grid_info/remap_indices.npy")).long() + self.regrid_weights_real = torch.from_numpy(np.load("/capstor/store/cscs/pasc/c38/real_grid_info/remap_weights.npy")) - self._corrected_tp_path = corrected_tp_path #TODO switch hanbdling paths to Path rather than pure strings self._n_month_hour_channels = n_month_hour_channels + target_open_dataset_kwargs = {} if start_date is not None and end_date is not None: assert start_date < end_date, "start_date must be before end_date" - self._output_dataset = open_dataset(target_anemoi_dataset_path, select=output_channel_names_real if self.real_target else output_channel_names, start=start_date, end=end_date, trim_edge=trim_edge) - else: - self._output_dataset = open_dataset(target_anemoi_dataset_path, select=output_channel_names_real if self.real_target else output_channel_names, trim_edge=trim_edge) + target_open_dataset_kwargs['start'] = start_date + target_open_dataset_kwargs['end'] = end_date + if trim_edge > 0 and not self.real_target: + target_open_dataset_kwargs['trim_edge'] = trim_edge + self._output_dataset = open_dataset(target_anemoi_dataset_path, select=output_channel_names_real if self.real_target else output_channel_names, **target_open_dataset_kwargs) assert self._output_dataset.shape[1] == len(output_channel_names) # Load ERA dataset, trimming the area and limiting the dates to the target dataset start_date = self._output_dataset.metadata()['start_date'] if start_date is None else start_date end_date = self._output_dataset.metadata()['end_date'] if end_date is None else end_date - min_lat = min(self._output_dataset.latitudes) - INPUT_MARGIN_DEGREES - max_lat = max(self._output_dataset.latitudes) + INPUT_MARGIN_DEGREES - min_lon = max(0, min(self._output_dataset.longitudes) - INPUT_MARGIN_DEGREES) - max_lon = max(self._output_dataset.longitudes) + INPUT_MARGIN_DEGREES + latitudes = self.latitude() + longitudes = self.longitude() + min_lat = min(latitudes) - INPUT_MARGIN_DEGREES + max_lat = max(latitudes) + INPUT_MARGIN_DEGREES + min_lon = max(0, min(longitudes) - INPUT_MARGIN_DEGREES) + max_lon = max(longitudes) + INPUT_MARGIN_DEGREES area=(max_lat, min_lon, min_lat, max_lon) self._input_dataset = open_dataset(input_anemoi_dataset_path, select=input_channel_names, start=start_date, end=end_date, area=area) @@ -87,17 +95,23 @@ def __init__(self, self._static_channels = [ChannelMetadata(name) if len(name.split('_'))==1 else ChannelMetadata(name.split('_')[0],name.split('_')[1]) for name in static_channel_names] - static_dataset = open_dataset(target_anemoi_dataset_path, select=static_channel_names, start=start_date, end=start_date, trim_edge=trim_edge) + static_open_dataset_kwargs = {} + if not self.real_target and trim_edge > 0: + static_open_dataset_kwargs['trim_edge'] = trim_edge + static_dataset = open_dataset(target_anemoi_dataset_path, select=static_channel_names, start=start_date, end=start_date, **static_open_dataset_kwargs) assert static_dataset.shape[1] == len(static_channel_names) # take first time point, and squeeze() to remove ensemble dimension static_data = static_dataset[0,:,:,:].squeeze() # Could also get these from stats, but one-time calculation is OK. self.static_mean = static_data.mean(axis=-1, keepdims=True) self.static_std = static_data.std(axis=-1, keepdims=True) - target_shape = static_dataset.field_shape + target_shape = self.image_shape() self.static_data_normalized = (static_data - self.static_mean.reshape((self.static_mean.shape[0],1))) \ / self.static_std.reshape((self.static_std.shape[0],1)) - self.static_data_normalized = torch.from_numpy(self.static_data_normalized.reshape(-1, *target_shape)) + self.static_data_normalized = torch.from_numpy(self.static_data_normalized) + self.static_data_normalized = regrid_icon_to_rotlatlon(self.static_data_normalized, self.regrid_indices_real, self.regrid_weights_real) + if trim_edge > 0 and self.real_target: + self.static_data_normalized = self.static_data_normalized[:, trim_edge:-trim_edge, trim_edge:-trim_edge] # self.normalize_input(np.flip(static_data.squeeze().reshape(-1, *target_shape), 1)) else: self.static_data_normalized = None @@ -113,9 +127,9 @@ def __init__(self, for name in input_channel_names] # Load stats for normalizing channels of input and output - cosmo_stats = self._output_dataset.statistics - self.output_mean = cosmo_stats['mean'][:] - self.output_std = cosmo_stats['stdev'][:] + target_stats = self._output_dataset.statistics + self.output_mean = target_stats['mean'][:] + self.output_std = target_stats['stdev'][:] input_stats = self._input_dataset.statistics self.input_mean = input_stats['mean'][:] @@ -155,8 +169,8 @@ def __init__(self, self.interpolator = GridData( self._input_dataset.longitudes, self._input_dataset.latitudes, - self._output_dataset.longitudes, - self._output_dataset.latitudes) + self.longitude(), + self.latitude()) # DO NOT SUBMIT: This is not implemented yet. @@ -166,26 +180,22 @@ def __getitem__(self, idx): # Pull input, replacing the corrected tp if applicable date_str = to_datetime(self._input_dataset.dates[idx]).strftime('%Y%m%d-%H%M') + # Don't reshape, but do squeeze ensemble dimension. input_data = self._input_dataset[idx].squeeze() - # TODO: Consider generalizing this to other channels, in case we have cp. - if ChannelMetadata('tp') in self._input_channels: - tp_idx = self._input_channels.index(ChannelMetadata('tp')) - corrected_tp_data = np.load(os.path.join(self._corrected_tp_path, f'{date_str}.npy')) - input_data[tp_idx,::] = corrected_tp_data - # input_data = self.normalize_input(input_data) # Pull target data # squeeze the ensemble dimesnsion + target_data = self._output_dataset[idx].squeeze() + + # next two steps only if target is cosmo, real has to be regridded first (done in training loop on gpu-s for efficiency) # reshape to image_shape # flip so that it starts in top-left corner (by default it is bottom left) - target_shape = self.image_shape() - target_data = self._output_dataset[idx] - target_data = np.flip(target_data \ - .squeeze() \ - .reshape(-1,*target_shape), - 1) - # target_data = self.normalize_output(target_data) + if not self.real_target: + target_shape = self.image_shape() + target_data = np.flip(target_data \ + .reshape(-1,*target_shape), + 1) return torch.from_numpy(target_data.copy()),\ torch.from_numpy(input_data),\ @@ -200,10 +210,14 @@ def __len__(self): # Question: Do we need an input longitude as well? def longitude(self) -> np.ndarray: """Get longitude values from the target dataset.""" + if self.real_target: + return self.lat_lon_real[:,1] return self._output_dataset.longitudes def latitude(self) -> np.ndarray: """Get latitude values from the target dataset.""" + if self.real_target: + return self.lat_lon_real[:,0] return self._output_dataset.latitudes def input_channels(self) -> List[ChannelMetadata]: @@ -225,6 +239,8 @@ def time(self) -> List: def image_shape(self) -> Tuple[int, int]: """Get the (height, width) of the data.""" + if self.real_target: + return 704,1088 return self._output_dataset.field_shape def input_shape(self) -> Tuple[int, int]: diff --git a/src/hirad/datasets/anemoi_dataset_copernicus_tp.py b/src/hirad/datasets/anemoi_dataset_copernicus_tp.py new file mode 100644 index 00000000..1576291f --- /dev/null +++ b/src/hirad/datasets/anemoi_dataset_copernicus_tp.py @@ -0,0 +1,410 @@ +from .base import DownscalingDataset, ChannelMetadata + +from anemoi.datasets import open_dataset +from anemoi.datasets.data.dataset import Dataset +import datetime +import os +import numpy as np +from pandas import to_datetime +import torch +from typing import List, Tuple +import yaml +import torch.nn.functional as F +import time +from pathlib import Path +# import zarr + +from .constants import REAL_TO_ERA_CHANNEL_MAP, ERA_TO_REAL_CHANNEL_MAP +from hirad.utils.console import PythonLogger +from hirad.utils.dataset_utils import GridData, regrid_icon_to_rotlatlon + + +logger = PythonLogger(__name__) + +# Margin to use for ERA dataset (to avoid nans from interpolation at boundary) +INPUT_MARGIN_DEGREES = 0.5 + +class AnemoiDatasetCopernicus(DownscalingDataset): + def __init__(self, + type: str, + input_anemoi_dataset_path: str, + target_anemoi_dataset_path: str, + corrected_tp_path: str, + start_date: datetime.datetime = None, + end_date: datetime.datetime = None, + input_channel_names: List[str] = [], + output_channel_names: List[str] = [], + static_channel_names: List[str] = [], + transform_channels: List[str] = [], + transform_input_means: dict = {}, + transform_input_stdevs: dict = {}, + transform_output_means: dict = {}, + transform_output_stdevs: dict = {}, + n_month_hour_channels: int = None, + trim_edge: int = 0, + ): + super().__init__() + + input_dataset = type.split('_')[1] + target_dataset = type.split('_')[-1] + self.real_target = target_dataset == 'real' + self.trim_edge = trim_edge + + if input_dataset != 'era5': + raise ValueError(f"Input dataset {input_dataset} not supported for AnemoiDataset. Only 'era5' is supported.") + if target_dataset != 'cosmo' and target_dataset !='real': + raise ValueError(f"Target dataset {target_dataset} not supported for AnemoiDataset. Only 'cosmo' and 'real' are supported.") + + if self.real_target: + # Map output channel names from real to era5 + output_channel_names_real = [ERA_TO_REAL_CHANNEL_MAP[name] for name in output_channel_names] + self.lat_lon_real = torch.load("/capstor/store/cscs/pasc/c38/real_grid_info/realch1-lat-lon", weights_only=False) + self.regrid_indices_real = torch.from_numpy(np.load("/capstor/store/cscs/pasc/c38/real_grid_info/remap_indices.npy")).long() + self.regrid_weights_real = torch.from_numpy(np.load("/capstor/store/cscs/pasc/c38/real_grid_info/remap_weights.npy")) + + self._corrected_tp_path = corrected_tp_path + #TODO switch hanbdling paths to Path rather than pure strings + self._n_month_hour_channels = n_month_hour_channels + target_open_dataset_kwargs = {} + if start_date is not None and end_date is not None: + assert start_date < end_date, "start_date must be before end_date" + target_open_dataset_kwargs['start'] = start_date + target_open_dataset_kwargs['end'] = end_date + if trim_edge > 0 and not self.real_target: + target_open_dataset_kwargs['trim_edge'] = trim_edge + self._output_dataset = open_dataset(target_anemoi_dataset_path, select=output_channel_names_real if self.real_target else output_channel_names, **target_open_dataset_kwargs) + assert self._output_dataset.shape[1] == len(output_channel_names) + + # Load ERA dataset, trimming the area and limiting the dates to the target dataset + start_date = self._output_dataset.metadata()['start_date'] if start_date is None else start_date + end_date = self._output_dataset.metadata()['end_date'] if end_date is None else end_date + latitudes = self.latitude() + longitudes = self.longitude() + min_lat = min(latitudes) - INPUT_MARGIN_DEGREES + max_lat = max(latitudes) + INPUT_MARGIN_DEGREES + min_lon = max(0, min(longitudes) - INPUT_MARGIN_DEGREES) + max_lon = max(longitudes) + INPUT_MARGIN_DEGREES + area=(max_lat, min_lon, min_lat, max_lon) + + self._input_dataset = open_dataset(input_anemoi_dataset_path, select=input_channel_names, start=start_date, end=end_date, area=area) + assert self._input_dataset.shape[1] == len(input_channel_names) + + # Check that we have the same number of time points in each dataset + assert self._input_dataset.shape[0] == self._output_dataset.shape[0] + + # Load static info and channel names + if static_channel_names: + self._static_channels = [ChannelMetadata(name) if len(name.split('_'))==1 + else ChannelMetadata(name.split('_')[0],name.split('_')[1]) + for name in static_channel_names] + static_open_dataset_kwargs = {} + if not self.real_target and trim_edge > 0: + static_open_dataset_kwargs['trim_edge'] = trim_edge + static_dataset = open_dataset(target_anemoi_dataset_path, select=static_channel_names, start=start_date, end=start_date, **static_open_dataset_kwargs) + assert static_dataset.shape[1] == len(static_channel_names) + # take first time point, and squeeze() to remove ensemble dimension + static_data = static_dataset[0,:,:,:].squeeze() + # Could also get these from stats, but one-time calculation is OK. + self.static_mean = static_data.mean(axis=-1, keepdims=True) + self.static_std = static_data.std(axis=-1, keepdims=True) + target_shape = self.image_shape() + self.static_data_normalized = (static_data - self.static_mean.reshape((self.static_mean.shape[0],1))) \ + / self.static_std.reshape((self.static_std.shape[0],1)) + self.static_data_normalized = torch.from_numpy(self.static_data_normalized) + self.static_data_normalized = regrid_icon_to_rotlatlon(self.static_data_normalized, self.regrid_indices_real, self.regrid_weights_real) + if trim_edge > 0 and self.real_target: + self.static_data_normalized = self.static_data_normalized[:, trim_edge:-trim_edge, trim_edge:-trim_edge] + # self.normalize_input(np.flip(static_data.squeeze().reshape(-1, *target_shape), 1)) + else: + self.static_data_normalized = None + self._static_channels = [] + + # Load target channel names + self._output_channels = [ChannelMetadata(name) if len(name.split('_'))==1 + else ChannelMetadata(name.split('_')[0],name.split('_')[1]) + for name in output_channel_names] + # Load era5 channel names + self._input_channels = [ChannelMetadata(name) if len(name.split('_'))==1 + else ChannelMetadata(name.split('_')[0],name.split('_')[1]) + for name in input_channel_names] + + # Load stats for normalizing channels of input and output + target_stats = self._output_dataset.statistics + self.output_mean = target_stats['mean'][:] + self.output_std = target_stats['stdev'][:] + + input_stats = self._input_dataset.statistics + self.input_mean = input_stats['mean'][:] + self.input_std = input_stats['stdev'][:] + + assert len(transform_channels) == len(transform_input_means) ==\ + len(transform_input_stdevs) == len(transform_output_means) == \ + len(transform_output_stdevs) + + # FEATURE: load the mean and std values for transformed channels and update the normalization statistics + self.input_transforms = {} + self.input_inverse_transforms = {} + self.output_transforms = {} + self.output_inverse_transforms = {} + for transform_descriptor in transform_channels: + channel, transformation = transform_descriptor.split('-') + input_channel_idx = input_channel_names.index(channel) if channel in input_channel_names else None + output_channel_idx = output_channel_names.index(channel) if channel in output_channel_names else None + if transformation.startswith('box_cox'): + lmbda_str = transformation.split('_')[-1] + lmbda = float(transformation.split('_')[-1])/(10**(len(lmbda_str)-1)) + print(f"Applying Box-Cox transformation with lambda={lmbda} to channel {channel} (input idx: {input_channel_idx} ({input_channel_names[input_channel_idx]}), output idx: {output_channel_idx} ({output_channel_names[output_channel_idx]}))") + if input_channel_idx is not None: + self.input_transforms[input_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_transform(x, lmbda) + self.input_inverse_transforms[input_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_inverse_transform(x, lmbda) + self.input_mean[input_channel_idx] = transform_input_means[transform_descriptor] + self.input_std[input_channel_idx] = transform_input_stdevs[transform_descriptor] + if output_channel_idx is not None: + self.output_transforms[output_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_transform(x, lmbda) + self.output_inverse_transforms[output_channel_idx] = lambda x, lmbda=lmbda: self.box_cox_inverse_transform(x, lmbda) + self.output_mean[output_channel_idx] = transform_output_means[transform_descriptor] + self.output_std[output_channel_idx] = transform_output_stdevs[transform_descriptor] + else: + raise ValueError(f"Transformation: {transformation} for channel {channel} not implemented.") + + # Initialize the interpolator + self.interpolator = GridData( + self._input_dataset.longitudes, + self._input_dataset.latitudes, + self.longitude(), + self.latitude()) + + + # DO NOT SUBMIT: This is not implemented yet. + # Question: Is it OK to change the signature to return 3 items? + def __getitem__(self, idx): + """Get input and target data. Transform and normalize, but do not interpolate.""" + + # Pull input, replacing the corrected tp if applicable + date_str = to_datetime(self._input_dataset.dates[idx]).strftime('%Y%m%d-%H%M') + # Don't reshape, but do squeeze ensemble dimension. + input_data = self._input_dataset[idx].squeeze() + # TODO: Consider generalizing this to other channels, in case we have cp. + if ChannelMetadata('tp') in self._input_channels: + tp_idx = self._input_channels.index(ChannelMetadata('tp')) + corrected_tp_data = np.load(os.path.join(self._corrected_tp_path, f'{date_str}.npy')) + input_data[tp_idx,::] = corrected_tp_data + # input_data = self.normalize_input(input_data) + + # Pull target data + # squeeze the ensemble dimesnsion + # next two steps only if target is cosmo, real has to be regridded first (done in training loop on gpu-s for efficiency) + # reshape to image_shape + # flip so that it starts in top-left corner (by default it is bottom left) + target_shape = self.image_shape() + target_data = self._output_dataset[idx].squeeze() + if not self.real_target: + target_data = np.flip(target_data \ + .reshape(-1,*target_shape), + 1) + # target_data = self.normalize_output(target_data) + + return torch.from_numpy(target_data.copy()),\ + torch.from_numpy(input_data),\ + date_str + + def get_static_data(self): + return self.static_data_normalized + + def __len__(self): + return len(self._output_dataset.dates) + + # Question: Do we need an input longitude as well? + def longitude(self) -> np.ndarray: + """Get longitude values from the target dataset.""" + if self.real_target: + return self.lat_lon_real[:,1] + return self._output_dataset.longitudes + + def latitude(self) -> np.ndarray: + """Get latitude values from the target dataset.""" + if self.real_target: + return self.lat_lon_real[:,0] + return self._output_dataset.latitudes + + def input_channels(self) -> List[ChannelMetadata]: + """Metadata for the input channels. A list of ChannelMetadata, one for each channel""" + return self._input_channels + + def output_channels(self) -> List[ChannelMetadata]: + """Metadata for the output channels. A list of ChannelMetadata, one for each channel""" + return self._output_channels + + def static_channels(self) -> List[ChannelMetadata]: + """Metadata for the static channels. A list of ChannelMetadata, one for each channel""" + return self._static_channels + + def time(self) -> List: + """Get time values from the dataset.""" + #TODO Choose the time format and convert to that, currently it's a string from a filename + return [to_datetime(dt64).strftime('%Y%m%d-%H%M') for dt64 in self._output_dataset.dates] + + def image_shape(self) -> Tuple[int, int]: + """Get the (height, width) of the data.""" + if self.real_target: + return 704,1088 + return self._output_dataset.field_shape + + def input_shape(self) -> Tuple[int, int]: + """Get the (height, width) of the input data.""" + return self._input_dataset.field_shape + + def normalization_stats(self): + """Get the mean and std stats for normalizing the input and output data.""" + return {"input_mean": self.input_mean, + "input_std": self.input_std, + "output_mean": self.output_mean, + "output_std": self.output_std} + + def stats_to_torch(self, device: torch.device, dtype: torch.dtype = torch.float32): + """Convert the mean and std stats to torch tensors on the specified device.""" + self.input_mean = torch.from_numpy(self.input_mean).to(device=device, dtype=dtype) + self.input_std = torch.from_numpy(self.input_std).to(device=device, dtype=dtype) + self.output_mean = torch.from_numpy(self.output_mean).to(device=device, dtype=dtype) + self.output_std = torch.from_numpy(self.output_std).to(device=device, dtype=dtype) + + def stats_to_numpy(self): + """Convert the mean and std stats to numpy arrays.""" + self.input_mean = self.input_mean.cpu().numpy() if isinstance(self.input_mean, torch.Tensor) else self.input_mean + self.input_std = self.input_std.cpu().numpy() if isinstance(self.input_std, torch.Tensor) else self.input_std + self.output_mean = self.output_mean.cpu().numpy() if isinstance(self.output_mean, torch.Tensor) else self.output_mean + self.output_std = self.output_std.cpu().numpy() if isinstance(self.output_std, torch.Tensor) else self.output_std + + def normalize_input(self, x: np.ndarray | torch.Tensor, mean: np.ndarray | torch.Tensor = None, std: np.ndarray | torch.Tensor = None) -> np.ndarray | torch.Tensor: + """Convert input from physical units to normalized data.""" + if mean is None: + mean = self.input_mean + if std is None: + std = self.input_std + for channel_idx, transform in self.input_transforms.items(): + x[:,channel_idx,::] = transform(x[:,channel_idx,::]) + return (x - self.input_mean[(None,) + (...,) + (None,) * (x.ndim - 2)]) \ + / self.input_std[(None,) + (...,) + (None,) * (x.ndim - 2)] + + + def denormalize_input(self, x: np.ndarray | torch.Tensor, mean: np.ndarray | torch.Tensor = None, std: np.ndarray | torch.Tensor = None) -> np.ndarray | torch.Tensor: + """Convert input from normalized data to physical units.""" + if mean is None: + mean = self.input_mean + if std is None: + std = self.input_std + x = x * self.input_std[(None,) + (...,) + (None,) * (x.ndim - 2)] \ + + self.input_mean[(None,) + (...,) + (None,) * (x.ndim - 2)] + for channel_idx, inverse_transform in self.input_inverse_transforms.items(): + x[:,channel_idx,::] = inverse_transform(x[:,channel_idx,::]) + return x + + + def normalize_output(self, x: np.ndarray | torch.Tensor, mean: np.ndarray | torch.Tensor = None, std: np.ndarray | torch.Tensor = None) -> np.ndarray | torch.Tensor: + """Convert output from physical units to normalized data.""" + if mean is None: + mean = self.output_mean + if std is None: + std = self.output_std + for channel_idx, transform in self.output_transforms.items(): + x[:,channel_idx,::] = transform(x[:,channel_idx,::]) + return (x - self.output_mean[(None,) + (...,) + (None,) * (x.ndim - 2)]) \ + / self.output_std[(None,) + (...,) + (None,) * (x.ndim - 2)] + + + def denormalize_output(self, x: np.ndarray | torch.Tensor, mean: np.ndarray | torch.Tensor = None, std: np.ndarray | torch.Tensor = None) -> np.ndarray | torch.Tensor: + """Convert output from normalized data to physical units.""" + if mean is None: + mean = self.output_mean + if std is None: + std = self.output_std + x = x * self.output_std[(None,) + (...,) + (None,) * (x.ndim - 2)] \ + + self.output_mean[(None,) + (...,) + (None,) * (x.ndim - 2)] + for channel_idx, inverse_transform in self.output_inverse_transforms.items(): + x[:,channel_idx,::] = inverse_transform(x[:,channel_idx,::]) + return x + + def box_cox_transform(self, channel_array: np.ndarray | torch.Tensor, lmbda: float) -> np.ndarray | torch.Tensor: + """Apply Box-Cox transformation to the data.""" + if isinstance(channel_array, torch.Tensor): + channel_array = torch.clamp(channel_array, min=0) + return (torch.pow(channel_array, lmbda) - 1) / lmbda + channel_array = np.clip(channel_array, 0, None) + return (np.power(channel_array, lmbda) - 1) / lmbda + + def box_cox_inverse_transform(self, channel_array: np.ndarray | torch.Tensor, lmbda: float) -> np.ndarray | torch.Tensor: + """Apply inverse Box-Cox transformation to the data.""" + if isinstance(channel_array, torch.Tensor): + channel_array = torch.clamp(channel_array, min=-1/lmbda) + return torch.pow((lmbda * channel_array) + 1, 1 / lmbda) + channel_array = np.clip(channel_array, -1/lmbda, None) + return np.power((lmbda * channel_array) + 1, 1 / lmbda) + + def make_time_grids(self, dates: list[str], device: torch.device, dtype: torch.dtype = torch.float32) -> torch.Tensor: + """ + Create multi-frequency cyclic sin/cos feature grids for hour and month (batched). + + Parameters + ---------- + dates : Sequence[str] + Date strings in the format 'YYYYMMDD-HHMM', length = B + + Returns + ------- + grid : torch.Tensor, shape (B, C, H, W) + Channels = [sin(k*hour), cos(k*hour), sin(k*month), cos(k*month) for each k] + """ + + B = len(dates) + + # --- parse month and hour --- + months = torch.tensor( + [int(d.split("-")[0][4:6]) for d in dates], + dtype=torch.float32, + device=device, + ) + hours = torch.tensor( + [int(d.split("-")[1][0:2]) for d in dates], + dtype=torch.float32, + device=device, + ) + + # normalize cyclic components + hours = (hours % 24) / 24.0 # (B,) + months = ((months - 1) % 12) / 12.0 # (B,) + + # frequencies + n_freq = self._n_month_hour_channels // 2 + freqs = torch.arange( + 1, n_freq + 1, dtype=torch.float32, device=device + ) # (K,) + + # shape helpers + hours = hours[:, None] # (B, 1) + months = months[:, None] # (B, 1) + + # --- hour encodings --- + hour_angles = 2 * torch.pi * hours * freqs # (B, K) + hour_feats = torch.stack( + [torch.sin(hour_angles), torch.cos(hour_angles)], + dim=2 + ) # (B, K, 2) + + # --- month encodings --- + month_angles = 2 * torch.pi * months * freqs + month_feats = torch.stack( + [torch.sin(month_angles), torch.cos(month_angles)], + dim=2 + ) # (B, K, 2) + + # concatenate and flatten channels + feats = torch.cat([hour_feats, month_feats], dim=1) # (B, 2K, 2) + feats = feats.reshape(B, -1) # (B, C) + + # expand to spatial grid + # grid = feats[:, :, None, None].expand(B, feats.shape[1], H, W) + + return feats + +ANEMOI_ERA5COPERNICUSTP_REAL = AnemoiDatasetCopernicus +ANEMOI_ERA5COPERNICUSTP_COSMO = AnemoiDatasetCopernicus diff --git a/src/hirad/datasets/dataset.py b/src/hirad/datasets/dataset.py index cdfa34b6..cb2124e7 100644 --- a/src/hirad/datasets/dataset.py +++ b/src/hirad/datasets/dataset.py @@ -24,6 +24,7 @@ from .era5_cosmo import ERA5_COSMO from .era5_real import ERA5_REAL from .anemoi_dataset import ANEMOI_ERA5_COSMO, ANEMOI_ERA5_REAL +from .anemoi_dataset_copernicus_tp import ANEMOI_ERA5COPERNICUSTP_COSMO, ANEMOI_ERA5COPERNICUSTP_REAL from .base import DownscalingDataset @@ -33,6 +34,8 @@ "era5_real": ERA5_REAL, "anemoi_era5_cosmo": ANEMOI_ERA5_COSMO, "anemoi_era5_real": ANEMOI_ERA5_REAL, + "anemoi_era5_copernicus_tp_real": ANEMOI_ERA5COPERNICUSTP_REAL, + "anemoi_era5_copernicus_tp_cosmo": ANEMOI_ERA5COPERNICUSTP_COSMO, } diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index 729c3e3a..96a8a9b6 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -16,6 +16,7 @@ from hirad.utils.inference_utils import save_results_as_torch from hirad.utils.function_utils import get_time_from_range from hirad.utils.checkpoint import load_checkpoint +from hirad.utils.dataset_utils import regrid_icon_to_rotlatlon from hirad.datasets import get_dataset_and_sampler_inference @@ -64,6 +65,10 @@ def main(cfg: DictConfig) -> None: dataset_cfg=dataset_cfg, times=times, has_lead_time=has_lead_time ) dataset.stats_to_torch(device=dist.device, dtype=input_dtype) + is_real_target = dataset_cfg.get("type").split("_")[-1] == "real" + if is_real_target: + dataset.regrid_indices_real = dataset.regrid_indices_real.to(dist.device) + dataset.regrid_weights_real = dataset.regrid_weights_real.to(dist.device, dtype=input_dtype) img_shape = dataset.image_shape() img_out_channels = len(dataset.output_channels()) @@ -254,11 +259,19 @@ def elapsed_time(self, _): savedir = os.path.join(output_path,f"{times[sampler[time_index]]}") os.makedirs(savedir,exist_ok=True) # continue + if is_real_target: + image_tar = regrid_icon_to_rotlatlon( + image_tar.to(dist.device, dtype=input_dtype), + dataset.regrid_indices_real, + dataset.regrid_weights_real, + ) + if dataset.trim_edge > 0: + image_tar = image_tar[:, :, dataset.trim_edge:-dataset.trim_edge, dataset.trim_edge:-dataset.trim_edge] if lead_time_label: lead_time_label = lead_time_label[0].to(dist.device).contiguous() else: lead_time_label = None - image_lr = dataset.interpolator(image_lr.to(dist.device, dtype=input_dtype)).reshape(*image_lr.shape[:-1], *image_tar.shape[-2:]).flip(-2) + image_lr = dataset.interpolator(image_lr.to(dist.device, dtype=input_dtype)).reshape(*image_lr.shape[:-1], *dataset.image_shape()).flip(-2) image_lr = dataset.normalize_input(image_lr) image_lr = image_lr.to(memory_format=torch.channels_last) random_seed = cfg.generation.get("random_seed", None)+index if cfg.generation.get("randomize", False) and cfg.generation.get("random_seed", None) is not None else None @@ -282,13 +295,11 @@ def elapsed_time(self, _): baseline = dataset.denormalize_input(image_lr)[0].squeeze().flip(-2).cpu().numpy() if image_reg is not None: mean_pred = dataset.denormalize_output(image_reg)[0].squeeze().flip(-2).cpu().numpy() - writer_threads.append( writer_executor.submit( save_results_as_torch, savedir, times[sampler[time_index]], - dataset, prediction_ensemble, image_tar, baseline, diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index f4f45ef8..147ec623 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -27,6 +27,7 @@ from hirad.utils.function_utils import get_time_from_range from hirad.utils.inference_utils import save_results_as_torch from hirad.utils.env_info import get_env_info, flatten_dict +from hirad.utils.dataset_utils import regrid_icon_to_rotlatlon from hirad.models import UNet, EDMPrecondSuperResolution from hirad.losses import ResidualLoss, RegressionLoss, RegressionLossCE from hirad.datasets import init_train_valid_datasets_from_config, get_dataset_and_sampler_inference @@ -95,14 +96,6 @@ def main(cfg: DictConfig) -> None: ) if dist.rank==0 and not os.path.exists(checkpoint_dir): os.makedirs(checkpoint_dir) # added creating checkpoint dir - visualize_checkpoints = False - if hasattr(cfg, "generation"): - visualization_dir = os.path.join( - cfg.training.io.get("checkpoint_dir", "."), "visualization" - ) - if dist.rank==0 and not os.path.exists(visualization_dir): - os.makedirs(visualization_dir) # added creating checkpoint dir - visualize_checkpoints = True if cfg.training.hp.batch_size_per_gpu == "auto" and \ cfg.training.hp.total_batch_size == "auto": raise ValueError("batch_size_per_gpu and total_batch_size can't be both set to 'auto'.") @@ -140,6 +133,7 @@ def main(cfg: DictConfig) -> None: sampler_start_idx=cur_nimg, ) dataset.interpolator.to_torch(device=dist.device) + is_real_target = dataset_cfg.get("type").split("_")[-1] == "real" logger0.info(f"Training on dataset with size {len(dataset)}") logger0.info(f"Validating on dataset with size {len(validation_dataset) if validation_dataset else 0}") @@ -162,22 +156,6 @@ def main(cfg: DictConfig) -> None: else: prob_channels = None - if visualize_checkpoints: - # Parse the inference input times - if cfg.generation.times_range and cfg.generation.times: - raise ValueError("Either times_range or times must be provided, but not both") - if cfg.generation.times_range: - times = get_time_from_range(cfg.generation.times_range, time_format="%Y%m%d-%H%M") - else: - times = cfg.generation.times - viz_dataset_cfg = OmegaConf.to_container(cfg.generation.dataset) - visualization_dataset, visualization_sampler = get_dataset_and_sampler_inference( - dataset_cfg=viz_dataset_cfg, times=times - ) - visualization_data_loader = torch.utils.data.DataLoader( - dataset=visualization_dataset, sampler=visualization_sampler, batch_size=1, pin_memory=True - ) - # Parse the patch shape if ( cfg.model.name == "patched_diffusion" @@ -450,26 +428,6 @@ def main(cfg: DictConfig) -> None: if regression_net: regression_net = torch.compile(regression_net) - # init the generator for inference to visualize checkpoint results - if visualize_checkpoints: - generator = Generator( - net_reg= model if regression_net is None else regression_net, - net_res= model if regression_net is not None else None, - batch_size=int(cfg.generation.num_ensembles//dist.world_size), - ensemble_size=cfg.generation.num_ensembles, - hr_mean_conditioning=cfg.model.hr_mean_conditioning, - n_out_channels=img_out_channels, - inference_mode="all" if regression_net is not None else "regression", - dist=dist, - ) - if use_patching: - generator.initialize_patching(img_shape=img_shape, - patch_shape=patch_shape, - boundary_pix=cfg.generation.boundary_pix, - overlap_pix=cfg.generation.overlap_pix, - ) - sampler_params = cfg.generation.sampler.params if "params" in cfg.generation.sampler else {} - generator.initialize_sampler(cfg.generation.sampler.type, **sampler_params) ############################################################################ # MAIN TRAINING LOOP # @@ -508,6 +466,10 @@ def main(cfg: DictConfig) -> None: .contiguous() ) + if is_real_target: + dataset.regrid_indices_real = dataset.regrid_indices_real.to(dist.device) + dataset.regrid_weights_real = dataset.regrid_weights_real.to(dist.device, dtype=input_dtype) + # turn off for lead time labels for now since we are not using them # TODO: implement lead time labels properly once we train on IFS? lead_time_label = None @@ -543,7 +505,18 @@ def main(cfg: DictConfig) -> None: tick_read_time = time.time() - tick_read_start_time img_lr = dataset.interpolator(img_lr.to(dist.device, dtype=input_dtype)).reshape(*img_lr.shape[:-1], *img_shape).flip(-2) img_lr = dataset.normalize_input(img_lr) - img_clean = dataset.normalize_output(img_clean.to(dist.device, dtype=input_dtype)) + if is_real_target: + img_clean = regrid_icon_to_rotlatlon( + img_clean.to(dist.device, dtype=input_dtype), + dataset.regrid_indices_real, + dataset.regrid_weights_real, + ) + if dataset.trim_edge > 0: + img_clean = img_clean[:, :, dataset.trim_edge:-dataset.trim_edge, dataset.trim_edge:-dataset.trim_edge] + img_clean = img_clean.flip(-2) + else: + img_clean = img_clean.to(dist.device, dtype=input_dtype) + img_clean = dataset.normalize_output(img_clean) date_embedding = None if n_month_hour_channels > 0: date_embedding = dataset.make_time_grids(*date_str, dist.device, dtype=input_dtype) @@ -721,7 +694,18 @@ def main(cfg: DictConfig) -> None: ) = next(validation_dataset_iterator) img_lr_valid = dataset.interpolator(img_lr_valid.to(dist.device)).reshape(*img_lr_valid.shape[:-1], *img_shape).flip(-2) img_lr_valid = dataset.normalize_input(img_lr_valid) - img_clean_valid = dataset.normalize_output(img_clean_valid.to(dist.device, dtype=input_dtype)) + if is_real_target: + img_clean_valid = regrid_icon_to_rotlatlon( + img_clean_valid.to(dist.device, dtype=input_dtype), + dataset.regrid_indices_real, + dataset.regrid_weights_real, + ) + if dataset.trim_edge > 0: + img_clean_valid = img_clean_valid[:, :, dataset.trim_edge:-dataset.trim_edge, dataset.trim_edge:-dataset.trim_edge] + img_clean = img_clean.flip(-2) + else: + img_clean_valid = img_clean_valid.to(dist.device, dtype=input_dtype) + img_clean_valid = dataset.normalize_output(img_clean_valid) date_embedding = None if n_month_hour_channels > 0: date_embedding = dataset.make_time_grids(*date_str, dist.device, dtype=input_dtype) @@ -828,97 +812,6 @@ def main(cfg: DictConfig) -> None: epoch=cur_nimg, ) - # Visualize samples - if dist.world_size > 1: - torch.distributed.barrier() - if is_time_for_periodic_task( - cur_nimg, - cfg.training.io.visualization_freq, - done, - cfg.training.hp.total_batch_size, - dist.rank, - ) and visualize_checkpoints: - with nvtx.annotate("visualization", color="red"): - if dist.rank == 0: - writer_executor = ThreadPoolExecutor( - max_workers=cfg.generation.perf.num_writer_workers - ) - writer_threads = [] - - times = visualization_dataset.time() - time_index = -1 - output_paths_list = [] - with torch.no_grad(): - for index, (img_clean_viz, img_lr_viz, *lead_time_label_viz) in enumerate( - iter(visualization_data_loader) - ): - time_index += 1 - logger0.info(f"starting index: {time_index}") - - # continue - if lead_time_label_viz: - lead_time_label_viz = lead_time_label_viz[0].to(dist.device).contiguous() - else: - lead_time_label_viz = None - - if use_apex_gn: - img_clean_viz = img_clean_viz.to( - dist.device, - dtype=input_dtype, - non_blocking=True, - ).to(memory_format=torch.channels_last) - img_lr_viz = img_lr_viz.to( - dist.device, - dtype=input_dtype, - non_blocking=True, - ).to(memory_format=torch.channels_last) - else: - img_clean_viz = ( - img_clean_viz.to(dist.device) - .to(input_dtype) - .contiguous() - ) - img_lr_viz = ( - img_lr_viz.to(dist.device) - .to(input_dtype) - .contiguous() - ) - with torch.autocast( - "cuda", dtype=amp_dtype, enabled=enable_amp - ): - image_pred_viz, image_reg_viz = generator.generate(img_lr_viz, lead_time_label_viz) - if dist.rank == 0: - # write out data in a seperate thread so we don't hold up inferencing - output_path = os.path.join(visualization_dir, f"{cur_nimg}_{times[visualization_sampler[time_index]]}") - output_paths_list.append(output_path) - if dist.rank==0 and not os.path.exists(output_path): - os.makedirs(output_path) - writer_threads.append( - writer_executor.submit( - save_results_as_torch, - output_path, - times[visualization_sampler[time_index]], - visualization_dataset, - image_pred_viz.cpu().numpy(), - img_clean_viz.cpu().numpy(), - img_lr_viz.cpu().numpy(), - image_reg_viz.cpu().numpy() if image_reg_viz is not None else None, - ) - ) - # make sure all the workers are done writing - if dist.rank == 0: - for thread in list(writer_threads): - thread.result() - writer_threads.remove(thread) - writer_executor.shutdown() - if cfg.logging.method == "mlflow" and cfg.logging.log_images: - for output_path in output_paths_list: - mlflow.log_artifacts(output_path, - os.path.join( - 'visualization', - os.path.split(output_path)[-1])) - - if dist.world_size > 1: torch.distributed.barrier() # Done. diff --git a/src/hirad/utils/dataset_utils.py b/src/hirad/utils/dataset_utils.py index e47c20e4..f5400c7d 100644 --- a/src/hirad/utils/dataset_utils.py +++ b/src/hirad/utils/dataset_utils.py @@ -4,6 +4,48 @@ from typing import Optional +def regrid_icon_to_rotlatlon( + data: torch.Tensor, + indices: torch.Tensor, + weights: torch.Tensor, + nx: int = 1170, + ny: int = 786, +) -> torch.Tensor: + """Regrid ICON unstructured data to a rotated lat-lon grid. + + Parameters + ---------- + data : torch.Tensor + Input data with the last axis being the unstructured grid dimension. + indices : torch.LongTensor + Remap indices of shape (n_target, n_stencil). + weights : torch.Tensor + Remap weights of shape (n_target, n_stencil). + nx, ny : int + Output grid dimensions. + + Returns + ------- + torch.Tensor + Regridded data of shape (*(batch,channel), ny, nx). + """ + out_shape = data.shape[:-1] + (ny, nx) + + # Gather stencil values: (..., n_target, n_stencil) + # indices: (n_target, n_stencil) -> expand to match data batch dims + values = data[..., indices] # (..., n_target, n_stencil) + + # Weighted sum: multiply then reduce over stencil dim + result = (values * weights).sum(dim=-1) # (..., n_target) + + # Clamp to stencil min/max to avoid extrapolation + vmin = values.amin(dim=-1) + vmax = values.amax(dim=-1) + result = result.clamp(min=vmin, max=vmax) + + return result.reshape(out_shape) + + class GridData(): """ Performs interpolation from irregular points to target grid using Delaunay triangulation. From af0c9cab6584944c8cd094e4b85c92eb9eb716b1 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Thu, 12 Mar 2026 11:48:05 +0100 Subject: [PATCH 258/302] fix bug in validation low res image flipping --- src/hirad/training/train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index 147ec623..b2cfdc9a 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -702,7 +702,7 @@ def main(cfg: DictConfig) -> None: ) if dataset.trim_edge > 0: img_clean_valid = img_clean_valid[:, :, dataset.trim_edge:-dataset.trim_edge, dataset.trim_edge:-dataset.trim_edge] - img_clean = img_clean.flip(-2) + img_clean_valid = img_clean_valid.flip(-2) else: img_clean_valid = img_clean_valid.to(dist.device, dtype=input_dtype) img_clean_valid = dataset.normalize_output(img_clean_valid) From 267f0274a31029c014945aeac6aeb04c9e982cfd Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 17 Mar 2026 12:06:16 +0100 Subject: [PATCH 259/302] clean up models --- src/hirad/inference/generate.py | 2 +- src/hirad/losses/__init__.py | 2 +- src/hirad/losses/loss.py | 618 +-------------- src/hirad/models/__init__.py | 7 +- src/hirad/models/dhariwal_unet.py | 259 ------ src/hirad/models/layers.py | 2 +- src/hirad/models/meta.py | 50 -- src/hirad/models/preconditioning.py | 1141 +-------------------------- src/hirad/models/song_unet.py | 244 ------ src/hirad/models/unet.py | 127 +-- src/hirad/models/utils.py | 66 -- src/hirad/training/train.py | 4 +- src/hirad/utils/train_helpers.py | 29 +- 13 files changed, 43 insertions(+), 2508 deletions(-) delete mode 100644 src/hirad/models/dhariwal_unet.py delete mode 100644 src/hirad/models/meta.py delete mode 100644 src/hirad/models/utils.py diff --git a/src/hirad/inference/generate.py b/src/hirad/inference/generate.py index 96a8a9b6..a8e77f03 100644 --- a/src/hirad/inference/generate.py +++ b/src/hirad/inference/generate.py @@ -290,7 +290,7 @@ def elapsed_time(self, _): if dist.rank == 0: batch_size = image_out.shape[0] # write out data in a seperate thread so we don't hold up inferencing - image_tar = image_tar[0].squeeze().flip(-2).cpu().numpy() + image_tar = image_tar[0].squeeze().cpu().numpy() prediction_ensemble = dataset.denormalize_output(image_out).squeeze().flip(-2).cpu().numpy() baseline = dataset.denormalize_input(image_lr)[0].squeeze().flip(-2).cpu().numpy() if image_reg is not None: diff --git a/src/hirad/losses/__init__.py b/src/hirad/losses/__init__.py index 1494a54c..ab2b2a93 100644 --- a/src/hirad/losses/__init__.py +++ b/src/hirad/losses/__init__.py @@ -1 +1 @@ -from .loss import ResidualLoss, RegressionLoss, RegressionLossCE +from .loss import ResidualLoss, RegressionLoss diff --git a/src/hirad/losses/loss.py b/src/hirad/losses/loss.py index ab3ecdc7..4030bfc2 100644 --- a/src/hirad/losses/loss.py +++ b/src/hirad/losses/loss.py @@ -25,327 +25,6 @@ from hirad.utils.patching import RandomPatching2D -class VPLoss: - """ - Loss function corresponding to the variance preserving (VP) formulation. - - Parameters - ---------- - beta_d: float, optional - Coefficient for the diffusion process, by default 19.9. - beta_min: float, optional - Minimum bound, by defaults 0.1. - epsilon_t: float, optional - Small positive value, by default 1e-5. - - Note: - ----- - Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and - Poole, B., 2020. Score-based generative modeling through stochastic differential - equations. arXiv preprint arXiv:2011.13456. - - """ - - def __init__( - self, beta_d: float = 19.9, beta_min: float = 0.1, epsilon_t: float = 1e-5 - ): - self.beta_d = beta_d - self.beta_min = beta_min - self.epsilon_t = epsilon_t - - def __call__( - self, - net: torch.nn.Module, - images: torch.Tensor, - labels: torch.Tensor, - augment_pipe: Optional[Callable] = None, - ): - """ - Calculate and return the loss corresponding to the variance preserving (VP) - formulation. - - The method adds random noise to the input images and calculates the loss as the - square difference between the network's predictions and the input images. - The noise level is determined by 'sigma', which is computed as a function of - 'epsilon_t' and random values. The calculated loss is weighted based on the - inverse of 'sigma^2'. - - Parameters: - ---------- - net: torch.nn.Module - The neural network model that will make predictions. - - images: torch.Tensor - Input images to the neural network. - - labels: torch.Tensor - Ground truth labels for the input images. - - augment_pipe: callable, optional - An optional data augmentation function that takes images as input and - returns augmented images. If not provided, no data augmentation is applied. - - Returns: - ------- - torch.Tensor - A tensor representing the loss calculated based on the network's - predictions. - """ - rnd_uniform = torch.rand([images.shape[0], 1, 1, 1], device=images.device) - sigma = self.sigma(1 + rnd_uniform * (self.epsilon_t - 1)) - weight = 1 / sigma**2 - y, augment_labels = ( - augment_pipe(images) if augment_pipe is not None else (images, None) - ) - n = torch.randn_like(y) * sigma - D_yn = net(y + n, sigma, labels, augment_labels=augment_labels) - loss = weight * ((D_yn - y) ** 2) - return loss - - def sigma( - self, t: Union[float, torch.Tensor] - ): # NOTE: also exists in preconditioning - """ - Compute the sigma(t) value for a given t based on the VP formulation. - - The function calculates the noise level schedule for the diffusion process based - on the given parameters `beta_d` and `beta_min`. - - Parameters - ---------- - t : Union[float, torch.Tensor] - The timestep or set of timesteps for which to compute sigma(t). - - Returns - ------- - torch.Tensor - The computed sigma(t) value(s). - """ - t = torch.as_tensor(t) - return ((0.5 * self.beta_d * (t**2) + self.beta_min * t).exp() - 1).sqrt() - - -class VELoss: - """ - Loss function corresponding to the variance exploding (VE) formulation. - - Parameters - ---------- - sigma_min : float - Minimum supported noise level, by default 0.02. - sigma_max : float - Maximum supported noise level, by default 100.0. - - Note: - ----- - Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and - Poole, B., 2020. Score-based generative modeling through stochastic differential - equations. arXiv preprint arXiv:2011.13456. - """ - - def __init__(self, sigma_min: float = 0.02, sigma_max: float = 100.0): - self.sigma_min = sigma_min - self.sigma_max = sigma_max - - def __call__(self, net, images, labels, augment_pipe=None): - """ - Calculate and return the loss corresponding to the variance exploding (VE) - formulation. - - The method adds random noise to the input images and calculates the loss as the - square difference between the network's predictions and the input images. - The noise level is determined by 'sigma', which is computed as a function of - 'sigma_min' and 'sigma_max' and random values. The calculated loss is weighted - based on the inverse of 'sigma^2'. - - Parameters: - ---------- - net: torch.nn.Module - The neural network model that will make predictions. - - images: torch.Tensor - Input images to the neural network. - - labels: torch.Tensor - Ground truth labels for the input images. - - augment_pipe: callable, optional - An optional data augmentation function that takes images as input and - returns augmented images. If not provided, no data augmentation is applied. - - Returns: - ------- - torch.Tensor - A tensor representing the loss calculated based on the network's - predictions. - """ - rnd_uniform = torch.rand([images.shape[0], 1, 1, 1], device=images.device) - sigma = self.sigma_min * ((self.sigma_max / self.sigma_min) ** rnd_uniform) - weight = 1 / sigma**2 - y, augment_labels = ( - augment_pipe(images) if augment_pipe is not None else (images, None) - ) - n = torch.randn_like(y) * sigma - D_yn = net(y + n, sigma, labels, augment_labels=augment_labels) - loss = weight * ((D_yn - y) ** 2) - return loss - - -class EDMLoss: - """ - Loss function proposed in the EDM paper. - - Parameters - ---------- - P_mean: float, optional - Mean value for `sigma` computation, by default -1.2. - P_std: float, optional: - Standard deviation for `sigma` computation, by default 1.2. - sigma_data: float, optional - Standard deviation for data, by default 0.5. - - Note - ---- - Reference: Karras, T., Aittala, M., Aila, T. and Laine, S., 2022. Elucidating the - design space of diffusion-based generative models. Advances in Neural Information - Processing Systems, 35, pp.26565-26577. - """ - - def __init__( - self, P_mean: float = -1.2, P_std: float = 1.2, sigma_data: float = 0.5 - ): - self.P_mean = P_mean - self.P_std = P_std - self.sigma_data = sigma_data - - def __call__(self, net, images, condition=None, labels=None, augment_pipe=None): - """ - Calculate and return the loss corresponding to the EDM formulation. - - The method adds random noise to the input images and calculates the loss as the - square difference between the network's predictions and the input images. - The noise level is determined by 'sigma', which is computed as a function of - 'P_mean' and 'P_std' random values. The calculated loss is weighted as a - function of 'sigma' and 'sigma_data'. - - Parameters: - ---------- - net: torch.nn.Module - The neural network model that will make predictions. - - images: torch.Tensor - Input images to the neural network. - - labels: torch.Tensor - Ground truth labels for the input images. - - augment_pipe: callable, optional - An optional data augmentation function that takes images as input and - returns augmented images. If not provided, no data augmentation is applied. - - Returns: - ------- - torch.Tensor - A tensor representing the loss calculated based on the network's - predictions. - """ - rnd_normal = torch.randn([images.shape[0], 1, 1, 1], device=images.device) - sigma = (rnd_normal * self.P_std + self.P_mean).exp() - weight = (sigma**2 + self.sigma_data**2) / (sigma * self.sigma_data) ** 2 - y, augment_labels = ( - augment_pipe(images) if augment_pipe is not None else (images, None) - ) - n = torch.randn_like(y) * sigma - if condition is not None: - D_yn = net( - y + n, - sigma, - condition=condition, - class_labels=labels, - augment_labels=augment_labels, - ) - else: - D_yn = net(y + n, sigma, labels, augment_labels=augment_labels) - loss = weight * ((D_yn - y) ** 2) - return loss - - -class EDMLossSR: - """ - Variation of the loss function proposed in the EDM paper for Super-Resolution. - - Parameters - ---------- - P_mean: float, optional - Mean value for `sigma` computation, by default -1.2. - P_std: float, optional: - Standard deviation for `sigma` computation, by default 1.2. - sigma_data: float, optional - Standard deviation for data, by default 0.5. - - Note - ---- - Reference: Mardani, M., Brenowitz, N., Cohen, Y., Pathak, J., Chen, C.Y., - Liu, C.C.,Vahdat, A., Kashinath, K., Kautz, J. and Pritchard, M., 2023. - Generative Residual Diffusion Modeling for Km-scale Atmospheric Downscaling. - arXiv preprint arXiv:2309.15214. - """ - - def __init__( - self, P_mean: float = -1.2, P_std: float = 1.2, sigma_data: float = 0.5 - ): - self.P_mean = P_mean - self.P_std = P_std - self.sigma_data = sigma_data - - def __call__(self, net, img_clean, img_lr, labels=None, augment_pipe=None): - """ - Calculate and return the loss corresponding to the EDM formulation. - - The method adds random noise to the input images and calculates the loss as the - square difference between the network's predictions and the input images. - The noise level is determined by 'sigma', which is computed as a function of - 'P_mean' and 'P_std' random values. The calculated loss is weighted as a - function of 'sigma' and 'sigma_data'. - - Parameters: - ---------- - net: torch.nn.Module - The neural network model that will make predictions. - - images: torch.Tensor - Input images to the neural network. - - labels: torch.Tensor - Ground truth labels for the input images. - - augment_pipe: callable, optional - An optional data augmentation function that takes images as input and - returns augmented images. If not provided, no data augmentation is applied. - - Returns: - ------- - torch.Tensor - A tensor representing the loss calculated based on the network's - predictions. - """ - rnd_normal = torch.randn([img_clean.shape[0], 1, 1, 1], device=img_clean.device) - sigma = (rnd_normal * self.P_std + self.P_mean).exp() - weight = (sigma**2 + self.sigma_data**2) / (sigma * self.sigma_data) ** 2 - - # augment for conditional generation - img_tot = torch.cat((img_clean, img_lr), dim=1) - y_tot, augment_labels = ( - augment_pipe(img_tot) if augment_pipe is not None else (img_tot, None) - ) - y = y_tot[:, : img_clean.shape[1], :, :] - y_lr = y_tot[:, img_clean.shape[1] :, :, :] - - n = torch.randn_like(y) * sigma - D_yn = net(y + n, y_lr, sigma, labels, augment_labels=augment_labels) - loss = weight * ((D_yn - y) ** 2) - return loss - class RegressionLoss: """ @@ -732,6 +411,8 @@ def __call__( y_lr_res = y_lr batch_size = y.shape[0] + # print(f"Shape of y: {y.shape}, y_lr: {y_lr.shape}") + # if using multi-iterations of patching, switch to optimized version if not use_patch_grad_acc or self.y_mean is None: # form residual @@ -770,14 +451,21 @@ def __call__( y = y - self.y_mean + # print(f"Shape of y after residual: y {y.shape} y_lr {y_lr.shape}") + if self.hr_mean_conditioning: y_lr = torch.cat((self.y_mean, y_lr), dim=1) + # print(f"Shape of y_lr after hr mean conditioning: y_lr {y_lr.shape}") + if static_channels is not None: y_lr = torch.cat( (y_lr, static_channels.expand(y_lr.shape[0], *static_channels.shape[1:])), dim=1, ) + + # print(f"Shape of y_lr after static channels diffusion: y_lr {y_lr.shape}") + # patchified training # conditioning: cat(y_mean, y_lr, input_interp, pos_embd), 4+12+100+4 # removed patch_embedding_selector due to compilation issue with dynamo. @@ -800,6 +488,7 @@ def __call__( else: date_embedding = date_embedding.to(img_lr.dtype, non_blocking=True).contiguous() img_lr = torch.cat((img_lr, date_embedding), dim=1) + # print(f"Shape of img_lr after date embedding diffusion patching: img_lr {img_lr.shape}") y_lr_patched = patching.apply(input=y_lr, additional_input=img_lr) y = y_patched @@ -812,6 +501,9 @@ def __call__( else: date_embedding = date_embedding.to(y_lr.dtype, non_blocking=True).contiguous() y_lr = torch.cat((y_lr, date_embedding), dim=1) + + # print(f"Final shapes before noise addition: y {y.shape} y_lr {y_lr.shape}") + # Add noise to the latent state n, sigma, weight = self.get_noise_params(y) @@ -845,287 +537,3 @@ def __call__( loss = weight * ((D_yn - y) ** 2) return loss - - - -class VELoss_dfsr: - """ - Loss function for dfsr model, modified from class VELoss. - - Parameters - ---------- - beta_start : float - Noise level at the initial step of the forward diffusion process, by default 0.0001. - beta_end : float - Noise level at the Final step of the forward diffusion process, by default 0.02. - num_diffusion_timesteps : int - Total number of forward/backward diffusion steps, by default 1000. - - - Note: - ----- - Reference: Ho J, Jain A, Abbeel P. Denoising diffusion probabilistic models. - Advances in neural information processing systems. 2020;33:6840-51. - """ - - def __init__( - self, - beta_start: float = 0.0001, - beta_end: float = 0.02, - num_diffusion_timesteps: int = 1000, - ): - # scheduler for diffusion: - self.beta_schedule = "linear" - self.beta_start = beta_start - self.beta_end = beta_end - self.num_diffusion_timesteps = num_diffusion_timesteps - betas = self.get_beta_schedule( - beta_schedule=self.beta_schedule, - beta_start=self.beta_start, - beta_end=self.beta_end, - num_diffusion_timesteps=self.num_diffusion_timesteps, - ) - self.betas = torch.from_numpy(betas).float() - self.num_timesteps = betas.shape[0] - - def get_beta_schedule( - self, beta_schedule, *, beta_start, beta_end, num_diffusion_timesteps - ): - """ - Compute the variance scheduling parameters {beta(0), ..., beta(t), ..., beta(T)} - based on the VP formulation. - - beta_schedule: str - Method to construct the sequence of beta(t)'s. - beta_start: float - Noise level at the initial step of the forward diffusion process, e.g., beta(0) - beta_end: float - Noise level at the final step of the forward diffusion process, e.g., beta(T) - num_diffusion_timesteps: int - Total number of forward/backward diffusion steps - """ - - def sigmoid(x): - return 1 / (np.exp(-x) + 1) - - if beta_schedule == "quad": - betas = ( - np.linspace( - beta_start**0.5, - beta_end**0.5, - num_diffusion_timesteps, - dtype=np.float64, - ) - ** 2 - ) - elif beta_schedule == "linear": - betas = np.linspace( - beta_start, beta_end, num_diffusion_timesteps, dtype=np.float64 - ) - elif beta_schedule == "const": - betas = beta_end * np.ones(num_diffusion_timesteps, dtype=np.float64) - elif beta_schedule == "jsd": # 1/T, 1/(T-1), 1/(T-2), ..., 1 - betas = 1.0 / np.linspace( - num_diffusion_timesteps, 1, num_diffusion_timesteps, dtype=np.float64 - ) - elif beta_schedule == "sigmoid": - betas = np.linspace(-6, 6, num_diffusion_timesteps) - betas = sigmoid(betas) * (beta_end - beta_start) + beta_start - else: - raise NotImplementedError(beta_schedule) - if betas.shape != (num_diffusion_timesteps,): - raise ValueError( - f"Expected betas to have shape ({num_diffusion_timesteps},), " - f"but got {betas.shape}" - ) - return betas - - def __call__(self, net, images, labels, augment_pipe=None): - """ - Calculate and return the loss corresponding to the variance preserving - formulation. - - The method adds random noise to the input images and calculates the loss as the - square difference between the network's predictions and the noise samples added - to the t-th step of the diffusion process. - The noise level is determined by 'beta_t' based on the given parameters 'beta_start', - 'beta_end' and the current diffusion timestep t. - - Parameters: - ---------- - net: torch.nn.Module - The neural network model that will make predictions. - - images: torch.Tensor - Input fluid flow data samples to the neural network. - - labels: torch.Tensor - Ground truth labels for the input fluid flow data samples. Not required for dfsr. - - augment_pipe: callable, optional - An optional data augmentation function that takes images as input and - returns augmented images. If not provided, no data augmentation is applied. - - Returns: - ------- - torch.Tensor - A tensor representing the loss calculated based on the network's - predictions. - """ - t = torch.randint( - low=0, high=self.num_timesteps, size=(images.size(0) // 2 + 1,) - ).to(images.device) - t = torch.cat([t, self.num_timesteps - t - 1], dim=0)[: images.size(0)] - e = torch.randn_like(images) - b = self.betas.to(images.device) - a = (1 - b).cumprod(dim=0).index_select(0, t).view(-1, 1, 1, 1) - x = images * a.sqrt() + e * (1.0 - a).sqrt() - - output = net(x, t, labels) - loss = (e - output).square() - - return loss - - -class RegressionLossCE: - """ - A regression loss function for deterministic predictions with probability - channels and lead time labels. Adapted from - :class:`physicsnemo.metrics.diffusion.loss.RegressionLoss`. In this version, - probability channels are evaluated using CrossEntropyLoss instead of - squared error. - Note: this loss does not apply any reduction. - - Attributes - ---------- - entropy : torch.nn.CrossEntropyLoss - Cross entropy loss function used for probability channels. - prob_channels : list[int] - List of channel indices to be treated as probability channels. - - Note - ---- - Reference: Mardani, M., Brenowitz, N., Cohen, Y., Pathak, J., Chen, C.Y., - Liu, C.C.,Vahdat, A., Kashinath, K., Kautz, J. and Pritchard, M., 2023. - Generative Residual Diffusion Modeling for Km-scale Atmospheric Downscaling. - arXiv preprint arXiv:2309.15214. - """ - - def __init__( - self, - prob_channels: list[int] = [4, 5, 6, 7, 8], - ): - """ - Arguments - ---------- - prob_channels: list[int], optional - List of channel indices from the target tensor to be treated as - probability channels. Cross entropy loss is computed over these - channels, while the remaining channels are treated as scalar - channels and the squared error loss is computed over them. By - default, [4, 5, 6, 7, 8]. - """ - self.entropy = torch.nn.CrossEntropyLoss(reduction="none") - self.prob_channels = prob_channels - - def __call__( - self, - net: torch.nn.Module, - img_clean: torch.Tensor, - img_lr: torch.Tensor, - lead_time_label: Optional[torch.Tensor] = None, - augment_pipe: Optional[ - Callable[[torch.Tensor], Tuple[torch.Tensor, Optional[torch.Tensor]]] - ] = None, - ) -> torch.Tensor: - """ - Calculate and return the loss for deterministic - predictions, treating specific channels as probability distributions. - - Parameters - ---------- - net : torch.nn.Module - The neural network model that will make predictions. - Expected signature: `net(input, img_lr, lead_time_label=lead_time_label, augment_labels=augment_labels)`, - where: - input (torch.Tensor): Tensor of shape (B, C_hr, H, W). Zero-filled. - y_lr (torch.Tensor): Low-resolution input of shape (B, C_lr, H, W) - lead_time_label (torch.Tensor, optional): Optional lead time - labels. If provided, should be of shape (B,). - augment_labels (torch.Tensor, optional): Optional augmentation - labels, returned by `augment_pipe`. - Returns: - torch.Tensor: Predictions of shape (B, C_hr, H, W) - - img_clean : torch.Tensor - High-resolution input images of shape (B, C_hr, H, W). - Used as ground truth and for data augmentation if `augment_pipe` is provided. - - img_lr : torch.Tensor - Low-resolution input images of shape (B, C_lr, H, W). - Used as input to the neural network. - - lead_time_label : Optional[torch.Tensor], optional - Lead time labels for temporal predictions, by default None. - Shape can vary based on model requirements, typically (B,) or scalar. - - augment_pipe : Optional[Callable[[torch.Tensor], Tuple[torch.Tensor, Optional[torch.Tensor]]]] - Data augmentation function. - Expected signature: - img_tot (torch.Tensor): Concatenated high and low resolution - images of shape (B, C_hr+C_lr, H, W). - Returns: - Tuple[torch.Tensor, Optional[torch.Tensor]]: - - Augmented images of shape (B, C_hr+C_lr, H, W) - - Optional augmentation labels - - Returns - ------- - torch.Tensor - A tensor of shape (B, C_loss, H, W) representing the pixel-wise - loss., where `C_loss = C_hr - len(prob_channels) + 1`. More - specifically, the last channel of the output tensor corresponds to - the cross-entropy loss computed over the channels specified in - `prob_channels`, while the first `C_hr - len(prob_channels)` - channels of the output tensor correspond to the squared error loss. - """ - all_channels = list(range(img_clean.shape[1])) # [0, 1, 2, ..., 10] - scalar_channels = [ - item for item in all_channels if item not in self.prob_channels - ] - weight = ( - 1.0 # (sigma ** 2 + self.sigma_data ** 2) / (sigma * self.sigma_data) ** 2 - ) - - img_tot = torch.cat((img_clean, img_lr), dim=1) - y_tot, augment_labels = ( - augment_pipe(img_tot) if augment_pipe is not None else (img_tot, None) - ) - y = y_tot[:, : img_clean.shape[1], :, :] - y_lr = y_tot[:, img_clean.shape[1] :, :, :] - - input = torch.zeros_like(y, device=img_clean.device) - - if lead_time_label is not None: - D_yn = net( - input, - y_lr, - lead_time_label=lead_time_label, - augment_labels=augment_labels, - ) - else: - D_yn = net( - input, - y_lr, - lead_time_label=lead_time_label, - augment_labels=augment_labels, - ) - loss1 = weight * (D_yn[:, scalar_channels] - y[:, scalar_channels]) ** 2 - loss2 = ( - weight - * self.entropy(D_yn[:, self.prob_channels], y[:, self.prob_channels])[ - :, None - ] - ) - loss = torch.cat((loss1, loss2), dim=1) - return loss \ No newline at end of file diff --git a/src/hirad/models/__init__.py b/src/hirad/models/__init__.py index c8fb3067..2315a38f 100644 --- a/src/hirad/models/__init__.py +++ b/src/hirad/models/__init__.py @@ -1,4 +1,3 @@ -from .utils import weight_init from .layers import ( Linear, Conv2d, @@ -8,8 +7,6 @@ PositionalEmbedding, FourierEmbedding ) -from .meta import ModelMetaData -from .song_unet import SongUNet, SongUNetPosEmbd, SongUNetPosLtEmbd -from .dhariwal_unet import DhariwalUNet +from .song_unet import SongUNet, SongUNetPosEmbd from .unet import UNet -from .preconditioning import EDMPrecondSuperResolution, EDMPrecondSR, EDMPrecond +from .preconditioning import EDMPrecondSuperResolution diff --git a/src/hirad/models/dhariwal_unet.py b/src/hirad/models/dhariwal_unet.py deleted file mode 100644 index 3880cd0a..00000000 --- a/src/hirad/models/dhariwal_unet.py +++ /dev/null @@ -1,259 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. -# SPDX-FileCopyrightText: All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Model architectures used in the paper "Elucidating the Design Space of -Diffusion-Based Generative Models". -""" - -from dataclasses import dataclass -from typing import List - -import numpy as np -import torch -from torch.nn.functional import silu -import torch.nn as nn - -from .layers import ( - Conv2d, - GroupNorm, - Linear, - PositionalEmbedding, - UNetBlock, -) -from .meta import ModelMetaData - - -@dataclass -class MetaData(ModelMetaData): - name: str = "DhariwalUNet" - # Optimization - jit: bool = False - cuda_graphs: bool = False - amp_cpu: bool = False - amp_gpu: bool = True - torch_fx: bool = False - # Data type - bf16: bool = True - # Inference - onnx: bool = False - # Physics informed - func_torch: bool = False - auto_grad: bool = False - - -class DhariwalUNet(nn.Module): - """ - Reimplementation of the ADM architecture, a U-Net variant, with optional - self-attention. - - This model supports conditional and unconditional setups, as well as several - options for various internal architectural choices such as encoder and decoder - type, embedding type, etc., making it flexible and adaptable to different tasks - and configurations. - - Parameters - ----------- - img_resolution : int - The resolution of the input/output image. - in_channels : int - Number of channels in the input image. - out_channels : int - Number of channels in the output image. - label_dim : int, optional - Number of class labels; 0 indicates an unconditional model. By default 0. - augment_dim : int, optional - Dimensionality of augmentation labels; 0 means no augmentation. By default 0. - model_channels : int, optional - Base multiplier for the number of channels across the network, by default 192. - channel_mult : List[int], optional - Per-resolution multipliers for the number of channels. By default [1,2,3,4]. - channel_mult_emb : int, optional - Multiplier for the dimensionality of the embedding vector. By default 4. - num_blocks : int, optional - Number of residual blocks per resolution. By default 3. - attn_resolutions : List[int], optional - Resolutions at which self-attention layers are applied. By default [32, 16, 8]. - dropout : float, optional - Dropout probability applied to intermediate activations. By default 0.10. - label_dropout : float, optional - Dropout probability of class labels for classifier-free guidance. By default 0.0. - - Reference - ---------- - Reference: Dhariwal, P. and Nichol, A., 2021. Diffusion models beat gans on image - synthesis. Advances in neural information processing systems, 34, pp.8780-8794. - - Note - ----- - Equivalent to the original implementation by Dhariwal and Nichol, available at - https://github.com/openai/guided-diffusion - - Example - -------- - >>> model = DhariwalUNet(img_resolution=16, in_channels=2, out_channels=2) - >>> noise_labels = torch.randn([1]) - >>> class_labels = torch.randint(0, 1, (1, 1)) - >>> input_image = torch.ones([1, 2, 16, 16]) - >>> output_image = model(input_image, noise_labels, class_labels) - """ - - def __init__( - self, - img_resolution: int, - in_channels: int, - out_channels: int, - label_dim: int = 0, - augment_dim: int = 0, - model_channels: int = 192, - channel_mult: List[int] = [1, 2, 3, 4], - channel_mult_emb: int = 4, - num_blocks: int = 3, - attn_resolutions: List[int] = [32, 16, 8], - dropout: float = 0.10, - label_dropout: float = 0.0, - ): - super().__init__(meta=MetaData()) - self.label_dropout = label_dropout - emb_channels = model_channels * channel_mult_emb - init = dict( - init_mode="kaiming_uniform", - init_weight=np.sqrt(1 / 3), - init_bias=np.sqrt(1 / 3), - ) - init_zero = dict(init_mode="kaiming_uniform", init_weight=0, init_bias=0) - block_kwargs = dict( - emb_channels=emb_channels, - channels_per_head=64, - dropout=dropout, - init=init, - init_zero=init_zero, - ) - - # Mapping. - self.map_noise = PositionalEmbedding(num_channels=model_channels) - self.map_augment = ( - Linear( - in_features=augment_dim, - out_features=model_channels, - bias=False, - **init_zero, - ) - if augment_dim - else None - ) - self.map_layer0 = Linear( - in_features=model_channels, out_features=emb_channels, **init - ) - self.map_layer1 = Linear( - in_features=emb_channels, out_features=emb_channels, **init - ) - self.map_label = ( - Linear( - in_features=label_dim, - out_features=emb_channels, - bias=False, - init_mode="kaiming_normal", - init_weight=np.sqrt(label_dim), - ) - if label_dim - else None - ) - - # Encoder. - self.enc = torch.nn.ModuleDict() - cout = in_channels - for level, mult in enumerate(channel_mult): - res = img_resolution >> level - if level == 0: - cin = cout - cout = model_channels * mult - self.enc[f"{res}x{res}_conv"] = Conv2d( - in_channels=cin, out_channels=cout, kernel=3, **init - ) - else: - self.enc[f"{res}x{res}_down"] = UNetBlock( - in_channels=cout, out_channels=cout, down=True, **block_kwargs - ) - for idx in range(num_blocks): - cin = cout - cout = model_channels * mult - self.enc[f"{res}x{res}_block{idx}"] = UNetBlock( - in_channels=cin, - out_channels=cout, - attention=(res in attn_resolutions), - **block_kwargs, - ) - skips = [block.out_channels for block in self.enc.values()] - - # Decoder. - self.dec = torch.nn.ModuleDict() - for level, mult in reversed(list(enumerate(channel_mult))): - res = img_resolution >> level - if level == len(channel_mult) - 1: - self.dec[f"{res}x{res}_in0"] = UNetBlock( - in_channels=cout, out_channels=cout, attention=True, **block_kwargs - ) - self.dec[f"{res}x{res}_in1"] = UNetBlock( - in_channels=cout, out_channels=cout, **block_kwargs - ) - else: - self.dec[f"{res}x{res}_up"] = UNetBlock( - in_channels=cout, out_channels=cout, up=True, **block_kwargs - ) - for idx in range(num_blocks + 1): - cin = cout + skips.pop() - cout = model_channels * mult - self.dec[f"{res}x{res}_block{idx}"] = UNetBlock( - in_channels=cin, - out_channels=cout, - attention=(res in attn_resolutions), - **block_kwargs, - ) - self.out_norm = GroupNorm(num_channels=cout) - self.out_conv = Conv2d( - in_channels=cout, out_channels=out_channels, kernel=3, **init_zero - ) - - def forward(self, x, noise_labels, class_labels, augment_labels=None): - # Mapping. - emb = self.map_noise(noise_labels) - if self.map_augment is not None and augment_labels is not None: - emb = emb + self.map_augment(augment_labels) - emb = silu(self.map_layer0(emb)) - emb = self.map_layer1(emb) - if self.map_label is not None: - tmp = class_labels - if self.training and self.label_dropout: - tmp = tmp * ( - torch.rand([x.shape[0], 1], device=x.device) >= self.label_dropout - ).to(tmp.dtype) - emb = emb + self.map_label(tmp) - emb = silu(emb) - - # Encoder. - skips = [] - for block in self.enc.values(): - x = block(x, emb) if isinstance(block, UNetBlock) else block(x) - skips.append(x) - - # Decoder. - for block in self.dec.values(): - if x.shape[1] != block.in_channels: - x = torch.cat([x, skips.pop()], dim=1) - x = block(x, emb) - x = self.out_conv(silu(self.out_norm(x))) - return x diff --git a/src/hirad/models/layers.py b/src/hirad/models/layers.py index 4da26b1f..dc5630e3 100644 --- a/src/hirad/models/layers.py +++ b/src/hirad/models/layers.py @@ -30,7 +30,7 @@ from einops import rearrange from torch.nn.functional import elu, gelu, leaky_relu, relu, sigmoid, silu, tanh -from hirad.models import weight_init +from hirad.utils.model_utils import weight_init _is_apex_available = False if torch.cuda.is_available(): diff --git a/src/hirad/models/meta.py b/src/hirad/models/meta.py deleted file mode 100644 index aab8e453..00000000 --- a/src/hirad/models/meta.py +++ /dev/null @@ -1,50 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. -# SPDX-FileCopyrightText: All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from dataclasses import dataclass - - -@dataclass -class ModelMetaData: - """Data class for storing essential meta data needed for all Hirad Models""" - - # Model info - name: str = "HiradModule" - # Optimization - jit: bool = False - cuda_graphs: bool = False - amp: bool = False - amp_cpu: bool = None - amp_gpu: bool = None - torch_fx: bool = False - # Data type - bf16: bool = False - # Inference - onnx: bool = False - onnx_gpu: bool = None - onnx_cpu: bool = None - onnx_runtime: bool = False - trt: bool = False - # Physics informed - var_dim: int = -1 - func_torch: bool = False - auto_grad: bool = False - - def __post_init__(self): - self.amp_cpu = self.amp if self.amp_cpu is None else self.amp_cpu - self.amp_gpu = self.amp if self.amp_gpu is None else self.amp_gpu - self.onnx_cpu = self.onnx if self.onnx_cpu is None else self.onnx_cpu - self.onnx_gpu = self.onnx if self.onnx_gpu is None else self.onnx_gpu diff --git a/src/hirad/models/preconditioning.py b/src/hirad/models/preconditioning.py index 74496a59..97f69217 100644 --- a/src/hirad/models/preconditioning.py +++ b/src/hirad/models/preconditioning.py @@ -28,691 +28,15 @@ import torch import torch.nn as nn -from .meta import ModelMetaData - network_module = importlib.import_module("hirad.models") -@dataclass -class VPPrecondMetaData(ModelMetaData): - """VPPrecond meta data""" - - name: str = "VPPrecond" - # Optimization - jit: bool = False - cuda_graphs: bool = False - amp_cpu: bool = False - amp_gpu: bool = True - torch_fx: bool = False - # Data type - bf16: bool = False - # Inference - onnx: bool = False - # Physics informed - func_torch: bool = False - auto_grad: bool = False - - -class VPPrecond(nn.Module): - """ - Preconditioning corresponding to the variance preserving (VP) formulation. - - Parameters - ---------- - img_resolution : int - Image resolution. - img_channels : int - Number of color channels. - label_dim : int - Number of class labels, 0 = unconditional, by default 0. - use_fp16 : bool - Execute the underlying model at FP16 precision?, by default False. - beta_d : float - Extent of the noise level schedule, by default 19.9. - beta_min : float - Initial slope of the noise level schedule, by default 0.1. - M : int - Original number of timesteps in the DDPM formulation, by default 1000. - epsilon_t : float - Minimum t-value used during training, by default 1e-5. - model_type :str - Class name of the underlying model, by default "SongUNet". - **model_kwargs : dict - Keyword arguments for the underlying model. - - Note - ---- - Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and - Poole, B., 2020. Score-based generative modeling through stochastic differential - equations. arXiv preprint arXiv:2011.13456. - """ - - def __init__( - self, - img_resolution: int, - img_channels: int, - label_dim: int = 0, - use_fp16: bool = False, - beta_d: float = 19.9, - beta_min: float = 0.1, - M: int = 1000, - epsilon_t: float = 1e-5, - model_type: str = "SongUNet", - **model_kwargs: dict, - ): - super().__init__() #meta=VPPrecondMetaData - self.img_resolution = img_resolution - self.img_channels = img_channels - self.label_dim = label_dim - self.use_fp16 = use_fp16 - self.beta_d = beta_d - self.beta_min = beta_min - self.M = M - self.epsilon_t = epsilon_t - self.sigma_min = float(self.sigma(epsilon_t)) - self.sigma_max = float(self.sigma(1)) - model_class = getattr(network_module, model_type) - self.model = model_class( - img_resolution=img_resolution, - in_channels=img_channels, - out_channels=img_channels, - label_dim=label_dim, - **model_kwargs, - ) # TODO needs better handling - - def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): - x = x.to(torch.float32) - sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) - class_labels = ( - None - if self.label_dim == 0 - else torch.zeros([1, self.label_dim], device=x.device) - if class_labels is None - else class_labels.to(torch.float32).reshape(-1, self.label_dim) - ) - dtype = ( - torch.float16 - if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") - else torch.float32 - ) - - c_skip = 1 - c_out = -sigma - c_in = 1 / (sigma**2 + 1).sqrt() - c_noise = (self.M - 1) * self.sigma_inv(sigma) - - F_x = self.model( - (c_in * x).to(dtype), - c_noise.flatten(), - class_labels=class_labels, - **model_kwargs, - ) - if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): - raise ValueError( - f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." - ) - - D_x = c_skip * x + c_out * F_x.to(torch.float32) - return D_x - - def sigma(self, t: Union[float, torch.Tensor]): - """ - Compute the sigma(t) value for a given t based on the VP formulation. - - The function calculates the noise level schedule for the diffusion process based - on the given parameters `beta_d` and `beta_min`. - - Parameters - ---------- - t : Union[float, torch.Tensor] - The timestep or set of timesteps for which to compute sigma(t). - - Returns - ------- - torch.Tensor - The computed sigma(t) value(s). - """ - t = torch.as_tensor(t) - return ((0.5 * self.beta_d * (t**2) + self.beta_min * t).exp() - 1).sqrt() - - def sigma_inv(self, sigma: Union[float, torch.Tensor]): - """ - Compute the inverse of the sigma function for a given sigma. - - This function effectively calculates t from a given sigma(t) based on the - parameters `beta_d` and `beta_min`. - - Parameters - ---------- - sigma : Union[float, torch.Tensor] - The sigma(t) value or set of sigma(t) values for which to compute the - inverse. - - Returns - ------- - torch.Tensor - The computed t value(s) corresponding to the provided sigma(t). - """ - sigma = torch.as_tensor(sigma) - return ( - (self.beta_min**2 + 2 * self.beta_d * (1 + sigma**2).log()).sqrt() - - self.beta_min - ) / self.beta_d - - def round_sigma(self, sigma: Union[float, List, torch.Tensor]): - """ - Convert a given sigma value(s) to a tensor representation. - - Parameters - ---------- - sigma : Union[float list, torch.Tensor] - The sigma value(s) to convert. - - Returns - ------- - torch.Tensor - The tensor representation of the provided sigma value(s). - """ - return torch.as_tensor(sigma) - - -@dataclass -class VEPrecondMetaData(ModelMetaData): - """VEPrecond meta data""" - - name: str = "VEPrecond" - # Optimization - jit: bool = False - cuda_graphs: bool = False - amp_cpu: bool = False - amp_gpu: bool = True - torch_fx: bool = False - # Data type - bf16: bool = False - # Inference - onnx: bool = False - # Physics informed - func_torch: bool = False - auto_grad: bool = False - - -class VEPrecond(nn.Module): - """ - Preconditioning corresponding to the variance exploding (VE) formulation. - - Parameters - ---------- - img_resolution : int - Image resolution. - img_channels : int - Number of color channels. - label_dim : int - Number of class labels, 0 = unconditional, by default 0. - use_fp16 : bool - Execute the underlying model at FP16 precision?, by default False. - sigma_min : float - Minimum supported noise level, by default 0.02. - sigma_max : float - Maximum supported noise level, by default 100.0. - model_type :str - Class name of the underlying model, by default "SongUNet". - **model_kwargs : dict - Keyword arguments for the underlying model. - - Note - ---- - Reference: Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and - Poole, B., 2020. Score-based generative modeling through stochastic differential - equations. arXiv preprint arXiv:2011.13456. - """ - - def __init__( - self, - img_resolution: int, - img_channels: int, - label_dim: int = 0, - use_fp16: bool = False, - sigma_min: float = 0.02, - sigma_max: float = 100.0, - model_type: str = "SongUNet", - **model_kwargs: dict, - ): - super().__init__() #meta=VEPrecondMetaData - self.img_resolution = img_resolution - self.img_channels = img_channels - self.label_dim = label_dim - self.use_fp16 = use_fp16 - self.sigma_min = sigma_min - self.sigma_max = sigma_max - model_class = getattr(network_module, model_type) - self.model = model_class( - img_resolution=img_resolution, - in_channels=img_channels, - out_channels=img_channels, - label_dim=label_dim, - **model_kwargs, - ) # TODO needs better handling - - def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): - x = x.to(torch.float32) - sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) - class_labels = ( - None - if self.label_dim == 0 - else torch.zeros([1, self.label_dim], device=x.device) - if class_labels is None - else class_labels.to(torch.float32).reshape(-1, self.label_dim) - ) - dtype = ( - torch.float16 - if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") - else torch.float32 - ) - - c_skip = 1 - c_out = sigma - c_in = 1 - c_noise = (0.5 * sigma).log() - - F_x = self.model( - (c_in * x).to(dtype), - c_noise.flatten(), - class_labels=class_labels, - **model_kwargs, - ) - if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): - raise ValueError( - f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." - ) - - D_x = c_skip * x + c_out * F_x.to(torch.float32) - return D_x - - def round_sigma(self, sigma: Union[float, List, torch.Tensor]): - """ - Convert a given sigma value(s) to a tensor representation. - - Parameters - ---------- - sigma : Union[float list, torch.Tensor] - The sigma value(s) to convert. - - Returns - ------- - torch.Tensor - The tensor representation of the provided sigma value(s). - """ - return torch.as_tensor(sigma) - - -@dataclass -class iDDPMPrecondMetaData(ModelMetaData): - """iDDPMPrecond meta data""" - - name: str = "iDDPMPrecond" - # Optimization - jit: bool = False - cuda_graphs: bool = False - amp_cpu: bool = False - amp_gpu: bool = True - torch_fx: bool = False - # Data type - bf16: bool = False - # Inference - onnx: bool = False - # Physics informed - func_torch: bool = False - auto_grad: bool = False - - -class iDDPMPrecond(nn.Module): - """ - Preconditioning corresponding to the improved DDPM (iDDPM) formulation. - - Parameters - ---------- - img_resolution : int - Image resolution. - img_channels : int - Number of color channels. - label_dim : int - Number of class labels, 0 = unconditional, by default 0. - use_fp16 : bool - Execute the underlying model at FP16 precision?, by default False. - C_1 : float - Timestep adjustment at low noise levels., by default 0.001. - C_2 : float - Timestep adjustment at high noise levels., by default 0.008. - M: int - Original number of timesteps in the DDPM formulation, by default 1000. - model_type :str - Class name of the underlying model, by default "DhariwalUNet". - **model_kwargs : dict - Keyword arguments for the underlying model. - - Note - ---- - Reference: Nichol, A.Q. and Dhariwal, P., 2021, July. Improved denoising diffusion - probabilistic models. In International Conference on Machine Learning - (pp. 8162-8171). PMLR. - """ - - def __init__( - self, - img_resolution, - img_channels, - label_dim=0, - use_fp16=False, - C_1=0.001, - C_2=0.008, - M=1000, - model_type="DhariwalUNet", - **model_kwargs, - ): - super().__init__() #meta=iDDPMPrecondMetaData - self.img_resolution = img_resolution - self.img_channels = img_channels - self.label_dim = label_dim - self.use_fp16 = use_fp16 - self.C_1 = C_1 - self.C_2 = C_2 - self.M = M - model_class = getattr(network_module, model_type) - self.model = model_class( - img_resolution=img_resolution, - in_channels=img_channels, - out_channels=img_channels * 2, - label_dim=label_dim, - **model_kwargs, - ) # TODO needs better handling - - u = torch.zeros(M + 1) - for j in range(M, 0, -1): # M, ..., 1 - u[j - 1] = ( - (u[j] ** 2 + 1) - / (self.alpha_bar(j - 1) / self.alpha_bar(j)).clip(min=C_1) - - 1 - ).sqrt() - self.register_buffer("u", u) - self.sigma_min = float(u[M - 1]) - self.sigma_max = float(u[0]) - - def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): - x = x.to(torch.float32) - sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) - class_labels = ( - None - if self.label_dim == 0 - else torch.zeros([1, self.label_dim], device=x.device) - if class_labels is None - else class_labels.to(torch.float32).reshape(-1, self.label_dim) - ) - dtype = ( - torch.float16 - if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") - else torch.float32 - ) - - c_skip = 1 - c_out = -sigma - c_in = 1 / (sigma**2 + 1).sqrt() - c_noise = ( - self.M - 1 - self.round_sigma(sigma, return_index=True).to(torch.float32) - ) - - F_x = self.model( - (c_in * x).to(dtype), - c_noise.flatten(), - class_labels=class_labels, - **model_kwargs, - ) - if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): - raise ValueError( - f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." - ) - - D_x = c_skip * x + c_out * F_x[:, : self.img_channels].to(torch.float32) - return D_x - - def alpha_bar(self, j): - """ - Compute the alpha_bar(j) value for a given j based on the iDDPM formulation. - - Parameters - ---------- - j : Union[int, torch.Tensor] - The timestep or set of timesteps for which to compute alpha_bar(j). - - Returns - ------- - torch.Tensor - The computed alpha_bar(j) value(s). - """ - j = torch.as_tensor(j) - return (0.5 * np.pi * j / self.M / (self.C_2 + 1)).sin() ** 2 - - def round_sigma(self, sigma, return_index=False): - """ - Round the provided sigma value(s) to the nearest value(s) in a - pre-defined set `u`. - - Parameters - ---------- - sigma : Union[float, list, torch.Tensor] - The sigma value(s) to round. - return_index : bool, optional - Whether to return the index/indices of the rounded value(s) in `u` instead - of the rounded value(s) themselves, by default False. - - Returns - ------- - torch.Tensor - The rounded sigma value(s) or their index/indices in `u`, depending on the - value of `return_index`. - """ - sigma = torch.as_tensor(sigma) - index = torch.cdist( - sigma.to(self.u.device).to(torch.float32).reshape(1, -1, 1), - self.u.reshape(1, -1, 1), - ).argmin(2) - result = index if return_index else self.u[index.flatten()].to(sigma.dtype) - return result.reshape(sigma.shape).to(sigma.device) - - -@dataclass -class EDMPrecondMetaData(ModelMetaData): - """EDMPrecond meta data""" - - name: str = "EDMPrecond" - # Optimization - jit: bool = False - cuda_graphs: bool = False - amp_cpu: bool = False - amp_gpu: bool = True - torch_fx: bool = False - # Data type - bf16: bool = False - # Inference - onnx: bool = False - # Physics informed - func_torch: bool = False - auto_grad: bool = False - - -class EDMPrecond(nn.Module): - """ - Improved preconditioning proposed in the paper "Elucidating the Design Space of - Diffusion-Based Generative Models" (EDM) - - Parameters - ---------- - img_resolution : int - Image resolution. - img_channels : int - Number of color channels (for both input and output). If your model - requires a different number of input or output chanels, - override this by passing either of the optional - img_in_channels or img_out_channels args - label_dim : int - Number of class labels, 0 = unconditional, by default 0. - use_fp16 : bool - Execute the underlying model at FP16 precision?, by default False. - sigma_min : float - Minimum supported noise level, by default 0.0. - sigma_max : float - Maximum supported noise level, by default inf. - sigma_data : float - Expected standard deviation of the training data, by default 0.5. - model_type :str - Class name of the underlying model, by default "DhariwalUNet". - img_in_channels: int - Optional setting for when number of input channels =/= number of output - channels. If set, will override img_channels for the input - This is useful in the case of additional (conditional) channels - img_out_channels: int - Optional setting for when number of input channels =/= number of output - channels. If set, will override img_channels for the output - **model_kwargs : dict - Keyword arguments for the underlying model. - - Note - ---- - Reference: Karras, T., Aittala, M., Aila, T. and Laine, S., 2022. Elucidating the - design space of diffusion-based generative models. Advances in Neural Information - Processing Systems, 35, pp.26565-26577. - """ - - def __init__( - self, - img_resolution, - img_channels, - label_dim=0, - use_fp16=False, - sigma_min=0.0, - sigma_max=float("inf"), - sigma_data=0.5, - model_type="DhariwalUNet", - img_in_channels=None, - img_out_channels=None, - **model_kwargs, - ): - super().__init__() #meta=EDMPrecondMetaData - self.img_resolution = img_resolution - if img_in_channels is not None: - img_in_channels = img_in_channels - else: - img_in_channels = img_channels - if img_out_channels is not None: - img_out_channels = img_out_channels - else: - img_out_channels = img_channels - - self.label_dim = label_dim - self.use_fp16 = use_fp16 - self.sigma_min = sigma_min - self.sigma_max = sigma_max - self.sigma_data = sigma_data - - model_class = getattr(network_module, model_type) - self.model = model_class( - img_resolution=img_resolution, - in_channels=img_in_channels, - out_channels=img_out_channels, - label_dim=label_dim, - **model_kwargs, - ) # TODO needs better handling - - def forward( - self, - x, - sigma, - condition=None, - class_labels=None, - force_fp32=False, - **model_kwargs, - ): - x = x.to(torch.float32) - sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) - class_labels = ( - None - if self.label_dim == 0 - else torch.zeros([1, self.label_dim], device=x.device) - if class_labels is None - else class_labels.to(torch.float32).reshape(-1, self.label_dim) - ) - dtype = ( - torch.float16 - if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") - else torch.float32 - ) - - c_skip = self.sigma_data**2 / (sigma**2 + self.sigma_data**2) - c_out = sigma * self.sigma_data / (sigma**2 + self.sigma_data**2).sqrt() - c_in = 1 / (self.sigma_data**2 + sigma**2).sqrt() - c_noise = sigma.log() / 4 - - arg = c_in * x - - if condition is not None: - arg = torch.cat([arg, condition], dim=1) - - F_x = self.model( - arg.to(dtype), - c_noise.flatten(), - class_labels=class_labels, - **model_kwargs, - ) - - if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): - raise ValueError( - f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." - ) - D_x = c_skip * x + c_out * F_x.to(torch.float32) - return D_x - - @staticmethod - def round_sigma(sigma: Union[float, List, torch.Tensor]): - """ - Convert a given sigma value(s) to a tensor representation. - - Parameters - ---------- - sigma : Union[float list, torch.Tensor] - The sigma value(s) to convert. - - Returns - ------- - torch.Tensor - The tensor representation of the provided sigma value(s). - """ - return torch.as_tensor(sigma) - -@dataclass -class EDMPrecondSuperResolutionMetaData(ModelMetaData): - """EDMPrecondSR meta data""" - - name: str = "EDMPrecondSuperResolution" - # Optimization - jit: bool = False - cuda_graphs: bool = False - amp_cpu: bool = False - amp_gpu: bool = True - torch_fx: bool = False - # Data type - bf16: bool = False - # Inference - onnx: bool = False - # Physics informed - func_torch: bool = False - auto_grad: bool = False - - class EDMPrecondSuperResolution(nn.Module): """ Improved preconditioning proposed in the paper "Elucidating the Design Space of Diffusion-Based Generative Models" (EDM). - This is a variant of `EDMPrecond` that is specifically designed for super-resolution + This is a variant of EDM Preconditioning that is specifically designed for super-resolution tasks. It wraps a neural network that predicts the denoised high-resolution image given a noisy high-resolution image, and additional conditioning that includes a low-resolution image, and a noise level. @@ -731,7 +55,7 @@ class EDMPrecondSuperResolution(nn.Module): by default False. model_type : str, optional Class name of the underlying model. Must be one of the following: - 'SongUNet', 'SongUNetPosEmbd', 'SongUNetPosLtEmbd', 'DhariwalUNet'. + 'SongUNet', 'SongUNetPosEmbd'. Defaults to 'SongUNetPosEmbd'. sigma_data : float, optional Expected standard deviation of the training data, by default 0.5. @@ -761,14 +85,14 @@ def __init__( img_out_channels: int, use_fp16: bool = False, model_type: Literal[ - "SongUNetPosEmbd", "SongUNetPosLtEmbd", "SongUNet", "DhariwalUNet" + "SongUNetPosEmbd", "SongUNet" ] = "SongUNetPosEmbd", sigma_data: float = 0.5, sigma_min=0.0, sigma_max=float("inf"), **model_kwargs: dict, ): - super().__init__() #meta=EDMPrecondSRMetaData + super().__init__() self.img_resolution = img_resolution self.img_in_channels = img_in_channels self.img_out_channels = img_out_channels @@ -929,460 +253,3 @@ def amp_mode(self, value: bool): if hasattr(sub_module, "amp_mode"): sub_module.amp_mode = value -# NOTE: This is a deprecated version of the EDMPrecondSuperResolution model. -# This was used to maintain backwards compatibility and allow loading old models. -@dataclass -class EDMPrecondSRMetaData(ModelMetaData): - """EDMPrecondSR meta data""" - - name: str = "EDMPrecondSR" - # Optimization - jit: bool = False - cuda_graphs: bool = False - amp_cpu: bool = False - amp_gpu: bool = True - torch_fx: bool = False - # Data type - bf16: bool = False - # Inference - onnx: bool = False - # Physics informed - func_torch: bool = False - auto_grad: bool = False - - -class EDMPrecondSR(EDMPrecondSuperResolution): - """ - Improved preconditioning proposed in the paper "Elucidating the Design Space of - Diffusion-Based Generative Models" (EDM) for super-resolution tasks - - Parameters - ---------- - img_resolution : int - Image resolution. - img_channels : int - Number of color channels. - img_in_channels : int - Number of input color channels. - img_out_channels : int - Number of output color channels. - use_fp16 : bool - Execute the underlying model at FP16 precision?, by default False. - sigma_min : float - Minimum supported noise level, by default 0.0. - sigma_max : float - Maximum supported noise level, by default inf. - sigma_data : float - Expected standard deviation of the training data, by default 0.5. - model_type :str - Class name of the underlying model, by default "SongUNetPosEmbd". - **model_kwargs : dict - Keyword arguments for the underlying model. - - Note - ---- - References: - - Karras, T., Aittala, M., Aila, T. and Laine, S., 2022. Elucidating the - design space of diffusion-based generative models. Advances in Neural Information - Processing Systems, 35, pp.26565-26577. - - Mardani, M., Brenowitz, N., Cohen, Y., Pathak, J., Chen, C.Y., - Liu, C.C.,Vahdat, A., Kashinath, K., Kautz, J. and Pritchard, M., 2023. - Generative Residual Diffusion Modeling for Km-scale Atmospheric Downscaling. - arXiv preprint arXiv:2309.15214. - """ - - def __init__( - self, - img_resolution, - img_channels, #deprecated - img_in_channels, - img_out_channels, - use_fp16=False, - sigma_min=0.0, - sigma_max=float("inf"), - sigma_data=0.5, - model_type="SongUNetPosEmbd", - scale_cond_input=True, #deprecated - **model_kwargs, - ): - warnings.warn( - "EDMPrecondSR is deprecated and will be removed in a future version. " - "Please use EDMPrecondSuperResolution instead.", - DeprecationWarning, - stacklevel=2, - ) - - if scale_cond_input: - warnings.warn( - "scale_cond_input=True does not properly scale the conditional input. " - "(see https://github.com/NVIDIA/modulus/issues/229). " - "This setup will be deprecated. " - "Please set scale_cond_input=False.", - DeprecationWarning, - ) - - super().__init__( - img_resolution=img_resolution, - img_in_channels=img_in_channels, - img_out_channels=img_out_channels, - use_fp16=use_fp16, - sigma_min=sigma_min, - sigma_max=sigma_max, - sigma_data=sigma_data, - model_type=model_type, - **model_kwargs, - ) - - # Store deprecated parameters for backward compatibility - self.img_channels = img_channels - self.scale_cond_input = scale_cond_input - - def forward( - self, - x, - img_lr, - sigma, - force_fp32=False, - **model_kwargs, - ): - """ - Forward pass of the EDMPrecondSR model wrapper. - - Parameters - ---------- - x : torch.Tensor - Noisy high-resolution image of shape (B, C_hr, H, W). - img_lr : torch.Tensor - Low-resolution conditioning image of shape (B, C_lr, H, W). - sigma : torch.Tensor - Noise level of shape (B) or (B, 1) or (B, 1, 1, 1). - force_fp32 : bool, optional - Whether to force FP32 precision regardless of the `use_fp16` attribute, - by default False. - **model_kwargs : dict - Additional keyword arguments to pass to the underlying model. - - Returns - ------- - torch.Tensor - Denoised high-resolution image of shape (B, C_hr, H, W). - """ - return super().forward( - x=x, img_lr=img_lr, sigma=sigma, force_fp32=force_fp32, **model_kwargs - ) - -class VEPrecond_dfsr(nn.Module): - """ - Preconditioning for dfsr model, modified from class VEPrecond, where the input - argument 'sigma' in forward propagation function is used to receive the timestep - of the backward diffusion process. - - Parameters - ---------- - img_resolution : int - Image resolution. - img_channels : int - Number of color channels. - label_dim : int - Number of class labels, 0 = unconditional, by default 0. - use_fp16 : bool - Execute the underlying model at FP16 precision?, by default False. - sigma_min : float - Minimum supported noise level, by default 0.02. - sigma_max : float - Maximum supported noise level, by default 100.0. - model_type :str - Class name of the underlying model, by default "SongUNet". - **model_kwargs : dict - Keyword arguments for the underlying model. - - Note - ---- - Reference: Ho J, Jain A, Abbeel P. Denoising diffusion probabilistic models. - Advances in neural information processing systems. 2020;33:6840-51. - """ - - def __init__( - self, - img_resolution: int, - img_channels: int, - label_dim: int = 0, - use_fp16: bool = False, - sigma_min: float = 0.02, - sigma_max: float = 100.0, - dataset_mean: float = 5.85e-05, - dataset_scale: float = 4.79, - model_type: str = "SongUNet", - **model_kwargs: dict, - ): - super().__init__() - self.img_resolution = img_resolution - self.img_channels = img_channels - self.label_dim = label_dim - self.use_fp16 = use_fp16 - model_class = getattr(network_module, model_type) - self.model = model_class( - img_resolution=img_resolution, - in_channels=self.img_channels, - out_channels=img_channels, - label_dim=label_dim, - **model_kwargs, - ) # TODO needs better handling - - def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): - x = x.to(torch.float32) - sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) - # print("sigma: ", sigma) - class_labels = ( - None - if self.label_dim == 0 - else torch.zeros([1, self.label_dim], device=x.device) - if class_labels is None - else class_labels.to(torch.float32).reshape(-1, self.label_dim) - ) - dtype = ( - torch.float16 - if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") - else torch.float32 - ) - - c_in = 1 - c_noise = sigma # Change the definitation of c_noise to avoid -inf values for zero sigma - - F_x = self.model( - (c_in * x).to(dtype), - c_noise.flatten(), - class_labels=class_labels, - **model_kwargs, - ) - - if F_x.dtype != dtype: - raise ValueError( - f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." - ) - - return F_x - - -class VEPrecond_dfsr_cond(nn.Module): - """ - Preconditioning for dfsr model with physics-informed conditioning input, modified - from class VEPrecond, where the input argument 'sigma' in forward propagation function - is used to receive the timestep of the backward diffusion process. The gradient of PDE - residual with respect to the vorticity in the governing Navier-Stokes equation is computed - as the physics-informed conditioning variable and is combined with the backward diffusion - timestep before being sent to the underlying model for noise prediction. - - Parameters - ---------- - img_resolution : int - Image resolution. - img_channels : int - Number of color channels. - label_dim : int - Number of class labels, 0 = unconditional, by default 0. - use_fp16 : bool - Execute the underlying model at FP16 precision?, by default False. - sigma_min : float - Minimum supported noise level, by default 0.02. - sigma_max : float - Maximum supported noise level, by default 100.0. - model_type :str - Class name of the underlying model, by default "SongUNet". - **model_kwargs : dict - Keyword arguments for the underlying model. - - Note - ---- - Reference: - [1] Song, Y., Sohl-Dickstein, J., Kingma, D.P., Kumar, A., Ermon, S. and - Poole, B., 2020. Score-based generative modeling through stochastic differential - equations. arXiv preprint arXiv:2011.13456. - [2] Shu D, Li Z, Farimani AB. A physics-informed diffusion model for high-fidelity - flow field reconstruction. Journal of Computational Physics. 2023 Apr 1;478:111972. - """ - - def __init__( - self, - img_resolution: int, - img_channels: int, - label_dim: int = 0, - use_fp16: bool = False, - sigma_min: float = 0.02, - sigma_max: float = 100.0, - dataset_mean: float = 5.85e-05, - dataset_scale: float = 4.79, - model_type: str = "SongUNet", - **model_kwargs: dict, - ): - super().__init__() - self.img_resolution = img_resolution - self.img_channels = img_channels - self.label_dim = label_dim - self.use_fp16 = use_fp16 - model_class = getattr(network_module, model_type) - self.model = model_class( - img_resolution=img_resolution, - in_channels=model_kwargs["model_channels"] * 2, - out_channels=img_channels, - label_dim=label_dim, - **model_kwargs, - ) # TODO needs better handling - - # modules to embed residual loss - self.conv_in = torch.nn.Conv2d( - img_channels, - model_kwargs["model_channels"], - kernel_size=3, - stride=1, - padding=1, - padding_mode="circular", - ) - self.emb_conv = torch.nn.Sequential( - torch.nn.Conv2d( - img_channels, - model_kwargs["model_channels"], - kernel_size=1, - stride=1, - padding=0, - ), - torch.nn.GELU(), - torch.nn.Conv2d( - model_kwargs["model_channels"], - model_kwargs["model_channels"], - kernel_size=3, - stride=1, - padding=1, - padding_mode="circular", - ), - ) - self.dataset_mean = dataset_mean - self.dataset_scale = dataset_scale - - def forward(self, x, sigma, class_labels=None, force_fp32=False, **model_kwargs): - x = x.to(torch.float32) - sigma = sigma.to(torch.float32).reshape(-1, 1, 1, 1) - class_labels = ( - None - if self.label_dim == 0 - else torch.zeros([1, self.label_dim], device=x.device) - if class_labels is None - else class_labels.to(torch.float32).reshape(-1, self.label_dim) - ) - dtype = ( - torch.float16 - if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") - else torch.float32 - ) - - c_in = 1 - c_noise = sigma - - # Compute physics-informed conditioning information using vorticity residual - dx = ( - self.voriticity_residual((x * self.dataset_scale + self.dataset_mean)) - / self.dataset_scale - ) - x = self.conv_in(x) - cond_emb = self.emb_conv(dx) - x = torch.cat((x, cond_emb), dim=1) - - F_x = self.model( - (c_in * x).to(dtype), - c_noise.flatten(), - class_labels=class_labels, - **model_kwargs, - ) - - if F_x.dtype != dtype: - raise ValueError( - f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." - ) - return F_x - - def voriticity_residual(self, w, re=1000.0, dt=1 / 32): - """ - Compute the gradient of PDE residual with respect to a given vorticity w using the - spectrum method. - - Parameters - ---------- - w: torch.Tensor - The fluid flow data sample (vorticity). - re: float - The value of Reynolds number used in the governing Navier-Stokes equation. - dt: float - Time step used to compute the time-derivative of vorticity included in the governing - Navier-Stokes equation. - - Returns - ------- - torch.Tensor - The computed vorticity gradient. - """ - - # w [b t h w] - w = w.clone() - w.requires_grad_(True) - nx = w.size(2) - device = w.device - - w_h = torch.fft.fft2(w[:, 1:-1], dim=[2, 3]) - # Wavenumbers in y-direction - k_max = nx // 2 - N = nx - k_x = ( - torch.cat( - ( - torch.arange(start=0, end=k_max, step=1, device=device), - torch.arange(start=-k_max, end=0, step=1, device=device), - ), - 0, - ) - .reshape(N, 1) - .repeat(1, N) - .reshape(1, 1, N, N) - ) - k_y = ( - torch.cat( - ( - torch.arange(start=0, end=k_max, step=1, device=device), - torch.arange(start=-k_max, end=0, step=1, device=device), - ), - 0, - ) - .reshape(1, N) - .repeat(N, 1) - .reshape(1, 1, N, N) - ) - # Negative Laplacian in Fourier space - lap = k_x**2 + k_y**2 - lap[..., 0, 0] = 1.0 - psi_h = w_h / lap - - u_h = 1j * k_y * psi_h - v_h = -1j * k_x * psi_h - wx_h = 1j * k_x * w_h - wy_h = 1j * k_y * w_h - wlap_h = -lap * w_h - - u = torch.fft.irfft2(u_h[..., :, : k_max + 1], dim=[2, 3]) - v = torch.fft.irfft2(v_h[..., :, : k_max + 1], dim=[2, 3]) - wx = torch.fft.irfft2(wx_h[..., :, : k_max + 1], dim=[2, 3]) - wy = torch.fft.irfft2(wy_h[..., :, : k_max + 1], dim=[2, 3]) - wlap = torch.fft.irfft2(wlap_h[..., :, : k_max + 1], dim=[2, 3]) - advection = u * wx + v * wy - - wt = (w[:, 2:, :, :] - w[:, :-2, :, :]) / (2 * dt) - - # establish forcing term - x = torch.linspace(0, 2 * np.pi, nx + 1, device=device) - x = x[0:-1] - X, Y = torch.meshgrid(x, x) - f = -4 * torch.cos(4 * Y) - - residual = wt + (advection - (1.0 / re) * wlap + 0.1 * w[:, 1:-1]) - f - residual_loss = (residual**2).mean() - dw = torch.autograd.grad(residual_loss, w)[0] - - return dw diff --git a/src/hirad/models/song_unet.py b/src/hirad/models/song_unet.py index a56f8613..ce0fedc5 100644 --- a/src/hirad/models/song_unet.py +++ b/src/hirad/models/song_unet.py @@ -38,25 +38,6 @@ PositionalEmbedding, UNetBlock, ) -from .meta import ModelMetaData - - -@dataclass -class MetaData(ModelMetaData): - name: str = "SongUNet" - # Optimization - jit: bool = False - cuda_graphs: bool = False - amp_cpu: bool = False - amp_gpu: bool = True - torch_fx: bool = False - # Data type - bf16: bool = True - # Inference - onnx: bool = False - # Physics informed - func_torch: bool = False - auto_grad: bool = False class SongUNet(nn.Module): @@ -1023,228 +1004,3 @@ def _get_lead_time_embedding(self): ) # (lead_time_steps, lead_time_channels, img_shape_y, img_shape_x) return grid - -class SongUNetPosLtEmbd(SongUNetPosEmbd): - """ - This model is adapted from SongUNetPosEmbd, with the incorporation of lead-time aware - embeddings. The lead-time embedding is activated by setting the - `lead_time_channels` and `lead_time_steps` parameters. - - Like SongUNetPosEmbd, this model provides two methods for selecting positional embeddings: - 1. Using a selector function (preferred method). See - :meth:`positional_embedding_selector` for details. - 2. Using global indices. See :meth:`positional_embedding_indexing` for - details. - - Parameters - ----------- - img_resolution : Union[List[int], int] - The resolution of the input/output image. Can be a single int for square images - or a list [height, width] for rectangular images. - in_channels : int - Number of channels in the input image. - out_channels : int - Number of channels in the output image. - label_dim : int, optional - Number of class labels; 0 indicates an unconditional model. By default 0. - augment_dim : int, optional - Dimensionality of augmentation labels; 0 means no augmentation. By default 0. - model_channels : int, optional - Base multiplier for the number of channels across the network. By default 128. - channel_mult : List[int], optional - Per-resolution multipliers for the number of channels. By default [1,2,2,2,2]. - channel_mult_emb : int, optional - Multiplier for the dimensionality of the embedding vector. By default 4. - num_blocks : int, optional - Number of residual blocks per resolution. By default 4. - attn_resolutions : List[int], optional - Resolutions at which self-attention layers are applied. By default [28]. - dropout : float, optional - Dropout probability applied to intermediate activations. By default 0.13. - label_dropout : float, optional - Dropout probability of class labels for classifier-free guidance. By default 0.0. - embedding_type : str, optional - Timestep embedding type: 'positional' for DDPM++, 'fourier' for NCSN++. - By default 'positional'. - channel_mult_noise : int, optional - Timestep embedding size: 1 for DDPM++, 2 for NCSN++. By default 1. - encoder_type : str, optional - Encoder architecture: 'standard' for DDPM++, 'residual' for NCSN++, 'skip' for skip connections. - By default 'standard'. - decoder_type : str, optional - Decoder architecture: 'standard' or 'skip' for skip connections. By default 'standard'. - resample_filter : List[int], optional - Resampling filter coefficients: [1,1] for DDPM++, [1,3,3,1] for NCSN++. By default [1,1]. - gridtype : str, optional - Type of positional grid to use: 'sinusoidal', 'learnable', 'linear', or 'test'. - Controls how positional information is encoded. By default 'sinusoidal'. - N_grid_channels : int, optional - Number of channels in the positional embedding grid. For 'sinusoidal' must be 4 or - multiple of 4. For 'linear' must be 2. By default 4. - lead_time_channels : int, optional - Number of channels in the lead time embedding. These are learned embeddings that - encode temporal forecast information. By default None. - lead_time_steps : int, optional - Number of discrete lead time steps to support. Each step gets its own learned - embedding vector. By default 9. - prob_channels : List[int], optional - Indices of probability output channels that should use softmax activation. - Used for classification outputs. By default empty list. - checkpoint_level : int, optional - Number of layers that should use gradient checkpointing (0 disables checkpointing). - Higher values trade memory for computation. By default 0. - additive_pos_embed : bool, optional - If True, adds a learned positional embedding after the first convolution layer. - Used in StormCast model. By default False. - use_apex_gn : bool, optional - A boolean flag indicating whether we want to use Apex GroupNorm for NHWC layout. - Need to set this as False on cpu. Defaults to False. - act : str, optional - The activation function to use when fusing activation with GroupNorm. Defaults to None. - profile_mode: - A boolean flag indicating whether to enable all nvtx annotations during profiling. - amp_mode : bool, optional - A boolean flag indicating whether mixed-precision (AMP) training is enabled. Defaults to False. - - - Note - ----- - Equivalent to the original implementation by Song et al., available at - https://github.com/yang-song/score_sde_pytorch - - Example - -------- - >>> import torch - >>> from physicsnemo.models.diffusion.song_unet import SongUNetPosLtEmbd - >>> from physicsnemo.utils.patching import GridPatching2D - >>> - >>> # Model initialization - in_channels must include original input channels (2), - >>> # positional embedding channels (N_grid_channels=4 by default) and - >>> # lead time embedding channels (4) - >>> model = SongUNetPosLtEmbd( - ... img_resolution=16, in_channels=2+4+4, out_channels=2, - ... lead_time_channels=4, lead_time_steps=9 - ... ) - >>> noise_labels = torch.randn([1]) - >>> class_labels = torch.randint(0, 1, (1, 1)) - >>> # The input has only the original 2 channels - positional embeddings and - >>> # lead time embeddings are added automatically inside the forward method - >>> input_image = torch.ones([1, 2, 16, 16]) - >>> lead_time_label = torch.tensor([3]) - >>> output_image = model( - ... input_image, noise_labels, class_labels, - ... lead_time_label=lead_time_label - ... ) - >>> output_image.shape - torch.Size([1, 2, 16, 16]) - >>> - >>> # Using global_index to select all the positional and lead time embeddings - >>> patching = GridPatching2D(img_shape=(16, 16), patch_shape=(16, 16)) - >>> global_index = patching.global_index(batch_size=1) - >>> output_image = model( - ... input_image, noise_labels, class_labels, - ... lead_time_label=lead_time_label, - ... global_index=global_index - ... ) - >>> output_image.shape - torch.Size([1, 2, 16, 16]) - - # NOTE: commented out doctest for embedding_selector due to compatibility issue - # >>> - # >>> # Using custom embedding selector to select all the positional and lead time embeddings - # >>> def patch_embedding_selector(emb): - # ... return patching.apply(emb[None].expand(1, -1, -1, -1)) - # >>> output_image = model( - # ... input_image, noise_labels, class_labels, - # ... lead_time_label=lead_time_label, - # ... embedding_selector=patch_embedding_selector - # ... ) - # >>> output_image.shape - # torch.Size([1, 2, 16, 16]) - - """ - - def __init__( - self, - img_resolution: Union[List[int], int], - in_channels: int, - out_channels: int, - label_dim: int = 0, - augment_dim: int = 0, - model_channels: int = 128, - channel_mult: List[int] = [1, 2, 2, 2, 2], - channel_mult_emb: int = 4, - num_blocks: int = 4, - attn_resolutions: List[int] = [28], - dropout: float = 0.13, - label_dropout: float = 0.0, - embedding_type: str = "positional", - channel_mult_noise: int = 1, - encoder_type: str = "standard", - decoder_type: str = "standard", - resample_filter: List[int] = [1, 1], - gridtype: str = "sinusoidal", - N_grid_channels: int = 4, - lead_time_channels: int = None, - lead_time_steps: int = 9, - prob_channels: List[int] = [], - checkpoint_level: int = 0, - additive_pos_embed: bool = False, - use_apex_gn: bool = False, - act: str = "silu", - profile_mode: bool = False, - amp_mode: bool = False, - ): - super().__init__( - img_resolution, - in_channels, - out_channels, - label_dim, - augment_dim, - model_channels, - channel_mult, - channel_mult_emb, - num_blocks, - attn_resolutions, - dropout, - label_dropout, - embedding_type, - channel_mult_noise, - encoder_type, - decoder_type, - resample_filter, - gridtype, - N_grid_channels, - checkpoint_level, - additive_pos_embed, - use_apex_gn, - act, - profile_mode, - amp_mode, - True, # Note: lead_time_mode=True is enforced here - lead_time_channels, - lead_time_steps, - prob_channels, - ) - - def forward( - self, - x, - noise_labels, - class_labels, - lead_time_label=None, - global_index: Optional[torch.Tensor] = None, - embedding_selector: Optional[Callable] = None, - augment_labels=None, - ): - return super().forward( - x=x, - noise_labels=noise_labels, - class_labels=class_labels, - global_index=global_index, - embedding_selector=embedding_selector, - augment_labels=augment_labels, - lead_time_label=lead_time_label, - ) - - # Nothing else is re-implemented, because everything is already in the parent SongUNetPosEmb \ No newline at end of file diff --git a/src/hirad/models/unet.py b/src/hirad/models/unet.py index f1edc6b1..ac645cfe 100644 --- a/src/hirad/models/unet.py +++ b/src/hirad/models/unet.py @@ -21,29 +21,9 @@ import torch import torch.nn as nn -from .meta import ModelMetaData - network_module = importlib.import_module("hirad.models") -@dataclass -class MetaData(ModelMetaData): - name: str = "UNet" - # Optimization - jit: bool = False - cuda_graphs: bool = False - amp_cpu: bool = False - amp_gpu: bool = True - torch_fx: bool = False - # Data type - bf16: bool = True - # Inference - onnx: bool = False - # Physics informed - func_torch: bool = False - auto_grad: bool = False - - class UNet(nn.Module): # TODO a lot of redundancy, need to clean up """ U-Net Wrapper for CorrDiff deterministic regression model. @@ -61,7 +41,7 @@ class UNet(nn.Module): # TODO a lot of redundancy, need to clean up Execute the underlying model at FP16 precision, by default False. model_type: str, optional Class name of the underlying model. Must be one of the following: - 'SongUNet', 'SongUNetPosEmbd', 'SongUNetPosLtEmbd', 'DhariwalUNet'. + 'SongUNet', 'SongUNetPosEmbd'. Defaults to 'SongUNetPosEmbd'. **model_kwargs : dict Keyword arguments passed to the underlying model `__init__` method. @@ -70,8 +50,7 @@ class UNet(nn.Module): # TODO a lot of redundancy, need to clean up -------- For information on model types and their usage: :class:`~models.song_unet.SongUNet`: Basic U-Net for diffusion models - :class:`~models.song_unet.SongUNetPosEmbd`: U-Net with positional embeddings - :class:`~models.song_unet.SongUNetPosLtEmbd`: U-Net with positional and lead-time embeddings + :class:`~models.song_unet.SongUNetPosEmbd`: U-Net with positional embeddings and lead time embeddings Please refer to the documentation of these classes for details on how to call and use these models directly. @@ -127,7 +106,7 @@ def __init__( img_out_channels: int, use_fp16: bool = False, model_type: Literal[ - "SongUNetPosEmbd", "SongUNetPosLtEmbd", "SongUNet", "DhariwalUNet" + "SongUNetPosEmbd", "SongUNet" ] = "SongUNetPosEmbd", **model_kwargs: dict, ): @@ -292,103 +271,3 @@ def amp_mode(self, value: bool): if hasattr(sub_module, "amp_mode"): sub_module.amp_mode = value -# TODO: implement amp_mode property for StormCastUNet (same as UNet) -class StormCastUNet(nn.Module): - """ - U-Net wrapper for StormCast; used so the same Song U-Net network can be re-used for this model. - - Parameters - ----------- - img_resolution : int or List[int] - The resolution of the input/output image. - img_channels : int - Number of color channels. - img_in_channels : int - Number of input color channels. - img_out_channels : int - Number of output color channels. - use_fp16: bool, optional - Execute the underlying model at FP16 precision?, by default False. - sigma_min: float, optional - Minimum supported noise level, by default 0. - sigma_max: float, optional - Maximum supported noise level, by default float('inf'). - sigma_data: float, optional - Expected standard deviation of the training data, by default 0.5. - model_type: str, optional - Class name of the underlying model, by default 'SongUNet'. - **model_kwargs : dict - Keyword arguments for the underlying model. - - """ - - def __init__( - self, - img_resolution, - img_in_channels, - img_out_channels, - use_fp16=False, - sigma_min=0, - sigma_max=float("inf"), - sigma_data=0.5, - model_type="SongUNet", - **model_kwargs, - ): - super().__init__() #meta=MetaData("StormCastUNet") - - if isinstance(img_resolution, int): - self.img_shape_x = self.img_shape_y = img_resolution - else: - self.img_shape_x = img_resolution[0] - self.img_shape_y = img_resolution[1] - - self.img_in_channels = img_in_channels - self.img_out_channels = img_out_channels - - self.use_fp16 = use_fp16 - self.sigma_min = sigma_min - self.sigma_max = sigma_max - self.sigma_data = sigma_data - model_class = getattr(network_module, model_type) - self.model = model_class( - img_resolution=img_resolution, - in_channels=img_in_channels, - out_channels=img_out_channels, - **model_kwargs, - ) - - def forward(self, x, force_fp32=False, **model_kwargs): - """Run a forward pass of the StormCast regression U-Net. - - Args: - x (torch.Tensor): input to the U-Net - force_fp32 (bool, optional): force casting to fp_32 if True. Defaults to False. - - Raises: - ValueError: If input data type is a mismatch with provided options - - Returns: - D_x (torch.Tensor): Output (prediction) of the U-Net - """ - - x = x.to(torch.float32) - dtype = ( - torch.float16 - if (self.use_fp16 and not force_fp32 and x.device.type == "cuda") - else torch.float32 - ) - - F_x = self.model( - x.to(dtype), - torch.zeros(x.shape[0], dtype=x.dtype, device=x.device), - class_labels=None, - **model_kwargs, - ) - - if (F_x.dtype != dtype) and not torch.is_autocast_enabled(): - raise ValueError( - f"Expected the dtype to be {dtype}, but got {F_x.dtype} instead." - ) - - D_x = F_x.to(torch.float32) - return D_x diff --git a/src/hirad/models/utils.py b/src/hirad/models/utils.py deleted file mode 100644 index e1cde9d8..00000000 --- a/src/hirad/models/utils.py +++ /dev/null @@ -1,66 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. -# SPDX-FileCopyrightText: All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import numpy as np -import torch - - -def weight_init(shape: tuple, mode: str, fan_in: int, fan_out: int): - """ - Unified routine for initializing weights and biases. - This function provides a unified interface for various weight initialization - strategies like Xavier (Glorot) and Kaiming (He) initializations. - - Parameters - ---------- - shape : tuple - The shape of the tensor to initialize. It could represent weights or biases - of a layer in a neural network. - mode : str - The mode/type of initialization to use. Supported values are: - - "xavier_uniform": Xavier (Glorot) uniform initialization. - - "xavier_normal": Xavier (Glorot) normal initialization. - - "kaiming_uniform": Kaiming (He) uniform initialization. - - "kaiming_normal": Kaiming (He) normal initialization. - fan_in : int - The number of input units in the weight tensor. For convolutional layers, - this typically represents the number of input channels times the kernel height - times the kernel width. - fan_out : int - The number of output units in the weight tensor. For convolutional layers, - this typically represents the number of output channels times the kernel height - times the kernel width. - - Returns - ------- - torch.Tensor - The initialized tensor based on the specified mode. - - Raises - ------ - ValueError - If the provided `mode` is not one of the supported initialization modes. - """ - if mode == "xavier_uniform": - return np.sqrt(6 / (fan_in + fan_out)) * (torch.rand(*shape) * 2 - 1) - if mode == "xavier_normal": - return np.sqrt(2 / (fan_in + fan_out)) * torch.randn(*shape) - if mode == "kaiming_uniform": - return np.sqrt(3 / fan_in) * (torch.rand(*shape) * 2 - 1) - if mode == "kaiming_normal": - return np.sqrt(1 / fan_in) * torch.randn(*shape) - raise ValueError(f'Invalid init mode "{mode}"') diff --git a/src/hirad/training/train.py b/src/hirad/training/train.py index b2cfdc9a..27346ea7 100755 --- a/src/hirad/training/train.py +++ b/src/hirad/training/train.py @@ -29,7 +29,7 @@ from hirad.utils.env_info import get_env_info, flatten_dict from hirad.utils.dataset_utils import regrid_icon_to_rotlatlon from hirad.models import UNet, EDMPrecondSuperResolution -from hirad.losses import ResidualLoss, RegressionLoss, RegressionLossCE +from hirad.losses import ResidualLoss, RegressionLoss from hirad.datasets import init_train_valid_datasets_from_config, get_dataset_and_sampler_inference from hirad.inference import Generator @@ -393,8 +393,6 @@ def main(cfg: DictConfig) -> None: ) elif cfg.model.name == "regression": loss_fn = RegressionLoss() - elif cfg.model.name == "lt_aware_ce_regression": - loss_fn = RegressionLossCE(prob_channels=prob_channels) # Instantiate the optimizer optimizer = torch.optim.Adam( diff --git a/src/hirad/utils/train_helpers.py b/src/hirad/utils/train_helpers.py index d8abc570..f3eaca0d 100644 --- a/src/hirad/utils/train_helpers.py +++ b/src/hirad/utils/train_helpers.py @@ -130,7 +130,11 @@ def is_time_for_periodic_task( else: return cur_nimg % freq < batch_size - +#TODO: When mlflow is working locally on a multi-node job, it runs into issue with writing +# to the same SQLite file. The current workaround is to only log system metrics from the +# main process. Find a workaround to log system metrics from all processes without causing +# conflicts in the SQLite file, such as using separate files for each process or using +# a different backend for mlflow tracking. def init_mlflow(cfg: DictConfig, dist: DistributedManager, write_dir: str=".") -> None: if dist.rank==0: print("Started activating initial mlflow run") @@ -141,7 +145,7 @@ def init_mlflow(cfg: DictConfig, dist: DistributedManager, write_dir: str=".") - if os.path.isfile(os.path.join(write_dir, 'run_id.txt')): with open(os.path.join(write_dir, 'run_id.txt'),'r') as f: run_id = f.read() - if dist.world_size<=4: + if dist.world_size<=4 or cfg.logging.uri is None: mlflow.system_metrics.set_system_metrics_node_id("node-0") if run_id: mlflow.start_run(run_id=run_id, log_system_metrics=False if dist.world_size>4 else True) @@ -162,14 +166,15 @@ def init_mlflow(cfg: DictConfig, dist: DistributedManager, write_dir: str=".") - if dist.world_size > 4: torch.distributed.barrier() - if (dist.rank!=0 and dist._local_rank==0) or (dist.rank==1 and dist.world_size>4): - print("Started actvating sub mlflow run.") - if cfg.logging.uri is not None: + if cfg.logging.uri is not None: + if (dist.rank!=0 and dist._local_rank==0) or (dist.rank==1 and dist.world_size>4): + print("Started actvating sub mlflow run.") + mlflow.set_tracking_uri(cfg.logging.uri) - mlflow.system_metrics.set_system_metrics_node_id(f"node-{(dist.rank//4)}" - if dist.rank!=1 - else "node-0") - mlflow.set_experiment(experiment_name=cfg.logging.experiment_name) - with open(os.path.join(write_dir, "run_id.txt"), 'r') as f: - run_id = f.read() - mlflow.start_run(run_id=run_id, log_system_metrics=True) + mlflow.system_metrics.set_system_metrics_node_id(f"node-{(dist.rank//4)}" + if dist.rank!=1 + else "node-0") + mlflow.set_experiment(experiment_name=cfg.logging.experiment_name) + with open(os.path.join(write_dir, "run_id.txt"), 'r') as f: + run_id = f.read() + mlflow.start_run(run_id=run_id, log_system_metrics=True) From d9ae1dc0b94cf5eec20a0caf2e9843ab99a4fe6e Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 17 Mar 2026 17:34:24 +0100 Subject: [PATCH 260/302] update environment --- ci/docker/Dockerfile | 38 +++++--------------------------------- ci/edf/modulus_env.toml | 7 ++++--- 2 files changed, 9 insertions(+), 36 deletions(-) diff --git a/ci/docker/Dockerfile b/ci/docker/Dockerfile index 4772d76d..696fe0b7 100644 --- a/ci/docker/Dockerfile +++ b/ci/docker/Dockerfile @@ -1,41 +1,13 @@ -# Following some suggestions in https://meteoswiss.atlassian.net/wiki/spaces/APN/pages/719684202/Clariden+Alps+environment+setup - -#FROM ubuntu:22.04 as builder -FROM nvcr.io/nvidia/pytorch:25.01-py3 - -COPY . /src +#FROM physicsnemo-cscs as builder +FROM jfrog.svc.cscs.ch/docker-group-csstaff/alps-images/ngc-physicsnemo:25.11-alps4-dev # setup -RUN apt-get update && apt-get install python3-pip python3-venv -y RUN pip install --upgrade \ pip - #ninja - #wheel - #packaging - #setuptools - -# update flash-attn -RUN MAX_JOBS=16 pip install --upgrade --no-build-isolation \ - flash-attn==2.7.4.post1 -v -# install the rest of dependencies -# TODO: Factor pydeps into a separate file(s) -# TODO: Add versions for things +# install dependencies RUN pip install \ anemoi-datasets \ cartopy \ - matplotlib \ - numpy \ - pandas \ - scipy \ - torch - - -# replace pynvml with nvidia-ml-py -RUN pip uninstall -y pynvml && pip install nvidia-ml-py - -#CMD ["python3.11" "src/input_data/interpolate_basic_test.py"] - - - - + mlflow \ + xskillscore diff --git a/ci/edf/modulus_env.toml b/ci/edf/modulus_env.toml index 55f43d8f..6227a244 100644 --- a/ci/edf/modulus_env.toml +++ b/ci/edf/modulus_env.toml @@ -1,10 +1,11 @@ -image = "/capstor/scratch/cscs/pstamenk/hirad.sqsh" +image = "/capstor/scratch/cscs/pstamenk/corr_diff.sqsh" mounts = ["/capstor", "/iopsstor", "/users"] # The initial directory in the container. workdir = "${PWD}" +[env] +PMIX_MCA_psec = "native" [annotations] -com.hooks.aws_ofi_nccl.enabled = "true" -com.hooks.aws_ofi_nccl.variant = "cuda12" \ No newline at end of file +com.hooks.cxi.enabled = "false" From 7b79229ae76d5886dcd92798fcf2e96bfdbb398e Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 17 Mar 2026 17:48:15 +0100 Subject: [PATCH 261/302] update sbatch scripts and configs for training, generation and validation --- src/hirad/conf/dataset/anemoi_era_cosmo.yaml | 2 +- src/hirad/conf/dataset/anemoi_era_real.yaml | 13 +++++++++---- .../conf/dataset/anemoi_era_real_inference.yaml | 17 +++++++++++++++++ src/hirad/conf/eval_real.yaml | 10 ++++++++-- src/hirad/conf/generate_era_cosmo.yaml | 2 +- src/hirad/conf/generate_era_real.yaml | 2 +- .../conf/training/era_cosmo_diffusion.yaml | 8 +++----- .../training/era_cosmo_diffusion_patched.yaml | 2 -- .../conf/training/era_cosmo_regression.yaml | 8 +++----- .../training/era_real_diffusion_patched.yaml | 8 +++----- .../conf/training/era_real_regression.yaml | 6 ++---- .../conf/training_era_cosmo_diffusion.yaml | 2 +- .../training_era_cosmo_diffusion_patched.yaml | 2 +- .../conf/training_era_cosmo_regression.yaml | 2 +- .../training_era_real_diffusion_patched.yaml | 2 +- .../conf/training_era_real_regression.yaml | 2 +- src/hirad/eval_precip.sh | 4 ++-- src/hirad/eval_wind.sh | 4 ++-- src/hirad/generate.sh | 6 +++--- src/hirad/inference/deterministic_sampler.py | 2 +- src/hirad/models/preconditioning.py | 5 +---- src/hirad/snapshots.sh | 4 ++-- src/hirad/train_diffusion.sh | 6 +++--- src/hirad/train_regression.sh | 6 +++--- 24 files changed, 70 insertions(+), 55 deletions(-) create mode 100644 src/hirad/conf/dataset/anemoi_era_real_inference.yaml diff --git a/src/hirad/conf/dataset/anemoi_era_cosmo.yaml b/src/hirad/conf/dataset/anemoi_era_cosmo.yaml index dac61d14..e3af9871 100644 --- a/src/hirad/conf/dataset/anemoi_era_cosmo.yaml +++ b/src/hirad/conf/dataset/anemoi_era_cosmo.yaml @@ -1,7 +1,7 @@ type: anemoi_era5_cosmo input_anemoi_dataset_path: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' corrected_tp_path: '/capstor/scratch/cscs/mmcgloho/basic-numpy/copernicus-era-1h/copernicus-interpolated/' -target_anemoi_dataset_path: '/capstor/store/mch/msopr/hirad-gen/anemoi-datasets/mch-co2-an-archive-0p02-2015-2020-1h-v3-pl13.zarr' +target_anemoi_dataset_path: '/capstor/store/cscs/pasc/c38/anemoi-datasets/mch-co2-an-archive-0p02-2015-2020-1h-v3-pl13.zarr' input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] output_channel_names: [2t, 10u, 10v, tp] static_channel_names: ['hsurf'] diff --git a/src/hirad/conf/dataset/anemoi_era_real.yaml b/src/hirad/conf/dataset/anemoi_era_real.yaml index 9d41dc07..2010f35e 100644 --- a/src/hirad/conf/dataset/anemoi_era_real.yaml +++ b/src/hirad/conf/dataset/anemoi_era_real.yaml @@ -1,10 +1,11 @@ type: anemoi_era5_real -input_anemoi_dataset_path: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' -corrected_tp_path: '/capstor/scratch/cscs/mmcgloho/basic-numpy/copernicus-era-1h/copernicus-interpolated/' +# input_anemoi_dataset_path: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +input_anemoi_dataset_path: '/capstor/store/mch/msopr/ml/datasets/aifs-ea-an-oper-0001-mars-n320-1979-2024-1h-v2-with-era51.zarr' +# corrected_tp_path: '/capstor/scratch/cscs/mmcgloho/basic-numpy/copernicus-era-1h/copernicus-interpolated/' target_anemoi_dataset_path: '/capstor/store/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-v1.0.zarr' input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] output_channel_names: [2t, 10u, 10v, tp] -# static_channel_names: ['z'] +static_channel_names: ['FIS'] transform_channels: ['tp-box_cox_025'] transform_input_means: {'tp-box_cox_025': -3.815209745618941} transform_input_stdevs: {'tp-box_cox_025': 0.22851179478814418} @@ -12,4 +13,8 @@ transform_output_means: {'tp-box_cox_025': -3.8121187083242556} transform_output_stdevs: {'tp-box_cox_025': 0.3345851858215482} n_month_hour_channels: 4 start_date: '2005-01-02' -end_date: '2020-12-31' \ No newline at end of file +end_date: '2020-12-31' +trim_edge: 41 +validation: True +validation_start_date: '2021-01-01' +validation_end_date: '2021-12-31' diff --git a/src/hirad/conf/dataset/anemoi_era_real_inference.yaml b/src/hirad/conf/dataset/anemoi_era_real_inference.yaml new file mode 100644 index 00000000..7d0380b4 --- /dev/null +++ b/src/hirad/conf/dataset/anemoi_era_real_inference.yaml @@ -0,0 +1,17 @@ +type: anemoi_era5_real +# input_anemoi_dataset_path: '/capstor/store/cscs/swissai/weatherbench/aifs-ea-an-oper-0001-mars-n320-1979-2023-1h-v1-with-ERA51.zarr' +input_anemoi_dataset_path: '/capstor/store/mch/msopr/ml/datasets/aifs-ea-an-oper-0001-mars-n320-1979-2024-1h-v2-with-era51.zarr' +# corrected_tp_path: '/capstor/scratch/cscs/mmcgloho/basic-numpy/copernicus-era-1h/copernicus-interpolated/' +target_anemoi_dataset_path: '/capstor/store/mch/msopr/ml/datasets/mch-realch1-fdb-1km-2005-2025-1h-pl13-v1.0.zarr' +input_channel_names: [2t, 10u, 10v, tcw, t_850, z_850, u_850, v_850, t_500, z_500, u_500, v_500, tp] +output_channel_names: [2t, 10u, 10v, tp] +static_channel_names: ['FIS'] +transform_channels: ['tp-box_cox_025'] +transform_input_means: {'tp-box_cox_025': -3.815209745618941} +transform_input_stdevs: {'tp-box_cox_025': 0.22851179478814418} +transform_output_means: {'tp-box_cox_025': -3.8121187083242556} +transform_output_stdevs: {'tp-box_cox_025': 0.3345851858215482} +n_month_hour_channels: 4 +start_date: '2023-01-01' +end_date: '2023-12-31' +trim_edge: 41 diff --git a/src/hirad/conf/eval_real.yaml b/src/hirad/conf/eval_real.yaml index 15f61db0..19ab0563 100644 --- a/src/hirad/conf/eval_real.yaml +++ b/src/hirad/conf/eval_real.yaml @@ -8,7 +8,7 @@ conv_factor_hourly: 1000 # Convert precip of ERA5 from meters to mm/h conv_factor: 24000 # Convert the precip of ERA5 from meters/h to mm/day wet_threshold: 0.1 # Threshold for wet-hour in mm/h log_interval: 24 # Log progress every N timesteps -land_sea_mask_path: '/path/to/land_sea_mask.npy' # Path to land-sea mask numpy file +land_sea_mask_path: '/capstor/store/cscs/pasc/c38/real_grid_info/lsm_real.npy' # Path to land-sea mask numpy file # Constants describing the grid for plotting lat_start: -4.42 @@ -17,9 +17,15 @@ lat_step: 0.01 lon_start: -6.82 lon_end: 4.80 lon_step: 0.01 -relax_zone: 19 +relax_zone: 41 height: 704 width: 1088 +# If data was generated in several steps, you HAVE TO SPECIFY TIME STEPS BELOW +# If you don't want to evaluate all generated samples, you can specify a range of time steps to evaluate. +# This is useful for debugging or if you only want to evaluate a subset of the generated data. +# Make sure that generated samples are available for the specified time steps. +# times_range: ['20230601-0000','20230831-2300',1] + # List of channels to evaluate/plot - comment out if all channels are to be used # plot_channels: ['2t', '10u', '10v', 'tp', 't_700', 'u_700', 'v_700', 'z_700', 'q_700', 'w_700'] \ No newline at end of file diff --git a/src/hirad/conf/generate_era_cosmo.yaml b/src/hirad/conf/generate_era_cosmo.yaml index 8c9b23fb..b3365aab 100644 --- a/src/hirad/conf/generate_era_cosmo.yaml +++ b/src/hirad/conf/generate_era_cosmo.yaml @@ -9,7 +9,7 @@ hydra: defaults: - _self_ # Dataset - - dataset/era_cosmo_inference + - dataset/anemoi_era_cosmo_inference # Sampler - sampler/stochastic diff --git a/src/hirad/conf/generate_era_real.yaml b/src/hirad/conf/generate_era_real.yaml index 0a4c0467..20f48b3b 100644 --- a/src/hirad/conf/generate_era_real.yaml +++ b/src/hirad/conf/generate_era_real.yaml @@ -9,7 +9,7 @@ hydra: defaults: - _self_ # Dataset - - dataset/era_real_inference + - dataset/anemoi_era_real_inference # Sampler - sampler/stochastic diff --git a/src/hirad/conf/training/era_cosmo_diffusion.yaml b/src/hirad/conf/training/era_cosmo_diffusion.yaml index 41c3cc0b..eed75c27 100644 --- a/src/hirad/conf/training/era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training/era_cosmo_diffusion.yaml @@ -4,7 +4,7 @@ hp: # Training duration based on the number of processed samples total_batch_size: "auto" # Total batch size - batch_size_per_gpu: 6 + batch_size_per_gpu: 20 # Batch size per GPU lr: 0.0002 # Learning rate @@ -14,7 +14,7 @@ hp: # LR decay rate lr_rampup: 1e6 # Rampup for learning rate, in number of samples - lr_decay_rate: 2e6 + lr_decay_rate: 1e6 # Learning rate decay threshold in number of samples, applied every lr_decay_rate samples. # Performance @@ -22,7 +22,7 @@ perf: fp_optimizations: amp-bf16 # Floating point mode, one of ["fp32", "fp16", "amp-fp16", "amp-bf16"] # "amp-{fp16,bf16}" activates Automatic Mixed Precision (AMP) with {float16,bfloat16} - dataloader_workers: 8 + dataloader_workers: 30 # DataLoader worker processes songunet_checkpoint_level: 0 # 0 means no checkpointing # Gradient checkpointing level, value is number of layers to checkpoint @@ -38,8 +38,6 @@ io: # How often to print progress save_checkpoint_freq: 200000 # How often to save the checkpoints, measured in number of processed samples - visualization_freq: 250000 - # how often to visualize network outputs validation_freq: 50000 # how often to record the validation loss, measured in number of processed samples validation_steps: 90 diff --git a/src/hirad/conf/training/era_cosmo_diffusion_patched.yaml b/src/hirad/conf/training/era_cosmo_diffusion_patched.yaml index 11874924..6480ec0d 100644 --- a/src/hirad/conf/training/era_cosmo_diffusion_patched.yaml +++ b/src/hirad/conf/training/era_cosmo_diffusion_patched.yaml @@ -46,8 +46,6 @@ io: # How often to print progress save_checkpoint_freq: 250000 # How often to save the checkpoints, measured in number of processed samples - visualization_freq: 250000 - # how often to visualize network outputs validation_freq: 50000 # how often to record the validation loss, measured in number of processed samples validation_steps: 50 diff --git a/src/hirad/conf/training/era_cosmo_regression.yaml b/src/hirad/conf/training/era_cosmo_regression.yaml index c8423b73..ad291dcc 100644 --- a/src/hirad/conf/training/era_cosmo_regression.yaml +++ b/src/hirad/conf/training/era_cosmo_regression.yaml @@ -22,7 +22,7 @@ perf: fp_optimizations: amp-bf16 # Floating point mode, one of ["fp32", "fp16", "amp-fp16", "amp-bf16"] # "amp-{fp16,bf16}" activates Automatic Mixed Precision (AMP) with {float16,bfloat16} - dataloader_workers: 8 + dataloader_workers: 30 # DataLoader worker processes songunet_checkpoint_level: 0 # 0 means no checkpointing # Gradient checkpointing level, value is number of layers to checkpoint @@ -32,13 +32,11 @@ perf: # I/O io: - print_progress_freq: 500 + print_progress_freq: 1000 # How often to print progress save_checkpoint_freq: 100000 # How often to save the checkpoints, measured in number of processed samples - visualization_freq: 50000 - # how often to visualize network output - validation_freq: 40000 + validation_freq: 50000 # how often to record the validation loss, measured in number of processed samples validation_steps: 55 # how many loss evaluations are used to compute the validation loss per checkpoint diff --git a/src/hirad/conf/training/era_real_diffusion_patched.yaml b/src/hirad/conf/training/era_real_diffusion_patched.yaml index 2f31f5ca..69f19ef3 100644 --- a/src/hirad/conf/training/era_real_diffusion_patched.yaml +++ b/src/hirad/conf/training/era_real_diffusion_patched.yaml @@ -4,7 +4,7 @@ hp: # Training duration based on the number of processed samples total_batch_size: "auto" # Total batch size - batch_size_per_gpu: 6 + batch_size_per_gpu: 8 # Batch size per GPU lr: 0.0002 # Learning rate @@ -31,7 +31,7 @@ perf: fp_optimizations: amp-bf16 # Floating point mode, one of ["fp32", "fp16", "amp-fp16", "amp-bf16"] # "amp-{fp16,bf16}" activates Automatic Mixed Precision (AMP) with {float16,bfloat16} - dataloader_workers: 10 + dataloader_workers: 30 # DataLoader worker processes songunet_checkpoint_level: 0 # 0 means no checkpointing # Gradient checkpointing level, value is number of layers to checkpoint @@ -46,9 +46,7 @@ io: # How often to print progress save_checkpoint_freq: 250000 # How often to save the checkpoints, measured in number of processed samples - visualization_freq: 250000 - # how often to visualize network outputs - validation_freq: 50000 + validation_freq: 100000 # how often to record the validation loss, measured in number of processed samples validation_steps: 45 # how many loss evaluations are used to compute the validation loss per checkpoint diff --git a/src/hirad/conf/training/era_real_regression.yaml b/src/hirad/conf/training/era_real_regression.yaml index 08cd6292..3fee0c2e 100644 --- a/src/hirad/conf/training/era_real_regression.yaml +++ b/src/hirad/conf/training/era_real_regression.yaml @@ -22,7 +22,7 @@ perf: fp_optimizations: amp-bf16 # Floating point mode, one of ["fp32", "fp16", "amp-fp16", "amp-bf16"] # "amp-{fp16,bf16}" activates Automatic Mixed Precision (AMP) with {float16,bfloat16} - dataloader_workers: 8 + dataloader_workers: 10 # DataLoader worker processes songunet_checkpoint_level: 0 # 0 means no checkpointing # Gradient checkpointing level, value is number of layers to checkpoint @@ -36,9 +36,7 @@ io: # How often to print progress save_checkpoint_freq: 100000 # How often to save the checkpoints, measured in number of processed samples - visualization_freq: 50000 - # how often to visualize network output - validation_freq: 40000 + validation_freq: 50000 # how often to record the validation loss, measured in number of processed samples validation_steps: 55 # how many loss evaluations are used to compute the validation loss per checkpoint diff --git a/src/hirad/conf/training_era_cosmo_diffusion.yaml b/src/hirad/conf/training_era_cosmo_diffusion.yaml index ef00c2b5..ef780a7e 100644 --- a/src/hirad/conf/training_era_cosmo_diffusion.yaml +++ b/src/hirad/conf/training_era_cosmo_diffusion.yaml @@ -10,7 +10,7 @@ defaults: - _self_ # Dataset - - dataset/era_cosmo + - dataset/anemoi_era_cosmo # Model - model/era_cosmo_diffusion diff --git a/src/hirad/conf/training_era_cosmo_diffusion_patched.yaml b/src/hirad/conf/training_era_cosmo_diffusion_patched.yaml index d618e897..b4ae6b29 100644 --- a/src/hirad/conf/training_era_cosmo_diffusion_patched.yaml +++ b/src/hirad/conf/training_era_cosmo_diffusion_patched.yaml @@ -10,7 +10,7 @@ defaults: - _self_ # Dataset - - dataset/era_cosmo + - dataset/anemoi_era_cosmo # Model - model/era_cosmo_diffusion_patched diff --git a/src/hirad/conf/training_era_cosmo_regression.yaml b/src/hirad/conf/training_era_cosmo_regression.yaml index 8bf93a06..18ee91da 100644 --- a/src/hirad/conf/training_era_cosmo_regression.yaml +++ b/src/hirad/conf/training_era_cosmo_regression.yaml @@ -11,7 +11,7 @@ defaults: - _self_ # Dataset - - dataset/era_cosmo + - dataset/anemoi_era_cosmo # Model - model/era_cosmo_regression diff --git a/src/hirad/conf/training_era_real_diffusion_patched.yaml b/src/hirad/conf/training_era_real_diffusion_patched.yaml index 908feccf..b24f95c5 100644 --- a/src/hirad/conf/training_era_real_diffusion_patched.yaml +++ b/src/hirad/conf/training_era_real_diffusion_patched.yaml @@ -10,7 +10,7 @@ defaults: - _self_ # Dataset - - dataset/era_real + - dataset/anemoi_era_real # Model - model/era_real_diffusion_patched diff --git a/src/hirad/conf/training_era_real_regression.yaml b/src/hirad/conf/training_era_real_regression.yaml index 8dab37e3..23eab0ca 100644 --- a/src/hirad/conf/training_era_real_regression.yaml +++ b/src/hirad/conf/training_era_real_regression.yaml @@ -11,7 +11,7 @@ defaults: - _self_ # Dataset - - dataset/era_real + - dataset/anemoi_era_real # Model - model/era_real_regression diff --git a/src/hirad/eval_precip.sh b/src/hirad/eval_precip.sh index 90c178fb..4630d490 100644 --- a/src/hirad/eval_precip.sh +++ b/src/hirad/eval_precip.sh @@ -21,8 +21,8 @@ ### CONFIG ### CONFIG_NAME="src/hirad/conf/eval_real.yaml" -srun --environment=./ci/edf/modulus_env.toml bash -c " - pip install -e . --no-dependencies +srun --mpi=pmix --network=disable_rdzv_get --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . # Diurnal cycle # python src/hirad/eval/diurnal_cycle_precip_mean_wet-hour.py --config-name=${CONFIG_NAME} diff --git a/src/hirad/eval_wind.sh b/src/hirad/eval_wind.sh index 6981d1b7..88f3f221 100644 --- a/src/hirad/eval_wind.sh +++ b/src/hirad/eval_wind.sh @@ -21,8 +21,8 @@ ### CONFIG ### CONFIG_NAME="src/hirad/conf/eval_real.yaml" -srun --environment=./ci/edf/modulus_env.toml bash -c " - pip install -e . --no-dependencies +srun --mpi=pmix --network=disable_rdzv_get --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . # Diurnal cycle # python src/hirad/eval/diurnal_cycle_temp_wind.py --config-name=${CONFIG_NAME} diff --git a/src/hirad/generate.sh b/src/hirad/generate.sh index 72e81c73..0fff0800 100644 --- a/src/hirad/generate.sh +++ b/src/hirad/generate.sh @@ -32,7 +32,7 @@ echo "Master port: $MASTER_PORT" export OMP_NUM_THREADS=1 -srun --environment=./ci/edf/modulus_env.toml bash -c " - pip install -e . --no-dependencies - python src/hirad/inference/generate.py --config-name=generate_era_cosmo.yaml +srun --mpi=pmix --network=disable_rdzv_get --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . + python src/hirad/inference/generate.py --config-name=generate_era_real.yaml " \ No newline at end of file diff --git a/src/hirad/inference/deterministic_sampler.py b/src/hirad/inference/deterministic_sampler.py index e502875e..af02c3bd 100644 --- a/src/hirad/inference/deterministic_sampler.py +++ b/src/hirad/inference/deterministic_sampler.py @@ -20,7 +20,7 @@ import nvtx import torch -from hirad.models import EDMPrecond +from hirad.models import EDMPrecondSuperResolution as EDMPrecond # ruff: noqa: E731 diff --git a/src/hirad/models/preconditioning.py b/src/hirad/models/preconditioning.py index 97f69217..0f9674f4 100644 --- a/src/hirad/models/preconditioning.py +++ b/src/hirad/models/preconditioning.py @@ -224,11 +224,8 @@ def round_sigma(sigma: Union[float, List, torch.Tensor]) -> torch.Tensor: torch.Tensor Tensor representation of sigma values. - See Also - -------- - EDMPrecond.round_sigma """ - return EDMPrecond.round_sigma(sigma) + return torch.as_tensor(sigma) @property def amp_mode(self): diff --git a/src/hirad/snapshots.sh b/src/hirad/snapshots.sh index 398b5b4d..5477f148 100644 --- a/src/hirad/snapshots.sh +++ b/src/hirad/snapshots.sh @@ -18,7 +18,7 @@ ### ENVIRONMENT #### #SBATCH -A a161 -srun --environment=./ci/edf/modulus_env.toml bash -c " - source ../hirad_env/hirad/bin/activate +srun --mpi=pmix --network=disable_rdzv_get --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . python src/hirad/eval/snapshots.py --config-name=src/hirad/conf/eval_real.yaml " \ No newline at end of file diff --git a/src/hirad/train_diffusion.sh b/src/hirad/train_diffusion.sh index 6f376187..dcc46375 100644 --- a/src/hirad/train_diffusion.sh +++ b/src/hirad/train_diffusion.sh @@ -32,7 +32,7 @@ export MASTER_PORT=29500 export OMP_NUM_THREADS=1 # python src/hirad/training/train.py --config-name=training_era_cosmo_testrun.yaml -srun --environment=./ci/edf/modulus_env.toml bash -c " - pip install -e . --no-dependencies - python src/hirad/training/train.py --config-name=training_era_cosmo_diffusion.yaml +srun --mpi=pmix --network=disable_rdzv_get --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . + python src/hirad/training/train.py --config-name=training_era_real_diffusion_patched.yaml " \ No newline at end of file diff --git a/src/hirad/train_regression.sh b/src/hirad/train_regression.sh index 03d195e7..e9e4271e 100644 --- a/src/hirad/train_regression.sh +++ b/src/hirad/train_regression.sh @@ -32,7 +32,7 @@ export MASTER_PORT=29500 export OMP_NUM_THREADS=1 -srun --environment=./ci/edf/modulus_env.toml bash -c " - pip install -e . --no-dependencies - python src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml +srun --mpi=pmix --network=disable_rdzv_get --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . + python src/hirad/training/train.py --config-name=training_era_real_regression.yaml " \ No newline at end of file From 430d8e3003868dd5f295495f52ba3ad99ac9b731 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Tue, 17 Mar 2026 18:22:34 +0100 Subject: [PATCH 262/302] old dataset path fix --- src/hirad/datasets/era5_cosmo.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hirad/datasets/era5_cosmo.py b/src/hirad/datasets/era5_cosmo.py index c7acf28e..ff58b0ac 100644 --- a/src/hirad/datasets/era5_cosmo.py +++ b/src/hirad/datasets/era5_cosmo.py @@ -13,7 +13,7 @@ logger = PythonLogger(__name__) # DATASET_ORIG_PATH = '/capstor/store/mch/msopr/hirad-gen/basic-torch/era5-cosmo-1h-linear-interpolation-full' -DATASET_ORIG_PATH = "/iopsstor/scratch/cscs/mmcgloho/basic-numpy/era5-cosmo-1h-all-channels/train/" +DATASET_ORIG_PATH = "/capstor/store/cscs/pasc/c38/basic-numpy/basic-numpy/era5-cosmo-1h-all-channels/train" class ERA5_COSMO(DownscalingDataset): def __init__(self, @@ -31,6 +31,8 @@ def __init__(self, #TODO switch hanbdling paths to Path rather than pure strings self._n_month_hour_channels = n_month_hour_channels self._dataset_path = dataset_path + print(f"Loading ERA5-COSMO dataset from path: {dataset_path}") + print(f"Input dir name: {input_dir_name}, output dir name: {output_dir_name}") self._era5_path = os.path.join(dataset_path, input_dir_name) self._cosmo_path = os.path.join(dataset_path, output_dir_name) self._info_path = os.path.join(DATASET_ORIG_PATH, 'info') From c380b907eceeb05c1550d408394685c8e1e8bec6 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 18 Mar 2026 09:44:18 +0100 Subject: [PATCH 263/302] add tests for utils, losses and part of models --- tests/__init__.py | 0 tests/losses/__init__.py | 0 tests/losses/test_loss.py | 762 ++++++++++++++++++++++++++++ tests/models/__init__.py | 0 tests/models/test_unet.py | 484 ++++++++++++++++++ tests/utils/__init__.py | 0 tests/utils/test_checkpoint.py | 336 ++++++++++++ tests/utils/test_console.py | 206 ++++++++ tests/utils/test_dataset_utils.py | 507 ++++++++++++++++++ tests/utils/test_env_info.py | 440 ++++++++++++++++ tests/utils/test_function_utils.py | 351 +++++++++++++ tests/utils/test_inference_utils.py | 423 +++++++++++++++ tests/utils/test_model_utils.py | 107 ++++ tests/utils/test_patching.py | 644 +++++++++++++++++++++++ tests/utils/test_train_helpers.py | 686 +++++++++++++++++++++++++ 15 files changed, 4946 insertions(+) create mode 100644 tests/__init__.py create mode 100644 tests/losses/__init__.py create mode 100644 tests/losses/test_loss.py create mode 100644 tests/models/__init__.py create mode 100644 tests/models/test_unet.py create mode 100644 tests/utils/__init__.py create mode 100644 tests/utils/test_checkpoint.py create mode 100644 tests/utils/test_console.py create mode 100644 tests/utils/test_dataset_utils.py create mode 100644 tests/utils/test_env_info.py create mode 100644 tests/utils/test_function_utils.py create mode 100644 tests/utils/test_inference_utils.py create mode 100644 tests/utils/test_model_utils.py create mode 100644 tests/utils/test_patching.py create mode 100644 tests/utils/test_train_helpers.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/losses/__init__.py b/tests/losses/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/losses/test_loss.py b/tests/losses/test_loss.py new file mode 100644 index 00000000..8989e76c --- /dev/null +++ b/tests/losses/test_loss.py @@ -0,0 +1,762 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +from unittest.mock import MagicMock + +import pytest +import torch + +from hirad.losses.loss import RegressionLoss, ResidualLoss +from hirad.utils.patching import RandomPatching2D + + +# --------------------------------------------------------------------------- +# Helpers / fixtures +# --------------------------------------------------------------------------- + +B, C_HR, C_LR, H, W = 2, 3, 4, 64, 64 + + +def _make_dummy_net(out_channels=C_HR): + """Return a callable that mimics a neural network.""" + net = MagicMock() + net.side_effect = lambda x, y_lr, *a, **kw: torch.zeros( + x.shape[0], out_channels, x.shape[2], x.shape[3], device=x.device + ) + return net + + +def _make_identity_augment(): + """Augmentation pipe that returns its input unchanged.""" + return lambda x: (x, None) + + +@pytest.fixture() +def img_clean(): + return torch.randn(B, C_HR, H, W) + + +@pytest.fixture() +def img_lr(): + return torch.randn(B, C_LR, H, W) + + +@pytest.fixture() +def static_channels(): + return torch.randn(1, 2, H, W) + + +@pytest.fixture() +def date_embedding(): + return torch.randn(B, 5) + + +@pytest.fixture() +def lead_time_label(): + return torch.randint(49, size=(B,)) + + +############################################################################ +# RegressionLoss # +############################################################################ + + +class TestRegressionLossBasic: + """Basic behaviour of RegressionLoss.""" + + def test_output_shape(self, img_clean, img_lr): + net = _make_dummy_net() + loss = RegressionLoss() + result = loss(net, img_clean, img_lr) + assert result.shape == img_clean.shape + + def test_loss_non_negative(self, img_clean, img_lr): + net = _make_dummy_net() + loss = RegressionLoss() + result = loss(net, img_clean, img_lr) + assert (result >= 0).all() + + def test_zero_loss_when_prediction_matches(self, img_clean, img_lr): + """If net returns img_clean exactly, the loss should be zero.""" + net = MagicMock() + # Net always returns the ground truth + net.side_effect = lambda x, y_lr, *a, **kw: img_clean + loss = RegressionLoss() + result = loss(net, img_clean, img_lr) + torch.testing.assert_close(result, torch.zeros_like(result)) + + def test_net_receives_zero_input(self, img_clean, img_lr): + """First argument to the network should be a zero tensor.""" + net = _make_dummy_net() + loss = RegressionLoss() + loss(net, img_clean, img_lr) + call_args = net.call_args + zero_input = call_args[0][0] + torch.testing.assert_close(zero_input, torch.zeros_like(zero_input)) + + def test_net_receives_lr_conditioning(self, img_clean, img_lr): + """Second argument to the network should contain the LR image.""" + net = _make_dummy_net() + loss = RegressionLoss() + loss(net, img_clean, img_lr) + call_args = net.call_args + y_lr_arg = call_args[0][1] + # Without static channels or date embedding, the conditioning + # should match the LR image. + torch.testing.assert_close(y_lr_arg, img_lr) + + +class TestRegressionLossAugmentation: + """Augmentation behaviour of RegressionLoss.""" + + def test_identity_augmentation_same_as_no_augmentation(self, img_clean, img_lr): + net = _make_dummy_net() + loss = RegressionLoss() + result_a = loss(net, img_clean, img_lr, augment_pipe=None) + result_b = loss(net, img_clean, img_lr, augment_pipe=_make_identity_augment()) + torch.testing.assert_close(result_a, result_b) + + def test_augment_pipe_is_called(self, img_clean, img_lr): + augment_pipe = MagicMock(side_effect=_make_identity_augment()) + net = _make_dummy_net() + loss = RegressionLoss() + loss(net, img_clean, img_lr, augment_pipe=augment_pipe) + augment_pipe.assert_called_once() + + +class TestRegressionLossStaticChannels: + """Static-channel conditioning for RegressionLoss.""" + + def test_lr_conditioning_includes_static_channels( + self, img_clean, img_lr, static_channels + ): + net = _make_dummy_net() + loss = RegressionLoss() + loss(net, img_clean, img_lr, static_channels=static_channels) + y_lr_arg = net.call_args[0][1] + torch.testing.assert_close( + y_lr_arg, torch.cat([img_lr, static_channels.expand(img_lr.shape[0], -1, -1, -1)], dim=1)) + + def test_output_shape_with_static_channels( + self, img_clean, img_lr, static_channels + ): + net = _make_dummy_net() + loss = RegressionLoss() + result = loss(net, img_clean, img_lr, static_channels=static_channels) + assert result.shape == img_clean.shape + + +class TestRegressionLossDateEmbedding: + """Date-embedding conditioning for RegressionLoss.""" + + def test_lr_conditioning_includes_date_embedding( + self, img_clean, img_lr, date_embedding + ): + net = _make_dummy_net() + loss = RegressionLoss() + loss(net, img_clean, img_lr, date_embedding=date_embedding) + y_lr_arg = net.call_args[0][1] + torch.testing.assert_close( + y_lr_arg, torch.cat([img_lr, date_embedding.unsqueeze(-1).unsqueeze(-1).expand(-1, -1, H, W)], dim=1) + ) + + def test_output_shape_with_date_embedding( + self, img_clean, img_lr, date_embedding + ): + net = _make_dummy_net() + loss = RegressionLoss() + result = loss(net, img_clean, img_lr, date_embedding=date_embedding) + assert result.shape == img_clean.shape + + def test_date_embedding_contiguous_without_apex( + self, img_clean, img_lr, date_embedding + ): + """Date embedding should be contiguous when not using apex GN.""" + net = _make_dummy_net() + loss = RegressionLoss() + loss(net, img_clean, img_lr, date_embedding=date_embedding, use_apex_gn=False) + y_lr_arg = net.call_args[0][1] + assert y_lr_arg.is_contiguous() + + +class TestRegressionLossLeadTime: + """Lead-time-label behaviour in RegressionLoss.""" + + def test_lead_time_label_passed_to_net( + self, img_clean, img_lr, lead_time_label + ): + net = _make_dummy_net() + loss = RegressionLoss() + loss(net, img_clean, img_lr, lead_time_label=lead_time_label) + kw = net.call_args[1] + assert "lead_time_label" in kw + torch.testing.assert_close(kw["lead_time_label"], lead_time_label) + + def test_no_lead_time_label_omitted_from_kwargs(self, img_clean, img_lr): + net = _make_dummy_net() + loss = RegressionLoss() + loss(net, img_clean, img_lr, lead_time_label=None) + kw = net.call_args[1] + assert "lead_time_label" not in kw + + +class TestRegressionLossCombined: + """Combined optional arguments for RegressionLoss.""" + + def test_all_optional_args( + self, img_clean, img_lr, static_channels, date_embedding, lead_time_label + ): + net = _make_dummy_net() + loss = RegressionLoss() + result = loss( + net, + img_clean, + img_lr, + static_channels=static_channels, + date_embedding=date_embedding, + lead_time_label=lead_time_label, + ) + assert result.shape == img_clean.shape + y_lr_arg = net.call_args[0][1] + expected_channels = C_LR + static_channels.shape[1] + date_embedding.shape[1] + assert y_lr_arg.shape[1] == expected_channels + + +############################################################################ +# ResidualLoss — init # +############################################################################ + + +class TestResidualLossInit: + """Tests for ResidualLoss initialization.""" + + def test_default_values(self): + reg_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + assert loss.P_mean == 0.0 + assert loss.P_std == 1.2 + assert loss.sigma_data == 0.5 + assert loss.hr_mean_conditioning is False + assert loss.y_mean is None + + def test_custom_values(self): + reg_net = _make_dummy_net() + loss = ResidualLoss( + regression_net=reg_net, + P_mean=1.0, + P_std=2.0, + sigma_data=1.0, + hr_mean_conditioning=True, + ) + assert loss.P_mean == 1.0 + assert loss.P_std == 2.0 + assert loss.sigma_data == 1.0 + assert loss.hr_mean_conditioning is True + + +############################################################################ +# ResidualLoss — get_noise_params # +############################################################################ + + +class TestResidualLossGetNoiseParams: + """Tests for ResidualLoss.get_noise_params.""" + + def test_return_shapes(self): + reg_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + y = torch.randn(B, C_HR, H, W) + n, sigma, weight = loss.get_noise_params(y) + assert n.shape == y.shape + assert sigma.shape == (B, 1, 1, 1) + assert weight.shape == (B, 1, 1, 1) + + def test_weight_and_sigma_positive(self): + reg_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + y = torch.randn(B, C_HR, H, W) + _, sigma, weight = loss.get_noise_params(y) + assert (weight > 0).all() + assert (sigma > 0).all() + + +############################################################################ +# ResidualLoss — __call__ basic # +############################################################################ + + +class TestResidualLossCallBasic: + """Basic behaviour of ResidualLoss.__call__.""" + + def test_output_shape_no_patching(self, img_clean, img_lr): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + result = loss(diff_net, img_clean, img_lr) + assert result.shape == img_clean.shape + + def test_loss_non_negative(self, img_clean, img_lr): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + result = loss(diff_net, img_clean, img_lr) + assert (result >= 0).all() + + def test_regression_net_called(self, img_clean, img_lr): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + loss(diff_net, img_clean, img_lr) + reg_net.assert_called_once() + + def test_diffusion_net_called(self, img_clean, img_lr): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + loss(diff_net, img_clean, img_lr) + diff_net.assert_called_once() + + def test_regression_net_receives_zero_input(self, img_clean, img_lr): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + loss(diff_net, img_clean, img_lr) + zero_input = reg_net.call_args[0][0] + torch.testing.assert_close(zero_input, torch.zeros_like(zero_input)) + + +############################################################################ +# ResidualLoss — shape validation # +############################################################################ + + +class TestResidualLossShapeValidation: + """Validation of img_clean / img_lr shapes.""" + + def test_batch_mismatch_raises(self): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + img_clean = torch.randn(2, C_HR, H, W) + img_lr = torch.randn(3, C_LR, H, W) + with pytest.raises(ValueError, match="Shape mismatch"): + loss(diff_net, img_clean, img_lr) + + def test_spatial_mismatch_raises(self): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + img_clean = torch.randn(B, C_HR, 32, 32) + img_lr = torch.randn(B, C_LR, 64, 64) + with pytest.raises(ValueError, match="Shape mismatch"): + loss(diff_net, img_clean, img_lr) + + def test_invalid_patching_type_raises(self, img_clean, img_lr): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + with pytest.raises(ValueError, match="RandomPatching2D"): + loss(diff_net, img_clean, img_lr, patching="not_a_patching_object") + + +############################################################################ +# ResidualLoss — augmentation # +############################################################################ + + +class TestResidualLossAugmentation: + """Augmentation in ResidualLoss.""" + + def test_augment_pipe_called(self, img_clean, img_lr): + augment_pipe = MagicMock(side_effect=_make_identity_augment()) + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + loss(diff_net, img_clean, img_lr, augment_pipe=augment_pipe) + augment_pipe.assert_called_once() + + def test_identity_augmentation_output_shape(self, img_clean, img_lr): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + result = loss( + diff_net, + img_clean, + img_lr, + augment_pipe=_make_identity_augment(), + ) + assert result.shape == img_clean.shape + + def test_identity_augmentation_same_as_no_augmentation(self, img_clean, img_lr): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + torch.manual_seed(0) # fix the seed because of random noise in the loss + result_a = loss(diff_net, img_clean, img_lr, augment_pipe=None) + torch.manual_seed(0) # reset the seed to ensure same noise is added + result_b = loss(diff_net, img_clean, img_lr, augment_pipe=_make_identity_augment()) + torch.testing.assert_close(result_a, result_b) + + +############################################################################ +# ResidualLoss — static channels # +############################################################################ + + +class TestResidualLossStaticChannels: + """Static-channel conditioning in ResidualLoss.""" + + def test_output_shape_with_static_channels( + self, img_clean, img_lr, static_channels + ): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + result = loss(diff_net, img_clean, img_lr, static_channels=static_channels) + assert result.shape == img_clean.shape + + def test_regression_net_receives_static_channels( + self, img_clean, img_lr, static_channels + ): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + loss(diff_net, img_clean, img_lr, static_channels=static_channels) + y_lr_reg = reg_net.call_args[0][1] + assert y_lr_reg.shape[1] == C_LR + static_channels.shape[1] + torch.testing.assert_close( + y_lr_reg[0, C_LR:, :, :], static_channels[0, :, :, :] + ) + torch.testing.assert_close( + y_lr_reg, torch.cat([img_lr, static_channels.expand(img_lr.shape[0], -1, -1, -1)], dim=1) + ) + + def test_diffusion_net_receives_static_channels( + self, img_clean, img_lr, static_channels + ): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + loss(diff_net, img_clean, img_lr, static_channels=static_channels) + y_lr_diff = diff_net.call_args[0][1] + # Diffusion net also gets static channels appended to y_lr + assert y_lr_diff.shape[1] == C_LR + static_channels.shape[1] + torch.testing.assert_close( + y_lr_diff[0, C_LR:, :, :], static_channels[0, :, :, :] + ) + torch.testing.assert_close( + y_lr_diff, torch.cat([img_lr, static_channels.expand(img_lr.shape[0], -1, -1, -1)], dim=1) + ) + +############################################################################ +# ResidualLoss — date embedding # +############################################################################ + + +class TestResidualLossDateEmbedding: + """Date-embedding conditioning in ResidualLoss.""" + + def test_output_shape_with_date_embedding( + self, img_clean, img_lr, date_embedding + ): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + result = loss(diff_net, img_clean, img_lr, date_embedding=date_embedding) + assert result.shape == img_clean.shape + + def test_regression_net_receives_date_embedding( + self, img_clean, img_lr, date_embedding + ): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + loss(diff_net, img_clean, img_lr, date_embedding=date_embedding) + y_lr_reg = reg_net.call_args[0][1] + assert y_lr_reg.shape[1] == C_LR + date_embedding.shape[1] + torch.testing.assert_close( + y_lr_reg[:, C_LR:, 0, 0], date_embedding + ) + torch.testing.assert_close( + y_lr_reg, torch.cat([img_lr, date_embedding.unsqueeze(-1).unsqueeze(-1).expand(-1, -1, H, W)], dim=1) + ) + + def test_diffusion_net_receives_date_embedding( + self, img_clean, img_lr, date_embedding + ): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + loss(diff_net, img_clean, img_lr, date_embedding=date_embedding) + y_lr_diff = diff_net.call_args[0][1] + assert y_lr_diff.shape[1] == C_LR + date_embedding.shape[1] + torch.testing.assert_close( + y_lr_diff[:, C_LR:, 0, 0], date_embedding + ) + torch.testing.assert_close( + y_lr_diff, torch.cat([img_lr, date_embedding.unsqueeze(-1).unsqueeze(-1).expand(-1, -1, H, W)], dim=1) + ) + +############################################################################ +# ResidualLoss — lead time label # +############################################################################ + + +class TestResidualLossLeadTime: + """Lead-time-label behaviour in ResidualLoss.""" + + def test_lead_time_label_passed_to_both_nets( + self, img_clean, img_lr, lead_time_label + ): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + loss(diff_net, img_clean, img_lr, lead_time_label=lead_time_label) + # Regression net + reg_kw = reg_net.call_args[1] + assert "lead_time_label" in reg_kw + torch.testing.assert_close(reg_kw["lead_time_label"], lead_time_label) + # Diffusion net + diff_kw = diff_net.call_args[1] + assert "lead_time_label" in diff_kw + torch.testing.assert_close(diff_kw["lead_time_label"], lead_time_label) + + def test_no_lead_time_label_omitted_from_kwargs(self, img_clean, img_lr): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + loss(diff_net, img_clean, img_lr, lead_time_label=None) + assert "lead_time_label" not in reg_net.call_args[1] + assert "lead_time_label" not in diff_net.call_args[1] + + +############################################################################ +# ResidualLoss — hr_mean_conditioning # +############################################################################ + + +class TestResidualLossHrMeanConditioning: + """High-resolution mean conditioning in ResidualLoss.""" + + def test_diffusion_conditioning_includes_mean(self, img_clean, img_lr): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net, hr_mean_conditioning=True) + loss(diff_net, img_clean, img_lr) + y_lr_diff = diff_net.call_args[0][1] + # y_lr should have y_mean (C_HR channels) prepended to img_lr (C_LR channels) + assert y_lr_diff.shape[1] == C_HR + C_LR + torch.testing.assert_close(y_lr_diff[:,:C_HR,:,:],torch.zeros_like(y_lr_diff[:,:C_HR,:,:])) + torch.testing.assert_close(y_lr_diff[:,C_HR:,:,:], img_lr) + + def test_diffusion_conditioning_excludes_mean_when_disabled( + self, img_clean, img_lr + ): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net, hr_mean_conditioning=False) + loss(diff_net, img_clean, img_lr) + y_lr_diff = diff_net.call_args[0][1] + assert y_lr_diff.shape[1] == C_LR + + +############################################################################ +# ResidualLoss — use_patch_grad_acc # +############################################################################ + + +class TestResidualLossPatchGradAcc: + """Test use_patch_grad_acc reuse of cached y_mean.""" + + def test_y_mean_cached_after_first_call(self, img_clean, img_lr): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + assert loss.y_mean is None + loss(diff_net, img_clean, img_lr, use_patch_grad_acc=True) + assert loss.y_mean is not None + + def test_regression_net_not_called_when_y_mean_cached( + self, img_clean, img_lr + ): + """When use_patch_grad_acc=True and y_mean is already cached, + the regression net should not be called again.""" + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + # First call populates y_mean + loss(diff_net, img_clean, img_lr, use_patch_grad_acc=True) + assert reg_net.call_count == 1 + # Second call should reuse y_mean + loss(diff_net, img_clean, img_lr, use_patch_grad_acc=True) + assert reg_net.call_count == 1 + + def test_regression_net_called_without_patch_grad_acc( + self, img_clean, img_lr + ): + """Without use_patch_grad_acc, regression net is called every time.""" + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + loss(diff_net, img_clean, img_lr, use_patch_grad_acc=False) + loss(diff_net, img_clean, img_lr, use_patch_grad_acc=False) + assert reg_net.call_count == 2 + + +############################################################################ +# ResidualLoss — patching integration # +############################################################################ + + +class TestResidualLossPatching: + """Tests for ResidualLoss with RandomPatching2D.""" + + @pytest.fixture() + def patching(self): + return RandomPatching2D( + img_shape=(H, W), patch_shape=(32, 32), patch_num=2 + ) + + def test_output_shape_with_patching(self, img_clean, img_lr, patching): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + result = loss(diff_net, img_clean, img_lr, patching=patching) + # Output should be (B * num_patches, C_HR, patch_H, patch_W) + assert result.shape[1] == C_HR + assert result.shape[2] == 32 + assert result.shape[3] == 32 + assert result.shape[0] == B * 2 # More samples due to patching + + def test_diffusion_net_receives_arguments(self, img_clean, img_lr, patching): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + loss(diff_net, img_clean, img_lr, patching=patching) + y_arg = diff_net.call_args[0][0] + y_lr_arg = diff_net.call_args[0][1] + sigma_arg = diff_net.call_args[0][2] + assert y_arg.shape == (B * 2, C_HR, 32, 32) + assert y_lr_arg.shape == (B * 2, 2*C_LR, 32, 32) + assert sigma_arg.shape == (2 * B, 1, 1, 1) + + def test_patching_with_static_channels( + self, img_clean, img_lr, static_channels, patching + ): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net) + result = loss( + diff_net, + img_clean, + img_lr, + static_channels=static_channels, + patching=patching, + ) + y_arg = diff_net.call_args[0][0] + y_lr_arg = diff_net.call_args[0][1] + assert y_arg.shape == (B * 2, C_HR, 32, 32) + assert y_lr_arg.shape == (B * 2, 2*C_LR + 2*static_channels.shape[1], 32, 32) + assert result.shape == (B * 2, C_HR, 32, 32) + + def test_patching_with_hr_mean_conditioning( + self, img_clean, img_lr, patching + ): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss( + regression_net=reg_net, hr_mean_conditioning=True + ) + result = loss(diff_net, img_clean, img_lr, patching=patching) + y_arg = diff_net.call_args[0][0] + y_lr_arg = diff_net.call_args[0][1] + assert y_arg.shape == (B * 2, C_HR, 32, 32) + assert y_lr_arg.shape == (B * 2, 2*C_LR+C_HR, 32, 32) + assert result.shape == (B * 2, C_HR, 32, 32) + + +############################################################################ +# ResidualLoss — combined options # +############################################################################ + + +class TestResidualLossCombined: + """Tests with multiple optional arguments combined.""" + + def test_all_optional_args_no_patching( + self, + img_clean, + img_lr, + static_channels, + date_embedding, + lead_time_label, + ): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss( + regression_net=reg_net, hr_mean_conditioning=True + ) + result = loss( + diff_net, + img_clean, + img_lr, + static_channels=static_channels, + date_embedding=date_embedding, + lead_time_label=lead_time_label, + ) + y_arg = diff_net.call_args[0][0] + y_lr_arg = diff_net.call_args[0][1] + assert y_arg.shape == (B, C_HR, 64, 64) + assert y_lr_arg.shape == (B, C_LR+C_HR+static_channels.shape[1]+date_embedding.shape[1], 64, 64) + assert result.shape == img_clean.shape + + def test_all_optional_args_with_patching( + self, + img_clean, + img_lr, + static_channels, + date_embedding, + lead_time_label, + ): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + patching = RandomPatching2D(img_shape=(H, W), patch_shape=(32, 32), patch_num=2) + loss = ResidualLoss( + regression_net=reg_net, hr_mean_conditioning=True + ) + result = loss( + diff_net, + img_clean, + img_lr, + static_channels=static_channels, + date_embedding=date_embedding, + patching=patching, + lead_time_label=lead_time_label, + ) + y_arg = diff_net.call_args[0][0] + y_lr_arg = diff_net.call_args[0][1] + assert y_arg.shape == (B * 2, C_HR, 32, 32) + assert y_lr_arg.shape == (B * 2, 2*C_LR + C_HR + 2*static_channels.shape[1] + date_embedding.shape[1], 32, 32) + assert result.shape == (B * 2, C_HR, 32, 32) + + def test_augment_with_hr_mean_conditioning_static_and_date( + self, img_clean, img_lr, static_channels, date_embedding + ): + reg_net = _make_dummy_net() + diff_net = _make_dummy_net() + loss = ResidualLoss(regression_net=reg_net, hr_mean_conditioning=True) + result = loss( + diff_net, + img_clean, + img_lr, + static_channels=static_channels, + date_embedding=date_embedding, + augment_pipe=_make_identity_augment(), + ) + y_arg = diff_net.call_args[0][0] + y_lr_arg = diff_net.call_args[0][1] + assert y_arg.shape == (B, C_HR, 64, 64) + assert y_lr_arg.shape == (B, C_LR+C_HR+static_channels.shape[1]+date_embedding.shape[1], 64, 64) + assert result.shape == img_clean.shape + assert (result >= 0).all() diff --git a/tests/models/__init__.py b/tests/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/models/test_unet.py b/tests/models/test_unet.py new file mode 100644 index 00000000..513c046d --- /dev/null +++ b/tests/models/test_unet.py @@ -0,0 +1,484 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 - 2024 NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +from unittest.mock import MagicMock, patch, PropertyMock + +import pytest +import torch +import torch.nn as nn + +from hirad.models.unet import UNet + + +# --------------------------------------------------------------------------- +# Helpers / fixtures +# --------------------------------------------------------------------------- + +B, C_IN, C_OUT, H, W = 2, 4, 3, 64, 64 + + +def _make_mock_model(out_channels=C_OUT): + """Return a MagicMock that behaves like a SongUNet-style model.""" + model = MagicMock(spec=nn.Module) + model.side_effect = lambda x, sigma, class_labels=None, **kw: torch.zeros( + x.shape[0], out_channels, x.shape[2], x.shape[3], + dtype=x.dtype, device=x.device, + ) + model.modules.return_value = iter([]) + return model + + +@pytest.fixture() +def img_x(): + return torch.randn(B, C_OUT, H, W) + + +@pytest.fixture() +def img_lr(): + return torch.randn(B, C_IN, H, W) + + +############################################################################ +# UNet — __init__ # +############################################################################ + + +class TestUNetInitResolution: + """Test UNet.__init__ resolution handling.""" + + @patch("hirad.models.unet.network_module") + def test_int_resolution_sets_square(self, mock_module): + mock_module.SongUNetPosEmbd = MagicMock() + unet = UNet(img_resolution=128, img_in_channels=C_IN, img_out_channels=C_OUT) + assert unet.img_shape_x == 128 + assert unet.img_shape_y == 128 + + @patch("hirad.models.unet.network_module") + def test_tuple_resolution_sets_height_width(self, mock_module): + mock_module.SongUNetPosEmbd = MagicMock() + unet = UNet(img_resolution=(96, 128), img_in_channels=C_IN, img_out_channels=C_OUT) + assert unet.img_shape_y == 96 + assert unet.img_shape_x == 128 + + @patch("hirad.models.unet.network_module") + def test_stores_channel_counts(self, mock_module): + mock_module.SongUNetPosEmbd = MagicMock() + unet = UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + assert unet.img_in_channels == C_IN + assert unet.img_out_channels == C_OUT + + +class TestUNetInitModelType: + """Test that UNet creates the correct underlying model type.""" + + @patch("hirad.models.unet.network_module") + def test_default_model_type_is_song_unet_pos_embd(self, mock_module): + mock_cls = MagicMock() + mock_module.SongUNetPosEmbd = mock_cls + UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + mock_cls.assert_called_once() + + @patch("hirad.models.unet.network_module") + def test_custom_model_type_song_unet(self, mock_module): + mock_cls = MagicMock() + mock_module.SongUNet = mock_cls + setattr(mock_module, "SongUNet", mock_cls) + UNet( + img_resolution=64, + img_in_channels=C_IN, + img_out_channels=C_OUT, + model_type="SongUNet", + ) + mock_cls.assert_called_once() + + @patch("hirad.models.unet.network_module") + def test_model_receives_combined_in_channels(self, mock_module): + mock_cls = MagicMock() + mock_module.SongUNetPosEmbd = mock_cls + UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + call_kwargs = mock_cls.call_args[1] + assert call_kwargs["in_channels"] == C_IN + C_OUT + + @patch("hirad.models.unet.network_module") + def test_model_receives_out_channels(self, mock_module): + mock_cls = MagicMock() + mock_module.SongUNetPosEmbd = mock_cls + UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + call_kwargs = mock_cls.call_args[1] + assert call_kwargs["out_channels"] == C_OUT + + @patch("hirad.models.unet.network_module") + def test_extra_kwargs_forwarded_to_model(self, mock_module): + mock_cls = MagicMock() + mock_module.SongUNetPosEmbd = mock_cls + UNet( + img_resolution=64, + img_in_channels=C_IN, + img_out_channels=C_OUT, + model_channels=256, + num_blocks=8, + ) + call_kwargs = mock_cls.call_args[1] + assert call_kwargs["model_channels"] == 256 + assert call_kwargs["num_blocks"] == 8 + + +############################################################################ +# UNet — use_fp16 property # +############################################################################ + + +class TestUNetUseFp16: + """Test use_fp16 property getter and setter.""" + + @patch("hirad.models.unet.network_module") + def test_default_fp16_is_false(self, mock_module): + mock_module.SongUNetPosEmbd = MagicMock() + unet = UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + assert unet.use_fp16 is False + + @patch("hirad.models.unet.network_module") + def test_set_fp16_true(self, mock_module): + mock_module.SongUNetPosEmbd = MagicMock() + unet = UNet( + img_resolution=64, + img_in_channels=C_IN, + img_out_channels=C_OUT, + use_fp16=True, + ) + assert unet.use_fp16 is True + + @patch("hirad.models.unet.network_module") + def test_set_fp16_via_setter(self, mock_module): + mock_module.SongUNetPosEmbd = MagicMock() + unet = UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + unet.use_fp16 = True + assert unet.use_fp16 is True + unet.use_fp16 = False + assert unet.use_fp16 is False + + @patch("hirad.models.unet.network_module") + def test_set_fp16_accepts_int_0_and_1(self, mock_module): + """Older checkpoints may store 0/1 instead of bool.""" + mock_module.SongUNetPosEmbd = MagicMock() + unet = UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + unet.use_fp16 = 1 + assert unet.use_fp16 == 1 + unet.use_fp16 = 0 + assert unet.use_fp16 == 0 + + @patch("hirad.models.unet.network_module") + def test_set_fp16_invalid_type_raises(self, mock_module): + mock_module.SongUNetPosEmbd = MagicMock() + unet = UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + with pytest.raises(ValueError, match="must be a boolean"): + unet.use_fp16 = "yes" + + @patch("hirad.models.unet.network_module") + def test_set_fp16_none_raises(self, mock_module): + mock_module.SongUNetPosEmbd = MagicMock() + unet = UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + with pytest.raises(ValueError, match="must be a boolean"): + unet.use_fp16 = None + + +############################################################################ +# UNet — forward # +############################################################################ + + +class TestUNetForwardBasic: + """Basic forward pass tests for UNet.""" + + @patch("hirad.models.unet.network_module") + def test_output_shape(self, mock_module): + mock_model = _make_mock_model() + mock_module.SongUNetPosEmbd = MagicMock(return_value=mock_model) + unet = UNet(img_resolution=H, img_in_channels=C_IN, img_out_channels=C_OUT) + x = torch.zeros(B, C_OUT, H, W) + lr = torch.randn(B, C_IN, H, W) + out = unet(x, lr) + assert out.shape == (B, C_OUT, H, W) + + @patch("hirad.models.unet.network_module") + def test_output_dtype_is_float32(self, mock_module): + mock_model = _make_mock_model() + mock_module.SongUNetPosEmbd = MagicMock(return_value=mock_model) + unet = UNet(img_resolution=H, img_in_channels=C_IN, img_out_channels=C_OUT) + x = torch.zeros(B, C_OUT, H, W) + lr = torch.randn(B, C_IN, H, W) + out = unet(x, lr) + assert out.dtype == torch.float32 + + @patch("hirad.models.unet.network_module") + def test_concatenates_x_and_img_lr(self, mock_module): + mock_model = _make_mock_model() + mock_module.SongUNetPosEmbd = MagicMock(return_value=mock_model) + unet = UNet(img_resolution=H, img_in_channels=C_IN, img_out_channels=C_OUT) + x = torch.ones(B, C_OUT, H, W) + lr = torch.ones(B, C_IN, H, W) * 2 + unet(x, lr) + model_input = mock_model.call_args[0][0] + assert model_input.shape[1] == C_OUT + C_IN + # First channels should be x, remaining should be img_lr + torch.testing.assert_close(model_input[:, :C_OUT], x) + torch.testing.assert_close(model_input[:, C_OUT:], lr) + + @patch("hirad.models.unet.network_module") + def test_model_receives_zero_sigma(self, mock_module): + mock_model = _make_mock_model() + mock_module.SongUNetPosEmbd = MagicMock(return_value=mock_model) + unet = UNet(img_resolution=H, img_in_channels=C_IN, img_out_channels=C_OUT) + x = torch.zeros(B, C_OUT, H, W) + lr = torch.randn(B, C_IN, H, W) + unet(x, lr) + sigma_arg = mock_model.call_args[0][1] + torch.testing.assert_close( + sigma_arg, torch.zeros(B, dtype=torch.float32) + ) + + @patch("hirad.models.unet.network_module") + def test_model_receives_none_class_labels(self, mock_module): + mock_model = _make_mock_model() + mock_module.SongUNetPosEmbd = MagicMock(return_value=mock_model) + unet = UNet(img_resolution=H, img_in_channels=C_IN, img_out_channels=C_OUT) + x = torch.zeros(B, C_OUT, H, W) + lr = torch.randn(B, C_IN, H, W) + unet(x, lr) + call_kwargs = mock_model.call_args[1] + assert call_kwargs["class_labels"] is None + + @patch("hirad.models.unet.network_module") + def test_kwargs_forwarded_to_model(self, mock_module): + mock_model = _make_mock_model() + mock_module.SongUNetPosEmbd = MagicMock(return_value=mock_model) + unet = UNet(img_resolution=H, img_in_channels=C_IN, img_out_channels=C_OUT) + x = torch.zeros(B, C_OUT, H, W) + lr = torch.randn(B, C_IN, H, W) + lead_time = torch.randint(49, size=(B,)) + unet(x, lr, lead_time_label=lead_time) + call_kwargs = mock_model.call_args[1] + assert "lead_time_label" in call_kwargs + torch.testing.assert_close(call_kwargs["lead_time_label"], lead_time) + + +class TestUNetForwardImgLrNone: + """Test forward pass when img_lr is None.""" + + @patch("hirad.models.unet.network_module") + def test_no_concatenation_when_img_lr_none(self, mock_module): + mock_model = _make_mock_model() + mock_module.SongUNetPosEmbd = MagicMock(return_value=mock_model) + unet = UNet(img_resolution=H, img_in_channels=C_IN, img_out_channels=C_OUT) + x = torch.zeros(B, C_OUT, H, W) + unet(x, img_lr=None) + model_input = mock_model.call_args[0][0] + # Without img_lr, input should only be x + assert model_input.shape[1] == C_OUT + + +class TestUNetForwardDtypeValidation: + """Test dtype enforcement in forward pass.""" + + @patch("hirad.models.unet.network_module") + def test_raises_on_dtype_mismatch(self, mock_module): + """Model should raise if the underlying model returns wrong dtype.""" + mock_model = MagicMock(spec=nn.Module) + # Return fp16 when fp32 is expected + mock_model.side_effect = lambda x, sigma, class_labels=None, **kw: torch.zeros( + x.shape[0], C_OUT, x.shape[2], x.shape[3], dtype=torch.float16 + ) + mock_model.modules.return_value = iter([]) + mock_module.SongUNetPosEmbd = MagicMock(return_value=mock_model) + unet = UNet(img_resolution=H, img_in_channels=C_IN, img_out_channels=C_OUT) + x = torch.zeros(B, C_OUT, H, W) + lr = torch.randn(B, C_IN, H, W) + with pytest.raises(ValueError, match="Expected the dtype"): + unet(x, lr) + + +class TestUNetForwardForceFp32: + """Test the force_fp32 flag.""" + + @patch("hirad.models.unet.network_module") + def test_force_fp32_uses_float32(self, mock_module): + mock_model = _make_mock_model() + mock_module.SongUNetPosEmbd = MagicMock(return_value=mock_model) + unet = UNet( + img_resolution=H, + img_in_channels=C_IN, + img_out_channels=C_OUT, + use_fp16=True, + ) + x = torch.zeros(B, C_OUT, H, W) + lr = torch.randn(B, C_IN, H, W) + unet(x, lr, force_fp32=True) + model_input = mock_model.call_args[0][0] + assert model_input.dtype == torch.float32 + + +############################################################################ +# UNet — round_sigma # +############################################################################ + + +class TestUNetRoundSigma: + """Test round_sigma method.""" + + @patch("hirad.models.unet.network_module") + def test_float_input(self, mock_module): + mock_module.SongUNetPosEmbd = MagicMock() + unet = UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + result = unet.round_sigma(0.5) + assert isinstance(result, torch.Tensor) + assert result.item() == pytest.approx(0.5) + + @patch("hirad.models.unet.network_module") + def test_list_input(self, mock_module): + mock_module.SongUNetPosEmbd = MagicMock() + unet = UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + result = unet.round_sigma([0.1, 0.5, 1.0]) + assert isinstance(result, torch.Tensor) + assert result.shape == (3,) + torch.testing.assert_close(result, torch.tensor([0.1, 0.5, 1.0])) + + @patch("hirad.models.unet.network_module") + def test_tensor_input(self, mock_module): + mock_module.SongUNetPosEmbd = MagicMock() + unet = UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + sigma = torch.tensor([0.2, 0.8]) + result = unet.round_sigma(sigma) + torch.testing.assert_close(result, sigma) + + @patch("hirad.models.unet.network_module") + def test_zero_input(self, mock_module): + mock_module.SongUNetPosEmbd = MagicMock() + unet = UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + result = unet.round_sigma(0.0) + assert result.item() == pytest.approx(0.0) + + +############################################################################ +# UNet — amp_mode property # +############################################################################ + + +class TestUNetAmpMode: + """Test amp_mode property getter and setter.""" + + @patch("hirad.models.unet.network_module") + def test_amp_mode_returns_none_when_model_lacks_attr(self, mock_module): + mock_model = MagicMock(spec=nn.Module) + # Remove amp_mode from mock so hasattr returns False + del mock_model.amp_mode + mock_module.SongUNetPosEmbd = MagicMock(return_value=mock_model) + unet = UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + assert unet.amp_mode is None + + @patch("hirad.models.unet.network_module") + def test_amp_mode_returns_model_value(self, mock_module): + mock_model = MagicMock(spec=nn.Module) + mock_model.amp_mode = True + mock_module.SongUNetPosEmbd = MagicMock(return_value=mock_model) + unet = UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + assert unet.amp_mode is True + + @patch("hirad.models.unet.network_module") + def test_amp_mode_setter_updates_model(self, mock_module): + mock_model = MagicMock(spec=nn.Module) + mock_model.amp_mode = False + sub_module = MagicMock() + sub_module.amp_mode = False + mock_model.modules.return_value = iter([mock_model, sub_module]) + mock_module.SongUNetPosEmbd = MagicMock(return_value=mock_model) + unet = UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + unet.amp_mode = True + assert mock_model.amp_mode is True + assert sub_module.amp_mode is True + + @patch("hirad.models.unet.network_module") + def test_amp_mode_setter_rejects_non_bool(self, mock_module): + mock_model = MagicMock(spec=nn.Module) + mock_model.amp_mode = False + mock_module.SongUNetPosEmbd = MagicMock(return_value=mock_model) + unet = UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + with pytest.raises(TypeError, match="amp_mode must be a boolean"): + unet.amp_mode = "yes" + + +############################################################################ +# UNet — _backward_compat_arg_mapper # +############################################################################ + + +class TestUNetBackwardCompat: + """Test _backward_compat_arg_mapper for version-based argument migration.""" + + def test_v010_removes_img_channels(self): + args = { + "img_resolution": 64, + "img_in_channels": C_IN, + "img_out_channels": C_OUT, + "img_channels": 10, + } + result = UNet._backward_compat_arg_mapper("0.1.0", args) + assert "img_channels" not in result + + def test_v010_removes_sigma_params(self): + args = { + "img_resolution": 64, + "img_in_channels": C_IN, + "img_out_channels": C_OUT, + "sigma_min": 0.002, + "sigma_max": 80.0, + "sigma_data": 0.5, + } + result = UNet._backward_compat_arg_mapper("0.1.0", args) + assert "sigma_min" not in result + assert "sigma_max" not in result + assert "sigma_data" not in result + + def test_v010_keeps_valid_args(self): + args = { + "img_resolution": 64, + "img_in_channels": C_IN, + "img_out_channels": C_OUT, + } + result = UNet._backward_compat_arg_mapper("0.1.0", args) + assert result["img_resolution"] == 64 + assert result["img_in_channels"] == C_IN + assert result["img_out_channels"] == C_OUT + + def test_non_v010_preserves_all_args(self): + args = { + "img_resolution": 64, + "img_in_channels": C_IN, + "img_out_channels": C_OUT, + "img_channels": 10, + "sigma_min": 0.002, + } + result = UNet._backward_compat_arg_mapper("0.2.0", args) + assert "img_channels" in result + assert "sigma_min" in result + + +############################################################################ +# UNet — nn.Module integration # +############################################################################ + + +class TestUNetModuleIntegration: + """Test that UNet behaves as a proper nn.Module.""" + + @patch("hirad.models.unet.network_module") + def test_is_nn_module(self, mock_module): + mock_module.SongUNetPosEmbd = MagicMock() + unet = UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + assert isinstance(unet, nn.Module) + + @patch("hirad.models.unet.network_module") + def test_model_attribute_exists(self, mock_module): + mock_module.SongUNetPosEmbd = MagicMock() + unet = UNet(img_resolution=64, img_in_channels=C_IN, img_out_channels=C_OUT) + assert hasattr(unet, "model") diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/utils/test_checkpoint.py b/tests/utils/test_checkpoint.py new file mode 100644 index 00000000..2565f921 --- /dev/null +++ b/tests/utils/test_checkpoint.py @@ -0,0 +1,336 @@ +import os +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest +import torch + +from hirad.utils.checkpoint import ( + _get_checkpoint_filename, + load_checkpoint, + save_checkpoint, +) + + +# --------------------------------------------------------------------------- +# Helpers / fixtures +# --------------------------------------------------------------------------- + + +def _make_mock_manager(model_parallel_rank: int = 0, group_names=()): + """Return a mock DistributedManager with the given parallel rank.""" + mgr = MagicMock() + mgr.group_names = group_names + mgr.group_rank.return_value = model_parallel_rank + return mgr + + +@pytest.fixture(autouse=True) +def _patch_distributed(): + """Patch DistributedManager for every test so we never touch real dist.""" + mgr = _make_mock_manager(model_parallel_rank=0, group_names=()) + with patch( + "hirad.utils.checkpoint.DistributedManager" + ) as MockDM: + MockDM.is_initialized.return_value = True + MockDM.return_value = mgr + yield MockDM + + +class _SimpleModel(torch.nn.Module): + """Tiny model used in save / load round-trip tests.""" + + def __init__(self): + super().__init__() + self.linear = torch.nn.Linear(4, 2) + + +############################################################################ +# _get_checkpoint_filename # +############################################################################ + + +class TestGetCheckpointFilenameWithIndex: + """When an explicit index is supplied.""" + + def test_returns_correct_filename(self, tmp_path): + result = _get_checkpoint_filename(str(tmp_path), index=3) + expected = str(tmp_path.resolve() / "checkpoint.0.3.pt") + assert result == expected + + def test_custom_base_name(self, tmp_path): + result = _get_checkpoint_filename(str(tmp_path), base_name="model", index=0) + expected = str(tmp_path.resolve() / "model.0.0.pt") + assert result == expected + + def test_custom_model_type(self, tmp_path): + result = _get_checkpoint_filename( + str(tmp_path), index=1, model_type="hirad" + ) + expected = str(tmp_path.resolve() / "checkpoint.0.1.hirad") + assert result == expected + + def test_saving_flag_ignored_when_index_given(self, tmp_path): + result_save = _get_checkpoint_filename(str(tmp_path), index=5, saving=True) + result_load = _get_checkpoint_filename(str(tmp_path), index=5, saving=False) + assert result_save == result_load + + +class TestGetCheckpointFilenameNoIndex: + """When no index is supplied (auto-detect from existing files).""" + + def test_no_existing_files_returns_index_zero(self, tmp_path): + result = _get_checkpoint_filename(str(tmp_path)) + expected = str(tmp_path.resolve() / "checkpoint.0.0.pt") + assert result == expected + + def test_loads_latest_when_files_exist(self, tmp_path): + # Create fake checkpoint files with indices 0, 1, 2 + for i in range(3): + (tmp_path / f"checkpoint.0.{i}.pt").touch() + result = _get_checkpoint_filename(str(tmp_path), saving=False) + expected = str(tmp_path.resolve() / "checkpoint.0.2.pt") + assert result == expected + + def test_saving_increments_latest_index(self, tmp_path): + for i in range(3): + (tmp_path / f"checkpoint.0.{i}.pt").touch() + result = _get_checkpoint_filename(str(tmp_path), saving=True) + expected = str(tmp_path.resolve() / "checkpoint.0.3.pt") + assert result == expected + + def test_non_contiguous_indices_picks_largest(self, tmp_path): + for i in [0, 5, 10]: + (tmp_path / f"checkpoint.0.{i}.pt").touch() + result = _get_checkpoint_filename(str(tmp_path), saving=False) + expected = str(tmp_path.resolve() / "checkpoint.0.10.pt") + assert result == expected + + +class TestGetCheckpointFilenameModelParallel: + """Ensure model-parallel rank is embedded correctly.""" + + def test_model_parallel_rank_in_filename(self, tmp_path, _patch_distributed): + mgr = _make_mock_manager(model_parallel_rank=3, group_names=("model_parallel",)) + _patch_distributed.return_value = mgr + result = _get_checkpoint_filename(str(tmp_path), index=0) + expected = str(tmp_path.resolve() / "checkpoint.3.0.pt") + assert result == expected + + +############################################################################ +# save_checkpoint # +############################################################################ + + +class TestSaveCheckpointDirectory: + """Test directory creation behaviour of save_checkpoint.""" + + def test_creates_directory_if_missing(self, tmp_path): + out = str(tmp_path / "new_dir" / "sub") + model = _SimpleModel() + save_checkpoint(out, model=model, epoch=0) + assert Path(out).is_dir() + + def test_existing_directory_is_fine(self, tmp_path): + model = _SimpleModel() + save_checkpoint(str(tmp_path), model=model, epoch=0) + # Just ensure no exception is raised + assert Path(tmp_path).is_dir() + + +class TestSaveCheckpointModel: + """Test model state-dict saving.""" + + def test_model_checkpoint_file_created(self, tmp_path): + model = _SimpleModel() + save_checkpoint(str(tmp_path), model=model, epoch=0) + expected = tmp_path.resolve() / f"{model.__class__.__name__}.0.0.pt" + assert expected.exists() + + def test_model_checkpoint_contains_state_dict(self, tmp_path): + model = _SimpleModel() + save_checkpoint(str(tmp_path), model=model, epoch=0) + file_name = tmp_path.resolve() / f"{model.__class__.__name__}.0.0.pt" + state = torch.load(file_name, map_location="cpu") + assert "linear.weight" in state + assert "linear.bias" in state + + +class TestSaveCheckpointTraining: + """Test optimizer / scheduler / scaler / metadata saving.""" + + def test_optimizer_state_saved(self, tmp_path): + model = _SimpleModel() + opt = torch.optim.SGD(model.parameters(), lr=0.01) + save_checkpoint(str(tmp_path), optimizer=opt, epoch=1) + ckpt_file = tmp_path.resolve() / "checkpoint.0.1.pt" + assert ckpt_file.exists() + ckpt = torch.load(ckpt_file, map_location="cpu") + assert "optimizer_state_dict" in ckpt + + def test_epoch_saved(self, tmp_path): + model = _SimpleModel() + opt = torch.optim.SGD(model.parameters(), lr=0.01) + save_checkpoint(str(tmp_path), optimizer=opt, epoch=5) + ckpt_file = tmp_path.resolve() / "checkpoint.0.5.pt" + ckpt = torch.load(ckpt_file, map_location="cpu") + assert ckpt["epoch"] == 5 + + def test_metadata_saved(self, tmp_path): + model = _SimpleModel() + opt = torch.optim.SGD(model.parameters(), lr=0.01) + meta = {"loss": 0.42, "note": "test run"} + save_checkpoint(str(tmp_path), optimizer=opt, epoch=0, metadata=meta) + ckpt_file = tmp_path.resolve() / "checkpoint.0.0.pt" + ckpt = torch.load(ckpt_file, map_location="cpu") + assert ckpt["metadata"] == meta + + def test_no_training_objects_no_checkpoint_file(self, tmp_path): + """If only a model is provided, no training checkpoint should be created.""" + model = _SimpleModel() + save_checkpoint(str(tmp_path), model=model, epoch=0) + training_ckpt = tmp_path.resolve() / "checkpoint.0.0.pt" + assert not training_ckpt.exists() + + def test_scheduler_state_saved(self, tmp_path): + model = _SimpleModel() + opt = torch.optim.SGD(model.parameters(), lr=0.01) + sched = torch.optim.lr_scheduler.StepLR(opt, step_size=10) + save_checkpoint(str(tmp_path), optimizer=opt, scheduler=sched, epoch=0) + ckpt_file = tmp_path.resolve() / "checkpoint.0.0.pt" + ckpt = torch.load(ckpt_file, map_location="cpu") + assert "scheduler_state_dict" in ckpt + + def test_scaler_state_saved(self, tmp_path): + model = _SimpleModel() + opt = torch.optim.SGD(model.parameters(), lr=0.01) + scaler = torch.cuda.amp.GradScaler() + save_checkpoint(str(tmp_path), optimizer=opt, scaler=scaler, epoch=0) + ckpt_file = tmp_path.resolve() / "checkpoint.0.0.pt" + ckpt = torch.load(ckpt_file, map_location="cpu") + assert "scaler_state_dict" in ckpt + + +############################################################################ +# load_checkpoint # +############################################################################ + + +class TestLoadCheckpointMissingDir: + """Loading from a non-existent directory should return 0 gracefully.""" + + def test_returns_zero_for_missing_dir(self, tmp_path): + result = load_checkpoint(str(tmp_path / "nonexistent")) + assert result == 0 + + +class TestLoadCheckpointModel: + """Round-trip save/load of model state dicts.""" + + def test_model_weights_restored(self, tmp_path): + model = _SimpleModel() + # Freeze initial weights for comparison + original_weight = model.linear.weight.data.clone() + save_checkpoint(str(tmp_path), model=model, epoch=0) + + # Mutate weights so we can confirm they get restored + with torch.no_grad(): + model.linear.weight.fill_(0.0) + assert not torch.equal(model.linear.weight.data, original_weight) + + load_checkpoint(str(tmp_path), model=model, epoch=0) + assert torch.equal(model.linear.weight.data, original_weight) + + def test_missing_model_file_is_graceful(self, tmp_path): + """If model checkpoint doesn't exist, load should not crash.""" + tmp_path.mkdir(exist_ok=True) + model = _SimpleModel() + # No save happened, but directory exists – should warn and skip + result = load_checkpoint(str(tmp_path), model=model, epoch=0) + # Should still return 0 (no training checkpoint either) + assert result == 0 + + +class TestLoadCheckpointTraining: + """Round-trip save/load of training state (optimizer, scheduler, epoch, metadata).""" + + def test_optimizer_restored(self, tmp_path): + model = _SimpleModel() + opt = torch.optim.SGD(model.parameters(), lr=0.01) + # Take a step so state is non-trivial + loss = model.linear(torch.randn(1, 4)).sum() + loss.backward() + opt.step() + original_state = {k: v for k, v in opt.state_dict().items()} + + save_checkpoint(str(tmp_path), optimizer=opt, epoch=1) + + # Create a fresh optimizer + opt2 = torch.optim.SGD(model.parameters(), lr=0.01) + load_checkpoint(str(tmp_path), optimizer=opt2, epoch=1) + assert opt2.state_dict()["param_groups"] == original_state["param_groups"] + + def test_epoch_returned(self, tmp_path): + model = _SimpleModel() + opt = torch.optim.SGD(model.parameters(), lr=0.01) + save_checkpoint(str(tmp_path), optimizer=opt, epoch=7) + loaded_epoch = load_checkpoint(str(tmp_path), epoch=7) + assert loaded_epoch == 7 + + def test_metadata_restored(self, tmp_path): + model = _SimpleModel() + opt = torch.optim.SGD(model.parameters(), lr=0.01) + meta = {"lr": 1e-3, "run_id": "abc"} + save_checkpoint(str(tmp_path), optimizer=opt, epoch=0, metadata=meta) + + restored_meta = {} + load_checkpoint(str(tmp_path), epoch=0, metadata_dict=restored_meta) + assert restored_meta == meta + + def test_missing_checkpoint_file_returns_zero(self, tmp_path): + """If training checkpoint file doesn't exist, return 0.""" + tmp_path.mkdir(exist_ok=True) + result = load_checkpoint(str(tmp_path), epoch=99) + assert result == 0 + + +class TestSaveLoadRoundTrip: + """Full round-trip tests combining model + training state.""" + + def test_full_round_trip(self, tmp_path): + model = _SimpleModel() + opt = torch.optim.SGD(model.parameters(), lr=0.01) + sched = torch.optim.lr_scheduler.StepLR(opt, step_size=5) + meta = {"step": 100} + + # Forward + backward to populate optimizer state + loss = model.linear(torch.randn(2, 4)).sum() + loss.backward() + opt.step() + sched.step() + + original_weight = model.linear.weight.data.clone() + + save_checkpoint( + str(tmp_path), model=model, optimizer=opt, + scheduler=sched, epoch=3, metadata=meta, + ) + + # Reset everything + with torch.no_grad(): + model.linear.weight.fill_(0.0) + opt2 = torch.optim.SGD(model.parameters(), lr=0.01) + sched2 = torch.optim.lr_scheduler.StepLR(opt2, step_size=5) + restored_meta = {} + + loaded_epoch = load_checkpoint( + str(tmp_path), model=model, optimizer=opt2, + scheduler=sched2, epoch=3, metadata_dict=restored_meta, + ) + + assert loaded_epoch == 3 + assert torch.equal(model.linear.weight.data, original_weight) + assert restored_meta == meta + assert opt2.state_dict()["param_groups"] == opt.state_dict()["param_groups"] + assert sched2.state_dict()["last_epoch"] == sched.state_dict()["last_epoch"] diff --git a/tests/utils/test_console.py b/tests/utils/test_console.py new file mode 100644 index 00000000..788aff4a --- /dev/null +++ b/tests/utils/test_console.py @@ -0,0 +1,206 @@ +import logging +import os +import tempfile +from unittest.mock import MagicMock, patch + +import pytest +from termcolor import colored + +from hirad.utils.console import PythonLogger, RankZeroLoggingWrapper + + +############################################################################ +# PythonLogger # +############################################################################ + + +class TestPythonLogger: + """Tests for PythonLogger.""" + + def test_default_name(self): + pl = PythonLogger() + assert pl.logger.name == "launch" + + def test_custom_name(self): + pl = PythonLogger(name="my_trainer") + assert pl.logger.name == "my_trainer" + + def test_log_calls_info(self, caplog): + pl = PythonLogger(name="test_log") + pl.logger.setLevel(logging.DEBUG) + with caplog.at_level(logging.INFO, logger="test_log"): + pl.log("hello") + assert "hello" in caplog.text + + def test_info_uses_light_blue(self, caplog): + pl = PythonLogger(name="test_info") + pl.logger.setLevel(logging.DEBUG) + expected = colored("info msg", "light_blue") + with caplog.at_level(logging.INFO, logger="test_info"): + pl.info("info msg") + assert expected in caplog.text + + def test_success_uses_light_green(self, caplog): + pl = PythonLogger(name="test_success") + pl.logger.setLevel(logging.DEBUG) + expected = colored("ok", "light_green") + with caplog.at_level(logging.INFO, logger="test_success"): + pl.success("ok") + assert expected in caplog.text + + def test_warning_uses_light_yellow(self, caplog): + pl = PythonLogger(name="test_warning") + pl.logger.setLevel(logging.DEBUG) + expected = colored("careful", "light_yellow") + with caplog.at_level(logging.WARNING, logger="test_warning"): + pl.warning("careful") + assert expected in caplog.text + + def test_error_uses_light_red(self, caplog): + pl = PythonLogger(name="test_error") + pl.logger.setLevel(logging.DEBUG) + expected = colored("bad", "light_red") + with caplog.at_level(logging.ERROR, logger="test_error"): + pl.error("bad") + assert expected in caplog.text + + def test_file_logging_creates_file(self, tmp_path): + log_file = str(tmp_path / "test.log") + pl = PythonLogger(name="test_file_create") + pl.logger.setLevel(logging.DEBUG) + pl.file_logging(log_file) + pl.log("file message") + assert os.path.exists(log_file) + with open(log_file) as f: + content = f.read() + assert "file message" in content + + def test_file_logging_removes_existing_file(self, tmp_path): + log_file = str(tmp_path / "old.log") + with open(log_file, "w") as f: + f.write("old content") + pl = PythonLogger(name="test_file_overwrite") + pl.logger.setLevel(logging.DEBUG) + pl.file_logging(log_file) + pl.log("new content") + with open(log_file) as f: + content = f.read() + assert "old content" not in content + assert "new content" in content + + def test_file_logging_formatter(self, tmp_path): + log_file = str(tmp_path / "fmt.log") + pl = PythonLogger(name="test_fmt") + pl.logger.setLevel(logging.DEBUG) + pl.file_logging(log_file) + pl.log("fmt check") + with open(log_file) as f: + content = f.read() + # Format: [HH:MM:SS - name - LEVEL] message + assert "test_fmt" in content + assert "INFO" in content + + def test_file_logging_level_is_debug(self, tmp_path): + log_file = str(tmp_path / "debug.log") + pl = PythonLogger(name="test_debug_level") + pl.logger.setLevel(logging.DEBUG) + pl.file_logging(log_file) + pl.logger.debug("debug msg") + with open(log_file) as f: + content = f.read() + assert "debug msg" in content + + +############################################################################ +# RankZeroLoggingWrapper # +############################################################################ + + +class TestRankZeroLoggingWrapper: + """Tests for RankZeroLoggingWrapper.""" + + def test_rank_zero_calls_method(self): + inner = MagicMock() + inner.log = MagicMock(return_value="logged") + dist = MagicMock() + dist.rank = 0 + + wrapper = RankZeroLoggingWrapper(inner, dist) + result = wrapper.log("hello") + inner.log.assert_called_once_with("hello") + assert result == "logged" + + def test_non_zero_rank_suppresses_method(self): + inner = MagicMock() + inner.log = MagicMock(return_value="logged") + dist = MagicMock() + dist.rank = 1 + + wrapper = RankZeroLoggingWrapper(inner, dist) + result = wrapper.log("hello") + inner.log.assert_not_called() + assert result is None + + def test_non_callable_attribute_returned_directly(self): + inner = MagicMock() + inner.some_value = 42 + dist = MagicMock() + dist.rank = 5 + + wrapper = RankZeroLoggingWrapper(inner, dist) + assert wrapper.some_value == 42 + + def test_rank_zero_passes_kwargs(self): + inner = MagicMock() + inner.configure = MagicMock(return_value="configured") + dist = MagicMock() + dist.rank = 0 + + wrapper = RankZeroLoggingWrapper(inner, dist) + result = wrapper.configure(level="DEBUG", verbose=True) + inner.configure.assert_called_once_with(level="DEBUG", verbose=True) + assert result == "configured" + + def test_multiple_ranks_only_zero_logs(self): + inner = MagicMock() + inner.info = MagicMock() + + for rank in range(4): + dist = MagicMock() + dist.rank = rank + wrapper = RankZeroLoggingWrapper(inner, dist) + wrapper.info("msg") + + # Only rank 0 should have triggered the call + inner.info.assert_called_once_with("msg") + + def test_wrapper_preserves_return_value(self): + inner = MagicMock() + inner.compute = MagicMock(return_value={"loss": 0.5}) + dist = MagicMock() + dist.rank = 0 + + wrapper = RankZeroLoggingWrapper(inner, dist) + result = wrapper.compute() + assert result == {"loss": 0.5} + + def test_wrapper_with_python_logger(self): + pl = PythonLogger(name="test_wrapped") + pl.logger.setLevel(logging.DEBUG) + dist = MagicMock() + dist.rank = 0 + + wrapper = RankZeroLoggingWrapper(pl, dist) + # Should not raise + wrapper.log("from wrapper") + + def test_wrapper_with_python_logger_non_zero_rank(self, caplog): + pl = PythonLogger(name="test_wrapped_silent") + pl.logger.setLevel(logging.DEBUG) + dist = MagicMock() + dist.rank = 3 + + wrapper = RankZeroLoggingWrapper(pl, dist) + with caplog.at_level(logging.INFO, logger="test_wrapped_silent"): + wrapper.log("should not appear") + assert "should not appear" not in caplog.text diff --git a/tests/utils/test_dataset_utils.py b/tests/utils/test_dataset_utils.py new file mode 100644 index 00000000..0a1a6af2 --- /dev/null +++ b/tests/utils/test_dataset_utils.py @@ -0,0 +1,507 @@ +import numpy as np +import pytest +import torch + +from hirad.utils.dataset_utils import GridData, regrid_icon_to_rotlatlon + + +############################################################################ +# regrid_icon_to_rotlatlon # +############################################################################ + + +class TestRegridIconToRotlatlon: + """Tests for the regrid_icon_to_rotlatlon function.""" + + @pytest.fixture + def simple_regrid_inputs(self): + """Create simple inputs for regrid testing.""" + n_unstructured = 20 + n_target = 6 # 3 x 2 grid + n_stencil = 3 + + data = torch.arange(n_unstructured, dtype=torch.float32) + indices = torch.tensor( + [[0, 1, 2], [3, 4, 5], [6, 7, 8], + [9, 10, 11], [12, 13, 14], [15, 16, 17]], + dtype=torch.long, + ) + weights = torch.tensor( + [[0.5, 0.3, 0.2]] * n_target, dtype=torch.float32 + ) + return data, indices, weights + + def test_output_shape_2d(self, simple_regrid_inputs): + data, indices, weights = simple_regrid_inputs + nx, ny = 3, 2 + result = regrid_icon_to_rotlatlon(data, indices, weights, nx=nx, ny=ny) + assert result.shape == (ny, nx) + + def test_output_shape_with_batch(self, simple_regrid_inputs): + _, indices, weights = simple_regrid_inputs + batch, channels, n_unstructured = 2, 4, 20 + nx, ny = 3, 2 + data = torch.randn(batch, channels, n_unstructured) + result = regrid_icon_to_rotlatlon(data, indices, weights, nx=nx, ny=ny) + assert result.shape == (batch, channels, ny, nx) + + def test_output_shape_with_channels(self, simple_regrid_inputs): + _, indices, weights = simple_regrid_inputs + channels, n_unstructured = 5, 20 + nx, ny = 3, 2 + data = torch.randn(channels, n_unstructured) + result = regrid_icon_to_rotlatlon(data, indices, weights, nx=nx, ny=ny) + assert result.shape == (channels, ny, nx) + + def test_uniform_weights_give_mean(self): + """When all weights are equal the result should be the mean of stencil values.""" + n_target = 4 + n_stencil = 3 + data = torch.tensor([1.0, 2.0, 3.0, 10.0, 20.0, 30.0], dtype=torch.float32) + indices = torch.tensor([[0, 1, 2], [0, 1, 2], [3, 4, 5], [3, 4, 5]], dtype=torch.long) + weights = torch.full((n_target, n_stencil), 1.0 / n_stencil) + result = regrid_icon_to_rotlatlon(data, indices, weights, nx=2, ny=2) + expected_vals = torch.tensor([2.0, 2.0, 20.0, 20.0]).reshape(2, 2) + torch.testing.assert_close(result, expected_vals) + + def test_single_weight_selects_value(self): + """Weight concentrated on one index should select that value.""" + data = torch.tensor([10.0, 20.0, 30.0], dtype=torch.float32) + indices = torch.tensor([[0, 1, 2]], dtype=torch.long) + weights = torch.tensor([[1.0, 0.0, 0.0]], dtype=torch.float32) + result = regrid_icon_to_rotlatlon(data, indices, weights, nx=1, ny=1) + assert result.item() == pytest.approx(10.0) + + def test_output_values_with_batch_and_channels(self): + """Test that values are correctly computed with batch and channel dimensions.""" + batch, channels, n_unstructured = 2, 3, 20 + nx, ny = 3, 2 + data = torch.arange(batch * channels * n_unstructured, dtype=torch.float32).reshape(batch, channels, n_unstructured) + indices = torch.tensor( + [[0, 1, 2], [3, 4, 5], [6, 7, 8], + [9, 10, 11], [12, 13, 14], [15, 16, 17]], + dtype=torch.long, + ) + weights = torch.tensor( + [[0.5, 0.3, 0.2]] * (nx * ny), dtype=torch.float32 + ) + result = regrid_icon_to_rotlatlon(data, indices, weights, nx=nx, ny=ny) + result_one_batch_channel = regrid_icon_to_rotlatlon(data[1, 2], indices, weights, nx=nx, ny=ny) + torch.testing.assert_close(result[1, 2], result_one_batch_channel) + + def test_result_clamped_to_stencil_range(self): + """Result should be clamped between min and max of stencil values.""" + data = torch.tensor([1.0, 2.0, 3.0], dtype=torch.float32) + indices = torch.tensor([[0, 1, 2]], dtype=torch.long) + # Weights that would extrapolate outside [1, 3] + weights = torch.tensor([[2.0, -0.5, -0.5]], dtype=torch.float32) + result = regrid_icon_to_rotlatlon(data, indices, weights, nx=1, ny=1) + assert result.item() == pytest.approx(1.0) # Should be clamped to min + weights = torch.tensor([[-0.5, -0.5, 2.0]], dtype=torch.float32) + result = regrid_icon_to_rotlatlon(data, indices, weights, nx=1, ny=1) + assert result.item() == pytest.approx(3.0) # Should be clamped to max + + def test_output_dtype_matches_input(self, simple_regrid_inputs): + data, indices, weights = simple_regrid_inputs + result = regrid_icon_to_rotlatlon(data, indices, weights, nx=3, ny=2) + assert result.dtype == data.dtype + + def test_constant_data_gives_constant_output(self): + """Constant input across the stencil should produce constant output.""" + val = 7.0 + data = torch.full((10,), val) + indices = torch.tensor([[0, 1, 2], [3, 4, 5]], dtype=torch.long) + weights = torch.tensor([[0.5, 0.3, 0.2], [0.1, 0.6, 0.3]]) + result = regrid_icon_to_rotlatlon(data, indices, weights, nx=1, ny=2) + torch.testing.assert_close(result, torch.full((2, 1), val)) + + +############################################################################ +# GridData — init # +############################################################################ + + +class TestGridDataInit: + """Tests for GridData initialization and input validation.""" + + @pytest.fixture + def regular_grid_points(self): + """Create a regular 5x5 grid of original points and target points inside it.""" + lons_orig, lats_orig = np.meshgrid( + np.linspace(0, 4, 5), np.linspace(0, 4, 5) + ) + lons_orig = lons_orig.ravel() + lats_orig = lats_orig.ravel() + + lons_target = np.array([1.0, 2.0, 3.0, 1.5]) + lats_target = np.array([1.0, 2.0, 3.0, 2.5]) + return lons_orig, lats_orig, lons_target, lats_target + + def test_creation(self, regular_grid_points): + gd = GridData(*regular_grid_points) + assert gd is not None + + def test_mismatched_orig_raises(self): + with pytest.raises(ValueError, match="Original longitude and latitude"): + GridData( + np.array([0.0, 1.0]), + np.array([0.0]), + np.array([0.5]), + np.array([0.5]), + ) + + def test_mismatched_target_raises(self): + with pytest.raises(ValueError, match="Target longitude and latitude"): + GridData( + np.array([0.0, 1.0]), + np.array([0.0, 1.0]), + np.array([0.5, 0.6]), + np.array([0.5]), + ) + + def test_initial_state_is_numpy(self, regular_grid_points): + gd = GridData(*regular_grid_points) + assert gd.is_torch is False + assert gd.device is None + assert isinstance(gd._lambda1, np.ndarray) + assert isinstance(gd._lambda2, np.ndarray) + assert isinstance(gd._lambda3, np.ndarray) + assert isinstance(gd._simplex_id, np.ndarray) + assert isinstance(gd._tri.simplices, np.ndarray) + + def test_barycentric_weights_sum_to_one(self, regular_grid_points): + gd = GridData(*regular_grid_points) + total = gd._lambda1 + gd._lambda2 + gd._lambda3 + np.testing.assert_allclose(total, 1.0, atol=1e-12) + + def test_barycentric_weights_non_negative_for_interior(self, regular_grid_points): + """Points inside the convex hull should have non-negative barycentric coords.""" + gd = GridData(*regular_grid_points) + inside = gd._simplex_id != -1 + assert np.all(gd._lambda1[inside] >= -1e-12) + assert np.all(gd._lambda2[inside] >= -1e-12) + assert np.all(gd._lambda3[inside] >= -1e-12) + + +############################################################################ +# GridData — to_torch / to_numpy # +############################################################################ + + +class TestGridDataDeviceConversion: + """Tests for to_torch and to_numpy conversions.""" + + @pytest.fixture + def grid_data(self): + lons_orig, lats_orig = np.meshgrid( + np.linspace(0, 4, 5), np.linspace(0, 4, 5) + ) + lons_target = np.array([1.0, 2.0, 3.0]) + lats_target = np.array([1.0, 2.0, 3.0]) + return GridData( + lons_orig.ravel(), lats_orig.ravel(), lons_target, lats_target + ) + + def test_to_torch_sets_flag(self, grid_data): + grid_data.to_torch() + assert grid_data.is_torch is True + assert grid_data.device == torch.device("cpu") + + def test_to_torch_produces_tensors(self, grid_data): + grid_data.to_torch() + assert isinstance(grid_data._lambda1, torch.Tensor) + assert isinstance(grid_data._lambda2, torch.Tensor) + assert isinstance(grid_data._lambda3, torch.Tensor) + assert isinstance(grid_data._simplex_id, torch.Tensor) + assert isinstance(grid_data._tri.simplices, torch.Tensor) + + def test_to_torch_string_device(self, grid_data): + grid_data.to_torch("cpu") + assert grid_data.device == torch.device("cpu") + assert grid_data.is_torch is True + + def test_to_numpy_restores_arrays(self, grid_data): + grid_data.to_torch() + grid_data.to_numpy() + assert grid_data.is_torch is False + assert grid_data.device is None + assert isinstance(grid_data._lambda1, np.ndarray) + assert isinstance(grid_data._lambda2, np.ndarray) + assert isinstance(grid_data._lambda3, np.ndarray) + assert isinstance(grid_data._simplex_id, np.ndarray) + assert isinstance(grid_data._tri.simplices, np.ndarray) + + def test_to_numpy_noop_when_already_numpy(self, grid_data): + """Calling to_numpy when already numpy should be a no-op.""" + lambda1_before = grid_data._lambda1 + grid_data.to_numpy() + assert grid_data._lambda1 is lambda1_before + + def test_roundtrip_preserves_values(self, grid_data): + lambda1_orig = grid_data._lambda1.copy() + lambda2_orig = grid_data._lambda2.copy() + lambda3_orig = grid_data._lambda3.copy() + simplex_id_orig = grid_data._simplex_id.copy() + tri_simplices_orig = grid_data._tri.simplices.copy() + + grid_data.to_torch() + grid_data.to_numpy() + + np.testing.assert_allclose(grid_data._lambda1, lambda1_orig, atol=1e-12) + np.testing.assert_allclose(grid_data._lambda2, lambda2_orig, atol=1e-12) + np.testing.assert_allclose(grid_data._lambda3, lambda3_orig, atol=1e-12) + np.testing.assert_array_equal(grid_data._simplex_id, simplex_id_orig) + np.testing.assert_array_equal(grid_data._tri.simplices, tri_simplices_orig) + + +############################################################################ +# GridData — interpolate (numpy) # +############################################################################ + + +class TestGridDataInterpolateNumpy: + """Tests for the interpolate method using numpy arrays.""" + + @pytest.fixture + def grid_data_on_regular(self): + """GridData with a regular grid as source and a few interior targets.""" + lons_orig, lats_orig = np.meshgrid( + np.linspace(0, 4, 5), np.linspace(0, 4, 5) + ) + lons_target = np.array([1.0, 2.0, 3.0]) + lats_target = np.array([1.0, 2.0, 3.0]) + return GridData( + lons_orig.ravel(), lats_orig.ravel(), lons_target, lats_target + ) + + def test_interpolate_constant_field(self, grid_data_on_regular): + """A constant field should interpolate to the same constant.""" + n_orig = len(grid_data_on_regular.longitudes_orig) + values = np.full((1, n_orig), 5.0) + result = grid_data_on_regular.interpolate(values) + np.testing.assert_allclose(result, 5.0, atol=1e-12) + + def test_interpolate_linear_field(self, grid_data_on_regular): + """A linear field f(x,y) = x + y should be reproduced exactly.""" + gd = grid_data_on_regular + lons = gd.longitudes_orig + lats = gd.latitudes_orig + values = (lons + lats)[np.newaxis, :] # (1, n_orig) + + result = gd.interpolate(values) # (1, n_target) + expected = gd.longitudes_target + gd.latitudes_target + np.testing.assert_allclose(result[0], expected, atol=1e-10) + + def test_interpolate_multichannel(self, grid_data_on_regular): + """Multiple channels should be interpolated independently.""" + gd = grid_data_on_regular + n_orig = len(gd.longitudes_orig) + n_channels = 3 + values = np.random.RandomState(42).randn(n_channels, n_orig) + result = gd.interpolate(values) + assert result.shape == (n_channels, len(gd.longitudes_target)) + + def test_interpolate_batch_and_channels(self, grid_data_on_regular): + """Batch + channel dimensions should be preserved.""" + gd = grid_data_on_regular + n_orig = len(gd.longitudes_orig) + batch, channels = 2, 3 + values = np.random.RandomState(0).randn(batch, channels, n_orig) + result = gd.interpolate(values) + assert result.shape == (batch, channels, len(gd.longitudes_target)) + + def test_interpolate_wrong_last_dim_raises(self, grid_data_on_regular): + with pytest.raises(ValueError, match="Expected values with shape"): + grid_data_on_regular.interpolate(np.zeros((1, 7))) + + def test_fill_value_for_outside_points(self): + """Points outside the convex hull should get fill_value.""" + lons_orig = np.array([0.0, 1.0, 0.0, 1.0]) + lats_orig = np.array([0.0, 0.0, 1.0, 1.0]) + # One inside, one far outside + lons_target = np.array([0.5, 10.0]) + lats_target = np.array([0.5, 10.0]) + + gd = GridData(lons_orig, lats_orig, lons_target, lats_target) + values = np.array([[1.0, 2.0, 3.0, 4.0]]) + result = gd.interpolate(values, fill_value=-999.0) + + # Outside point should be fill_value + assert result[0, 1] == -999.0 + + def test_fill_value_default_nan(self): + """Default fill_value should be NaN.""" + lons_orig = np.array([0.0, 1.0, 0.0, 1.0]) + lats_orig = np.array([0.0, 0.0, 1.0, 1.0]) + lons_target = np.array([0.5, 10.0]) + lats_target = np.array([0.5, 10.0]) + + gd = GridData(lons_orig, lats_orig, lons_target, lats_target) + values = np.array([[1.0, 2.0, 3.0, 4.0]]) + result = gd.interpolate(values) + assert np.isnan(result[0, 1]) + + def test_callable_alias(self, grid_data_on_regular): + """__call__ should produce the same result as interpolate.""" + gd = grid_data_on_regular + n_orig = len(gd.longitudes_orig) + values = np.random.RandomState(7).randn(1, n_orig) + np.testing.assert_array_equal(gd(values), gd.interpolate(values)) + + def test_channel_batch_consistency(self, grid_data_on_regular): + """Interpolate should give consistent results across channels and batches.""" + gd = grid_data_on_regular + n_orig = len(gd.longitudes_orig) + batch, channels = 2, 3 + values = np.random.RandomState(123).randn(batch, channels, n_orig) + result = gd.interpolate(values) + result_one_batch_channel = gd.interpolate(values[1, 1]) + + np.testing.assert_allclose(result[1, 1], result_one_batch_channel, atol=1e-12) + + +############################################################################ +# GridData — interpolate (torch) # +############################################################################ + + +class TestGridDataInterpolateTorch: + """Tests for interpolation using PyTorch tensors.""" + + @pytest.fixture + def grid_data_torch(self): + lons_orig, lats_orig = np.meshgrid( + np.linspace(0, 4, 5), np.linspace(0, 4, 5) + ) + lons_target = np.array([1.0, 2.0, 3.0]) + lats_target = np.array([1.0, 2.0, 3.0]) + gd = GridData( + lons_orig.ravel(), lats_orig.ravel(), lons_target, lats_target + ) + gd.to_torch() + return gd + + def test_interpolate_constant_field_torch(self, grid_data_torch): + gd = grid_data_torch + n_orig = len(gd.longitudes_orig) + values = torch.full((1, n_orig), 5.0) + result = gd.interpolate(values) + assert isinstance(result, torch.Tensor) + torch.testing.assert_close(result, torch.full_like(result, 5.0), atol=1e-6, rtol=0) + + def test_interpolate_linear_field_torch(self, grid_data_torch): + gd = grid_data_torch + lons = torch.from_numpy(gd.longitudes_orig) + lats = torch.from_numpy(gd.latitudes_orig) + values = (lons + lats).unsqueeze(0) # (1, n_orig) + result = gd.interpolate(values) # (1, n_target) + expected = torch.from_numpy(gd.longitudes_target + gd.latitudes_target) + torch.testing.assert_close(result[0], expected, atol=1e-6, rtol=1e-5) + + def test_interpolate_multichannel_torch(self, grid_data_torch): + gd = grid_data_torch + n_orig = len(gd.longitudes_orig) + n_channels = 3 + values = torch.randn(n_channels, n_orig) + result = gd.interpolate(values) + assert result.shape == (n_channels, len(gd.longitudes_target)) + + def test_interpolate_batch_and_channels_torch(self, grid_data_torch): + gd = grid_data_torch + n_orig = len(gd.longitudes_orig) + batch, channels = 2, 3 + values = torch.randn(batch, channels, n_orig) + result = gd.interpolate(values) + assert result.shape == (batch, channels, len(gd.longitudes_target)) + + def test_interpolate_wrong_last_dim_raises_torch(self, grid_data_torch): + with pytest.raises(ValueError, match="Expected values with shape"): + grid_data_torch.interpolate(torch.zeros((1, 7))) + + def test_fill_value_for_outside_points_torch(self): + lons_orig = np.array([0.0, 1.0, 0.0, 1.0]) + lats_orig = np.array([0.0, 0.0, 1.0, 1.0]) + lons_target = np.array([0.5, 10.0]) + lats_target = np.array([0.5, 10.0]) + + gd = GridData(lons_orig, lats_orig, lons_target, lats_target) + gd.to_torch() + values = torch.tensor([[1.0, 2.0, 3.0, 4.0]]) + result = gd.interpolate(values, fill_value=-999.0) + assert result[0, 1].item() == -999.0 + + + def test_fill_value_default_nan_torch(self): + lons_orig = np.array([0.0, 1.0, 0.0, 1.0]) + lats_orig = np.array([0.0, 0.0, 1.0, 1.0]) + lons_target = np.array([0.5, 10.0]) + lats_target = np.array([0.5, 10.0]) + + gd = GridData(lons_orig, lats_orig, lons_target, lats_target) + gd.to_torch() + values = torch.tensor([[1.0, 2.0, 3.0, 4.0]]) + result = gd.interpolate(values) + assert torch.isnan(result[0, 1]) + + def test_channel_batch_consistency(self, grid_data_torch): + gd = grid_data_torch + n_orig = len(gd.longitudes_orig) + batch, channels = 2, 3 + values = torch.randn(batch, channels, n_orig) + result = gd.interpolate(values) + result_one_batch_channel = gd.interpolate(values[1, 1]) + torch.testing.assert_close(result[1, 1], result_one_batch_channel, atol=1e-6, rtol=0) + + def test_numpy_and_torch_agree(self): + """Numpy and Torch paths should produce the same results.""" + lons_orig, lats_orig = np.meshgrid( + np.linspace(0, 4, 5), np.linspace(0, 4, 5) + ) + lons_target = np.array([1.0, 2.5, 3.5]) + lats_target = np.array([1.0, 2.5, 3.5]) + + gd_np = GridData( + lons_orig.ravel(), lats_orig.ravel(), lons_target, lats_target + ) + rng = np.random.RandomState(99) + values_np = rng.randn(2, len(lons_orig.ravel())) + + result_np = gd_np.interpolate(values_np) + + gd_np.to_torch() + values_torch = torch.from_numpy(values_np) + result_torch = gd_np.interpolate(values_torch) + + np.testing.assert_allclose( + result_np, result_torch.numpy(), atol=1e-10 + ) + + +############################################################################ +# GridData — outside hull warning # +############################################################################ + + +class TestGridDataOutsideHull: + """Test behaviour when target points lie outside the convex hull.""" + + def test_outside_hull_warning(self, capsys): + lons_orig = np.array([0.0, 1.0, 0.0]) + lats_orig = np.array([0.0, 0.0, 1.0]) + lons_target = np.array([0.25, -5.0]) + lats_target = np.array([0.25, -5.0]) + + GridData(lons_orig, lats_orig, lons_target, lats_target) + captured = capsys.readouterr() + assert "outside the convex hull" in captured.out + + def test_no_warning_when_all_inside(self, capsys): + lons_orig, lats_orig = np.meshgrid( + np.linspace(0, 4, 5), np.linspace(0, 4, 5) + ) + lons_target = np.array([1.0, 2.0]) + lats_target = np.array([1.0, 2.0]) + + GridData(lons_orig.ravel(), lats_orig.ravel(), lons_target, lats_target) + captured = capsys.readouterr() + assert "outside the convex hull" not in captured.out diff --git a/tests/utils/test_env_info.py b/tests/utils/test_env_info.py new file mode 100644 index 00000000..b6cda265 --- /dev/null +++ b/tests/utils/test_env_info.py @@ -0,0 +1,440 @@ +import os +from types import ModuleType +from unittest.mock import MagicMock, PropertyMock, patch + +import pytest + +from hirad.utils.env_info import ( + flatten_dict, + get_env_info, + get_git_info, + get_module_git_info, + get_module_version, +) + + +############################################################################ +# get_module_version # +############################################################################ + + +class TestGetModuleVersion: + """Tests for get_module_version.""" + + def test_returns_version_string(self): + module = ModuleType("fake_mod") + module.__version__ = "1.2.3" + assert get_module_version(module) == "1.2.3" + + def test_returns_none_when_no_version_attr(self): + module = ModuleType("no_ver") + assert get_module_version(module) is None + + def test_returns_none_when_version_is_not_string(self): + module = ModuleType("bad_ver") + module.__version__ = (1, 2, 3) + assert get_module_version(module) is None + + def test_returns_none_when_version_is_none(self): + module = ModuleType("none_ver") + module.__version__ = None + assert get_module_version(module) is None + + def test_returns_none_when_version_is_int(self): + module = ModuleType("int_ver") + module.__version__ = 42 + assert get_module_version(module) is None + + def test_accepts_semver_string(self): + module = ModuleType("semver") + module.__version__ = "0.0.1-alpha" + assert get_module_version(module) == "0.0.1-alpha" + + +############################################################################ +# get_git_info # +############################################################################ + + +class TestGetGitInfo: + """Tests for get_git_info.""" + + def test_returns_none_for_non_git_path(self, tmp_path): + result = get_git_info(str(tmp_path)) + assert result is None + + def test_returns_dict_with_expected_keys(self): + mock_commit = MagicMock() + mock_commit.hexsha = "abc123" + + mock_head = MagicMock() + mock_head.commit = mock_commit + + mock_remote = MagicMock() + mock_remote.url = "https://github.com/user/repo.git" + + mock_repo = MagicMock() + mock_repo.head = mock_head + mock_repo.index.diff.return_value = [] + mock_repo.untracked_files = [] + mock_repo.remotes = [mock_remote] + + with patch("hirad.utils.env_info.Repo", return_value=mock_repo): + result = get_git_info("/some/path") + + assert result is not None + assert result["sha1"] == "abc123" + assert result["diff"] == "" + assert result["untracked_files"] == [] + assert result["remotes"] == ["https://github.com/user/repo.git"] + + def test_untracked_files_are_sorted(self): + mock_commit = MagicMock() + mock_commit.hexsha = "def456" + + mock_head = MagicMock() + mock_head.commit = mock_commit + + mock_repo = MagicMock() + mock_repo.head = mock_head + mock_repo.index.diff.return_value = [] + mock_repo.untracked_files = ["c.py", "a.py", "b.py"] + mock_repo.remotes = [] + + with patch("hirad.utils.env_info.Repo", return_value=mock_repo): + result = get_git_info("/some/path") + + assert result["untracked_files"] == ["a.py", "b.py", "c.py"] + + def test_diff_content_bytes_decoded(self): + mock_commit = MagicMock() + mock_commit.hexsha = "aaa" + + mock_head = MagicMock() + mock_head.commit = mock_commit + + diff_item = MagicMock() + diff_item.a_blob.abspath = "/a/file.py" + diff_item.b_blob.abspath = "/b/file.py" + diff_item.diff = b"+new line" + + mock_repo = MagicMock() + mock_repo.head = mock_head + mock_repo.index.diff.return_value = [diff_item] + mock_repo.untracked_files = [] + mock_repo.remotes = [] + + with patch("hirad.utils.env_info.Repo", return_value=mock_repo): + result = get_git_info("/some/path") + + assert "+new line" in result["diff"] + + def test_diff_content_string_passthrough(self): + mock_commit = MagicMock() + mock_commit.hexsha = "bbb" + + mock_head = MagicMock() + mock_head.commit = mock_commit + + diff_item = MagicMock() + diff_item.a_blob.abspath = "/a/file.py" + diff_item.b_blob.abspath = "/b/file.py" + diff_item.diff = "+already a string" + + mock_repo = MagicMock() + mock_repo.head = mock_head + mock_repo.index.diff.return_value = [diff_item] + mock_repo.untracked_files = [] + mock_repo.remotes = [] + + with patch("hirad.utils.env_info.Repo", return_value=mock_repo): + result = get_git_info("/some/path") + + assert "+already a string" in result["diff"] + + def test_diff_content_none_treated_as_empty(self): + mock_commit = MagicMock() + mock_commit.hexsha = "ccc" + + mock_head = MagicMock() + mock_head.commit = mock_commit + + diff_item = MagicMock() + diff_item.a_blob.abspath = "/a/file.py" + diff_item.b_blob.abspath = "/b/file.py" + diff_item.diff = None + + mock_repo = MagicMock() + mock_repo.head = mock_head + mock_repo.index.diff.return_value = [diff_item] + mock_repo.untracked_files = [] + mock_repo.remotes = [] + + with patch("hirad.utils.env_info.Repo", return_value=mock_repo): + result = get_git_info("/some/path") + + # The diff should contain the header lines but no diff content + assert "--- a/a/file.py" in result["diff"] + assert "+++ b/b/file.py" in result["diff"] + + def test_diff_multiple_diff_items_concatenated(self): + mock_commit = MagicMock() + mock_commit.hexsha = "eee" + + mock_head = MagicMock() + mock_head.commit = mock_commit + + diff_item1 = MagicMock() + diff_item1.a_blob.abspath = "/a/file1.py" + diff_item1.b_blob.abspath = "/b/file1.py" + diff_item1.diff = "+line in file1" + + diff_item2 = MagicMock() + diff_item2.a_blob.abspath = "/a/file2.py" + diff_item2.b_blob.abspath = "/b/file2.py" + diff_item2.diff = "+line in file2" + + mock_repo = MagicMock() + mock_repo.head = mock_head + mock_repo.index.diff.return_value = [diff_item1, diff_item2] + mock_repo.untracked_files = [] + mock_repo.remotes = [] + + with patch("hirad.utils.env_info.Repo", return_value=mock_repo): + result = get_git_info("/some/path") + + assert "+line in file1" in result["diff"] + assert "+line in file2" in result["diff"] + assert result["diff"].count("--- a/") == 2 + assert result["diff"].count("+++ b/") == 2 + assert diff_item1.a_blob.abspath in result["diff"] + assert diff_item1.b_blob.abspath in result["diff"] + assert diff_item2.a_blob.abspath in result["diff"] + assert diff_item2.b_blob.abspath in result["diff"] + + def test_multiple_remotes(self): + mock_commit = MagicMock() + mock_commit.hexsha = "ddd" + + mock_head = MagicMock() + mock_head.commit = mock_commit + + remote1 = MagicMock() + remote1.url = "https://github.com/user/repo1.git" + remote2 = MagicMock() + remote2.url = "git@github.com:user/repo2.git" + + mock_repo = MagicMock() + mock_repo.head = mock_head + mock_repo.index.diff.return_value = [] + mock_repo.untracked_files = [] + mock_repo.remotes = [remote1, remote2] + + with patch("hirad.utils.env_info.Repo", return_value=mock_repo): + result = get_git_info("/some/path") + + assert len(result["remotes"]) == 2 + + +############################################################################ +# get_module_git_info # +############################################################################ + + +class TestGetModuleGitInfo: + """Tests for get_module_git_info.""" + + def test_returns_none_when_no_file_attr(self): + module = ModuleType("no_file") + # ModuleType does not set __file__ by default + assert get_module_git_info(module) is None + + def test_returns_none_for_relative_path(self): + module = ModuleType("rel_path") + module.__file__ = "relative/path.py" + assert get_module_git_info(module) is None + + def test_delegates_to_get_git_info(self, tmp_path): + module = ModuleType("abs_path") + module.__file__ = str(tmp_path / "pkg" / "mod.py") + + fake_git_info = {"sha1": "abc", "diff": "", "untracked_files": [], "remotes": []} + with patch("hirad.utils.env_info.get_git_info", return_value=fake_git_info) as mock_fn: + result = get_module_git_info(module) + + mock_fn.assert_called_once_with(str(tmp_path / "pkg")) + assert result == fake_git_info + + def test_returns_none_when_get_git_info_returns_none(self, tmp_path): + module = ModuleType("no_git") + module.__file__ = str(tmp_path / "mod.py") + + with patch("hirad.utils.env_info.get_git_info", return_value=None): + assert get_module_git_info(module) is None + + +############################################################################ +# flatten_dict # +############################################################################ + + +class TestFlattenDict: + """Tests for flatten_dict.""" + + def test_already_flat(self): + d = {"a": 1, "b": 2} + assert flatten_dict(d) == {"a": 1, "b": 2} + + def test_one_level_nesting(self): + d = {"a": {"x": 1, "y": 2}, "b": 3} + assert flatten_dict(d) == {"a.x": 1, "a.y": 2, "b": 3} + + def test_two_level_nesting(self): + d = {"a": {"b": {"c": 42}}} + assert flatten_dict(d) == {"a.b.c": 42} + + def test_custom_separator(self): + d = {"a": {"b": 1}} + assert flatten_dict(d, sep="/") == {"a/b": 1} + + def test_custom_parent_key(self): + d = {"x": 1} + assert flatten_dict(d, parent_key="root") == {"root.x": 1} + + def test_empty_dict(self): + assert flatten_dict({}) == {} + + def test_mixed_nested_and_flat(self): + d = {"a": 1, "b": {"c": 2}, "d": {"e": {"f": 3}}} + expected = {"a": 1, "b.c": 2, "d.e.f": 3} + assert flatten_dict(d) == expected + + def test_preserves_non_dict_values(self): + d = {"a": [1, 2], "b": {"c": "text"}, "d": None} + expected = {"a": [1, 2], "b.c": "text", "d": None} + assert flatten_dict(d) == expected + + +############################################################################ +# get_env_info # +############################################################################ + + +class TestGetEnvInfo: + """Tests for get_env_info.""" + + def _make_module(self, name, version=None, file_path=None): + """Create a fake module with optional version and __file__.""" + mod = ModuleType(name) + if version is not None: + mod.__version__ = version + if file_path is not None: + mod.__file__ = file_path + return mod + + def test_returns_tuple_of_two(self): + with patch("hirad.utils.env_info.get_module_git_info", return_value=None): + result = get_env_info() + assert isinstance(result, tuple) + assert len(result) == 2 + + def test_always_includes_python_version(self): + with patch("hirad.utils.env_info.get_module_git_info", return_value=None): + info, _ = get_env_info(flatten=False) + assert "python" in info + assert "version" in info["python"] + + def test_flatten_true_flattens_output(self): + with patch("hirad.utils.env_info.get_module_git_info", return_value=None): + info, _ = get_env_info(flatten=True) + assert "python.version" in info + + def test_flatten_false_keeps_nested(self): + with patch("hirad.utils.env_info.get_module_git_info", return_value=None): + info, _ = get_env_info(flatten=False) + assert isinstance(info.get("python"), dict) + + def test_excludes_builtin_modules(self): + import sys + + with patch("hirad.utils.env_info.get_module_git_info", return_value=None): + info, _ = get_env_info(flatten=False) + for name in sys.builtin_module_names: + assert name not in info + + def test_exclude_prefixes_filters_modules(self): + fake_mod = self._make_module("fakepkg_test", version="1.0.0") + with patch.dict("sys.modules", {"fakepkg_test": fake_mod}): + with patch("hirad.utils.env_info.get_module_git_info", return_value=None): + info, _ = get_env_info(flatten=False, exclude_prefixes=["fakepkg_test"]) + assert "fakepkg_test" not in info + + def test_exclude_prefixes_filters_submodules(self): + fake_sub = self._make_module("fakepkg_test.sub", version="2.0.0") + with patch.dict("sys.modules", {"fakepkg_test.sub": fake_sub}): + with patch("hirad.utils.env_info.get_module_git_info", return_value=None): + info, _ = get_env_info(flatten=False, exclude_prefixes=["fakepkg_test"]) + assert "fakepkg_test.sub" not in info + + def test_module_with_version_included(self): + fake_mod = self._make_module("mypkg_env_test", version="3.1.4") + with patch.dict("sys.modules", {"mypkg_env_test": fake_mod}): + with patch("hirad.utils.env_info.get_module_git_info", return_value=None): + info, _ = get_env_info(flatten=False) + assert "mypkg_env_test" in info + assert info["mypkg_env_test"]["version"] == "3.1.4" + + def test_module_with_git_info_included(self): + fake_mod = self._make_module("gitpkg_test", version="0.1.0") + git_info = { + "sha1": "abc123", + "diff": "", + "untracked_files": [], + "remotes": ["https://example.com/repo.git"], + } + with patch.dict("sys.modules", {"gitpkg_test": fake_mod}): + with patch( + "hirad.utils.env_info.get_module_git_info", + side_effect=lambda m: git_info.copy(), + ): + info, _ = get_env_info(flatten=False) + assert "gitpkg_test" in info + assert "git" in info["gitpkg_test"] + assert info["gitpkg_test"]["git"]["sha1"] == "abc123" + assert "diff" not in info["gitpkg_test"]["git"] + + def test_diffs_collected_in_second_element(self): + fake_mod = self._make_module("diffpkg_test", version="1.0.0") + git_info = { + "sha1": "aaa", + "diff": "+added line\n", + "untracked_files": [], + "remotes": [], + } + with patch.dict("sys.modules", {"diffpkg_test": fake_mod}): + with patch( + "hirad.utils.env_info.get_module_git_info", + side_effect=lambda m: git_info.copy(), + ): + _, diffs_str = get_env_info(flatten=False) + assert "+added line" in diffs_str + + def test_modules_without_version_and_git_excluded(self): + fake_mod = self._make_module("bare_mod_test") + with patch.dict("sys.modules", {"bare_mod_test": fake_mod}): + with patch("hirad.utils.env_info.get_module_git_info", return_value=None): + info, _ = get_env_info(flatten=False) + assert "bare_mod_test" not in info + + def test_version_suffix_modules_skipped(self): + """Modules ending with .version or ._version are skipped.""" + fake_ver = self._make_module("somepkg.version", version="1.0") + fake_uver = self._make_module("somepkg._version", version="1.0") + with patch.dict( + "sys.modules", + {"somepkg.version": fake_ver, "somepkg._version": fake_uver}, + ): + with patch("hirad.utils.env_info.get_module_git_info", return_value=None): + info, _ = get_env_info(flatten=False) + assert "somepkg.version" not in info + assert "somepkg._version" not in info diff --git a/tests/utils/test_function_utils.py b/tests/utils/test_function_utils.py new file mode 100644 index 00000000..57a69072 --- /dev/null +++ b/tests/utils/test_function_utils.py @@ -0,0 +1,351 @@ +import datetime + +import numpy as np +import pytest +import torch + +from hirad.utils.function_utils import ( + InfiniteSampler, + StackedRandomGenerator, + get_time_from_range, + time_range, +) + + +############################################################################ +# time_range # +############################################################################ + + +class TestTimeRange: + """Tests for the time_range generator.""" + + def test_basic_hourly_range(self): + start = datetime.datetime(2024, 1, 1, 0, 0) + end = datetime.datetime(2024, 1, 1, 3, 0) + step = datetime.timedelta(hours=1) + result = list(time_range(start, end, step)) + assert result == [ + datetime.datetime(2024, 1, 1, 0, 0), + datetime.datetime(2024, 1, 1, 1, 0), + datetime.datetime(2024, 1, 1, 2, 0), + ] + + def test_inclusive_range(self): + start = datetime.datetime(2024, 1, 1, 0, 0) + end = datetime.datetime(2024, 1, 1, 3, 0) + step = datetime.timedelta(hours=1) + result = list(time_range(start, end, step, inclusive=True)) + assert result == [ + datetime.datetime(2024, 1, 1, 0, 0), + datetime.datetime(2024, 1, 1, 1, 0), + datetime.datetime(2024, 1, 1, 2, 0), + datetime.datetime(2024, 1, 1, 3, 0), + ] + + def test_exclusive_does_not_include_end(self): + start = datetime.datetime(2024, 6, 1, 12, 0) + end = datetime.datetime(2024, 6, 1, 14, 0) + step = datetime.timedelta(hours=1) + result = list(time_range(start, end, step, inclusive=False)) + assert end not in result + + def test_empty_range_when_start_equals_end(self): + t = datetime.datetime(2024, 1, 1, 0, 0) + result = list(time_range(t, t, datetime.timedelta(hours=1))) + assert result == [] + + def test_inclusive_single_element_when_start_equals_end(self): + t = datetime.datetime(2024, 1, 1, 0, 0) + result = list(time_range(t, t, datetime.timedelta(hours=1), inclusive=True)) + assert result == [t] + + def test_empty_range_when_start_after_end(self): + start = datetime.datetime(2024, 1, 2) + end = datetime.datetime(2024, 1, 1) + result = list(time_range(start, end, datetime.timedelta(hours=1))) + assert result == [] + + def test_sub_hourly_step(self): + start = datetime.datetime(2024, 1, 1, 0, 0) + end = datetime.datetime(2024, 1, 1, 0, 30) + step = datetime.timedelta(minutes=10) + result = list(time_range(start, end, step)) + assert len(result) == 3 + assert result == [ + datetime.datetime(2024, 1, 1, 0, 0), + datetime.datetime(2024, 1, 1, 0, 10), + datetime.datetime(2024, 1, 1, 0, 20), + ] + + def test_daily_step(self): + start = datetime.datetime(2024, 1, 1) + end = datetime.datetime(2024, 1, 4) + step = datetime.timedelta(days=1) + result = list(time_range(start, end, step)) + assert len(result) == 3 + assert result[0] == datetime.datetime(2024, 1, 1) + assert result[-1] == datetime.datetime(2024, 1, 3) + + +############################################################################ +# get_time_from_range # +############################################################################ + + +class TestGetTimeFromRange: + """Tests for get_time_from_range.""" + + def test_basic_range_with_default_interval(self): + times = get_time_from_range(["2024-01-01T00:00:00", "2024-01-01T03:00:00"]) + assert len(times) == 4 # inclusive: 00, 01, 02, 03 + assert times[0] == "2024-01-01T00:00:00" + assert times[-1] == "2024-01-01T03:00:00" + + def test_custom_interval(self): + times = get_time_from_range( + ["2024-01-01T00:00:00", "2024-01-01T06:00:00", 2] + ) + assert len(times) == 4 # 00, 02, 04, 06 + assert times == [ + "2024-01-01T00:00:00", + "2024-01-01T02:00:00", + "2024-01-01T04:00:00", + "2024-01-01T06:00:00", + ] + + def test_single_time_when_start_equals_end(self): + times = get_time_from_range(["2024-06-15T12:00:00", "2024-06-15T12:00:00"]) + assert times == ["2024-06-15T12:00:00"] + + def test_multi_day_range(self): + times = get_time_from_range( + ["2024-01-01T00:00:00", "2024-01-02T00:00:00", 6] + ) + assert len(times) == 5 # 00, 06, 12, 18, 00+1day + assert times[-2] == "2024-01-01T18:00:00" + + def test_custom_time_format(self): + fmt = "%Y%m%d-%H%M" + times = get_time_from_range(["20240101-0000", "20240101-0300"], time_format=fmt) + assert len(times) == 4 + assert times[0] == "20240101-0000" + assert times[-1] == "20240101-0300" + + def test_returns_strings(self): + times = get_time_from_range(["2024-01-01T00:00:00", "2024-01-01T02:00:00"]) + assert all(isinstance(t, str) for t in times) + + +############################################################################ +# StackedRandomGenerator # +############################################################################ + + +class TestStackedRandomGenerator: + """Tests for StackedRandomGenerator.""" + + def test_randn_shape(self): + gen = StackedRandomGenerator(device="cpu", seeds=[1, 2, 3]) + out = gen.randn([3, 4, 5]) + assert out.shape == (3, 4, 5) + + def test_randn_batch_mismatch_raises(self): + gen = StackedRandomGenerator(device="cpu", seeds=[1, 2]) + with pytest.raises(ValueError, match="Expected first dimension"): + gen.randn([5, 4]) + + def test_randn_reproducibility(self): + gen1 = StackedRandomGenerator(device="cpu", seeds=[42, 99]) + gen2 = StackedRandomGenerator(device="cpu", seeds=[42, 99]) + out1 = gen1.randn([2, 8]) + out2 = gen2.randn([2, 8]) + assert torch.allclose(out1, out2) + + def test_randn_different_seeds_give_different_output(self): + gen1 = StackedRandomGenerator(device="cpu", seeds=[1, 2]) + gen2 = StackedRandomGenerator(device="cpu", seeds=[3, 4]) + out1 = gen1.randn([2, 100]) + out2 = gen2.randn([2, 100]) + assert not torch.allclose(out1, out2) + + def test_randn_like(self): + gen = StackedRandomGenerator(device="cpu", seeds=[10, 20]) + template = torch.zeros(2, 3, 4) + out = gen.randn_like(template) + assert out.shape == template.shape + assert out.dtype == template.dtype + + def test_randint_shape(self): + gen = StackedRandomGenerator(device="cpu", seeds=[1, 2, 3]) + out = gen.randint(0, 10, size=[3, 5]) + assert out.shape == (3, 5) + + def test_randint_batch_mismatch_raises(self): + gen = StackedRandomGenerator(device="cpu", seeds=[1]) + with pytest.raises(ValueError, match="Expected first dimension"): + gen.randint(0, 10, size=[4, 5]) + + def test_randint_values_in_range(self): + gen = StackedRandomGenerator(device="cpu", seeds=[7, 8]) + out = gen.randint(0, 5, size=[2, 100]) + assert (out >= 0).all() + assert (out < 5).all() + + def test_randint_reproducibility(self): + gen1 = StackedRandomGenerator(device="cpu", seeds=[42, 99]) + gen2 = StackedRandomGenerator(device="cpu", seeds=[42, 99]) + out1 = gen1.randint(0, 100, size=[2, 50]) + out2 = gen2.randint(0, 100, size=[2, 50]) + assert torch.equal(out1, out2) + + def test_randint_different_seeds_give_different_output(self): + gen1 = StackedRandomGenerator(device="cpu", seeds=[1, 2]) + gen2 = StackedRandomGenerator(device="cpu", seeds=[3, 4]) + out1 = gen1.randint(0, 100, size=[2, 50]) + out2 = gen2.randint(0, 100, size=[2, 50]) + assert not torch.equal(out1, out2) + + def test_single_seed(self): + gen = StackedRandomGenerator(device="cpu", seeds=[0]) + out = gen.randn([1, 10]) + assert out.shape == (1, 10) + + +############################################################################ +# InfiniteSampler # +############################################################################ + + +class TestInfiniteSampler: + """Tests for InfiniteSampler.""" + + @pytest.fixture + def simple_dataset(self): + """A minimal dataset with 10 items.""" + return list(range(10)) + + def test_yields_indices(self, simple_dataset): + sampler = InfiniteSampler(simple_dataset, shuffle=False) + it = iter(sampler) + indices = [next(it) for _ in range(10)] + assert indices == list(range(10)) + + def test_infinite_iteration(self, simple_dataset): + sampler = InfiniteSampler(simple_dataset, shuffle=False) + it = iter(sampler) + # Should be able to draw more samples than the dataset size + indices = [next(it) for _ in range(25)] + assert len(indices) == 25 + + def test_loops_over_dataset(self, simple_dataset): + sampler = InfiniteSampler(simple_dataset, shuffle=False) + it = iter(sampler) + first_pass = [next(it) for _ in range(10)] + second_pass = [next(it) for _ in range(10)] + assert first_pass == list(range(10)) + assert second_pass == list(range(10)) + + def test_shuffle_produces_different_order(self, simple_dataset): + sampler = InfiniteSampler(simple_dataset, shuffle=True, seed=42) + it = iter(sampler) + indices = [next(it) for _ in range(10)] + # With shuffling, the indices should not be in sorted order + # (extremely unlikely for seed=42 with 10 items) + assert indices != list(range(10)) + + def test_going_through_full_dataset_with_shuffle(self, simple_dataset): + sampler = InfiniteSampler(simple_dataset, shuffle=True, seed=123) + it = iter(sampler) + seen = set() + for _ in range(10): + idx = next(it) + assert idx not in seen # should see each index once before repeats + seen.add(idx) + + def test_seed_reproducibility(self, simple_dataset): + sampler1 = InfiniteSampler(simple_dataset, shuffle=True, seed=123) + sampler2 = InfiniteSampler(simple_dataset, shuffle=True, seed=123) + it1 = iter(sampler1) + it2 = iter(sampler2) + for _ in range(30): + assert next(it1) == next(it2) + + def test_different_seeds_different_order(self, simple_dataset): + sampler1 = InfiniteSampler(simple_dataset, shuffle=True, seed=1) + sampler2 = InfiniteSampler(simple_dataset, shuffle=True, seed=999) + it1 = iter(sampler1) + it2 = iter(sampler2) + seq1 = [next(it1) for _ in range(20)] + seq2 = [next(it2) for _ in range(20)] + assert seq1 != seq2 + + def test_distributed_sampling(self, simple_dataset): + """Each rank should yield non-overlapping indices.""" + sampler0 = InfiniteSampler( + simple_dataset, rank=0, num_replicas=2, shuffle=False + ) + sampler1 = InfiniteSampler( + simple_dataset, rank=1, num_replicas=2, shuffle=False + ) + it0 = iter(sampler0) + it1 = iter(sampler1) + indices0 = [next(it0) for _ in range(5)] + indices1 = [next(it1) for _ in range(5)] + # The two ranks should receive different indices + assert set(indices0) != set(indices1) + + def test_start_idx(self, simple_dataset): + sampler_default = InfiniteSampler(simple_dataset, shuffle=False, start_idx=0) + sampler_offset = InfiniteSampler(simple_dataset, shuffle=False, start_idx=5) + it_default = iter(sampler_default) + it_offset = iter(sampler_offset) + # Skip the first 5 from the default sampler + for _ in range(5): + next(it_default) + # Now they should be aligned + for _ in range(10): + assert next(it_default) == next(it_offset) + + def test_start_idx_larger_than_dataset_size(self, simple_dataset): + sampler = InfiniteSampler(simple_dataset, shuffle=False, start_idx=12) + it = iter(sampler) + # start_idx=12 should wrap around to index 2 on the first yield + assert next(it) == 2 + + def test_window_size_zero_no_shuffle_effect(self, simple_dataset): + sampler = InfiniteSampler( + simple_dataset, shuffle=True, seed=42, window_size=0.0 + ) + it = iter(sampler) + # With window_size=0, window rounds to 0, so no swapping occurs. + # Items come out in seed-shuffled initial order but stay fixed. + indices = [next(it) for _ in range(10)] + assert len(set(indices)) == 10 # all unique in first pass + + # --- Validation tests --- + + def test_empty_dataset_raises(self): + with pytest.raises(ValueError, match="at least one item"): + InfiniteSampler([]) + + def test_invalid_num_replicas_raises(self, simple_dataset): + with pytest.raises(ValueError, match="num_replicas must be positive"): + InfiniteSampler(simple_dataset, num_replicas=0) + + def test_invalid_rank_raises(self, simple_dataset): + with pytest.raises(ValueError, match="rank must be non-negative"): + InfiniteSampler(simple_dataset, rank=-1, num_replicas=2) + + def test_rank_exceeds_replicas_raises(self, simple_dataset): + with pytest.raises(ValueError, match="rank must be non-negative"): + InfiniteSampler(simple_dataset, rank=3, num_replicas=2) + + def test_invalid_window_size_raises(self, simple_dataset): + with pytest.raises(ValueError, match="window_size must be between"): + InfiniteSampler(simple_dataset, window_size=1.5) + + def test_negative_window_size_raises(self, simple_dataset): + with pytest.raises(ValueError, match="window_size must be between"): + InfiniteSampler(simple_dataset, window_size=-0.1) + diff --git a/tests/utils/test_inference_utils.py b/tests/utils/test_inference_utils.py new file mode 100644 index 00000000..d6d4ae50 --- /dev/null +++ b/tests/utils/test_inference_utils.py @@ -0,0 +1,423 @@ +import pytest +import os +import numpy as np +import torch +from unittest.mock import MagicMock, patch + +from hirad.utils.inference_utils import ( + calculate_bounds, + regression_step, + diffusion_step, + save_results_as_torch, +) + + +############################################################################ +# calculate_bounds # +############################################################################ + + +class TestCalculateBounds: + """Tests for calculate_bounds.""" + + def test_single_array(self): + arr = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + vmin, vmax = calculate_bounds(arr) + assert vmin == 1.0 + assert vmax == 5.0 + + def test_multiple_arrays(self): + a = np.array([1.0, 5.0]) + b = np.array([-3.0, 2.0]) + c = np.array([0.0, 10.0]) + vmin, vmax = calculate_bounds(a, b, c) + assert vmin == -3.0 + assert vmax == 10.0 + + def test_no_arrays(self): + vmin, vmax = calculate_bounds() + assert vmin is None + assert vmax is None + + def test_all_none(self): + vmin, vmax = calculate_bounds(None, None) + assert vmin is None + assert vmax is None + + def test_some_none(self): + arr = np.array([2.0, 8.0]) + vmin, vmax = calculate_bounds(None, arr, None) + assert vmin == 2.0 + assert vmax == 8.0 + + def test_single_value_array(self): + arr = np.array([42.0]) + vmin, vmax = calculate_bounds(arr) + assert vmin == 42.0 + assert vmax == 42.0 + + def test_negative_values(self): + arr = np.array([-10.0, -5.0, -1.0]) + vmin, vmax = calculate_bounds(arr) + assert vmin == -10.0 + assert vmax == -1.0 + + def test_masked_array(self): + data = np.array([1.0, np.nan, 3.0, 4.0, np.nan]) + masked = np.ma.masked_invalid(data) + vmin, vmax = calculate_bounds(masked) + assert vmin == 1.0 + assert vmax == 4.0 + + def test_masked_array_all_masked(self): + data = np.ma.array([1.0, 2.0], mask=[True, True]) + vmin, vmax = calculate_bounds(data) + assert vmin == None + assert vmax == None + + def test_mixed_masked_and_regular(self): + regular = np.array([0.0, 5.0]) + masked = np.ma.masked_invalid(np.array([np.nan, 10.0, np.nan])) + vmin, vmax = calculate_bounds(regular, masked) + assert vmin == 0.0 + assert vmax == 10.0 + + def test_2d_array(self): + arr = np.array([[1.0, 2.0], [3.0, 4.0]]) + vmin, vmax = calculate_bounds(arr) + assert vmin == 1.0 + assert vmax == 4.0 + + def test_scalar_input(self): + vmin, vmax = calculate_bounds(np.float64(3.14)) + assert vmin == pytest.approx(3.14) + assert vmax == pytest.approx(3.14) + + +############################################################################ +# regression_step # +############################################################################ + + +class TestRegressionStep: + """Tests for regression_step.""" + + def _make_mock_net(self, output_shape): + """Create a mock network that returns a tensor of the given shape.""" + net = MagicMock() + net.return_value = torch.randn(output_shape) + return net + + def test_batch_size_greater_than_1_raises(self): + net = self._make_mock_net((1, 4, 8, 8)) + img_lr = torch.randn(2, 3, 8, 8) # batch_size=2 + latents_shape = torch.Size([1, 4, 8, 8]) + with pytest.raises(ValueError, match="batch size of 1"): + regression_step(net, img_lr, latents_shape) + + def test_batch_size_1_succeeds(self): + net = self._make_mock_net((1, 4, 8, 8)) + img_lr = torch.randn(1, 3, 8, 8) + latents_shape = torch.Size([1, 4, 8, 8]) + result = regression_step(net, img_lr, latents_shape) + assert result.shape == torch.Size([1, 4, 8, 8]) + + def test_output_replicated_when_latents_batch_gt_1(self): + net = self._make_mock_net((1, 4, 8, 8)) + img_lr = torch.randn(1, 3, 8, 8) + latents_shape = torch.Size([5, 4, 8, 8]) + result = regression_step(net, img_lr, latents_shape) + assert result.shape == torch.Size([5, 4, 8, 8]) + + def test_net_called_with_img_lr(self): + net = self._make_mock_net((1, 4, 8, 8)) + img_lr = torch.randn(1, 3, 8, 8) + latents_shape = torch.Size([1, 4, 8, 8]) + regression_step(net, img_lr, latents_shape) + assert net.called + + def test_with_lead_time_label(self): + net = self._make_mock_net((1, 4, 8, 8)) + img_lr = torch.randn(1, 3, 8, 8) + latents_shape = torch.Size([1, 4, 8, 8]) + lead_time = torch.tensor([1.0]) + result = regression_step( + net, img_lr, latents_shape, lead_time_label=lead_time + ) + assert result.shape == torch.Size([1, 4, 8, 8]) + # Verify lead_time_label was passed to the net + _, kwargs = net.call_args + assert "lead_time_label" in kwargs + + def test_with_static_channels(self): + net = self._make_mock_net((1, 4, 8, 8)) + img_lr = torch.randn(1, 3, 8, 8) + static = torch.randn(1, 2, 8, 8) + latents_shape = torch.Size([1, 4, 8, 8]) + result = regression_step( + net, img_lr, latents_shape, static_channels=static + ) + assert result.shape == torch.Size([1, 4, 8, 8]) + # Net should receive img_lr concatenated with static channels (3+2=5) + _, kwargs = net.call_args + assert kwargs["img_lr"].shape[1] == 5 + + def test_with_date_embedding(self): + net = self._make_mock_net((1, 4, 8, 8)) + img_lr = torch.randn(1, 3, 8, 8) + date_emb = torch.randn(1, 4) + latents_shape = torch.Size([1, 4, 8, 8]) + result = regression_step( + net, img_lr, latents_shape, date_embedding=date_emb + ) + assert result.shape == torch.Size([1, 4, 8, 8]) + # Net should receive img_lr concatenated with date embedding (3+4=7) + _, kwargs = net.call_args + assert kwargs["img_lr"].shape[1] == 7 + + def test_with_all_optional_inputs(self): + net = self._make_mock_net((1, 4, 8, 8)) + img_lr = torch.randn(1, 3, 8, 8) + static = torch.randn(1, 2, 8, 8) + date_emb = torch.randn(1, 4) + lead_time = torch.tensor([1.0]) + latents_shape = torch.Size([1, 4, 8, 8]) + result = regression_step( + net, + img_lr, + latents_shape, + lead_time_label=lead_time, + static_channels=static, + date_embedding=date_emb, + ) + assert result.shape == torch.Size([1, 4, 8, 8]) + # img_lr should have 3 + 2 (static) + 4 (date) = 9 channels + _, kwargs = net.call_args + assert kwargs["img_lr"].shape[1] == 9 + + +############################################################################ +# diffusion_step # +############################################################################ + + +class TestDiffusionStep: + """Tests for diffusion_step.""" + + def test_img_lr_shape_mismatch_raises(self): + net = MagicMock() + sampler_fn = MagicMock() + img_lr = torch.randn(1, 3, 16, 16) + with pytest.raises(ValueError, match="does not match expected shape"): + diffusion_step( + net=net, + sampler_fn=sampler_fn, + img_shape=(32, 32), + img_out_channels=4, + rank_batches=[[0]], + img_lr=img_lr, + rank=0, + device=torch.device("cpu"), + ) + + def test_mean_hr_shape_mismatch_raises(self): + net = MagicMock() + sampler_fn = MagicMock() + img_lr = torch.randn(1, 3, 32, 32) + mean_hr = torch.randn(1, 4, 16, 16) + with pytest.raises(ValueError, match="does not match expected shape"): + diffusion_step( + net=net, + sampler_fn=sampler_fn, + img_shape=(32, 32), + img_out_channels=4, + rank_batches=[[0]], + img_lr=img_lr, + rank=0, + device=torch.device("cpu"), + mean_hr=mean_hr, + ) + + def test_mean_hr_batch_size_not_1_raises(self): + net = MagicMock() + sampler_fn = MagicMock() + img_lr = torch.randn(1, 3, 32, 32) + mean_hr = torch.randn(2, 4, 32, 32) + with pytest.raises(ValueError, match="batch size 1"): + diffusion_step( + net=net, + sampler_fn=sampler_fn, + img_shape=(32, 32), + img_out_channels=4, + rank_batches=[[0]], + img_lr=img_lr, + rank=0, + device=torch.device("cpu"), + mean_hr=mean_hr, + ) + + def test_empty_rank_batches(self): + net = MagicMock() + sampler_fn = MagicMock() + img_lr = torch.randn(1, 3, 8, 8) + # Empty batches of seeds + with pytest.raises(ValueError, match="rank_batches is empty"): + diffusion_step( + net=net, + sampler_fn=sampler_fn, + img_shape=(8, 8), + img_out_channels=4, + rank_batches=[], + img_lr=img_lr, + rank=0, + device=torch.device("cpu"), + ) + + def test_missmatch_batch_size_and_image_shape(self): + net = MagicMock() + sampler_fn = MagicMock() + img_lr = torch.randn(1, 3, 8, 8) + # rank_batches has batch size 2 but img_shape is for batch size 1 + with pytest.raises(ValueError, match="does not match img_lr batch size"): + diffusion_step( + net=net, + sampler_fn=sampler_fn, + img_shape=(8, 8), + img_out_channels=4, + rank_batches=[[0,1], [2,3]], + img_lr=img_lr, + rank=0, + device=torch.device("cpu"), + ) + + def test_generates_correct_number_of_samples(self): + net = MagicMock() + generated = torch.randn(1, 4, 8, 8) + sampler_fn = MagicMock(return_value=generated) + img_lr = torch.randn(1, 3, 8, 8) + result = diffusion_step( + net=net, + sampler_fn=sampler_fn, + img_shape=(8, 8), + img_out_channels=4, + rank_batches=[[0], [1], [2]], + img_lr=img_lr, + rank=0, + device=torch.device("cpu"), + ) + assert result.shape == torch.Size([3, 4, 8, 8]) + assert sampler_fn.call_count == 3 + + def test_passes_additional_args_to_sampler(self): + net = MagicMock() + generated = torch.randn(1, 4, 8, 8) + sampler_fn = MagicMock(return_value=generated) + img_lr = torch.randn(1, 3, 8, 8) + mean_hr = torch.randn(1, 4, 8, 8) + lead_time = torch.tensor([1.0]) + static = torch.randn(1, 2, 8, 8) + date_emb = torch.randn(1, 4) + + diffusion_step( + net=net, + sampler_fn=sampler_fn, + img_shape=(8, 8), + img_out_channels=4, + rank_batches=[[42]], + img_lr=img_lr, + rank=0, + device=torch.device("cpu"), + mean_hr=mean_hr, + lead_time_label=lead_time, + static_channels=static, + date_embedding=date_emb, + ) + + _, kwargs = sampler_fn.call_args + assert "mean_hr" in kwargs + assert "lead_time_label" in kwargs + assert "static_channels" in kwargs + assert "date_embedding" in kwargs + + +############################################################################ +# save_results_as_torch # +############################################################################ + + +class TestSaveResultsAsTorch: + """Tests for save_results_as_torch.""" + + def test_creates_output_directory(self, tmp_path): + output_dir = tmp_path / "results" / "nested" + image_pred = torch.randn(2, 4, 8, 8) + image_hr = torch.randn(1, 4, 8, 8) + image_lr = torch.randn(1, 3, 8, 8) + mean_pred = torch.randn(1, 4, 8, 8) + + save_results_as_torch( + str(output_dir), "step_0", image_pred, image_hr, image_lr, mean_pred + ) + assert output_dir.exists() + + def test_saves_all_files_with_mean(self, tmp_path): + image_pred = torch.randn(2, 4, 8, 8) + image_hr = torch.randn(1, 4, 8, 8) + image_lr = torch.randn(1, 3, 8, 8) + mean_pred = torch.randn(1, 4, 8, 8) + + save_results_as_torch( + str(tmp_path), "step_0", image_pred, image_hr, image_lr, mean_pred + ) + + assert os.path.isfile(tmp_path / "step_0-regression-prediction") + assert os.path.isfile(tmp_path / "step_0-target") + assert os.path.isfile(tmp_path / "step_0-predictions") + assert os.path.isfile(tmp_path / "step_0-baseline") + + def test_skips_regression_when_mean_is_none(self, tmp_path): + image_pred = torch.randn(2, 4, 8, 8) + image_hr = torch.randn(1, 4, 8, 8) + image_lr = torch.randn(1, 3, 8, 8) + + save_results_as_torch( + str(tmp_path), "step_1", image_pred, image_hr, image_lr, None + ) + + assert not os.path.isfile(tmp_path / "step_1-regression-prediction") + assert os.path.isfile(tmp_path / "step_1-target") + assert os.path.isfile(tmp_path / "step_1-predictions") + assert os.path.isfile(tmp_path / "step_1-baseline") + + def test_saved_tensors_are_loadable_and_correct(self, tmp_path): + image_pred = torch.randn(2, 4, 8, 8) + image_hr = torch.randn(1, 4, 8, 8) + image_lr = torch.randn(1, 3, 8, 8) + mean_pred = torch.randn(1, 4, 8, 8) + + save_results_as_torch( + str(tmp_path), "t0", image_pred, image_hr, image_lr, mean_pred + ) + + loaded_pred = torch.load(tmp_path / "t0-predictions", weights_only=True) + loaded_hr = torch.load(tmp_path / "t0-target", weights_only=True) + loaded_lr = torch.load(tmp_path / "t0-baseline", weights_only=True) + loaded_mean = torch.load(tmp_path / "t0-regression-prediction", weights_only=True) + + assert torch.equal(loaded_pred, image_pred) + assert torch.equal(loaded_hr, image_hr) + assert torch.equal(loaded_lr, image_lr) + assert torch.equal(loaded_mean, mean_pred) + + def test_different_time_steps_dont_overwrite(self, tmp_path): + t1 = torch.randn(1, 4, 8, 8) + t2 = torch.randn(1, 4, 8, 8) + + save_results_as_torch(str(tmp_path), "step_0", t1, t1, t1, None) + save_results_as_torch(str(tmp_path), "step_1", t2, t2, t2, None) + + loaded_0 = torch.load(tmp_path / "step_0-target", weights_only=True) + loaded_1 = torch.load(tmp_path / "step_1-target", weights_only=True) + + assert torch.equal(loaded_0, t1) + assert torch.equal(loaded_1, t2) \ No newline at end of file diff --git a/tests/utils/test_model_utils.py b/tests/utils/test_model_utils.py new file mode 100644 index 00000000..68471282 --- /dev/null +++ b/tests/utils/test_model_utils.py @@ -0,0 +1,107 @@ +import pytest +import numpy as np +import torch + +from hirad.utils.model_utils import weight_init + + +class TestWeightInitShape: + """Test that weight_init returns tensors of the correct shape.""" + + @pytest.mark.parametrize("mode", [ + "xavier_uniform", + "xavier_normal", + "kaiming_uniform", + "kaiming_normal", + ]) + @pytest.mark.parametrize("shape", [ + (1,), + (3, 3), + (64, 32, 3, 3), + (128, 64, 5, 5), + ]) + def test_output_shape(self, mode, shape): + result = weight_init(shape, mode, fan_in=32, fan_out=64) + assert result.shape == torch.Size(shape) + + @pytest.mark.parametrize("mode", [ + "xavier_uniform", + "xavier_normal", + "kaiming_uniform", + "kaiming_normal", + ]) + def test_output_is_tensor(self, mode): + result = weight_init((4, 4), mode, fan_in=4, fan_out=4) + assert isinstance(result, torch.Tensor) + + +class TestWeightInitValues: + """Test that weight_init produces values within expected bounds.""" + + def test_xavier_uniform_bound(self): + fan_in, fan_out = 256, 512 + bound = np.sqrt(6 / (fan_in + fan_out)) + result = weight_init((10000,), "xavier_uniform", fan_in, fan_out) + assert result.min() >= -bound - 1e-7 + assert result.max() <= bound + 1e-7 + + def test_kaiming_uniform_bound(self): + fan_in, fan_out = 256, 512 + bound = np.sqrt(3 / fan_in) + result = weight_init((10000,), "kaiming_uniform", fan_in, fan_out) + assert result.min() >= -bound - 1e-7 + assert result.max() <= bound + 1e-7 + + def test_xavier_normal_mean_and_std(self): + fan_in, fan_out = 256, 512 + expected_std = np.sqrt(2 / (fan_in + fan_out)) + result = weight_init((100000,), "xavier_normal", fan_in, fan_out) + assert abs(result.mean().item()) < 0.05 + assert abs(result.std().item() - expected_std) < 0.01 + + def test_kaiming_normal_mean_and_std(self): + fan_in, fan_out = 256, 512 + expected_std = np.sqrt(1 / fan_in) + result = weight_init((100000,), "kaiming_normal", fan_in, fan_out) + assert abs(result.mean().item()) < 0.05 + assert abs(result.std().item() - expected_std) < 0.01 + + +class TestWeightInitSymmetry: + """Test that uniform modes are centered around zero.""" + + @pytest.mark.parametrize("mode", ["xavier_uniform", "kaiming_uniform"]) + def test_uniform_centered_around_zero(self, mode): + result = weight_init((100000,), mode, fan_in=128, fan_out=128) + assert abs(result.mean().item()) < 0.05 + + +class TestWeightInitScaling: + """Test that changing fan_in/fan_out changes the scale of outputs.""" + + @pytest.mark.parametrize("mode", [ + "xavier_uniform", + "xavier_normal", + "kaiming_uniform", + "kaiming_normal", + ]) + def test_larger_fan_in_reduces_scale(self, mode): + small_fan = weight_init((10000,), mode, fan_in=16, fan_out=16) + large_fan = weight_init((10000,), mode, fan_in=1024, fan_out=1024) + assert small_fan.std() > large_fan.std() + + +class TestWeightInitInvalidMode: + """Test that invalid modes raise ValueError.""" + + @pytest.mark.parametrize("mode", [ + "invalid", + "", + "Xavier_uniform", + "KAIMING_NORMAL", + "he_normal", + "glorot_uniform", + ]) + def test_invalid_mode_raises_value_error(self, mode): + with pytest.raises(ValueError, match="Invalid init mode"): + weight_init((4, 4), mode, fan_in=4, fan_out=4) \ No newline at end of file diff --git a/tests/utils/test_patching.py b/tests/utils/test_patching.py new file mode 100644 index 00000000..05c7d8ac --- /dev/null +++ b/tests/utils/test_patching.py @@ -0,0 +1,644 @@ +import math + +import pytest +import torch + +from hirad.utils.patching import ( + BasePatching2D, + GridPatching2D, + RandomPatching2D, + image_batching, + image_fuse, +) + + +############################################################################ +# BasePatching2D — init # +############################################################################ + + +class TestBasePatching2DInit: + """Tests for BasePatching2D initialization and validation.""" + + def test_non_2d_img_shape_raises(self): + """img_shape with wrong number of dimensions should raise ValueError.""" + with pytest.raises(ValueError, match="img_shape must be 2D"): + # Use GridPatching2D as a concrete subclass + GridPatching2D(img_shape=(64, 64, 3), patch_shape=(32, 32)) + + def test_non_2d_patch_shape_raises(self): + """patch_shape with wrong number of dimensions should raise ValueError.""" + with pytest.raises(ValueError, match="patch_shape must be 2D"): + GridPatching2D(img_shape=(64, 64), patch_shape=(32, 32, 1)) + + def test_patch_larger_than_image_warns(self): + """patch_shape larger than img_shape should issue a warning.""" + with pytest.warns(UserWarning, match="larger than"): + GridPatching2D(img_shape=(32, 32), patch_shape=(64, 64)) + + def test_patch_clamped_to_image_shape(self): + """patch_shape should be clamped to img_shape when it exceeds it.""" + with pytest.warns(UserWarning): + patcher = GridPatching2D(img_shape=(32, 48), patch_shape=(64, 64)) + assert patcher.patch_shape == (32, 48) + + def test_valid_shapes_stored(self): + patcher = GridPatching2D(img_shape=(64, 128), patch_shape=(32, 32)) + assert patcher.img_shape == (64, 128) + assert patcher.patch_shape == (32, 32) + + +############################################################################ +# BasePatching2D — global_index # +############################################################################ + + +class TestBasePatching2DGlobalIndex: + """Tests for the global_index method.""" + + def test_global_index_shape(self): + patcher = GridPatching2D(img_shape=(64, 64), patch_shape=(32, 32)) + gi = patcher.global_index(batch_size=1) + assert gi.ndim == 4 + assert gi.shape[1] == 2 + assert gi.shape[2] == patcher.patch_shape[0] + assert gi.shape[3] == patcher.patch_shape[1] + + def test_global_index_values_within_image(self): + """All global indices should fall within the original image dimensions.""" + patcher = GridPatching2D( + img_shape=(64, 128), patch_shape=(32, 32), overlap_pix=4 + ) + gi = patcher.global_index(batch_size=1) + assert gi[:, 0].min() >= 0 + assert gi[:, 1].min() >= 0 + # Padded indices may exceed image shape, but y/x coords should be valid + assert gi[:, 0].max() < patcher.img_shape[0] + patcher.patch_shape[0] + assert gi[:, 1].max() < patcher.img_shape[1] + patcher.patch_shape[1] + + def test_global_index_values_simple_case(self): + """ In a simple 4x4 image with 2x2 patches and no overlap, global indices should be predictable. """ + patcher = GridPatching2D(img_shape=(4, 4), patch_shape=(2, 2), overlap_pix=0) + gi = patcher.global_index(batch_size=1) + expected_indices = torch.tensor([ + [[[0, 0], [1, 1]], [[0, 1], [0, 1]]], + [[[2, 2], [3, 3]], [[0, 1], [0, 1]]], + [[[0, 0], [1, 1]], [[2, 3], [2, 3]]], + [[[2, 2], [3, 3]], [[2, 3], [2, 3]]] + ]) + assert torch.equal(gi.cpu(), expected_indices) + + def test_global_index_device(self): + patcher = GridPatching2D(img_shape=(32, 32), patch_shape=(16, 16)) + gi = patcher.global_index(batch_size=1, device="cpu") + assert gi.device == torch.device("cpu") + + +############################################################################ +# BasePatching2D — fuse not implemented # +############################################################################ + + +class TestBasePatching2DFuse: + """Tests that fuse raises NotImplementedError for subclasses that don't implement it.""" + + def test_random_patching_fuse_raises(self): + """RandomPatching2D does not implement fuse.""" + patcher = RandomPatching2D( + img_shape=(64, 64), patch_shape=(32, 32), patch_num=4 + ) + dummy = torch.randn(4, 3, 32, 32) + with pytest.raises(NotImplementedError, match="fuse"): + patcher.fuse(dummy) + + +############################################################################ +# BasePatching2D — apply abstract method # +############################################################################ + + +class TestBasePatching2DApply: + """Tests that apply raises NotImplementedError for subclasses that don't implement it.""" + + def test_grid_patching_apply_raises(self): + """BasePatching2D does not implement apply.""" + with pytest.raises(TypeError, match="apply"): + patcher = BasePatching2D(img_shape=(64, 64), patch_shape=(32, 32)) + + +############################################################################ +# RandomPatching2D — init # +############################################################################ + + +class TestRandomPatching2DInit: + """Tests for RandomPatching2D initialization.""" + + def test_patch_num_stored(self): + patcher = RandomPatching2D( + img_shape=(64, 64), patch_shape=(32, 32), patch_num=8 + ) + assert patcher.patch_num == 8 + + def test_patch_indices_generated_on_init(self): + patcher = RandomPatching2D( + img_shape=(64, 64), patch_shape=(32, 32), patch_num=5 + ) + assert len(patcher.patch_indices) == 5 + + def test_patch_indices_within_bounds(self): + img_h, img_w = 100, 120 + patch_h, patch_w = 30, 40 + patcher = RandomPatching2D( + img_shape=(img_h, img_w), patch_shape=(patch_h, patch_w), patch_num=20 + ) + for py, px in patcher.patch_indices: + assert 0 <= py <= img_h - patch_h + assert 0 <= px <= img_w - patch_w + + +############################################################################ +# RandomPatching2D — set / reset indices # +############################################################################ + + +class TestRandomPatching2DIndices: + """Tests for patch index manipulation.""" + + def test_reset_changes_indices(self): + """Resetting should produce new random indices (with overwhelming probability).""" + patcher = RandomPatching2D( + img_shape=(256, 256), patch_shape=(32, 32), patch_num=50 + ) + old_indices = list(patcher.patch_indices) + patcher.reset_patch_indices() + # Extremely unlikely to be identical for 50 patches on a 256x256 image + assert patcher.patch_indices != old_indices + + def test_get_patch_indices_returns_current(self): + patcher = RandomPatching2D( + img_shape=(64, 64), patch_shape=(16, 16), patch_num=3 + ) + assert patcher.get_patch_indices() is patcher.patch_indices + + def test_set_patch_num_updates_count_and_indices(self): + patcher = RandomPatching2D( + img_shape=(64, 64), patch_shape=(16, 16), patch_num=3 + ) + patcher.set_patch_num(10) + assert patcher.patch_num == 10 + assert len(patcher.patch_indices) == 10 + + +############################################################################ +# RandomPatching2D — apply # +############################################################################ + + +class TestRandomPatching2DApply: + """Tests for RandomPatching2D.apply.""" + + @pytest.fixture + def patcher_and_input(self): + img_shape = (64, 64) + patch_shape = (16, 16) + patch_num = 4 + patcher = RandomPatching2D(img_shape, patch_shape, patch_num) + batch_size, channels = 2, 3 + x = torch.randn(batch_size, channels, *img_shape) + return patcher, x, batch_size, channels + + def test_output_shape(self, patcher_and_input): + patcher, x, batch_size, channels = patcher_and_input + out = patcher.apply(x) + assert out.shape == ( + batch_size * patcher.patch_num, + channels, + patcher.patch_shape[0], + patcher.patch_shape[1], + ) + + def test_output_values_match_input_slices(self, patcher_and_input): + """Each patch in the output should correspond to the correct slice of input.""" + patcher, x, batch_size, _ = patcher_and_input + out = patcher.apply(x) + for i, (py, px) in enumerate(patcher.patch_indices): + expected = x[ + :, :, + py : py + patcher.patch_shape[0], + px : px + patcher.patch_shape[1], + ] + torch.testing.assert_close( + out[batch_size * i : batch_size * (i + 1)], expected + ) + + def test_apply_with_additional_input(self, patcher_and_input): + """Additional input should be concatenated along channel dim.""" + patcher, x, batch_size, channels = patcher_and_input + add_channels = 2 + additional = torch.randn(batch_size, add_channels, 32, 32) + out = patcher.apply(x, additional_input=additional) + assert out.shape[1] == channels + add_channels + assert torch.allclose(out[:, :channels], patcher.apply(x)) + assert torch.allclose(out[:, channels:], torch.nn.functional.interpolate( + additional, size=patcher.patch_shape, mode="bilinear").repeat(patcher.patch_num, 1, 1, 1)) + + def test_apply_single_patch(self): + """Test with a single patch.""" + patcher = RandomPatching2D( + img_shape=(32, 32), patch_shape=(32, 32), patch_num=1 + ) + x = torch.randn(1, 1, 32, 32) + out = patcher.apply(x) + torch.testing.assert_close(out, x) + + +############################################################################ +# GridPatching2D — init # +############################################################################ + + +class TestGridPatching2DInit: + """Tests for GridPatching2D initialization.""" + + def test_patch_num_no_overlap(self): + """Without overlap, patches should tile the image exactly.""" + patcher = GridPatching2D(img_shape=(64, 64), patch_shape=(32, 32)) + expected_x = math.ceil(64 / 32) + expected_y = math.ceil(64 / 32) + assert patcher.patch_num == expected_x * expected_y + + def test_patch_num_with_overlap(self): + patcher = GridPatching2D( + img_shape=(64, 64), patch_shape=(32, 32), overlap_pix=8 + ) + expected_x = math.ceil(64 / (32 - 8)) + expected_y = math.ceil(64 / (32 - 8)) + assert patcher.patch_num == expected_x * expected_y + + def test_patch_num_with_boundary(self): + patcher = GridPatching2D( + img_shape=(64, 64), patch_shape=(32, 32), boundary_pix=4 + ) + expected_x = math.ceil(64 / (32 - 4)) + expected_y = math.ceil(64 / (32 - 4)) + assert patcher.patch_num == expected_x * expected_y + + def test_patch_num_with_overlap_and_boundary(self): + patcher = GridPatching2D( + img_shape=(64, 64), patch_shape=(32, 32), + overlap_pix=8, boundary_pix=10 + ) + expected_x = math.ceil(64 / (32 - 8 - 10)) + expected_y = math.ceil(64 / (32 - 8 - 10)) + assert patcher.patch_num == expected_x * expected_y + + def test_non_divisible_image_shape(self): + """Image dimensions that don't divide evenly by stride should still work.""" + patcher = GridPatching2D(img_shape=(100, 77), patch_shape=(32, 32)) + assert patcher.patch_num == math.ceil(100 / 32) * math.ceil(77 / 32) + + +############################################################################ +# GridPatching2D — apply # +############################################################################ + + +class TestGridPatching2DApply: + """Tests for GridPatching2D.apply.""" + + @pytest.fixture + def grid_patcher_and_input(self): + img_shape = (64, 64) + patch_shape = (32, 32) + patcher = GridPatching2D(img_shape, patch_shape) + batch_size, channels = 2, 3 + x = torch.randn(batch_size, channels, *img_shape) + return patcher, x, batch_size, channels + + def test_output_shape(self, grid_patcher_and_input): + patcher, x, batch_size, channels = grid_patcher_and_input + out = patcher.apply(x) + assert out.shape == ( + batch_size * patcher.patch_num, + channels, + patcher.patch_shape[0], + patcher.patch_shape[1], + ) + + def test_output_shape_with_additional_input(self, grid_patcher_and_input): + patcher, x, batch_size, channels = grid_patcher_and_input + add_channels = 5 + additional = torch.randn(batch_size, add_channels, 16, 16) + out = patcher.apply(x, additional_input=additional) + assert out.shape == ( + batch_size * patcher.patch_num, + channels + add_channels, + patcher.patch_shape[0], + patcher.patch_shape[1], + ) + + def test_apply_with_overlap(self): + patcher = GridPatching2D( + img_shape=(64, 64), patch_shape=(32, 32), overlap_pix=8 + ) + x = torch.randn(1, 1, 64, 64) + out = patcher.apply(x) + assert out.shape == (patcher.patch_num, 1, 32, 32) + + def test_apply_with_boundary(self): + patcher = GridPatching2D( + img_shape=(64, 64), patch_shape=(32, 32), boundary_pix=4 + ) + x = torch.randn(1, 1, 64, 64) + out = patcher.apply(x) + assert out.shape == (patcher.patch_num, 1, 32, 32) + + def test_apply_with_overlap_and_boundary(self): + patcher = GridPatching2D( + img_shape=(64, 64), patch_shape=(32, 32), + overlap_pix=8, boundary_pix=10 + ) + x = torch.randn(1, 1, 64, 64) + out = patcher.apply(x) + assert out.shape == (patcher.patch_num, 1, 32, 32) + + +############################################################################ +# GridPatching2D — fuse # +############################################################################ + + +class TestGridPatching2DFuse: + """Tests for GridPatching2D.fuse.""" + + def test_fuse_output_shape(self): + img_shape = (64, 64) + patcher = GridPatching2D(img_shape, patch_shape=(32, 32)) + batch_size, channels = 2, 3 + patches = torch.randn( + batch_size * patcher.patch_num, channels, + patcher.patch_shape[0], patcher.patch_shape[1], + ) + fused = patcher.fuse(patches, batch_size=batch_size) + assert fused.shape == (batch_size, channels, *img_shape) + + def test_fuse_output_shape_with_overlap(self): + img_shape = (64, 128) + patcher = GridPatching2D( + img_shape, patch_shape=(32, 32), overlap_pix=8 + ) + batch_size, channels = 2, 2 + patches = torch.randn( + batch_size * patcher.patch_num, channels, + patcher.patch_shape[0], patcher.patch_shape[1], + ) + fused = patcher.fuse(patches, batch_size=batch_size) + assert fused.shape == (batch_size, channels, *img_shape) + + +############################################################################ +# GridPatching2D — roundtrip (apply → fuse) # +############################################################################ + + +class TestGridPatching2DRoundtrip: + """Tests that apply followed by fuse reconstructs the original image.""" + + @pytest.mark.parametrize( + "img_shape, patch_shape, overlap_pix, boundary_pix", + [ + ((64, 64), (32, 32), 0, 0), + ((64, 64), (32, 32), 8, 0), + ((64, 64), (32, 32), 0, 4), + ((64, 64), (32, 32), 8, 4), + ((100, 77), (32, 32), 4, 2), + ((48, 96), (24, 48), 6, 0), + ], + ) + def test_roundtrip_reconstructs_image( + self, img_shape, patch_shape, overlap_pix, boundary_pix + ): + """Patching and then fusing should recover the original image.""" + patcher = GridPatching2D( + img_shape, patch_shape, + overlap_pix=overlap_pix, boundary_pix=boundary_pix, + ) + batch_size, channels = 2, 3 + x = torch.randn(batch_size, channels, *img_shape) + patches = patcher.apply(x) + reconstructed = patcher.fuse(patches, batch_size=batch_size) + torch.testing.assert_close(reconstructed, x, atol=1e-5, rtol=1e-5) + + def test_roundtrip_single_patch_covers_image(self): + """A single patch covering the full image should roundtrip exactly.""" + img_shape = (32, 32) + patcher = GridPatching2D(img_shape, patch_shape=(32, 32)) + x = torch.randn(1, 1, *img_shape) + patches = patcher.apply(x) + reconstructed = patcher.fuse(patches, batch_size=1) + torch.testing.assert_close(reconstructed, x) + + def test_roundtrip_preserves_dtype(self): + patcher = GridPatching2D( + img_shape=(64, 64), patch_shape=(32, 32), overlap_pix=4 + ) + x = torch.randn(1, 1, 64, 64, dtype=torch.float64) + patches = patcher.apply(x) + reconstructed = patcher.fuse(patches, batch_size=1) + assert reconstructed.dtype == x.dtype + + +############################################################################ +# image_batching — function # +############################################################################ + + +class TestImageBatching: + """Tests for the image_batching standalone function.""" + + def test_output_shape_no_overlap(self): + x = torch.randn(2, 3, 64, 64) + out = image_batching(x, patch_shape_y=32, patch_shape_x=32, + overlap_pix=0, boundary_pix=0) + patch_num = math.ceil(64 / 32) * math.ceil(64 / 32) + assert out.shape == (patch_num * 2, 3, 32, 32) + + def test_output_shape_with_interp(self): + batch_size = 2 + x = torch.randn(batch_size, 3, 64, 64) + interp = torch.randn(batch_size, 5, 32, 32) + out = image_batching(x, 32, 32, overlap_pix=0, boundary_pix=0, + input_interp=interp) + assert out.shape == (math.ceil(64 / 32) * math.ceil(64 / 32) * batch_size, 3 + 5, 32, 32) + + def test_invalid_patch_shape_x_raises(self): + x = torch.randn(1, 1, 64, 64) + with pytest.raises(ValueError, match="patch_shape_x"): + image_batching(x, patch_shape_y=32, patch_shape_x=2, + overlap_pix=1, boundary_pix=1) + + def test_invalid_patch_shape_y_raises(self): + x = torch.randn(1, 1, 64, 64) + with pytest.raises(ValueError, match="patch_shape_y"): + image_batching(x, patch_shape_y=2, patch_shape_x=32, + overlap_pix=1, boundary_pix=1) + + def test_interp_batch_mismatch_raises(self): + x = torch.randn(2, 3, 64, 64) + interp = torch.randn(3, 5, 32, 32) # wrong batch size + with pytest.raises(ValueError, match="batch size"): + image_batching(x, 32, 32, 0, 0, input_interp=interp) + + def test_interp_shape_mismatch_raises(self): + x = torch.randn(2, 3, 64, 64) + interp = torch.randn(2, 5, 16, 16) # wrong spatial dims + with pytest.raises(ValueError, match="patch shape"): + image_batching(x, 32, 32, 0, 0, input_interp=interp) + + def test_patch_too_small_for_overlap_and_boundary_x_raises(self): + x = torch.randn(1, 1, 64, 64) + with pytest.raises(ValueError, match="patch_shape_x"): + image_batching(x, 32, 11, overlap_pix=5, boundary_pix=3) + + def test_patch_too_small_for_overlap_and_boundary_y_raises(self): + x = torch.randn(1, 1, 64, 64) + with pytest.raises(ValueError, match="patch_shape_y"): + image_batching(x, 11, 32, overlap_pix=5, boundary_pix=3) + + def test_int32_input_preserves_dtype(self): + x = torch.randint(0, 100, (1, 1, 32, 32), dtype=torch.int32) + out = image_batching(x, 16, 16, 0, 0) + assert out.dtype == torch.int32 + + def test_int64_input_preserves_dtype(self): + x = torch.randint(0, 100, (1, 1, 32, 32), dtype=torch.int64) + out = image_batching(x, 16, 16, 0, 0) + assert out.dtype == torch.int64 + + def test_patch_is_matching_the_original(self): + """Patches should match the corresponding slices of the original image.""" + x = torch.randn(3, 2, 32, 32) + patches = image_batching(x, 16, 16, overlap_pix=0, boundary_pix=0) + expected_patches = torch.cat([ + x[:, :, 0:16, 0:16], + x[:, :, 16:32, 0:16], + x[:, :, 0:16, 16:32], + x[:, :, 16:32, 16:32], + ], dim=0) + torch.testing.assert_close(patches, expected_patches) + + def test_patch_is_matching_the_original_with_overlap_and_boundary(self): + """Patches should match the corresponding slices of the original image, even with overlap and boundary.""" + x = torch.randn(3, 2, 32, 32) + patches = image_batching(x, 16, 16, overlap_pix=4, boundary_pix=2) + # test if the patches at the corners and center match the expected slices of the original image + # where padding is applied, compare only to the valid region of the original image + # padding can be changed without affecting the validity of the extracted patch region, so we focus on the original image slices + expected_patch_middle = x[:, :, 8:24, 8:24] + expected_patch_top_left = x[:, :, 0:14, 0:14] + expected_patch_bottom_left = x[:, :, 28:, 0:14] + expected_patch_top_right = x[:, :, 0:14, 28:] + expected_patch_bottom_right = x[:, :, 28:, 28:] + torch.testing.assert_close(patches[3*5:3*6], expected_patch_middle) + torch.testing.assert_close(patches[0:3,:,2:,2:], expected_patch_top_left) + torch.testing.assert_close(patches[3*3:3*4,:,:4,2:], expected_patch_bottom_left) + torch.testing.assert_close(patches[3*12:3*13, :, 2:, :4], expected_patch_top_right) + torch.testing.assert_close(patches[3*15:, :, :4, :4], expected_patch_bottom_right) + +############################################################################ +# image_fuse — function # +############################################################################ + + +class TestImageFuse: + """Tests for the image_fuse standalone function.""" + + def test_output_shape(self): + batch_size = 2 + img_shape_y, img_shape_x = 64, 64 + patch_shape_y, patch_shape_x = 32, 32 + patch_num_x = math.ceil(img_shape_x / patch_shape_x) + patch_num_y = math.ceil(img_shape_y / patch_shape_y) + patch_num = patch_num_x * patch_num_y + channels = 3 + patches = torch.randn(patch_num * batch_size, channels, + patch_shape_y, patch_shape_x) + out = image_fuse(patches, img_shape_y, img_shape_x, + batch_size, overlap_pix=0, boundary_pix=0) + assert out.shape == (batch_size, channels, img_shape_y, img_shape_x) + + def test_fuse_constant_patches(self): + """Fusing constant-valued patches should yield a constant image.""" + val = 5.0 + img_shape = (32, 32) + patcher = GridPatching2D(img_shape, patch_shape=(16, 16)) + patches = torch.full( + (patcher.patch_num, 1, 16, 16), val + ) + fused = image_fuse(patches, img_shape[0], img_shape[1], + batch_size=1, overlap_pix=0, boundary_pix=0) + torch.testing.assert_close(fused, torch.full((1, 1, *img_shape), val)) + + #TODO: after normalizing by overlap count, the output may not be exactly the same as the input for integer types, so we would need to round and cast back to the original dtype. + # We can add it after implementing that logic in image_fuse, but first we have to see if it would affect existing model checkpoints. + # def test_int32_dtype_preserved(self): + # x = torch.randint(0, 100, (1, 1, 8, 8), dtype=torch.int32) + # patches = image_batching(x, 4, 4, 0, 0) + # fused = image_fuse(patches, 8, 8, batch_size=1, + # overlap_pix=0, boundary_pix=0) + # print(fused) + # assert fused.dtype == torch.int32 + + # def test_int64_dtype_preserved(self): + # x = torch.randint(0, 100, (1, 1, 8, 8), dtype=torch.int64) + # patches = image_batching(x, 4, 4, 0, 0) + # fused = image_fuse(patches, 8, 8, batch_size=1, + # overlap_pix=0, boundary_pix=0) + # assert fused.dtype == torch.int64 + + +############################################################################ +# image_batching + image_fuse — roundtrip # +############################################################################ + + +class TestImageBatchingFuseRoundtrip: + """Tests that image_batching followed by image_fuse recovers the original.""" + + @pytest.mark.parametrize( + "img_shape_y, img_shape_x, patch_shape_y, patch_shape_x, overlap_pix, boundary_pix", + [ + (64, 64, 32, 32, 0, 0), + (64, 64, 32, 32, 8, 0), + (64, 64, 32, 32, 0, 4), + (64, 64, 32, 32, 8, 4), + (48, 96, 24, 48, 0, 0), + (100, 77, 32, 32, 4, 2), + ], + ) + def test_roundtrip( + self, img_shape_y, img_shape_x, + patch_shape_y, patch_shape_x, + overlap_pix, boundary_pix, + ): + batch_size, channels = 2, 3 + x = torch.randn(batch_size, channels, img_shape_y, img_shape_x) + patches = image_batching( + x, patch_shape_y, patch_shape_x, overlap_pix, boundary_pix + ) + reconstructed = image_fuse( + patches, img_shape_y, img_shape_x, + batch_size, overlap_pix, boundary_pix, + ) + torch.testing.assert_close(reconstructed, x, atol=1e-5, rtol=1e-5) + + def test_roundtrip_channels_last(self): + """Roundtrip should work with channels_last memory format.""" + x = torch.randn(2, 3, 64, 64).to(memory_format=torch.channels_last) + patches = image_batching(x, 32, 32, 0, 0) + reconstructed = image_fuse(patches, 64, 64, batch_size=2, + overlap_pix=0, boundary_pix=0) + torch.testing.assert_close( + reconstructed.contiguous(), x.contiguous(), atol=1e-5, rtol=1e-5 + ) diff --git a/tests/utils/test_train_helpers.py b/tests/utils/test_train_helpers.py new file mode 100644 index 00000000..09d6a421 --- /dev/null +++ b/tests/utils/test_train_helpers.py @@ -0,0 +1,686 @@ +import warnings +from unittest.mock import MagicMock, patch + +import numpy as np +import pytest +import torch +from omegaconf import DictConfig, OmegaConf + +from hirad.utils.train_helpers import ( + check_model_health, + compute_num_accumulation_rounds, + handle_and_clip_gradients, + init_mlflow, + is_time_for_periodic_task, + set_patch_shape, + set_seed, +) + + +############################################################################ +# set_patch_shape # +############################################################################ + + +class TestSetPatchShape: + """Tests for set_patch_shape.""" + + def test_patch_equals_image_disables_patching(self): + use_patching, img, patch = set_patch_shape((128, 128), (128, 128)) + assert use_patching is False + assert img == (128, 128) + assert patch == (128, 128) + + def test_none_patch_defaults_to_image(self): + use_patching, img, patch = set_patch_shape((128, 256), (None, None)) + assert use_patching is False + assert patch == (128, 256) + + def test_patch_larger_than_image_clamped(self): + use_patching, img, patch = set_patch_shape((64, 64), (128, 128)) + assert use_patching is False + assert patch == (64, 64) + + def test_valid_square_patch_enables_patching(self): + use_patching, img, patch = set_patch_shape((256, 256), (64, 64)) + assert use_patching is True + assert patch == (64, 64) + + def test_patch_not_multiple_of_32_raises(self): + with pytest.raises(ValueError, match="multiple of 32"): + set_patch_shape((256, 256), (50, 50)) + + def test_rectangular_patch_raises(self): + with pytest.raises(NotImplementedError, match="Rectangular patch"): + set_patch_shape((256, 256), (64, 128)) + + def test_img_shape_returned_unchanged(self): + _, img, _ = set_patch_shape((100, 200), (None, None)) + assert img == (100, 200) + + def test_patch_32_is_valid(self): + use_patching, _, patch = set_patch_shape((256, 256), (32, 32)) + assert use_patching is True + assert patch == (32, 32) + + +############################################################################ +# set_seed # +############################################################################ + + +class TestSetSeed: + """Tests for set_seed.""" + + def test_reproducibility(self): + set_seed(42) + a_np = np.random.rand(5) + a_torch = torch.rand(5) + + set_seed(42) + b_np = np.random.rand(5) + b_torch = torch.rand(5) + + np.testing.assert_array_equal(a_np, b_np) + torch.testing.assert_close(a_torch, b_torch) + + def test_different_ranks_give_different_seeds(self): + set_seed(0) + a_np = np.random.rand(100) + a_torch = torch.rand(100) + + set_seed(1) + b_np = np.random.rand(100) + b_torch = torch.rand(100) + + assert not np.array_equal(a_np, b_np) + assert not torch.allclose(a_torch, b_torch) + + def test_large_rank_wraps(self): + """Ranks larger than 2^31 should still work due to modulo.""" + large_rank = (1 << 31) + 5 + set_seed(large_rank) + a_np = np.random.rand(5) + a_torch = torch.rand(5) + + set_seed(5) + b_np = np.random.rand(5) + b_torch = torch.rand(5) + # rank % (1<<31) should give 5 in both cases + np.testing.assert_array_equal(a_np, b_np) + torch.testing.assert_close(a_torch, b_torch) + + +############################################################################ +# compute_num_accumulation_rounds # +############################################################################ + + +class TestComputeNumAccumulationRounds: + """Tests for compute_num_accumulation_rounds.""" + + def test_single_gpu_no_accumulation(self): + batch_gpu_total, num_rounds = compute_num_accumulation_rounds( + total_batch_size=16, batch_size_per_gpu=16, world_size=1 + ) + assert batch_gpu_total == 16 + assert num_rounds == 1 + + def test_multi_gpu_no_accumulation(self): + batch_gpu_total, num_rounds = compute_num_accumulation_rounds( + total_batch_size=32, batch_size_per_gpu=8, world_size=4 + ) + assert batch_gpu_total == 8 + assert num_rounds == 1 + + def test_accumulation_rounds(self): + batch_gpu_total, num_rounds = compute_num_accumulation_rounds( + total_batch_size=64, batch_size_per_gpu=8, world_size=2 + ) + assert batch_gpu_total == 32 + assert num_rounds == 4 + + def test_none_batch_size_per_gpu_defaults_to_total(self): + batch_gpu_total, num_rounds = compute_num_accumulation_rounds( + total_batch_size=32, batch_size_per_gpu=None, world_size=2 + ) + assert batch_gpu_total == 16 + assert num_rounds == 1 + + def test_batch_size_per_gpu_larger_than_total_clamped(self): + batch_gpu_total, num_rounds = compute_num_accumulation_rounds( + total_batch_size=16, batch_size_per_gpu=64, world_size=2 + ) + assert batch_gpu_total == 8 + assert num_rounds == 1 + + def test_invalid_batch_sizes_raise(self): + """total_batch_size not divisible properly should raise ValueError.""" + with pytest.raises(ValueError, match="total_batch_size must be equal"): + compute_num_accumulation_rounds( + total_batch_size=17, batch_size_per_gpu=4, world_size=2 + ) + + def test_world_size_1_full_accumulation(self): + batch_gpu_total, num_rounds = compute_num_accumulation_rounds( + total_batch_size=64, batch_size_per_gpu=16, world_size=1 + ) + assert batch_gpu_total == 64 + assert num_rounds == 4 + + def test_exact_division(self): + batch_gpu_total, num_rounds = compute_num_accumulation_rounds( + total_batch_size=128, batch_size_per_gpu=16, world_size=4 + ) + assert batch_gpu_total == 32 + assert num_rounds == 2 + assert 16 * 2 * 4 == 128 + + +############################################################################ +# handle_and_clip_gradients # +############################################################################ + + +class TestHandleAndClipGradients: + """Tests for handle_and_clip_gradients.""" + + @pytest.fixture + def simple_model(self): + """Create a simple linear model with computed gradients.""" + model = torch.nn.Linear(4, 2, bias=False) + x = torch.randn(1, 4) + loss = model(x).sum() + loss.backward() + return model + + def test_nan_gradients_replaced(self): + model = torch.nn.Linear(4, 2, bias=False) + x = torch.randn(1, 4) + loss = model(x).sum() + loss.backward() + # Inject NaN into gradient + model.weight.grad[0, 0] = float("nan") + handle_and_clip_gradients(model) + assert torch.isfinite(model.weight.grad).all() + + def test_inf_gradients_replaced(self): + model = torch.nn.Linear(4, 2, bias=False) + x = torch.randn(1, 4) + loss = model(x).sum() + loss.backward() + model.weight.grad[0, 0] = float("inf") + model.weight.grad[1, 0] = float("-inf") + handle_and_clip_gradients(model) + assert torch.isfinite(model.weight.grad).all() + + def test_gradient_clipping(self, simple_model): + # Set a large gradient + simple_model.weight.grad.fill_(100.0) + handle_and_clip_gradients(simple_model, grad_clip_threshold=1.0) + grad_norm = torch.nn.utils.clip_grad_norm_( + simple_model.parameters(), float("inf") + ) + assert grad_norm <= 1.0 + 1e-6 + + def test_no_clipping_when_none(self, simple_model): + original_grad = simple_model.weight.grad.clone() + handle_and_clip_gradients(simple_model, grad_clip_threshold=None) + torch.testing.assert_close(simple_model.weight.grad, original_grad) + + def test_params_without_grad_skipped(self): + """Parameters without gradients should not cause errors.""" + model = torch.nn.Linear(4, 2, bias=True) + # Only weight has grad, bias does not + model.weight.grad = torch.randn_like(model.weight) + model.bias.grad = None + handle_and_clip_gradients(model) # Should not raise + + +############################################################################ +# check_model_health # +############################################################################ + + +class TestCheckModelHealth: + """Tests for check_model_health.""" + + @pytest.fixture + def logger(self): + return MagicMock() + + def test_healthy_model_returns_true(self, logger): + model = torch.nn.Linear(4, 2) + x = torch.randn(1, 4) + loss = model(x).sum() + loss.backward() + assert check_model_health(model, step=0, logger=logger) is True + logger.warning.assert_not_called() + + def test_nan_weights_returns_false(self, logger): + model = torch.nn.Linear(4, 2) + with torch.no_grad(): + model.weight[0, 0] = float("nan") + result = check_model_health(model, step=5, logger=logger) + assert result is False + logger.warning.assert_called_once() + assert "Weights" in logger.warning.call_args[0][0] + + def test_inf_weights_returns_false(self, logger): + model = torch.nn.Linear(4, 2) + with torch.no_grad(): + model.weight[0, 0] = float("inf") + result = check_model_health(model, step=3, logger=logger) + assert result is False + logger.warning.assert_called_once() + assert "Weights" in logger.warning.call_args[0][0] + + def test_nan_gradients_returns_false(self, logger): + model = torch.nn.Linear(4, 2) + x = torch.randn(1, 4) + loss = model(x).sum() + loss.backward() + model.weight.grad[0, 0] = float("nan") + result = check_model_health(model, step=10, logger=logger) + assert result is False + assert "Gradients" in logger.warning.call_args[0][0] + + def test_inf_gradients_returns_false(self, logger): + model = torch.nn.Linear(4, 2) + x = torch.randn(1, 4) + loss = model(x).sum() + loss.backward() + model.weight.grad[1, 1] = float("inf") + result = check_model_health(model, step=7, logger=logger) + assert result is False + assert "Gradients" in logger.warning.call_args[0][0] + + def test_no_grad_params_healthy(self, logger): + """Parameters without gradients should not cause false negatives.""" + model = torch.nn.Linear(4, 2, bias=True) + # No backward called, so no gradients + result = check_model_health(model, step=0, logger=logger) + assert result is True + + def test_step_number_in_warning(self, logger): + model = torch.nn.Linear(4, 2) + with torch.no_grad(): + model.weight[0, 0] = float("nan") + check_model_health(model, step=42, logger=logger) + assert "42" in logger.warning.call_args[0][0] + + +############################################################################ +# is_time_for_periodic_task # +############################################################################ + + +class TestIsTimeForPeriodicTask: + """Tests for is_time_for_periodic_task.""" + + def test_exact_frequency_match(self): + assert is_time_for_periodic_task( + cur_nimg=100, freq=100, done=False, batch_size=10, rank=0 + ) is True + + def test_within_batch_of_frequency(self): + # cur_nimg=105, freq=100 => 105 % 100 = 5 < batch_size=10 + assert is_time_for_periodic_task( + cur_nimg=105, freq=100, done=False, batch_size=10, rank=0 + ) is True + + def test_not_time_yet(self): + # cur_nimg=50, freq=100 => 50 % 100 = 50 >= batch_size=10 + assert is_time_for_periodic_task( + cur_nimg=50, freq=100, done=False, batch_size=10, rank=0 + ) is False + + def test_done_always_returns_true(self): + assert is_time_for_periodic_task( + cur_nimg=50, freq=100, done=True, batch_size=10, rank=0 + ) is True + + def test_rank_0_only_blocks_other_ranks(self): + assert is_time_for_periodic_task( + cur_nimg=100, freq=100, done=False, batch_size=10, + rank=1, rank_0_only=True, + ) is False + + def test_rank_0_only_allows_rank_0(self): + assert is_time_for_periodic_task( + cur_nimg=100, freq=100, done=False, batch_size=10, + rank=0, rank_0_only=True, + ) is True + + def test_rank_0_only_false_allows_any_rank(self): + assert is_time_for_periodic_task( + cur_nimg=100, freq=100, done=False, batch_size=10, + rank=3, rank_0_only=False, + ) is True + + def test_done_overrides_rank_0_only(self): + """done=True should return True even for non-zero ranks with rank_0_only.""" + assert is_time_for_periodic_task( + cur_nimg=50, freq=100, done=True, batch_size=10, + rank=2, rank_0_only=True, + ) is False # rank_0_only check happens first + + def test_zero_cur_nimg(self): + # 0 % freq = 0 < batch_size => True + assert is_time_for_periodic_task( + cur_nimg=0, freq=100, done=False, batch_size=10, rank=0 + ) is True + + def test_batch_size_equals_freq(self): + # Every step should trigger when batch_size >= freq + assert is_time_for_periodic_task( + cur_nimg=37, freq=100, done=False, batch_size=100, rank=0 + ) is True + + +############################################################################ +# init_mlflow # +############################################################################ + + +class TestInitMlflow: + """Tests for init_mlflow.""" + + @pytest.fixture + def base_cfg(self): + """Minimal config DictConfig for init_mlflow.""" + return OmegaConf.create( + { + "logging": { + "uri": "http://mlflow-server:5000", + "experiment_name": "test_experiment", + "run_name": "test_run", + }, + } + ) + + @pytest.fixture + def cfg_no_uri(self): + """Config with logging.uri set to None.""" + return OmegaConf.create( + { + "logging": { + "uri": None, + "experiment_name": "test_experiment", + "run_name": "test_run", + }, + } + ) + + @pytest.fixture + def dist_rank0_single(self): + """DistributedManager mock: rank 0, world_size 1.""" + dist = MagicMock() + dist.rank = 0 + dist.world_size = 1 + dist._local_rank = 0 + return dist + + @pytest.fixture + def dist_rank0_multi(self): + """DistributedManager mock: rank 0, world_size 4.""" + dist = MagicMock() + dist.rank = 0 + dist.world_size = 4 + dist._local_rank = 0 + return dist + + @pytest.fixture + def dist_rank0_large(self): + """DistributedManager mock: rank 0, world_size 8 (>4).""" + dist = MagicMock() + dist.rank = 0 + dist.world_size = 8 + dist._local_rank = 0 + return dist + + @pytest.fixture + def mock_mlflow(self): + """Patch mlflow and related utilities used in init_mlflow.""" + with patch("hirad.utils.train_helpers.mlflow") as m_mlflow, \ + patch("hirad.utils.train_helpers.get_env_info") as m_env, \ + patch("hirad.utils.train_helpers.flatten_dict") as m_flat: + # get_env_info returns (dict, git_diff_string) + m_env.return_value = ({"pkg": {"version": "1.0"}}, "diff contents") + # flatten_dict passthrough + m_flat.side_effect = lambda x: x + # active_run mock + mock_run = MagicMock() + mock_run.info.run_id = "new-run-id-123" + m_mlflow.active_run.return_value = mock_run + yield { + "mlflow": m_mlflow, + "get_env_info": m_env, + "flatten_dict": m_flat, + } + + # --- Rank 0, fresh run (no existing run_id.txt) --- + + def test_rank0_fresh_run_sets_tracking_uri(self, base_cfg, dist_rank0_single, mock_mlflow, tmp_path): + init_mlflow(base_cfg, dist_rank0_single, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].set_tracking_uri.assert_called_once_with( + "http://mlflow-server:5000" + ) + + def test_rank0_fresh_run_sets_experiment(self, base_cfg, dist_rank0_single, mock_mlflow, tmp_path): + init_mlflow(base_cfg, dist_rank0_single, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].set_experiment.assert_called_once_with( + experiment_name="test_experiment" + ) + + def test_rank0_fresh_run_starts_with_run_name(self, base_cfg, dist_rank0_single, mock_mlflow, tmp_path): + init_mlflow(base_cfg, dist_rank0_single, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].start_run.assert_called_once_with( + run_name="test_run", log_system_metrics=True + ) + + def test_rank0_fresh_run_saves_run_id(self, base_cfg, dist_rank0_single, mock_mlflow, tmp_path): + init_mlflow(base_cfg, dist_rank0_single, write_dir=str(tmp_path)) + run_id_file = tmp_path / "run_id.txt" + assert run_id_file.exists() + assert run_id_file.read_text() == "new-run-id-123" + + def test_rank0_fresh_run_logs_params(self, base_cfg, dist_rank0_single, mock_mlflow, tmp_path): + init_mlflow(base_cfg, dist_rank0_single, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].log_params.assert_called_once() + + def test_rank0_fresh_run_logs_env_info(self, base_cfg, dist_rank0_single, mock_mlflow, tmp_path): + init_mlflow(base_cfg, dist_rank0_single, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].log_dict.assert_any_call( + {"pkg": {"version": "1.0"}}, "environment.json" + ) + + def test_rank0_fresh_run_logs_git_diff(self, base_cfg, dist_rank0_single, mock_mlflow, tmp_path): + init_mlflow(base_cfg, dist_rank0_single, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].log_text.assert_called_once_with( + "diff contents", "git_diff.txt" + ) + + def test_rank0_fresh_run_logs_config(self, base_cfg, dist_rank0_single, mock_mlflow, tmp_path): + init_mlflow(base_cfg, dist_rank0_single, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].log_dict.assert_any_call(base_cfg, "config.json") + + # --- Rank 0, no git diff --- + + def test_rank0_no_git_diff_skips_log_text(self, base_cfg, dist_rank0_single, mock_mlflow, tmp_path): + mock_mlflow["get_env_info"].return_value = ({"pkg": {}}, "") + init_mlflow(base_cfg, dist_rank0_single, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].log_text.assert_not_called() + + # --- Rank 0, resuming from checkpoint (run_id.txt exists) --- + + def test_rank0_resume_reads_run_id(self, base_cfg, dist_rank0_single, mock_mlflow, tmp_path): + (tmp_path / "run_id.txt").write_text("existing-run-id-456") + init_mlflow(base_cfg, dist_rank0_single, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].start_run.assert_called_once_with( + run_id="existing-run-id-456", log_system_metrics=True + ) + + def test_rank0_resume_does_not_log_params(self, base_cfg, dist_rank0_single, mock_mlflow, tmp_path): + (tmp_path / "run_id.txt").write_text("existing-run-id-456") + init_mlflow(base_cfg, dist_rank0_single, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].log_params.assert_not_called() + + def test_rank0_resume_does_not_overwrite_run_id(self, base_cfg, dist_rank0_single, mock_mlflow, tmp_path): + (tmp_path / "run_id.txt").write_text("existing-run-id-456") + init_mlflow(base_cfg, dist_rank0_single, write_dir=str(tmp_path)) + # File content should remain unchanged + assert (tmp_path / "run_id.txt").read_text() == "existing-run-id-456" + + # --- URI handling --- + + def test_rank0_none_uri_skips_set_tracking(self, cfg_no_uri, dist_rank0_single, mock_mlflow, tmp_path): + init_mlflow(cfg_no_uri, dist_rank0_single, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].set_tracking_uri.assert_not_called() + + # --- System metrics node ID --- + + def test_rank0_small_world_sets_node_id(self, base_cfg, dist_rank0_multi, mock_mlflow, tmp_path): + init_mlflow(base_cfg, dist_rank0_multi, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].system_metrics.set_system_metrics_node_id.assert_called_once_with( + "node-0" + ) + + def test_rank0_large_world_disables_system_metrics(self, base_cfg, dist_rank0_large, mock_mlflow, tmp_path): + with patch("hirad.utils.train_helpers.torch"): + init_mlflow(base_cfg, dist_rank0_large, write_dir=str(tmp_path)) + # world_size > 4: log_system_metrics=False + mock_mlflow["mlflow"].start_run.assert_called_once() + _, kwargs = mock_mlflow["mlflow"].start_run.call_args + assert kwargs["log_system_metrics"] is False + + def test_rank0_small_world_enables_system_metrics(self, base_cfg, dist_rank0_multi, mock_mlflow, tmp_path): + init_mlflow(base_cfg, dist_rank0_multi, write_dir=str(tmp_path)) + _, kwargs = mock_mlflow["mlflow"].start_run.call_args + assert kwargs["log_system_metrics"] is True + + # --- Distributed barrier for large world_size --- + + def test_large_world_calls_barrier(self, base_cfg, dist_rank0_large, mock_mlflow, tmp_path): + with patch("hirad.utils.train_helpers.torch") as m_torch: + init_mlflow(base_cfg, dist_rank0_large, write_dir=str(tmp_path)) + m_torch.distributed.barrier.assert_called_once() + + def test_small_world_skips_barrier(self, base_cfg, dist_rank0_multi, mock_mlflow, tmp_path): + with patch("hirad.utils.train_helpers.torch") as m_torch: + init_mlflow(base_cfg, dist_rank0_multi, write_dir=str(tmp_path)) + m_torch.distributed.barrier.assert_not_called() + + # --- Sub-node MLflow activation (non-rank-0 local rank 0) --- + + def test_sub_node_rank4_local0_starts_run(self, base_cfg, mock_mlflow, tmp_path): + """rank=4, _local_rank=0, world_size=8 should activate sub mlflow.""" + dist = MagicMock() + dist.rank = 4 + dist.world_size = 8 + dist._local_rank = 0 + (tmp_path / "run_id.txt").write_text("existing-run-id-456") + with patch("hirad.utils.train_helpers.torch"): + init_mlflow(base_cfg, dist, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].start_run.assert_called_once_with( + run_id="existing-run-id-456", log_system_metrics=True + ) + + def test_sub_node_sets_correct_node_id(self, base_cfg, mock_mlflow, tmp_path): + """rank=4, world_size=8 should set node id to 'node-1'.""" + dist = MagicMock() + dist.rank = 4 + dist.world_size = 8 + dist._local_rank = 0 + (tmp_path / "run_id.txt").write_text("run-id") + with patch("hirad.utils.train_helpers.torch"): + init_mlflow(base_cfg, dist, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].system_metrics.set_system_metrics_node_id.assert_called_once_with( + "node-1" + ) + + def test_rank1_large_world_activates_sub_mlflow(self, base_cfg, mock_mlflow, tmp_path): + """rank=1, world_size=8 should activate sub mlflow (special case).""" + dist = MagicMock() + dist.rank = 1 + dist.world_size = 8 + dist._local_rank = 1 + (tmp_path / "run_id.txt").write_text("run-id") + with patch("hirad.utils.train_helpers.torch"): + init_mlflow(base_cfg, dist, write_dir=str(tmp_path)) + # rank=1 special case: node_id is "node-0" + mock_mlflow["mlflow"].system_metrics.set_system_metrics_node_id.assert_called_once_with( + "node-0" + ) + mock_mlflow["mlflow"].start_run.assert_called_once_with( + run_id="run-id", log_system_metrics=True + ) + + def test_rank1_small_world_skips_sub_mlflow(self, base_cfg, mock_mlflow, tmp_path): + """rank=1, world_size=4 should NOT activate sub mlflow.""" + dist = MagicMock() + dist.rank = 1 + dist.world_size = 4 + dist._local_rank = 1 + init_mlflow(base_cfg, dist, write_dir=str(tmp_path)) + # rank != 0, not local_rank 0, world_size <= 4 => no start_run + mock_mlflow["mlflow"].start_run.assert_not_called() + + def test_non_local_rank0_non_rank1_skips_sub_mlflow(self, base_cfg, mock_mlflow, tmp_path): + """rank=2, _local_rank=2, world_size=8 should not activate sub mlflow.""" + dist = MagicMock() + dist.rank = 2 + dist.world_size = 8 + dist._local_rank = 2 + with patch("hirad.utils.train_helpers.torch"): + init_mlflow(base_cfg, dist, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].start_run.assert_not_called() + + def test_non_local_rank0_non_node_0_skips_sub_mlflow(self, base_cfg, mock_mlflow, tmp_path): + """rank=5, _local_rank=1, world_size=8 should not activate sub mlflow.""" + dist = MagicMock() + dist.rank = 5 + dist.world_size = 8 + dist._local_rank = 1 + with patch("hirad.utils.train_helpers.torch"): + init_mlflow(base_cfg, dist, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].start_run.assert_not_called() + + def test_sub_node_sets_tracking_uri(self, base_cfg, mock_mlflow, tmp_path): + """Sub-node should set tracking URI when configured.""" + dist = MagicMock() + dist.rank = 4 + dist.world_size = 8 + dist._local_rank = 0 + (tmp_path / "run_id.txt").write_text("run-id") + with patch("hirad.utils.train_helpers.torch"): + init_mlflow(base_cfg, dist, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].set_tracking_uri.assert_called_with( + "http://mlflow-server:5000" + ) + + def test_sub_node_none_uri_skips_starting_mlflow(self, cfg_no_uri, mock_mlflow, tmp_path): + """Sub-node should skip starting mlflow when URI is None.""" + dist = MagicMock() + dist.rank = 4 + dist.world_size = 8 + dist._local_rank = 0 + (tmp_path / "run_id.txt").write_text("run-id") + with patch("hirad.utils.train_helpers.torch"): + init_mlflow(cfg_no_uri, dist, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].set_tracking_uri.assert_not_called() + mock_mlflow["mlflow"].start_run.assert_not_called() + + def test_sub_node_sets_experiment(self, base_cfg, mock_mlflow, tmp_path): + """Sub-node should set the experiment name.""" + dist = MagicMock() + dist.rank = 4 + dist.world_size = 8 + dist._local_rank = 0 + (tmp_path / "run_id.txt").write_text("run-id") + with patch("hirad.utils.train_helpers.torch"): + init_mlflow(base_cfg, dist, write_dir=str(tmp_path)) + mock_mlflow["mlflow"].set_experiment.assert_called_with( + experiment_name="test_experiment" + ) + From 52ab20a30b7bbdf5839b0cb476a38be11deae986 Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 18 Mar 2026 12:29:01 +0100 Subject: [PATCH 264/302] readme update --- .gitignore | 1 - README.md | 202 +++++++++++++++++++++++++++++++---------------------- 2 files changed, 118 insertions(+), 85 deletions(-) diff --git a/.gitignore b/.gitignore index 8ba7696f..a02162e7 100644 --- a/.gitignore +++ b/.gitignore @@ -199,5 +199,4 @@ mlruns/ .secrets.env out core -*.png *.nc diff --git a/README.md b/README.md index a6cbe610..084a672d 100644 --- a/README.md +++ b/README.md @@ -2,90 +2,162 @@ HiRAD-Gen is short for high-resolution atmospheric downscaling using generative models. This repository contains the code and configuration required to train and use the model. -[Setup clariden/santis](#setup-claridensantis) +[Inference - clariden/santis](#running-inference-on-alps) +[Setup - clariden/santis](#setup-claridensantis) [Regression training - clariden/santis](#run-regression-model-training-alps) [Diffusion training - clariden/santis](#run-diffusion-model-training-alps) -[Inference - clariden/santis](#running-inference-on-alps) -[Installation - uenv/venv - deprecated](#installation-alps-uenvvenv---deprecated) ## Setup clariden/santis container environment -Container environment setup needed to run training and inference experiments on clariden/santis is contained in this repository under `ci/edf/modulus_env.toml`. Image squash is on clariden/alps under `/capstor/scratch/cscs/pstamenk/hirad.sqsh`. All the jobs can be run using this environment without additional installations and setup. +Container environment setup needed to run training and inference experiments on clariden/santis is contained in this repository under `ci/edf/modulus_env.toml`. Image squash is on clariden/alps under `/capstor/scratch/cscs/pstamenk/corr_diff.sqsh`. All the jobs can be run using this environment without additional installations and setup. -## Training +## Inference -### Run regression model training (Alps) +### Running inference on Alps -1. Script for running the training of regression model is in `src/hirad/train_regression.sh`. Here, you can change the sbatch settings. +1. Script for running the inference is in `src/hirad/generate.sh`. Inside this script set the following: ```bash ### OUTPUT ### -#SBATCH --output=your_path_to_output_log -#SBATCH --error=your_path_to_output_error +#SBATCH --output=path_to_output_log +#SBATCH --error=path_to_output_error ``` ```bash -#SBATCH -A your_compute_group +#SBATCH -A compute_group ``` ```bash -srun bash -c " - . ./{your_env_name}/bin/activate - python src/hirad/training/train.py --config-name=training_era_cosmo_regression.yaml +srun --mpi=pmix --network=disable_rdzv_get --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . + python src/hirad/inference/generate.py --config-name=main-config-file-in-src/hirad/conf.yaml " ``` 2. Set up the following config files in `src/hirad/conf`: -- In `training_era_cosmo_regression.yaml` set: +- In main config file (by default `generate_era_real.yaml`) set: ``` hydra: run: - dir: your_path_to_save_training_outputs + dir: your_path_to_save_inference_output +``` +- In generation config file (by default `generation/era_real.yaml`): +Choose the inference mode: ``` -- All other parameters for training regression can be changed in the main config file `training_era_cosmo_regression.yaml` and config files the main config is referencing (default values are working for debugging purposes). +inference_mode: all/regression/diffusion +``` +by default `all` does both regression and diffusion. Depending on mode, regression and/or diffusion model pretrained weights should be provided: +``` +io: + res_ckpt_path: path_to_directory_containing_diffusion_training_model_checkpoints + reg_ckpt_path: path_to_directory_containing_regression_training_model_checkpoints +``` +Finally, from the dataset, subset of time steps can be chosen to do inference for. + +One way is to list steps under `times:` in format `%Y%m%d-%H%M` for era5_cosmo dataset. + +The other way is to specify `times_range:` with three items: first time step (`%Y%m%d-%H%M`), last time step (`%Y%m%d-%H%M`), hour shift (int). Hour shift specifies distance in hours between closest time steps for specific dataset. 3. Submit the job with: ```bash -sbatch src/hirad/train_regression.sh +sbatch src/hirad/generate.sh ``` -### Run diffusion model training (Alps) -Before training diffusion model, checkpoint for regression model has to exist. +### Visualizing results -1. Script for running the training of diffusion model is in `src/hirad/train_diffusion.sh`. Here, you can change the sbatch settings. Inside this script set the following: +After generation is finished, visualization of results can be done using `src/hirad/snapshots.sh`. Set: ```bash ### OUTPUT ### -#SBATCH --output=your_path_to_output_log -#SBATCH --error=your_path_to_output_error +#SBATCH --output=path_to_output_log ``` ```bash -#SBATCH -A your_compute_group +### ENVIRONMENT #### +#SBATCH -A compute_group +``` +```bash +srun --mpi=pmix --network=disable_rdzv_get --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . + python src/hirad/eval/snapshots.py --config-name=src/hirad/conf/config-file-in-src/hirad/conf.yaml +" +``` +In config file (by default `eval_real.yaml`) set: +```bash +# Path to the inference output directory +inference_output_dir: '/path/to/generated/results/directory' +results_dir_name: 'name_of_directory_to_save_output_plots' +``` +If you want to generate plots for subset of times from inference set (follow same convection as in generate config): +``` +times: list of times to visualize +times_range: [start time, end time, time step] to visualize +``` + +Other setting can be changed according to output grid. + +Submit the job with: +```bash +sbatch src/hirad/snapshots.sh +``` + +### Evaluation of generated data + +Evaluation of generated samples can be done using `src/hirad/eval_precip.sh` and `src/hirad/eval_wind.sh`. Set: +```bash +### OUTPUT ### +#SBATCH --output=path_to_output_log + +### ENVIRONMENT #### +#SBATCH -A compute_group + +### CONFIG ### +CONFIG_NAME="src/hirad/conf/config_file.yaml" +``` +Default config file is the same as for visualization `eval_real.yaml`, and requires to set the same fileds. In both `eval_precip.sh` and `eval_wind.sh` there are several python scripts called. They are all commented out by default. Comment out the ones you want to run. + +Submit jobs with: +```bash +sbatch src/hirad/eval_precip.sh +sbatch src/hirad/eval_wind.sh +``` + +## Training + +### Run regression model training (Alps) + +1. Script for running the training of regression model is in `src/hirad/train_regression.sh`. Here, you can change the sbatch settings. +Inside this script set the following: +```bash +### OUTPUT ### +#SBATCH --output=path_to_output_log +#SBATCH --error=path_to_output_error +``` +```bash +#SBATCH -A compute_group +``` +```bash +srun --mpi=pmix --network=disable_rdzv_get --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . + python src/hirad/training/train.py --config-name=main-config-file-in-src/hirad/conf.yaml +" ``` 2. Set up the following config files in `src/hirad/conf`: -- In `training_era_cosmo_diffusion.yaml` set: +- In main config file (by default `training_era_real_regression.yaml`) set: ``` hydra: run: - dir: your_path_to_save_training_output -``` -- In `training/era_cosmo_diffusion.yaml` set: -``` -io: - regression_checkpoint_path: path_to_directory_containing_regression_training_model_checkpoints + dir: your_path_to_save_training_outputs ``` -- All other parameters for training regression can be changed in the main config file `training_era_cosmo_diffusion.yaml` and config files the main config is referencing (default values are working for debugging purposes). +- All other parameters for training regression can be changed in the main config file and config files the main config is referencing (default values are working for debugging purposes). 3. Submit the job with: ```bash -sbatch src/hirad/train_diffusion.sh +sbatch src/hirad/train_regression.sh ``` -## Inference - -### Running inference on Alps +### Run diffusion model training (Alps) +Before training diffusion model, checkpoint for regression model has to exist. -1. Script for running the inference is in `src/hirad/generate.sh`. -Inside this script set the following: +1. Script for running the training of diffusion model is in `src/hirad/train_diffusion.sh`. Here, you can change the sbatch settings. Inside this script set the following: ```bash ### OUTPUT ### #SBATCH --output=your_path_to_output_log @@ -95,71 +167,33 @@ Inside this script set the following: #SBATCH -A your_compute_group ``` ```bash -srun bash -c " - . ./{your_env_name}/bin/activate - python src/hirad/inference/generate.py --config-name=generate_era_cosmo.yaml +srun --mpi=pmix --network=disable_rdzv_get --environment=./ci/edf/modulus_env.toml bash -c " + pip install -e . + python src/hirad/training/train.py --config-name=main-config-file-in-src/hirad/conf.yaml " ``` 2. Set up the following config files in `src/hirad/conf`: -- In `generate_era_cosmo.yaml` set: +- In main config file (by default `training_era_real_diffusion_patched.yaml`) set: ``` hydra: run: - dir: your_path_to_save_inference_output -``` -- In `generation/era_cosmo.yaml`: -Choose the inference mode: -``` -inference_mode: all/regression/diffusion + dir: your_path_to_save_training_output ``` -by default `all` does both regression and diffusion. Depending on mode, regression and/or diffusion model pretrained weights should be provided: +- In training config file (by default `training/era_real_diffusion_patched.yaml`) set: ``` io: - res_ckpt_path: path_to_directory_containing_diffusion_training_model_checkpoints - reg_ckpt_path: path_to_directory_containing_regression_training_model_checkpoints + regression_checkpoint_path: path_to_directory_containing_regression_training_model_checkpoints ``` -Finally, from the dataset, subset of time steps can be chosen to do inference for. - -One way is to list steps under `times:` in format `%Y%m%d-%H%M` for era5_cosmo dataset. - -The other way is to specify `times_range:` with three items: first time step (`%Y%m%d-%H%M`), last time step (`%Y%m%d-%H%M`), hour shift (int). Hour shift specifies distance in hours between closest time steps for specific dataset. - -- In `dataset/era_cosmo_inference.yaml` set the `dataset_path` if different from default. Make sure that specified times or times_range is contained in dataset_path. +- All other parameters for training regression can be changed in the main config file and config files the main config is referencing (default values are working for debugging purposes). 3. Submit the job with: ```bash -sbatch src/hirad/generate.sh +sbatch src/hirad/train_diffusion.sh ``` ## MLflow logging During training MLflow can be used to log metrics. Logging config files for regression and diffusion are located in `src/hirad/conf/logging/`. Set `method` to `mlflow` and specify `uri` if you want to log on remote server, otherwise run will be logged locally in output directory. Other options can also be modified here. - -## Installation (Alps uenv/venv) - deprecated - -To set up the environment for **HiRAD-Gen** on Alps supercomputer, follow these steps: - -1. **Start the PyTorch user environment**: - ```bash - uenv start pytorch/v2.6.0:v1 --view=default - ``` - -2. **Create a Python virtual environment** (replace `{env_name}` with your desired environment name): - ```bash - python -m venv ./{env_name} - ``` - -3. **Activate the virtual environment**: - ```bash - source ./{env_name}/bin/activate - ``` - -4. **Install project dependencies**: - ```bash - pip install -e . - ``` - -This will set up the necessary environment to run HiRAD-Gen within the Alps infrastructure. From af8725ce24da5d4cc9078220b3249e02ea7cadcd Mon Sep 17 00:00:00 2001 From: Petar Stamenkovic Date: Wed, 18 Mar 2026 14:27:56 +0100 Subject: [PATCH 265/302] add example showcase to README --- README.md | 51 +++++++++++++++++++++++++++- docs/images/showcase/10-input.png | Bin 0 -> 488140 bytes docs/images/showcase/10-target.png | Bin 0 -> 1553936 bytes docs/images/showcase/10u-pred.png | Bin 0 -> 1607605 bytes docs/images/showcase/10v-input.png | Bin 0 -> 520501 bytes docs/images/showcase/10v-pred.png | Bin 0 -> 1661484 bytes docs/images/showcase/10v-target.png | Bin 0 -> 1628750 bytes docs/images/showcase/2t-input.png | Bin 0 -> 576646 bytes docs/images/showcase/2t-pred.png | Bin 0 -> 1578848 bytes docs/images/showcase/2t-target.png | Bin 0 -> 1477692 bytes docs/images/showcase/tp-input.png | Bin 0 -> 289420 bytes docs/images/showcase/tp-pred1.png | Bin 0 -> 319974 bytes docs/images/showcase/tp-pred2.png | Bin 0 -> 331094 bytes docs/images/showcase/tp-pred3.png | Bin 0 -> 333549 bytes docs/images/showcase/tp-pred4.png | Bin 0 -> 328897 bytes docs/images/showcase/tp-target.png | Bin 0 -> 316939 bytes 16 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 docs/images/showcase/10-input.png create mode 100644 docs/images/showcase/10-target.png create mode 100644 docs/images/showcase/10u-pred.png create mode 100644 docs/images/showcase/10v-input.png create mode 100644 docs/images/showcase/10v-pred.png create mode 100644 docs/images/showcase/10v-target.png create mode 100644 docs/images/showcase/2t-input.png create mode 100644 docs/images/showcase/2t-pred.png create mode 100644 docs/images/showcase/2t-target.png create mode 100644 docs/images/showcase/tp-input.png create mode 100644 docs/images/showcase/tp-pred1.png create mode 100644 docs/images/showcase/tp-pred2.png create mode 100644 docs/images/showcase/tp-pred3.png create mode 100644 docs/images/showcase/tp-pred4.png create mode 100644 docs/images/showcase/tp-target.png diff --git a/README.md b/README.md index 084a672d..72e4b9d1 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,60 @@ HiRAD-Gen is short for high-resolution atmospheric downscaling using generative models. This repository contains the code and configuration required to train and use the model. -[Inference - clariden/santis](#running-inference-on-alps) +[Showcase](#Showcase) [Setup - clariden/santis](#setup-claridensantis) +[Inference - clariden/santis](#running-inference-on-alps) [Regression training - clariden/santis](#run-regression-model-training-alps) [Diffusion training - clariden/santis](#run-diffusion-model-training-alps) +## Showcase + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Input ERA5PredictionTarget REAL-CH1
2t
10u
10v
tp
+ +### Ensemble Total Preceipitatin 1h + + + + + + + + + + +
+ ## Setup clariden/santis container environment Container environment setup needed to run training and inference experiments on clariden/santis is contained in this repository under `ci/edf/modulus_env.toml`. Image squash is on clariden/alps under `/capstor/scratch/cscs/pstamenk/corr_diff.sqsh`. All the jobs can be run using this environment without additional installations and setup. diff --git a/docs/images/showcase/10-input.png b/docs/images/showcase/10-input.png new file mode 100644 index 0000000000000000000000000000000000000000..022e2718edf42768c327a74f3dae343e4b2c7d1b GIT binary patch literal 488140 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%0A0z?E5Aoud$MwO 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
  • c4DG6htKAGjv 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

    841m9VpVdH3lZ>}}04@X3xnXxmuW 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%0DP0_Uq;5P*NO~n$sZj9mXT)Kn*WVM9+!TCeu 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૶H~*Y%{#zL#ET3+8I4MDz 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 P(#1PFxb4c{;rsPpP$qB< literal 0 HcmV?d00001 diff --git a/docs/images/showcase/10-target.png b/docs/images/showcase/10-target.png new file mode 100644 index 0000000000000000000000000000000000000000..c940b28e5b2824253d25f343a370eaeb024c472f GIT binary patch literal 1553936 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

  • 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
    sf6R=HJ92)j#-=?y9^55CbcVTG$*r$6 zw&emN5dWglFi*1)@4)-kSXbUj)jMiXF82fDTbDN6y%#fteYQWfYAyHdGy_YaF5 z8($OH7AzeQv3BZKE zvhSqXwa0FAwf{%3^$En9x8nhRq3jdr!MXQE1e;?>*RPL%V0lIgJ}V=RJvpUheb**R zvF2}f?zPH8jfbB^?5GNyF4L`d=zPq})aX{}CFo7p%Veg30&EaMdnmK?Yx};>zO(62 zwI?_s;QB4!JxOMns?bMkQgs!g!en_OY4|-I^>@LO3YcT(Jk>ZmhUH9hG2;0wk^h?$ z0WfD|WLze^j7hlncJJ}^p4fc)@94(=G0r9eX)eD$A3+iW9}@y6pI|H|@TmVK+;;Cj z2u!bWh0#b8>C6c2k?42Iu(7@LAh^^7Q*4R~MS4kvZBb4|35WX>;|gHx?$A0o75FWtbEO!KbqI|7UmD^NReZRm9n=$v6mhy4w zXif_M9m?4)q6&wP90fQ}ezH5%KZAt4Jt%n)>yZcl*aDpc0dJCRe+dn-3iD4#mgakg zOdB40y|oreI_yc`O8Q6SUjEd`{_FoE>#yS4j^AZ%xW(PwT?!Nl?u8;PF2y0ZQ;HV} z?oRRI4#mAdf#OcE;*b`H;0__-;lI|}@4NPApXcm%FuCVDGuNR0>7!KgUqfS}$BmL` zZ0o}GF*mo)Ixp5q5}|_e`8I}e_s#--k5?_h}VTFIWb*0tk z77ET6g4 zO8ViJ_SRRPRQGa&4NZtnX~8FjtBLY!AfOr#u+R`LN{XW$J*r`^AafdWQu=iD#Bdh$pMbpnBuZM5d8>;5iMn5Gchr&+Awb2{$=G711 zTVX3d?;n!mMjt>^UryJNx{JLjjvreO*=+DM+e8>5ZOC$TV|MKy5+7pSc77d682MKo zjlw$r2%U)}VRWD%MIUnqvc1Hr2G9D2KhO3-wOn|uBIHVRK!eNb4Z=@U7HQaz$X-f$ zZxe1}zq>p#-f$juvt|4c+meiIX7t?oSb%%|-R|e_?;EJOteIecar)0mKB=2;4SE8C zo>J*yc1c%J} z%?3WoG2+~P?rNfVnR+kq6@on9_uiYXV8T$Nkj}SDbJ(R-9{foIn353Hff=M&!clXK=7jJr((#F)^or0?c_)P9)Y`q-Sc^nmV`}ENH4af8Tkq>A9yyjA zRz)Ld@ptjg4nn(Li=|Mv&G8T1xVqmLm^VYRC|9*Q|0|~p0migHsH`=mElOJZ`!nf6 zVvlV1$(yWdc?@ZteVCFO^hy%yb*B z9wE#>?9iqdB;A{s{jcV2A8-Psv+A;>Qh2NG*48>h^qKeAlQBc$nkc_79(6?XmoFtz zA{aGgVKXJ)p);$_YU3r<4(-3of@l`JHXc}x)VE_|a`%DV&ynzb{Y_79$0_tFjHm5< zMN?%`LvcxDuMRNCsOC)&geBNZ4fwHA@hrvB^~DloQsKz|5(WF$;gx6!oK9~<>TmBF zT|Cv`<}dHPDfU9hVSj(ES0oj{ne4mRuSJzpL0d~xz$M1OsPgiA9*aVvkY3I?I%J6n= zDdG(KOt#?FfD?~O;C0)RHhns}d(Jxe9d_F^&k*d8NYl@4}L z&iw%|2k+#8ZPJ>y2?Hz5>G-W_BR%czSd!JBHCIwLW4{l*H5SS17 za`9WApV=5`Q=f_%vzz=I+Jp@vr`uSkvB&h3wu+biirK%5wC8~Ia3P=vz$H5U=bjeP ze>L1Ca*fsOgmM{j;TZExK=arumpAR*fwtj)ji4Sa5IE zWV9iw{y*#*U$fDKv*<7snCnA+AaGFWw<^D0ami0r-WZ_)fHy-wKB2a3av6;`t9ahk zxjPGU_*G0;W7uq_0-mP}&b|hYmNl&w8`IR#AJkCEYL2UnB7C1yX@;{IgdWQyjZRx< zo}#o=S&Di)2UdQz=;wsMSi?ZUot%nsK)-T7n*zS$U><*s0f`>%tZ%1L{_C^>#R`}@ z?2OU{c^N>^>Z8A}l%JGCsK8ADgb6M*PA9G05i{u%g4(3G!G8a+I;Nx<6zcda$1pM` z^k@H=3(nrsvO#1bjTXK06J%MK`teipxZK2M-p^6+InKV3Pgy0oa2);7S0C3EJuW4g zeTpuR$y84G$$*dgp5u_jcS>t#kbn;+){ZA{iPw0b0eqj+H;{=7`qIGhzA%+g9eX&@ zf8yjSy);_eld9NUXfDe`$=$m8zPF;&t<;e)$ml`s=}r$w)YQLhE&B3|i$oE3Fgx6w z#3+bG!nSSPB;2;=#=M!rTuW?)t{_97!ofI^I(6e1k6Oz)Jti_o!%hGFM(5S{b?Bkq z%2dGIn<&L=nZS`JNaVX%$(r!LLOXWXHfzltcz}d<0;c8LIHHwRNBlKLs}S$1k|ZC% zt=qAfyzt1A2^S$roOyS%nz_AH6D@;n#j8dlJ-x+X_>eR0q!jpQfbfX3S8!Z$bGoei zS4F905_%UKUd1V;$9sh0#hl3qm%g1OpzPyf&*FZ#fxyp;@ld{1KY8KeiE3`9cPdn3 zmTO0yjX}s|jU{%+Je?aQv5oWtc<=o6OO$F_lGCjSs0&&d-Ay>~Lh3hcNWUIvX1!P4RPr`bbvS_Bn?wW89Ce1gsXfr3vc z3N6!w-pZr%9sQiq$1N3q+;k3j%BIA9(?-IapO6wp5rrqnVlqU8}NB$y%+@H!k(%oAjI?iP?v783eaK`hrQRcu0(5zEKK1a%fz{ap z>nK+nn|x~c3)1tx5k6H7if@r#q0Rf*`8R@D6CrnQ>bGc%1!gw;7*>_P{TQy_{t5Aj zGR^}?%}pNSMUGf-oDz;5w}xI=6566#42S)~X=|UMxwlpy`|QF$_RoK_V#;n_Dqh!? z9=E_E8`Y&&p<;b``7RSybS7+}9M3fQi@??gUnRt7NPr=nC+_5vwm>tVJe{>mNt5!p z1Q7K8dmQfVFn_r7XPiHN0Bp3(DK-KCJmW;equIQ&&|AX;gv;NI-aJGy$=;yQpfn=( zN8-^btFLUdE3uQne?}IHqCFX=Iek|80QE?wRQc)bgxrYpXZabcn-~P7V_e~&iuUaj z`+G9!sTMMn>t{}upundXzd424YPo0+lX&XuTLW;C|2k8-|MAoGDOYURlEyfmyB0cA z!3@u*kj#*}IgO-{+=B_6fioexBW3>>a~j8fFuvT;!Xx81k~^qf{f&12*2k9oCCI%A zDDq!WD4y1-hR4wbQvpBCQOM7f`c??I>$oIR*!amTD~$4?<)x(fAKOv*ZXbwDlh}I(8{evqtXu@~% zGYw9=;SP2IRw?K3S8VvB%xel2lD@6Ezl0E+mF@D2d$KbsH$7k!i=2+xrc2 zKY07qGCUW$uUyU8INmHn2*UNxhc}jlw}E3eqgeDjUj_p4ctWP#aTsTzz}wVII=@M_ z(1r{y#GxhYb{#m|U07@2qLlk&oNk#)RovM5_LHSXY=Ibyi zzbr+Q#pSHGd`8&hDWi4tZarPP^b;d>F#(z9Z1*X8l}}XxE$h?O#SVOe&c1B z7>m?*fbfu9l4zDBm9`d(j}fENKVhZhk%LZ+rF>y=gRlf4uiYzCrH5* zE~*(2u~j|=OTZ-Z>|*r#q=$#gIv_=6aaoXp)7}GTcHRIS2ntr%b6&t$?iL^9(OCT< z8`(xsLgr@lKJyY3D_n6){n1|>Opr(uqVBtX>1SJ@wZEXwOodCB_@#e9=g_dOt~uS} zWj(!}jP!WliP31po&&E_7-+}`5^+6bFuY$% zr5D-75EboHrwhn#pS1!M6rX7b3MlX}(P-2R`po^R^L{mz`l=8`&3vyC%+Sp6ivsdvf<;}0z1`SAm z*56P%_d4s7kQQKOac12d-HygvA}GxXe`>$Sz4bp{+JBT7?*P=<@S!2m`{wygmQXh= zC54-hJQB!zJH7sezigJKhn8$QoFVIt?TkM8PEpex;l&rtNY1+Nl`cy9NG&FyWaZU& zuQMnO_#|}qT4q0fjHTw|&X1xmQn4{9^}-{AXBzPYwy?!zY!+)VQ>|wk9tON1%>^j{ zc5P#Y0jZds@Cp8{e;qx1z3G;mTXYjoC7M}g+W4X~Ld6VU{ySKlF&i}F?0bSgP3$`A z^JZ)90%D#$`f1XdCL{pi45Y4($`Umonbq(|2rS-1ue1OfQ`3x{$%_4sE0t%KT zK8&wr9RK~eokGq5p{TMlu&$QUw~r*8Z8o+C4952K4!@>`NqtPKBsdFRmI6%i+uk$> z`v&zj4+HLe^64>clDvw=G56bEixovJz^q}oMz%5G^(CE?woT2cPF~_bJ7`{ZDZ>aw z?C+UD1g0$^$3@xhj^^5Cq37Z@iOv<*qXb*Szbax6m4pXAf;Mu69Z%Nan5G@UmwRr?6`b zW*aN!1+bT3J1;!=A+KD8=ri6s2wf@}o|?BuV`9Q4qfLe`Ai9M(viMi4gZX*toh zzq)c0vj19|{y1Wff$;(JLGm4fNj{z^NByM?@%hhFRLVr8W@}PwiFqswtw$bTiDva= zC~c{^WnVM3*8-TKDCRukoT8!Obns56N$0*3n(vDPTJJ0Xb(^0rL$eX6?DQ`kZHqf~ z%HKU5#!n_%;DoS7{I)0VkIaAmP9>gm z0Z|t;3I2@ecp@*j>9{=g=^cUp*P$?W_jDHY$?Uc%^CQ2~wKMrZSv?k==`}h`)@e0j zqSzmW zLC}o3kl)eTFDBI;_aTru$UE$O1sCr2!3ph?m9RxdKwitQE(e#I>RzSqpYSE3% z?HyCk$gT2rm!T;r2qJ$^xZyW|UbOZ8L+W)`w?`#_DzxLZ6V5FMIOu>a@W$Gkp8sdp zBdHUv>Zv)4<1u4|tJ%8GuYsIN{8oOTq_kkr#+sCu^Cv4 zwI>Fo?s`kwb(yy{4a(}sP#;s(Q1M6z`>qw|R@L8|?DJ$ve3QQPs2)1T?GV%hfcBlG zRUy10j3N)m|Nrakzcg((>}CIt_c3YxF6e?-#!Fu4e?OmvX>2|+KPv}LRRpOME?=b3 zKm5CFf9?&EfB87TFY-dr|E>t+_x`8K#!YjWQM^sqnp)gMz>55Su&Rivme%CTA;R0`FW`?Lb(zl!lUG@nZGVZEJ zAqOQB;j_F0<`@{!MS_*}`lD0f3_pS^4_d3as6xDvBf6NwcN7n!{2@9t6>@rAL+Y}p)_Ez2i%85SLD^1IBL$3chxSRnVx#a+Q z5yek&GajG!BYTLMnnGGU8$k-{QctxZJ9P}#S4|;kd>J8Pb`gZ+tht&Obc_~SCAFeQ zpn+_!>6S~z!uWkQiKh~n(`(y$5rs;8|P5O3n%i_n%l`c=cp zY>!|KMAwAZ0?pB-PeIbLS%f~3vdzK=UzK|C+4jB%Y!b_LrNG3N|ICbpKiS-pS_Wh_ zNtMPUZbtPO^H#r+VT~J*d*XNtP{#%g-mgAh3FGc3K~fXUCvlf0Dqstr8@PEO^;^!c znPJWBK-!8|9#k5iWv057;nBWf-Pg{docR$>Ssh5Q8JFsK@YR^dy!(2p%kp?q)>3M; z$_|b&(jyUgv2lz4r%jZH>haXXaNt0(H)H*>rQZH9VJ_XI7EN6`jYnkiceI^Q6Nk;J*L26WaKM zSMFFT53=JN+S3H?c z3PTFpN2NcFrTiT}wSopv+D#PTNU3(55@TU>mPHP$Hcc3aw;PNMM#=84Sgjw_b z$3fCX%;Em)XFgp|jw5#6Tc*q(;687G|$t9%L8y{ll8jH%R(gK|#Xrb%Um6LJ6mc zj>XJR^k5Oh-<+Lhnf79~hSg~tc-__~ zYGVy|Ty!HYV0JP z#i&WehbRR5cDkN$c#)dxiyczm`$s;AM$Z!3;NO}BAK7-cA*8XLhtDwwRipA}lK~z# z{G5B*1A4MyBLwlROx^Ogh!R=HYhil$#PJ*xg(3 zD`+$S95EMH-in>!lTBBoRG3{6d{R(M*;|owbN{|RA}OqVRn{8|FwGD6I9=oIv%ODX zz|PAqU3wJOML*C;lI8{q&!kcRta^fwKR#A}J- z6<3GrN!lrBlx#uT1m&G$>E`j35~Cw@kOJx2CpK4pP&Xg2d0EcPOUqA8(&1a2wBX9` z{bk`V8w12|Tz@^tc}aBzPsmA%bJ>NN*$F zLU`uLXZt*)9t*BCb~oI(9f`Vwy;%o4lkyb>Iqx9y;z_4W8an~tkEpTOpmrh%KG`QF zFA2?`l=&eK?)?~Bl0@H`Com&I?JV)kW}b#=P*gAm^TmJ%yfiL>qs1&SZmhz|zvZRW zaZ>!9bRW;tr00|_A5A_>fA3Cax)V|Gn?{JgSD;Tzu(E+?6?yS|Bq97E>)B4hb2N%P zi4WaI<`2xWzB3*SU{*~leo}FNt%&*MnLuE!Zq1;j=LQv!rkh-YkWe@PY%38K6@Ua{ zLxz957rAfFg*;j@=;12j>!xh+3@Rgs%v#1Y+|+S&Zy6>FL=yWwX?y+q5c2)b${dFr z*BkSg(R|6!*!O`(FI=}Io>XCzJv=1IUcu7~g&3c4OGyrW^o#a8QrHC&^E*rR{$@AY z4UO$MFX>rCL6YZ@VZzqp=vqrs(&>1(yC$fz(sVjK(avD{jzW#zRaAy2Uv?HMo4F;e zh_OkT=}T8>Gm}S7F5n1c(Eolu9o6qO|k z!CBeiE1jxm$bUZi@-2;@o1fGlUa&FE)RU%--TL%pytbfbwtHqG{a!+Rc{WT?EL8BXyZw~Z%R8hO*a;~ z>`-D3rs~RBt=RrzxuiZma_Lrmx7d#@gZK8~$yMCEgE_xfSJ`|qQCJZdc7$&SZf ztsCp}N=4w0rwG?)Z?664r(Q(<6M`3E>B76h%A=OCLLhs#Y=UJ?OkVlXY{^;T@l5fn z4ub~%?s`S5O?-;?X6N~mg__zu?TJ;S&~;pKUnqsYq9c1dwhhDw{5`r1{|Yf~z(xKp zNTC0eCEh!&kA@FaTcGb5Xl=1T=9uUU-oMKwO~|&Uh`^3iStMTb# z6!KfJI7jzQk?9kBLz()PY*pc_>%p1qB+6-u*rs(44UbKu1eI?jP zm6YI~R8J5=&}OBXv}+VE0ek5%@K3M(T~S~-P@3~qH?Q`E5 zy7jTT;ev%^7taJLmK$eJ9?M->7ec>wQw|VxlC_8;74V_}N1zA8ySc)9r_>-GI9e*3`Q`AbGH2ytBY}gEfkV(x2{vb}D^_eA5BxyO$SyO=_g;2KFtB&eAVe zWZmG`mctutnYY@ls~5dwpaFc5*=M1Vh0Dy7L0Qfib$9v4DK1v7HuR>e5{@{uOsIfJ z>oc^;#B*nbrq1rCN!6#FXI_ew+7Gz?Z-*!KTyjl6AvJCobk!O6ep_30JR2EXK3Hb6 z_Wp}bYWrsU-}Hov$KV>JbA1tx?fXIBGV?+mU&wCL{?^8WPM1Q3bfCM6SqEs=efBiZ zPw1LBL4-w4i5vV|!h_{agU^IJC}*!`LlRg|b^N`S@o%0?tp2OR6-%&_A+}H2*mgku z=J5k-_Ydaq3I$~N21JMNPIqg?;T$;2M3f85dVhCrV7+Q&V^(4YR- z5Ay3TmwKQa>nf+Im<uy zuy4{g{iFwmkeIm*QeaZ6wOG*#~~Q_FpLPfP^6VSBJ0dNIL#z-;%g94LyQLE^@ERnzc$EGjuCI8sY7FCrbh(jCp%Ur!sKz4i z-o7kjP`ObnStn~g*)=X?5>&NBkD>MclSfbcYu`V!GJ<63>{LX$XHo++3$2kDyLx7N ziGS4zk82SehO!1xRGoq4UY|V(=;=a69^#A&h>Qd7bFcsh@>N-f3S!)K7q)-~=9?@czYZn#SSDIoVFDwXxE1e9~#&GbIgeTlvujBEU+8XiBI0zFoSR*qFqOJ zUP60GF?TQ)8p9;RUzES1^o46XBfw_Swn zMw^*`I}c$%kw=6pB+UaB@!d2&zVZ< z;7sx8t8k1IUt=i|U;C+zk|M7cPkWg*08Gkw$C$NbF4pgBtpWdi$D{nFHfZN@c*Pfvfm48T|7~ za<6EcCiZae42!G|h(jlcocb?2=2tQFgY=rKn#@Pe$Y zo@`>$vPPN!2VPLGQ_+(WrZiuiY7Zl%{O0EqjD&5C|G8QM^@f7<_!9U5rzy}5ya-`w z@4d>jKI!aG5Rx5we7lUq+SBYB{*|@&-Af0cx$B1OWX-=eu95O^#h!b0;MM_K3z!mF z-zdbEe0HxAWEcoOgEM6xu3tifHtfdR;o$nr8^@Xj@LkbE4OQUQ;K3}For&@lYt6WD z`yOkYslM32pHudXlmMVpBSY)$+)njuv6@EF=bd?UrSsus(4@U&%lU65Bue9xJwJz6N+k{-`FU?R=Xbcr}6&e5Tc^$c7v77-T z&BrYe05ATytDS4DZ>oC>2&k?AAO8?0iH`l z5|E`;7g+{e3o@Y)YI%vrlYyF>Mrq#DzyjuJ+ktvRsN=H(^PO6NNtbe(3M{PO!Ek9*635(F{R$U_#le75j-b=J7KhsH zeU~IvY5b1ff<<3OkyoGJUqoyd_r6uNX~I(}s(5ug{f*cAH12UsP65(hM#r4hPfZ~Q zl37y4P1a%mD(`g@`Mv*UDop5BT$H}~2uMOcwdW#cp*fk9} z(b3Onrtw#$<>8}vEHbM*aUvUww};Xk*1@L#8osj@*I3${lS{FxFYGL)zs<%Iu$J)5 z-Hh{!t^B$`?_YeP5s^=|4&n*lV4T)tQ<@vt{AuGoe3Q}^zbNban05?)q_GaL2RQi|g3jutu4mm;+z?;PD_ZoN_Jme@#)Mi6Srtoe4cuw2=X;>Ctb38zL8XpyrXfw*QnXsGoB`QQ)+qMJDJt*C*uu zIoMvLXjvwEc?y-^q4bN>T}wQku_m?v(jD9>OzZ9obg}}iz(^I={Hs$X!NT;X)cT`LI z6Ahph02Pa|HnvdL{PVQ1L4p>ZGzVYj=gEx9vkBrqj1QvS2~t^f1BNozpo1X;=_bF$ z{3$4HsQ9Zhrvuvi)YF;&hg=Fr5s6Ws}j0E z-TWbsa#7Pb8q90%BWkJgS1J-aNTeH(duX3C6l;UT8O7gi&w?8KW%cFbx zik~dKPYab?V*0{{_NZ>vts-}ubtcF&;0f7@#!H_lxDbaju>H#+;8WBN{jUr3xURsZ zvnQm7)p|G;WubpRH__N4E1KjzF*0E1;znUZJMX6r1cf+{3I%y`Z$72Bx3Y_=WaP3)5)*rxoTeLR< zqMI@DEFOME1TJeSVp$0>Q@_aq#kI2g_4Zgz&uvMgfJ(ulrVeoIf~UDCd{=Ur!!m)? z^(8O>q=8R;!000LhzRJNeEx?qu+yv2aq;*6XGQ*xVC{x!ZC=XDaL?}`N1ELilW%Cc z{I6<4hi^yFb5Fn@{h;oEZ4pp44W5l-4&)iuYtZvF(WBdstG)U^6?6KU%U_tMFPmM% zm*Jd5Ax!I6_6iJG$*wdUWEjPzqU0=0#RU9=qr~eY(X)*5ofV_(^dJ<6CN_f&w7Zz- zmlg+~wRY?qI{Tm6gLy&1YwzR3(ZokrH{@cgca$aH|1A3jtji$#6u~cM${f^kRv$iX zI=C$OdzsKFS{uCeLgJprjq&|bc!9z-PI>ufdUb?GD8YWmBK2~i3MFBuh2vw~L3NzM z6J>aK)*B;?9KKRz%Lzs->)s@^9GsKpr=Di}pvk1&POTDNv7{&E-3ZYSEz%AO9{NYs z8m7{sKxV9cu}CN&GMdSf7@{?NLzowj5&5dsdDORoM3)JnrC2CNSli7LiS+|eLXthp z_yQNp8@*d#o>1A@Z&Zdo9@E$;KiuJzjdI{7L@xQq!$+80DTiXPDZ4_2t*uJ!jpMcy zJoz%Pm;klhw%tX6O%MFK&-ukW8sj}qjRofq--_3j;^_VWYv0N$wJ?i_ZLQP2OL@c_ zbpBKuiWjYJr*iR~OHwjMO*E zsFj{&QCj^d?D$&G>x@dR)XGWKaoQm{Ry-7LOQ|`aWIeaJ)DG|w_byn9Wmo1JY=Y^`>=vZqj5#V71;VhM^&YPEPn1i=4r(zDa~BA$#oOvYY=>R!l<+D&i+CvYUihn+`^hYKMfw zT76bBMZ|k`-onJ2Ry2=nRbU{7$k`cv%)BG5JL)-p=94nkt2 zBP|t`g)y#2?oxdqO>xgvYzBBG@XwQ^Z#1R**07{WD=+e|<0(Su5_4s~s4?#x?3u=hVG0WI^$_c2Ltr{uJI)8KtebgJkF0XF4jp^Zr3 zb76-4pCdq}dGf}Gnn)i;E47#0)lgu464w&Ha)bAm=|`&!S*YOJoTAOo|rRU$S{qvIMwqQxlD?tp)PQE@* z1rw!0(i>8q7zOL07l!z4C$gYOX2th<_pzIglqZUue=( zrHPr@N41_E6-3t4KbfZ}9do6?o}w!qacBy@W2CD}?XT>S(8twqAz2$l!&JUH6+*nu z=|?Us4-62>R?A!{Yqh&v>2B#=7J^~E#S=3f<>lxlL2isp zOypfQHFVw02N9{_$ukou@V>xa#%bjwEPSwMwc&k@U^XFqmd5me-LpqSPp+HyUgVvx%d4{QkH5a`% z>Z0PM0jOEkc4(B%O+hR>xHIx_km}{Hh=g-ofNj0Ly zU7_(iq0x4=;_Ti1mi#4o9&m$0OChsqSjPrFpFA6wr57DJE&p(;fqvKC+Q{zI+FF0> zopEFHE`Ju$)q0E29369>!25t<*uTIpisj2h0LWUS`MT-{2M1w zOYIqGs)d!dB`b74B^CL;Y#_>}hoEbQ z5!T{AVuJJIi4t&>+yz3VzdE#&m*3f8d1Lg>4A68-X^VfWRD6rBgF_uc%toSE8&nnH zW^KhVh3|Hfr)FrWQ%QNl2y0J^{K7R8oK$;#Y-{vA_f6gGv@V&Fzki9Ofed$V;uBJ@ zGEBgLa!lF6GKdNp&zAEj+*^od<5O7J<9F#3h40;8ooE`2w7vSbQH${PIO6Hx%0aI2 z?<`6mGI0s7B%H^+sk(ddryDb^6`7S!oKYz*Ce9|8X=}S9;=;x&lh*7v!o!e{Ld}B} zp9fpoKE~?xMvVqAermk=2OLb;;nX(Lx|Owe4zrzsA71J7)F#| zezGWuAaNPJX47-<6SMaN*}?YxBt5`HrIN*kxMV0K$5V;3-K4O0rLvl(*>PIx&J!kh z!pz+$3SUk`9w#e#*vmMC&^Iq7oTG>j`jDL?cRjkr`6V1FQoK>7hK;c6;GBiwxj)JQ z_cNi5B#^){ZKBn$%n4*CkO+CroEIjqgoEk5yK|x~$n{0cMup!7@vhN|%Me7r&S}~u z2-!HBW^H44^jdyr&dUTBpQw-n||tePwg0GRMTg z?jO;IhqXZL0d>BHQsFm~SMQ1TOJ0a)cqNySQiEfq3cyi3KjyAC3UrduuE(V_JNe3P zRz!_{VB?WRO)_2LBM^|x`X^%Go99dRpgH(STuK#_dl+BPxqKr#JFJG3 z!omZ`X1SB~OEA1U|41$NCNX1osm*>&tD<0ki!%IE&iL~Nbu5Jx|YB|T?q3R&!DJM*_9QL%%I`h1Di_J zwxez*D&Eh$6~C-3_Wk|%`N$Ieo9|aFG;aN{#lQ+Osg)fi~j zQ<093ygt?|kLryhSLiPDoA%h>!FAe(z)tE3NSHlZ(hsFoq44hPl3j`4$qOO$P$Vh|D%r5|T#j?MK#~oF`>&||8`4~8qUpNd za3FQB9ttq>0O`g}Nqf-kv#+k~c9a5lly=oz1Dk&xMnPC)p2|hWBp?sdeq!0bF4ODN z>YpF$Q-N^K0w$(LQR<(66OBOczfDD7RidG$^&Ab;IQ^tOepr&~YG|fjN=SxgU)Z+>OuZ@vcj*>v!))!p(6!;1<}z@)jr)L=^ij$ zAj0u`_*%6W84HdC4J;m#`4wUbivS|z978se)oe@N+8ju2MSyXV-tE>6YR`77gl%TFP|JXba_mg^nT$XJ8IR*!CO2yr&F|dxRhfyV8a-jA@0}ETb ziWv$_J~Jj@y^6S2r-c;E*=;%d*j?M92j9=~CcU22s9GBM#|f7er?o6FvTKcZS3MpME$#n(rn2EOQB-58(=e`uDcquQ{1rIX<|W-N8N=>iw^<%2QY7 ziwX0RDbpCwfn2ls&gTRgY+jd(OeDdVlSmyf>r-$1czU1XdX*7t&$swvhlhu~BdhWCpJ5$&VzN;)1{3UIyz5;in)FDH zmZ|^nb@(PyQsalW#0DpM6vjz@%u5LcxJt0}DyK|ash>1QTPu#IIAcs%c@?>-k(kG- z&MJ2XbBsHT7jIlB^CddhkO0}q5E2y*4voAl>DDAsAM43+lNQX!{tCX0d~_P#D2MFp z`K3T9DBjpeL6y51(+ywwYn2yQ4JnyZ$A!=K0=SSXTC4u7w1b!F(ZKmB1_`gA7F1Jc z{b?hu8Af3<+pk*bQ_v&A#!F1(^BrCDGI7;F4_k?q#xUDCKcLl zR~>N7lT^b6gcpbokY_8XP7+9DQOC;&3bR=GUEi-lk+k!;VN36$L(|NLp?GaV>S#&k zqdH%5Ne)w65ROWHX00SLFw}R*Fz%ZZ)dI;S$vNjlc}vTz{Bm&uM~YEuI3An0S#Qim zXr*-ZvB9$bB4RV-BrS4khQKT}l0ryuiosZMkl;!;Wv)EsZDMvD#V&UEY%%$hFS0Pr zu#c4BQE4w=t9N9+g4N(x-#r>)`bE?h}{(rWNNM-1=uW z%BV4M$I@=$JXFe!v^-2_Nl76W#>J)U?yB}dnPzjX31@*#v*hOB)|DN~63MA~bFGk#i0ZJ+bNUhB{? zVL9>NW`;o)0Ok4jH%D(u8fj}Cqy<*Ft6sI#2j0?-DAE)E@W?Q)GCt2~3`zcMI&WA| zb>N%lK?QMy9me-p-DskLd9LSI4(_hP8*aSz(tN53tTj9^+B6e(naLN|c=nJ~xEy<1 zKG4n?;fcZJ= zz(esfXcd1JiAPz;k6L~#9`HivH<#bs;=TnhgF@zP#d@Y+K3f;iH7k~0sTq~TP^|-8 zNw{4e@6yUgm&zh)b^(99`VR~TszkE%vsRdZ?Wc#w#9rX*6TTf`ek%4`z*(17?ElBs zTYp6vu-(5B(k;>r64EV5H-Z9!bax{y-CYBc5(9#SbTD4#ZG`}tQ#V7cd4l$Q4fM2zc@7m=+PD)2E zA8asJ9aG41hu+dRqg98RRd9!NidF#wGDP|)AD~^2HTK?Y`p@ct z<42>5a6hp;!AzAfP!8iw#G~rNA7T`s=it3ZMtjfgIjz7}|9IWNcvE z9hAKvY|{T6X6x&mD*O#iw(<`DnC=v&oXMqkfBoy)>?=E~u)58Csb}LnH1zgiv}-zt zZBp7gamV8cv(^mH^yedEvv+!SYkJ^|k4T3USv_E1sus8y7gbDA0>>2Fv-^uoIFdph zH<9qBHGaw8wx*pz-wvcNF^(-El9RuXUy{&X<*FJYS$?EuczdR+F%|m7iRiV!% zU70QWV@99bZQWaFgLBHmGW#J6e08;X#l39iVnAsU6mijYd$pLSMMTghV-_?KXX6b8 z_wtS8%AA2(XWSA+gWlQT{@eai?n3On&s94SJ7p7mwL&xzD6=3LG5ki8G9kfu z1nJuIRMwhS>%n`n;6LEVNcBdWvJLt##G}Si?b_=zN+{Ygq-zkiPybl-F^R#ZcR(0? z`Mb|1NdlOQd)@h!miY94cbEPL(_92StMl*GkU`DK_8zW>u1thR^!&Mm@%$&Vf3~o5TE^CSYs32(Z+1_vtK> zQtA%TQT|`ae0sJaVN^|ANnXWQY9W0q&hJiMJ`HMlv=Z8xWU75`oI=vdmB`A$kb-z>Pp9(E9i) zbUP28rOu~FH-po2HubUx;hmQ(Kf@Pg%C3NhB8U%12-XjIm_)vW6w>YYp;aNsot8`t z+tl4&=~%A`wi4%=k*%T_Ay4K#3_#@C0@+HNijUyk=ncB6Yux*c4L+&RKABNWtQH#1MtY?m37exPPeL4)`5tir~< zDPzf~t9-+!L<(^4ehk~rj#q5r1rtzj{VIANe;onl61i*3tM>Q|TcW;WF0scA8{$E$ zd`h$qw6M;{VofNNk8mV<#WY#Wb@5Fofkdb@X#VZgsT5eut=6g;DonlP^qQ>_ZXKp# zc+H8?5DMW!YwpIj9%bHm>j_H*sGzg6AKQp5*YDq^rrWa$<_FG5o z6mT^^o}LUBITdz5g7!J8ZM^vce6*}vX4`D%o;^x&8IJlCRLUAhj2C!pq9c4!#rGR@ zZUvCo8DjyPq}&bpzzM)s=*j20_sOYA3fSD3D@NF}F6Fs~75fh&jv^ASvGUoCD*~)a zQ$)kZTDL{)PE-ATQiHj4MHC#y6@$l1zlV}l1gpKHPHd$M4l3Hb1MP2x0ptZ3cU0b= z2EgfNy8G3iPz*0k?fl+m4<_ICTwFRH7P_+K%?Y57Nh2>ih)fLhozvQXsLaZDv!)~C z-Z*pe!5zFe(fw>dw%qOqL}4A1L)$|b3)7jxeb*t!b5 zIk)BcG$9;5eku9Yn^`z9kMy%HjCk?|X@lrfOmC*Wtx!Xm^9Kb~ zyA?qE>FrXs9I;93m3!C^yT;yWe{6$suAS%`*Jy!%M<{+65QuA=JP_Z+_c6nkH}~{- zv3g^X&u>27npeV{vM};9t@)XtpPu1$HY_h)afSLh++}}w)?NKr+SMP!t$M#8!09pc zYCDG4{yj>lnNYqdL%ZfySHYy+D^&O?xO?;lM4Z7|?PkGexIZL~A=_-$_FdHP2l6}u z;Gh`kN%4Sb4|yx7U$Kj!>5Xl?)Uam%bI|#3ykbF&zvqYB^Z^m85V&f*n}mwg*4^B| zTI75CRw{h6^l=J<*v5NOJpM+A%5{Ro=#_L|8nY>s^P`hRl2Zcp;&HliC$;3;K>CL7 z(wLz701*4*i#?ipN)=`sK4LOFvDZHCE(N(^~gIg+12*`+okTd1Lx%pW(F!PRX=^GCd6s=~$S`zO|I%;mK4UBh|ky`rkLP8^$Z+1F zL1VI6^A4dQo!^ME)ATgvC=j2-Vy7kf)%FUZqZ@NT8R^?j_}3{QR&`C|K2CjA<0zwg zQRH+}-?|r~X}Z2i!A&+ohJB2jU;Mlft@;^3bS11|C} z65Y#xW^JZV6*DxmV$ECYltNP3l={DSXti5|;;&U4m^KAtSJO`QxFmqLh95|z@VC!1 z=ZC4dA+o0^EpSJg3|9-7`*|AlA+%=LyCIR&X1KK4Zk^FsmYsz~XOsKS zZ&<(Hku`{}@*SWF}z9?y#khu&pPldxZMm8wDI_Fy>%;>-{hMv)4J9O(tXTHtnuS z-);VG5a>FHH|DSJSw>EelFY-l%m?%HA(x%)qAvs_td$FfL~h5xE#80~+S zr}B?3Wf!l%Q{>`5xZ`f69kIwk#8%Mss&10SH&HTM@zgk0nGlMUV1r`m7YoMLIe=J_9YJT-BktB&E_r`-@BXf50^$#`Gemy(JJnxgShSE6D+TuZSSKY<_gH;B zObSXdQl1rEQlCQ42+4*}LXMj)Ss_Rz#(LYDSDeV!^fP zlpqynpqOe|%h_l?6$=LG(h{fj*oB3nRsAo^&b}A#^jWL9h8U2d%k@(==_X{3ukzu%;HWP1s1IOqc;5r6!9ztwztY6Q8aVkjt1W(P&+`hq#zCcKLY?}2(j@armXS`C9apI+hEFF!g% zybp1$GB0d8xJM>@N*!LlQAA#8@rNz;j^zFbe+o@R1P>Ri*IBe=<_Le|LdG2gTlYHe zmbix!CwrYEePrM&DpO`4)BH**#UBZXqx6-JXQ;y5Ozj-}9gYRiFNU`z(io_PrdQ0# zvFV22Gs*BX^`Uj|gNm~E>b6^-N4R^9>g3yu$BQrNNcDI?{oMa`<(!jw_?mn7Z8Aqk zs)MIUA?2hEIXBYe)&w8)#bH?Hup<&B_F3xIg7p=IYuGa*bTGQqivTCjqQN{;7q=o| zwt~ZYhoVjnkB|uyvs;TZ)yMFji+_~+fu$iUhv2{zog@aGZ^Hh|U1Dt2<}Q*d-rML0 zvYU(FO=_5u2LP6pQQ3qh>aw{DnR!9w zRC7ij9(~Ha6QNmRGWO@m3n1wH2)7F(Llkl%B~mAT8ULGuS@D|$-l!3RyAVXM;pk# z%GO?;vUQCSruWf8Y%BK*vK6-jeX_!91!cSj#afK!wSdBaCPGSY|Y2eOAaRjIk~ zC;(WrIEXcf>EMwq1OS7aMd^D#TW8D(61vQ{0w$D`@XBA3RUxpD*mIesV7M z$;F(~&C~8ywC!opR5BD|_J_E)Ij}K4-nCz|Eez_#Jmnw?!~qiHsU&8HysGPUiy)-f zxXu{X7kcY_GX6nHx$hyNGb+UJ4SrBJ0V;sBv{6^+1>xShp#6{asZGT7nOPXBd&dNH zYqFynwxc@kXLr_EADGp1)mop$@$?ma`kcNv!w((c2gr%(a@9Lb9$~-8s5IB~#@HT5o_+ zxdF2wBNB)`zRMeOl z@0v-0G(CA~W7u4~bvyXmb9;dPzoPa(*@cPoPoeva4~lu^J937#`e(R|kKbr(bp9K8 zc+y*9b}gCPvilg>NBAzZ=MUl8(~-9~VB<~dnGx{P=nQFoEg7&-ZGoa&Z1^8_K6Cxg zA{A@gGl;;d887Ndxl-9NGO#%HQVluS1XN#n-m|K5zZYaLiwA$A{x!jp{*w8p9n(*| znSTMBrQZZ-1kz&Roa#!a%rpc6GvOcd$;XV9WLUbyTz4f`94Z$ym_|6aXrA4SELO0v zkiCgqV4;v6kI?zRlg**4K#fZ5`Q6MYo+MeO=6W%b`dy7!SX(;D@I#YI8AwebgWkuq zjr<_KvZ+;Nw^o(uTh7sGwH8%p6%q1z`pxJUjUxa+B7#N0AyG|fhf({j4QbvHeEG?j z&ML)xCGFtDL_sM~ZD}S%RugZ}e`Zx)v0$4zvq=8a8!H=)sgKu~2*#Bt=}`U|s268k zG{n-*qW!1GJftkdQF)WHQgfcLh9*l5gl;6~%gUY+{rjp?g`1D%C)sH74_S6<*Qw}( zf}+NmJ5N;J`|$4@m)Gb4MgoR4nmNZLgm{=(&GDa=q?I36VI|4AXvMk2)LUzdfg#+Q zq@AW=sb^aL9S%Yt71@*M_yKwlcWX{>S@u^iQtC0rMa$V!AQr@m#{b6ghBWnJJDiIB zbKOd9kN>Kv4;DrPc0Jh4oE(W!BIK=0q_nA{74f_FMQ5BWZHU*0if+%8n4;*>bYKSA z_=eufk-Vc|>-i@?>G(s0wAXZ>(AzjFzr@=b>-3}KpX?iB671qaqd4^aJk*Jp=(_l; zogAn(^IFt(c2PxVfGE_6=unwV7x#ebh06FeB6?h82TS`;wZSoplHjL{mtA*dOV(RN7o@HWdw!644{eVbzVF(G$6*H?kD1M-NLHtF> zUX}sb;ddfAgTjWBD~dh`5f{4$eT$lDsQ73k3}Gm_O5Qk0_ly$yT2tB%t8WmYX3qDe zJiK0P?-^b|h}J*Yc^xepDc6&()(cF~wtf1gX|P8!j;TS@BZp0J9vzQy%k3AVN$)C~3UD!RODYJYKegGSo$ z+LiUQ2_Ct>U(i|3UEo#}X|Vw~JX!Q`I5)kDP&av42G4@$n7hwG^|%Gh9(k3=um4m< zRKnU>^lxdaW=?YO7qHE38353D`!>suVRfW$v_-!M;Z-KtV8|8y>wTLZWD2w;bCT!S zzVgc)j+x)?YcwN4usC%fU>rnTHewuk&UU|_Ya@48_iIZlbZ3W>Pn=GsVV;y=1ia&^ z2A3_cJN_D?y9H9jvmin4e3rg-DCmW|wHGh3hv7HUtTNKjN(&#-%sHR!5)fun6(JzM z1=IjQ)Cw@m<382{0J@=vF~i~TOUd}rCkW*3`QrqP9fZUWtLV9ZS#LW2RdP_SR-)|* zBA?VmepDWk?cB(n+2_zy?*PRzxT|!TtyFE4H92WCHL<^dVvJV)~FZU3LQbc#V%-IEuoDL-2I zTXXrZd?X`$c|)A-`%=fGdyJ80%I7bAx2{cJ&6z?@OFGfl?7|AYss7Rcf64wQ*&{U69n*Ztb)IeSMG_1h;-+2~g(KPI=;02l z7%{m=+(Q==7*B=6%hoda;|^t;qc7?i%b#c#&r)T@slbg(N0sX8q~J51zIeF}(nD+^ z`J16w>EsA72z^7svIi*vUpY7)>>6wkBjeOKebmS%VJ6Di`Biy_)jD7sWD^P@ zA4q8xt9yZw)*$k|)Fk<-445NzX8?~oAxdT4WQOLI^Jih_xHe&>mV85HJgl#hlEO+{ zq!~N~-RskiTYcNjuKYzONT+N^s+da0!+wdQq<^fqp+%Y^DI6w21#mA`Ic7_~lrH)d zWyX)Flof?iP?9gxga1*x#DXY$ZF_ub1Bi z66gVKqt<4Kb#v?YjZ*R1`6Ox*8#b=QXj`^TLd7{xb}os-e4U)iW@hJFSAUcAYC%eS z@ps$Zi{GA>zp5`xq67b;M!d`(-nrB(>$`+R;I!;p?mHa2yq1IDFH`qW8S@Z4|2}aQ zI~Xac02@{@4Q`V4F)MB&98yS(%)#4QgVIkVd=nJdf$m5hC%gal`Z`tBVpqQ22EfuF zV|6di|5#^syxmf#oERUpY-Abp1`}52161vA>ctQ=(S;A601k-f_(LDn(WPTRKqsW% zgOJVXw68*^%0JiO56p>`n=3W_p_n0xL9B-@UENaeNIz#^&mM-iO)ooc=#S$FsA>A) z3PZ?8C5rz_XAtmE$70e;N%s42u{PFOfh`LoJO_I$@_Y9tj1Q#DU&;|JzACX)8>D$c z>tGFzPRmgUyvrgTsuqYR;m%=Jqmt(9E0n`iKFk>kZ4Y*zKgIa?DwiQm-(n)*U7Ne! zRVTNyRo19r*pOY?s^ zWCqfAP#zZ?Gg=Obtzm*SXDwR<9x-KTf~vVoEkFP5+Q}ssL%$-kk;IEBvWvE;0_Gsv zf=lW~ExeeJMbsK9xCv-_6X+TAlBBm4`D&JGFL2GS(V~0Qd9x>D`J#HA_pR+;%_CDt zBc?cRnXQI{y62aY^Lj)+)iwV!D-_0j#VS3p+1A+k=Lp=}pKJ~8d3t?SG>OO(J29
    8?q#C{LdgX*5CK`+p^?=aBH{kD2QfF%ffC~__i_W+~6bi2(hW- z2XSl;P1K9-e*z^O7Q9G>?Q^5zyBG_LvIO!gzw?Qx`>ER1-a(6}c_7}(jECgvMT?N4 z9)>?Avez7En>R`zxCi%h8BE_=RO6(73FLH92L>Z=-x_<5=Il_jbo4CM9o$arCLMPg zLSnfNVoA2X)&~xeOvE{RYIYtvxiG_w)s{<}4R-(`MY#b_r*4Bt1k#65k-6SOT!-~u z{oOqez*k`u?56?m&(7qm({W}?hu`%HOmCDqsqA>H6ypj_b&4WPKf&JSrlX2iC?v!)13)&Yj_vA zB<>pD11C->_F8nuOaMUHUfY62YtX9Bz;hHn2o?5d;n05@pVQ^ zkr-Z`8;SpY?y=l)nNXBf6tdf};{IuwFCgb15n0pA&%q&(?g^#&Pxiq+ntPdt96>;Q zB=BMyI>AjIDtj&O*53fOQQqr1Xqn_SfX_a>vg9cD0Sd%dDO~F|jYjv+9sDUFg$<{- zOMs{tF&i!C~Z-(g&+ILd-{U#4r(3wKsx z5>)_2)faQp_UE;&BhhJ%_3NX;ChS;84lt6Uu&khnCr1gOq-h5hlVrf_;wkDK9`?bv zr>VRh`Mxb(E@S~-5jq$K@Y5_NCAf%dK(JA74W)T4K&N7uDy|_n+#-=K3Vo0)Q{<<8){@lRfqIs`7@DwPqykp^>-ltuRgd%DCClOHJF)(khNud!k*>U2rC$K^N z0G)_Z_Pr#2IMX&c#(u9%B)hJw(I|$j@82+U6_o$SnYa@W%Dq$Sv3`_O4raN@d&*-5 z)HvTj#k-iB1Dnq#&M-E6R6d<kUJSg4;E=U%t-AM=aAK1=Tv= zvzn`OOS|5({1wnG4L4{L3)ipsM~VZes}@Eat*)agJ=(TIEwhdj6uE)>OY zG;=BzU2za3O`BP>l>P|BUdIKr44P^OH%)?V^2Fhy`Q!T%4R=N zh2U*_DSLRV2}f+?TVA|G4(wpApv(}&GPQ&ik^LpQr~TFUH^H8C|51@s>z^BE3nyLeH_3;!R>jf|A4^AH|LZz#}~LK_J!$7-3h@I#hN$aW4TWv94*K%;mBes z#FDDRa*9hdft=CV2+8l0L_|5KQA?bz(WOwoZehoyYNTq=MbSn#?lA_)q8YrLrsp<= zXN!y3li}dl%#CH&D9i`Re2-`4ODSIaUIoUXVfuZ~BctA#{gzJpgiZ>9-~pPypo2aY*o^>3#L+c=c6#&=IDEu@5q7KR!- z>v4+H^@!+s{vj5%@iR1YxW8diHb9zim;yALhkDH8$-1l=D)HmuMB+^Vr;>s^MP{i( z?K8M9H5ViF*ldj?D*Pn>{=?87`H8f$4cqni+&|t1SBg~zMRT&q zbYmxS>3!mR9_l*QU_AAIgeKBH>3a{vTxct0xr@5?tqlWDc+(qSBa~66$15$QM-jHt zaH1XBauw2kBFT*+pOEL!)MGWJC#Ux~n~rB{81ZLty8RIYtp;rbdCIqc-*|_` z=`ofH4sbD8;<3j+Y-RRvYY@#XpJY_u01(izbJ$?Tjv8z1zMSv}ua@YOti^b1|W zhfSI*lUY^>NEVdrn&sR&>MrgGteMjb%BySh9KBy2k|S7vlAO4v zlvGxyu94GPel<5DJj@yOHw~TT$f>%{TJ@{s5crO%H6P{iIHA*r>j%FF;U6E-DkMkF zQO?v6H%CV*jw7U{jT4I14-c6C zgc!6V*Cz(LiZ8g|`D4`00FW*QZBF*bfCFN&RW!9+Xk5$hj^A>t?L4msCWjznzT4^j z%hQk)0UM!_9^PmIHz&Gk%R=BbW|Kv1y?#i~t6JYJ>}nEe=efKG5<-y(-P+IB_!!o6 zcJ*r9ez8i`Qk}XJFp+YY=Mos<2hG`mgPMePz+Z0WumWirp%lhYLGM$g7mf8*npx!o zgb{Ka3yv?7%ib46*6cHl3wxRDPsX-RMS-zRHH^~cg!3aj<02rzfq=_Mi7?F zA!p#44lQX~yCt9_-f3(I#KF(RlC9Kwf3Wpu@8_$>^|Rp}yj#;rfmpnCuX(+Qrf;Ym z-mRR9$&O>nOYT^C-l;tlWbdtPMmR}|wYBwTbDr2J4Ybiu?fw^pyvPJ8lN^mMK%vd0JmA<>?nr~2hH9EZU74jf4SHQ%j}xtJ^;Zx`UUQ^>4VZ~II1Icc_MX}g zP$Gw|j>`5SWvYg+tBqoP?>jgP_6CYB=RXRQg@w~%n>79 zg|U>;wU;_I`E&%1r4yJx?;YuV-9d8C$nXMMrz8zOaOaa&Kl=74MDbH(dL-W04 zoP5Tl$Q^Ip6@Vc>Lrs?b_g@er4wqit<2N5<`4T(&_QeivO?+x|y+gPTUJ?RxHxG5L zn7TQ^UTJBRLjo9ydw&*R4MyqAL`d_rBX&0QOk_Y3Vwg%IF7KFalGn5TME~k+Dx4x7 zKj8F=YqmEm^6#IlE`1V*-pk{@_hDl)h^}I^Qqof|@D|8h_hD&Mi(@T)muyl~#rWL( z9YA-1QTuW!m^=CAx&cIdj!?bQUgV}=4?qKah$T5aPT_-CC0r z{sN0;vRE_2Ev&!@d!StM+RNXu;b~2&(tTlhBFuKIHsv0QoE3cAt(MI+BSr&2x&TE$ zeTRXx^cV65Bqx%Lvd5f?F9IX!G8ew9swaz%KAjFT**VB3P?i-4JniIf5)H9npj!?c zi{_Wt%o^SHn_2Qzj^$)PH(XN+Zt;Y4xUiI}%KcnJfT5IUNKH&6R!6PB_o6(_svmmc z8|_A1daLb|yu35ro40NwvhEU$5e5vqhRUoTgGF3Tj?vv|?w+#aAwBgHC#;QQ=eav) zPYO<>hQOv6+D<51H;V~gwKe80owGEVO z$-y*0k2N|n3lRaI;2w;y48Y0RdFc_pTY+nmeT$QHOj;`&L^=86X9D;1ThInPJRZ>& zvFM#aYD8-v_LVzLcC-d1RhtV_LJ|g3Z`X^n4G%%&Q{gG7ZYG5(#NOH=-JP(YiU?qS zVp8mHihS(KI<`NO|O$UUj&9}*i%pU`z_ir;r@hW^I?B#I8f4Oo5M$GumqSOE`10mW{|$H#{h+c)%U zUWaMTzCiNF4mk8&mH%4$6bL0?2^kdMzRm|6Tp5EuKTc%yu_XLBpefmn2LMIXLWKT1 z!@N2@J`?n@^xqzcCA#=SeW|H$8eIHqgrAxcL`K@4cBl+GSY&oYc2_89x;-xvLHPmI zxdFXghk`4e@wwlhs^H)2s&Vle|2!HY#`+4s>Cx9X&~(n&F(@`tD<4K){L4b5rf+K{=KZBVkztIxP zMaikdLMz{8qoFiu(G0#%XLdJg@Pe&^rXQ0TU0P*+x|WKF`64<}t^c(3VY2tm+qkK! z`MF0m;%6Hw@D(q#Yh)Dt90y;eey74eVVi@B8|#p7$nfu@DGOuxrA)s{uppqznBk62 z1kcOHF972R3-#8V>hJdKTNMXZ$vf(1VtItd=+>fk_Tx+ugq60+eBxr7BvBZC^XJvJ zk-Gtpvgd-SRWnB#CXR^62YIRJ!jaIky9OSf%Z;EuFlr= zjf_&Vi>cy^b$l74_C=@uat@d$b`NVet21F9GO1#2ac&#-fazig5ss`Qd*wo^ZU_sI z=+q-;C(~Cj+~Z@$VKAbD{KqU3Y8cL^sYu1uTp}4y8p)4;^zYnxt}Br*x|?JvY1luF zaY)y4;u4z6f0gXzC`P|;oWZp^Im72gEjR}He;If7jw7Xx%EXbj_rU@7DC0E?d zJ&77?vL?>lr8BL7`-j2(~)!_?}m50njWKdj^3yHRve`?zM+@}1_+ zF;vu=`z@5TQVBynh6isNhW}djr@|Jz6TciT=%8e@epAsw*~1KK!lsIJ&L|JopEVBDITn|q_62uQVNtSs~;@L^w zayJF+guC<7(-+F74kBn1mfA6s$o3i&n7AixUKvBuW4`pTR|07QZ}~*3$FcFJ+kd-z z_)B8{jna$FObT8jR9~igP(C48T}|hhNY0NPXcd2KWc30}_D!ssxX+}O9}mH-n1u0H zl^ELTe{BAk;i&n5{mmSuyRl%_&|ReDM@nf>8ShS z)&NCXm8YT$x%7S$KT^lD#WZLs}S#C4_zX}?^DK0 zBkaxfzHh#G!^vD#J6=&uHxI4Hbu+rRTa!GYTx*AQyCADI|#x> zPSei&Xd+LnSh9bk>uI=-XJm z;x4?t-|`zg!=0LOsPkKDPgsZPCF|bxHOJdbd0vw4@ut1?pNisjzqPaYdRE`Q@ikmU zs`E{DNf{M8+PjGdh<-MCo?i)RuK#7;VCeeC_;Jxjbj6S$?JD5)?@2eL_k!%EP>C%C zFymc;4$T>&@Nm8l&zUM`nCr4Cw-8mk;Cs1rY9KLTH~sNV2dEnL=;+9QrqgitFS{?q zXt}8>S^axKlRZJT(2?4_O{ZWsmVW%X(CvlmNM`8qgDvp;>BFuUaJLpjT&p8^#h_h( z=&;3c*<05>bI$U?zrJE}$ zF#9c$VwERO-gcYV2!>-AvU{RIYO@}dFYSCidS-Q?S`;NO(2*a=dk4~1;sQktSH-D& zC@@}mfPzQd&nnu6RE)CYOI90uuc>2H|6S!{XmP&N@7LiC`0{BR^XWQw;Jt149%7Nm ziP8yEEs-VK375gId$da%-lI}oaGsPh^haQjudM#v*%Ww5A9V|-kAQ=hId3#7p3kse z)V9;cftqRJyFZd=>U3}7*5q>?w_n7`+Me8?hvd1~3mM(ayQlfSiUr7=4^~0Rou=pK zuza&Lz#p^zIt%Vvt53TSydJrb{OlDE(Pn??TOm}T{glMF-EnRA>xpq{c>jB__&-LG z(}QDqxUJ;*36Y$U;N@S$AcU{`&-5XxTEKi$l@^1T_T!?Q7_pxyeAI!1T06(v;Ox!n z1FwRYyAvpb$m95mkI{c;m1CLe>g4|C6tOfLQLvqaDe^%G#dBe9;LGD^q4jIGWk7)H z3=Gl_Pf*|Ei!Hf{Jyxkr?o+%9iDH=jH}ZUgMl+h*iHHNvwa6$r!m&%Ci=r|lM0H>x z2qpX{P{jJ)qyUFE&=jSFU9e)Ne2|4ooKzVV$EIIXy0-|3+Jw#FakAseYR>2!k-qku ze4g0OWh~da^m{CR6?JG%QFe1tC;=BWT`aMRrhUju6VQtA(V(m|A)bdm8k^M_fQX_- zHi|IzA!a_BkN8qSusrsCElrSKBwa9|$l#?N|DZzVb4VTnP9Hee4+poJhzaj-XPB$i z$U?G(N`ISBnZ=8KeT!hmE_Nr;<>SeF3J-boFH@`7hep;1vWS8RIFMf*v$kL3@LL&e z4HJaZQfasX=w;Fvdfy*#VpC`PCfqb}HizFnKA{*Apmt`ucpV0bAghmR)1`4J7jCmw zwp52N*7I#1DFZH>Gpz;^)HTdhnp!C*|0Ru@yi)hqOul7Kw!3pGX{8(pE$$ZJ#PfF& zAedvLgIT$&EpT!ipilR6#tzG+ab`0tFKAav9Wk<5nhel$-j2PQCyIr06>~SpF9smJ zwce_3si&SkUexST?k80w84W+b6KNiEfPk3Ya!a+N@{K+_1;6``9Q*BOxs zE*B3Jefn*rqqmm9%_Mu={Wh*xBTsop5_Y@FMeTg(q3@7u}lAcS_D5k{HXx3yPu9XMG7 z>Ti%^e_Drzu^9yKO!Ivak&^1g6n^n`0*?BU*Y66wTsYnQCtTdu{et04`T7*^C7-{t z`tds-i1djp(3eN#a?-YaMI~0fo~-^(CDfT`_~S+YzhBe}W#aXZk#FZ|=0lo)qO+>= z&Drp7s6Ld4bOk1sTcf#xGVj&!Zn)+G&N23+O!W=E_`(o8EZsF5mYoy&6b~+3`~d%+ z;meYG^@8Igu5VhSJ}MU0)49vmMn6OXj}{8jA$`Xpa6Y0N(02ln{LX>1X2P^jN82hp z@`4-cpn%&zXN{GWqzZOWZxv~zTG;KPY6{ka7r^rBsor8+SxcU1CF*C3aB2DD`?aV4 ztu5@6bF^T!q_rf8p}NBPUM`Wb=VhUgtZ1IfasSsA>uTtS*dJwl{sqcZgH|9DOaNKa zqn&f;+GOm#uP}4RORt?Zyx=#W-S>ys_=~)418%cKPgN~QBWFcFt*GfFSv5{qsUU&w z-m0&veS2(U$y~g7IGmfhe9sPUP99TKyghzem3HKF0_7d)yBkYC6^3<|DJn`i`dEKk zud&y|6Un(&)+?ew_*T;s*ChA5P4LTytW%$nZG2Cnu21H-#8Zu{8BCvdbU7l4-uM>o zZF;~Hu`-`tTR$Ty>!D4+fq%|`Hg1EHv)0IILswnqmP)H%o>NRdkHbMV8+~j1Cj<%B z+m06|^PGX-;Tg^Ugeu{D3YWB!ad3lIH0@!RGF}BU2jnP{=O~erf1lLM80snkN#A%6 zXikjqk9lGZCR#qi*G-m!Ir7eB25`{t`I9>tJ%PZ&cZ~sU#o(=4aLo7il-S1U-hvujyWsmbmNwW8(}3 zw9xy@v?U&|@a*!c=XKZ>6{H`yb%;!0OwH!Kwli5YBzOK47DcQ^v??GyZ&`&lXm+=;$D1#l!cVL&0}gD+lukf z?VVCXa|AU5lVO66wQwz>)jr|$+ho~m$1d5OjkG0rai0boALqNy{m|Yx zeFAiFl1|(3+pr!)Sm-R{_7EdT-S=4A@a&+<7AkB!OfXtSuNn~|> zUyW=n74|aRhJGn{>paiJ#XBmX6t{Ewx?5brSoD}epM@2>*GcsJ0$;C!`d z|3Bq!1?h$lArxaK!dF;_&>fibKUm(C&x(acLC5PxKhl3#@GNY;OU!h3DCpj&F(%Z3 z;N*<4`I}}IHnvF&=O5+5*Gy_jCS8ZD4RvS#J8(P>RJLx(5{3u>lUDlf+c>jYOX;h| z!S!BS4n^NyUB)&yzZ$>%wV5<|S-U+%7VFXN^WLL+-1h2cJ`RL#+=N^OsC{|fh8)Cl z)iYG&w;J42@Y8nmUrZl*yH%)eB5d zQ5Ap@w*4RNsk8h)rraPv68WF5f8GiWdvD5E1G$U+1vobngNNJql6a;ME0U$vuQVl5 z-o`~d&!v2pMtQ4ntUmRGzR6&g>YwL9Rk8W!o0#Og4p}^$vz|iezgmg*aij9N(6k>1 zo4OlT7qep^bOA)7Y4hSrOiB75rG(U(Qst38?yYkFBEYIYo(GjB3p&4y`RZX8SGO{D z=YQAIW9Cd(uP}A5`hDN5?1awUOoHQYw{zTLKhBp!`w?T6I^4CByLil?2kIm<*+TcWOYD=dT?-2Ucb4`qol81Hs*H@e;^+Fo3o zfs6buiNY;>vnZh6Z0o*m`|?&Eio}8{Ud&!y7T@fI8>9M(!fGpemwr)0f_LFY1<@*> zYN9=h2hl^W)s;>=6|#LEzYI_m{K)%u-=UJbFuSyqRa1&JqOyIL*!+=GmbFI8_E=en zT?rFg;0f?8=3ky7e+1zm+=5w2ar337UdAM$8e0NG?2W%Ll^DH(z5EL!wLqE8TMzyQ z*l#q$q=m?8UZaD{8V!ObGRiWZv+j2-s==FurYtDO_}EX|+jh{Og!9)0^+N6oKc-|yxHC&b5@zmLSw1cFf_G~^#) zgO*p!%fMnn8-^}yRW*=668?duk z%*h%oIu$`*9c-m`yF=aKsQ!3AqksPCEV#e%Q#gGDtgJ1pPmY3t8&1^~hNf?G(e%c_ z{t7O|t+DU+qjT-lKf$oY5;hIHpXZeh581q{dM;F4($`q-=mW|nwb5^-n0Ju8wB`n*m|`^ zg@|#UHg7{)G%sJ<^$+EF<|6^L3;5vV{ZvFw_YOpTmv432Jk_;j=A=*I3(etU8gm{$ z^QfTTN}cZBq8Wrg*1dNp(MRBPN$768uyn)!^?G+b%dOA{i+v%pnA%$|HD&=rkvMNu z{mvonYD=GNxItrPYD^={CJ9LjIz=fiKiR>5EVnzXGsR~)|G0$mr0#qYHl}TJ=V=Gj zti65Yj!C`JPbdCWTxFxOauM2K{&mM~YV??u<~E^=&#_WUUCMH0m{aHXk#FvN%K48N zPQ~Jx+@hDL_4>qX<=GVzM~Yd8rq|zmj{4)km#8#ajf@P$LsBfM&4Tc|Z{8T3fCdz#-Nx98jWGK#R%tf;rZfd_g z#*i`Pw5<_od$A=kWDi9xX@WqhMpYTRmbasjGa9nIraj z;)?R4wmcuTMWnq@1{t` zYIB6QX__a3@dLI9?V@Eyw(Dhx&=G8z4_>q;8WXPoAYqpY_gWU$yX*XjZ+erbEZmyF z)Yuv{PhFH1B#6;$#>5SD8!T-qH-A+f2{wU8*VfuejU1w`N3I!#1hFhy{Qn7I5sFpC;Czjg!;p@ll zTS4FIc0E7TC4sMwS}L?7@me$k zSAAz;qdduo+VS>vw|0Anrj^Z%v4LB$+P?nqZ?)VNSF3k7fVsljt)%HGA@=GpH%mH- z=rEfkYjDHZTqsU=Naq(XBl6^E2U|&^rR9X6ttqqyFeCm~N7IwF-8#Fz8T##`s+0%C zSWx48dIz~cY20L4ZH|eufIpkXkMTtPKC32o~*J8mxuBlKPp@0MpZKO z>zx(_N3UT4NPsv#Gv%^xC%TbKe1D1Wh>|TyE)<}gzI7)wleAfb4Q6V8|6dulm3KY{ zAwoXizvZ@j9S2oLL-e5Z&Fi7`gJ#usUwlGr#EsBTIf(i#7@e+$zB)zM=1D!(Ck7wv zQPP#z$T}epKPT#8w_!&Rt5vR@(fYj#T`}ICJ1+P7?i(~S^loVzMAx-e4ONfKbYVl~ zKxwO5oqyi+6s`In&MZy$1XocZ3en<(`dnrzYRNKUSB^i833RgV^yCD*I@we$?Z3gt-87){lAbf+>bRVVSZdADK8tfj6-bdi{GLF5w@~^>pF~nt z?VP{HBh-U~u*1pe>tnaN+ij2k9J>1&8&@c0%wa3Ir*h0%HG?;q!Qc1W7LEKf`yVgo zBwW{+oDW2};e4LKo=xIP?Hvqj|6ePfD^(VsBZ`>k^FTnuf9!VkThHIy5(1u&R^iM*a`64$UXR2W#9wzYFI=J6JMgSu>DC!JC|SGr)R|=RDUH8J z-6ojrv z5;Rg+Df@RaKZiso68?glM;bW`WB5b^j!>}AD-R}-UW^0QIG@=R>Tcp28 zoHePBic#}mt4sgU=kdgqFgCg9@tP&SZEgzbV5f?*c)l#_Xl>2nMsZEj zw%yPSF&`CdQ_4>Iw+IpxCI#YD(nPCMnBzzm|5RWl#4nJSHuB9 zhc@F2sFQh*zHG~%hTA3A%*w*&9XxQ||5!W9kVy#wd~n4LPyVqDju$gKLLweP!*_HL zu#pQVy?fSU^64U7jIl(QT%mRh&tUwbNt4Qpl6x1$o!L_9HzUQeaQIaq&F_+@9)nW+ zBCsAKocv=Dv@a~-u7ytaNhXH6(1_*^U2yo=`&eTNcVZYZHQL*uxPJ~O8k@#f>bFB3 zO2QP!kMP*gALPb)m|ACvzdM?h6T=%|2e3gZ4g?QEyZ-yU_w9ubsQE}=kF+eX8R;Ry z)ClBjB7S*k8)=`hJH+f#Pi^jo0W)+p>nB#iXGF;ZJ&1{BVbC?Bib>nSV%ihm9 z&Cr3%c^|R{R>NAkAL$4cjH!ZN(C8RKyJ{0rM7b4@w?QX3TI&Boq#=C{>5%km%4 z`ayg2??r6rXyjPtSpQxI5a}UKU>>W<_^%XwMr!sw3Vmp_JE1TagZ*Sxa^GQFMaYA< z{G#e(jc9yxEZ)o_q*hW0Nbx>9O}itv&~kp>ig%OcsVN~ zmWU)ZHs4ijF*x-|eT`;|!+)ZIQj~}Y2|s-*Ik-as=gkI4`ky8AGM(Amt3&=1oeOw6 zS{*05bRp1XkNTxs!p7ayPQ}HrVa%|rH$BvZ2-7~J`TmCKmk-rr0_H{%Xyo>x-NM$_ zs#5#sy7w3M|4j- z%`!gQ&ws77-;t~LRWKf`b28nF&Jg*^v;vHzTsCLm&#Q0!ayXPssixu|YESXHK8Ki@ zj)6QYP;XROnQFgfQgR)G#7og2n~yW3LBwgyb2Yo2t2Fyz%80+oR>D(y%Ips_=!D!lD-RB1=i(KMUT!c)y>$-FEh$oB#|*y-mGA zGSmQyx%c|LbXNhh!eo2WGQJ*-K{;B%n zv?KQm+NAEwjESv;SwUK_%+8@<%4jXeYcS(tk z0w(SY!f)Ww{%B0(iM%VgTq26GrWs#C(yT?nmUe>23}E29CAjdfX|VOym=^199T3Sp({QEtKWu5}`( zM}N+BxTK(pWd|{6*NkcM0w{cU9i0Mf_#7CqNl2EgE9W@;Q}zs-5)RKjkw{wz?3^5p`(m*;@cUsfc;%mw%WoKRgomIGsM;(?p|c1#k3xZxwGT z!RAIEH^`JB=h#vBMKy6R>{jvIS9_K3%27MlO+H&Zu%I88e-#vp4;w(n)VQYRXR&R{#Q4~c__{C|S$$pn z0}hR8`fEjE+j<1-T!4UIb@dk0hCRF1(=Z(0j9eKWSEl;=oF3TjK*p`+tt3w!Z()QH z1;Zr7F_f;j<(5YUJ(R&`eqoK39p6NPo$dKY0zaX9ST+Vn)fgiM9YDE8*#>8hI;((h zPYx2l0;}Y38ULXL9qyy|9O{9W2oo91rRFF%QyCvSWGyVLdfv341b%K%s}OBya@h@T zF_E_AnV1aL9`LyZ8^dXEu>E+L6!9aG4DX?@;}llc{1;EOj%m`n#Z&{O2~tsk#=i*U zMH`ZWh>^NJ_%(1Ct(!*pyWD*8N0~#{((Sb#!qdN zyYMf%&{L5F?G-G7`6ew1P`@oz(~{VErJ38eG-97 zS62@2RblpC2oBV}q`WHlh>5^&bJ!({vnJ#Rh*JaFmi8>0JzkHf@hdvEd)C-rw8og3>3dw%@&Mh&RG zkl_y9ufGgA`;bRXO=PtHH@_=0!~cL%(U|cHQW8D6bhV#;^_#;hytIt9y z$)KPOWchX-F;Or>tHh#m%jeXX9^ZDpYt_uh14F|aoC?Vk_VuaMxOkXZ`qIT^2KtPlMHaZL~d zPl!L6E@k|im94Cvgo%!@L*A25>)w-tYeyvD7x_BHE)Oeo*W29<-@7-=oksEz|9guG z{y8_(A37hweAr$x%Z+uqJmg)OQDtK{JeGOyFF4O}%zW>hvrT_{jveA)P+L{)j(EJ7 zw2qC?-*9K_IN7Xln~N1bolXt2ZYiyCJ4DO&1En>5B~f_+Ud&5cMkmF)NH19`#Daf?rW$eI3y!ut zLnhmLTDl&20|>T2H*x;bKHWbk{sXTGiegI@e=Z`J@P(E;|F(JdZhs6iaG95J8J8g# zM}#I3YikWO%x7@0pZ7<_*xsKT)FyR3dz#dGgB%$a+>TMT*S>U-9`Xd$M@3vpIrfT? zkB^E&h@RKN#958OdFsN}pSlD43*o+WP!4BByhTH6%>!hP)?=lZ@NlwF2nruD^_q z7_^Mv+{>2tr{jE~4+S0+UP`!LeZc_{E)kH~|g*+?U9gc9a>uYMUcJc2!Yp&@jb3n8=Wui067#z;gRwyxiBO z;Dr!_$Jo9?RrX6f%N0pJ!?ogj{yV{Owv)XxWhc*%fAy(=;jtFbpSQ$TV|LjWR>dQS zzK`7wBpP{I_f~lBlZKJi>IZcXzIv-4v@48=ZGVmbM8Z>VcUWG3J9GBv4->N)KB0cX zG2e(YGddqb#GiugHyG%|gXh0baDq0{;s2IZYKVqYen97_yh6ruFtf&aG+H!l3U4W) zROxlr$V0sP>vjT zF4ZFnyG{dx6xgZ6v~BNCh`4+ECIkKmMu!`Gk;sY$2I}hWZxT=)PnOY3b62q8z{aQ} zQsjQi_|kY)D4`|>LYuYwLE+(Msy2G{$%H&MzAPCyt?S6l#P#YigzmU9Tzw+v2)yUWQFQOmXyX*B)TW@eJJFnsVE(!+a zGJ(3^A;D+R%+1z|6~8m<9vBGTQEO3k_P)zHsD@7IWqOARG%UBNZM~Xe*Zr8|4B`(& zy2i%N-H(7P-#ov&EMOw0-?g}mG_8u}|7=2QoF66=waf+I{%pIPBJ1z>V|IbnT4Kd3 zw%)~hse4@>bM}MZpMQccp<<(p+f6mENb@fz{|x~7JN*X$ocyiCfs{%w;zW~`*oc`a zLKF^mUn@6MJqSx$;(^r;YZgZ7cPd?$P;6OkW)R#PtT z$rMvVulP`4c4CJmS_wd=$C3U-%sirj+$;@T&cI}@^hCgekCz}Ovz15v0?WPkp(u;` zWvz(H(bn&?Q2e||_!=6`6KfDjc=poh?YjFhu47rraYHi?DV@A~RA^pdOkL{lF-D~r zsf)kIkL4KnyW#EdfYN(_D1l+tx1tRZv?6LibD1opOg;pMnwb;f6PA{qQ6+%1L`yQ} ztb@f&+y?)cc!_B-{5;%fW&(Ycw>%Gec1rY=NOrr(G7;fKtf@E85J5teaS8ezmJVgG z3tF+rFnF9~!z*+@zjeQfPKb!O-jS=1Wvr8aUS+Hn9Tss2l(UQ4!CgUrL=D$IcC>;C zY(@_`<6F=tGn`ZKjFbxlyYN} zJnU~dwp!gMTGG>UwiFZLG01$mi`X1?%ZfHqn|BW*4tuA~8Z#D^?>p7uNfsU2b?6lX zsD`+>F*^n<%X6g2h3kPou1q0zGDSDk4Zu34TY0;3ndIf{jv*{lNA;T_iy_;GM7=Jr z#aGnHAa=Q9`c}=>kz>$`8^3Mv+oAi$KhKtutSIW2IIH(`@8EaEi;WGCV{3EDcu~%7 z5p|`v)tkL9RgC+__;Nha5uHK+s zg>ue*)fT@HtjjBKKwHKe2pP2z zHjn-AVfI+K*gQis6=`WaF6V9eEDVeE-VdeWJ3UpT6oVC7in{7>Jvz zG;&%38EdZcuQ>&L(*VyPH)Vx~p9{Wu37+gH9TcLnN1txe0^e8K4t)6k1ZKMpMBK(X zdjd@1>ktxiEY0FYdRo_lkYk_laF1VZ=l6fJ* zHNac@nPoZ4#VaE+Q_(b{g!p=H#6m3x%G8EJ`Mzwx-%NzhEo+L6ykhP(mw|K-HXUg4 zU!ROkoOYpB#}BAfr9_{Om7stR8fh{HN3C50#LVC%=B1qLqlVDHN7gE4`9->aM`)KB ziLqkBsoaZIkz*#j~-E)Zm1Me*4ZyYY`kY3)~1k7neZBBWpl2puYG1qKPX-; z&HRw45tp07O~;i(nez;TG?rV}j-Y)FLY54&R*?fgG(E2RF)jZUaHN~bY^nRi+qHIx z8hnGt^R||kK)zXpcOlF}27|SGx1QKSaH5uQYtFVw{@wU}APsiH$ckk{#F{0Z7`}q2 z?{A1d{ApLRVk2vcxc_+60dnv2@n795Dv#Ja=~O=F)IsQU56tn}sJNYb2kW%YN>Q+0 z`3z?5*S$ZfowvCH!A3-NwenvabZ`wx1P^t7>V7S~auN3|Tda7!6Yn*AJ7+g$^95aY z>ODZLK08K-%j>4PygeJI-9ZtC0ME82Cnn5}E3)YE58&mKmk%{jxw6eS;Xlkw=E%=Pi#4#0*eGWII%g;kXwdQ0oO|7sJ!2u3$B zT_P)Y6waG-<4<(vZPhaOR)5?Rb108&kUw6REwoOKSSgrmR$6=rrlww4yi2K)y}VLs zWjk)_rpTtb2rrnC%7Fv?fAL6WXa9`J+Zr%%1L^owX6UPflcyH4@ghg+|*oH|?o zN2CXKxCPx2Vt?FJ$W^?rkjqnZN@z@Imx#@E_mmw0LxkwB& zA58rg&$Db5!#tCS?V~rbju|4Av9}D_p<|)nvs3e%zmJ4RCGeO5u=dr?z10yIz^0Jfr4pg75dbmifLV?q&g#wu^n) zF{`Dd7cqAKP3``d-=FikH6G*Sl62e&-U9F3PFcU!Ceu-%xZU0{Gvs=i-EWVIzs#F| zO^P{iHhNZkK4uW30(F48`MW%X&=-H^y0>*sxHD2JNbjTOfwTXUfP+*lH~imsLjEf* zv*2y*+&jcsfm7bLu7{f7*(~Tz`>5;h^KC3BkHNU>ul{YIn*SH0$4u=>^SpcMXK~Mt zB!~5$>Dum9DStz#e8b=P@|wjnP5W)LcdXa{5cf75=-t=5t2BAhcfuO$=Mbi_q=t~+D)B~2 z)w59Rs1~fL`YU$@Ao|6O9zTxy?{U+{l610|Oz;PHhjWxrT8VqY0~_l7Gc;E;=sU7s zOE!}mU3QtM4_6o3k+Ns=Px?;ZqA2ahW%L5R%Mj%@8%EQflc2z3N1N1}NMm!m|DK|k zLAN!Ft7p{NM{|%}SenGDh(SM^aUA<@IgsFs6^l?^#A|m^FJgn|XpN*FyQAc@LZD-= zqpuSNkWGkTroDzmOBMvvq3h>Usj9U z6~ccb+_sbtG$KqW;~bQgKE#O`*BnS7a@pvFoFkbaj`MyYvRNeUC`HN0_@z1s@CrTP z<_SfMD>qsK<_p*N#Mwv#*wHgYu^vz#;p-#wF{)S9!?c&3aeAmN{Z*fpBZJ7qSMjlo{1lfvA`%R^OOqq z3HMH9N1^9STp1sc0io_Nsf^PsImJ{9`NX7GV)mEQ1#daE26lpxG3`>=49Ge)e?$|= zf=A4fKNrQ*y^7g(gwo7sIl``dHd*y8GA4k7rp#ckW>pqR4)(cC{N`y>R{)ov(m=X&dPoDRu zvvR6@o*t5hfe$&ss0adbx7wHQAw;{ni>FNny;-^0vf!$d=*y!nyRu(@GUfgPiRsyD zv`)*0RDqJ;MXn%{M0JqSfaEhHP{*ylnpcqj;1vF6_sR>ZQOk7HRjh92srx?P5YBtg zPtbMKt|ocG@TtFXjsu=> z*Tmb!B}N{Gz$6dW{nquOpVO7>j=^NYo)Z_&0m9EqWnzvH(82pNMf)t|@+?<91d8cp zUn(=0xeoN|ftn(gL3K8sY6HC7rQQIraNfZw52<2Su`P4&bNc9*ijyDi8NY%il*D;E zO&rw5c4mL3F7}}Opav1iXG?7&y&yMa7Vo+?Bcf>6f_B)W4xY&FTo#Z!ExZ@MCekKU zUttyMX6oA>24qz_%nv4@JDR%vk{&$!bB6q5D5b|lO zN6FFENRn_a!^AN>vZ**d3)&@g2o&t9i83nuZjl}Hr1w~MMH+?`qO;6%es>w4DB(2l z5}W0@q%&~Kkem@}J>zTR7pOM?Ge+0`g?9wetnFgL>Y*rg zRri(x@$+jWgy3lDDVDiU)g)d4p$}K1kvdxCKxXsNjEpx+kF+YQ%PT%hECbaa9=vF$ zz_TjVdFy=>^Cif0hnZxWd))84tdinn!9{DKBX=_P6Y}!&usb4OBdSt;4v&1*yUa=d zQr`_;L(QH%oWbaiNk&NxAWN`YtdZ=wZxJGy7tYw*T`%@S(AkT=>!c0n9zHq zDVI*a)v4i4$P`50{;cB6tgUSL9vzus;69w<@(c=KWXSeQ6uI8TOj?}zlS>VO=kG3u5OITj-R zTJSmoA*Up0<#3}@pyux#{`{X5gp8^%geQaK(Ncgg5>IZwP>C(Y`Tbh)`QMQ^yD@k| zy9h>Y%slV4%q7ovOZDxbCBOo_n(;=)dWlvW0s)F=rC&9**L47j1EKXVk>ef&P5L~3 z*l}lNWE$u(dg3L#cfdk@Adk@fQ5aD$Km2W55DVBL_N+XjgK)1~18%5y z3PoaHiEapldP*}w&V4!DlkqqfT@X-1f5AJT9@2Imee3@Gwgqn~g(z{9!M?TX%h*D` zhS $!#B)=qAJi3SnJGw`@lWe>Ju2xzwA%eXKj`nm~csjC=rm9SzTjs8n=67|5Xh z=ZHx7t4h(Lm+Rwht|mf30B34NtMdQ363VDunWu0Y_z=IQ0t6&4Ai=zGNz+RliC z2~b3UN$1y6ka)dMCp$W`tMZ(G|c!JMEd(a=M zrGCyz3pls-{GT!M_Pv%?fqK^e(skc>=Qfqnavhr9mhv8=36NSDH5a^+dS|lUx>n9G z*7>7%;0WwiQWcu{cz-2y1CJeHOe}SI7mo0$9-Dw%`|GPy2}6d>8e;pMgGvJ;6%CD;h3k)+0;}l zr3*0DQB9m^Y8`jTdZ{3FFNJQH_0Dta(_(LiQH7$VoNaz1nCYBucS9GHt|rNKFUA_; zY&n=ev?)kD6ftbzY|! zCZ9|7iSZ<6Xv3T@&*=;n;jw1E$DaV6AY6zxg{Q3f_tY$s2lJ6w4!s#dOgEQ9p<3iR z!Kmegt!WWLPprO;#Ysa273 zC8ux}f{F|s_&RcWxR#(oWc7_|DL zKs)>jNwwPSQh|GZxN+?30~*#Rr@nXPBL;=^to5UyCFW0&TJjw11{N-o)nBq{7mkW9roso9`p(6fe?jAd8>^1QK8?09nrr9g@gV$CWWWEQycr|( zsGGL*HD^Q%XBlz6yPVw*QAS=ErA@jE*)&yiH;!=|a*{clfFO&ZM6yebOgSEW~|HltT>4 zyc&$`Ns(%e;?cD-j#dr>;81RVxv~|&Ov?^cvE>5Xff03S+see5HG;5UwMtC$q|Ub* z==dRRKIq^6>)j)uhE0S`&V0=7r5in>&nIg=v{J|VwkL44+dBAfW*}e?z^05@j`_`e z>n!VitIPVIDEcM>_|s&UT~AyT#kY_Xs(K`8w1}z4(!>&pZ24>7-_^^#MigJ(8Ai(B z0F&UN!b*$D$BWyyIDoVf2=q=FaKH6FJ7H*SoMtU@0SyLuA7#9sUf}1RFmq$K(p6Y| zTMKhd&La>?zN;zBDe<@!T7#>1u35P5!9T3b?`b~b%=!fgUBNCF&6S#}M>75Z#*hA) z7#{tp098NCn$Q=i38yl>SMhP3+w}Jt0wg0G)#vH;s23|4ByRj#s4Qzvgb~)FOME%m zrGma1yRHavwR!g^iW{M}Wa@l8=ex(Hsgbc0lbVd8eKOzl6ewoTGgo#5l5O~6C+z-U z6!tiehq^WqK(Be5K-ExJSF5=7v|UJ9O^FL@nxTLD)*d-1^~(PW>;Vz)c>@N6oo18#(1MvB2UuMTfe5oS>t7r zyO`Wm_z==Mt$$op;=iV_!>b49$+dgO%fJRH2&p;6SXZ7hink(KokXNuqE)Z5xN7@RZ`nL`*FHx&BL>tNw0Z1%byxD zQKn5_+ZX24+gy?SgLO^BINWd&n3+yNmK(Rej;|BbJXw!#nr8l;Y^8sWXY+9h{M~0cE`uc79A4kK+*=9GV@F_8Kg zt?$b?6s6lOd+9YQ9I3^fp_PI6jgBdPZrIc|S<=|auS$Qy&DDo}^~vK@MOK+eY}se^aFo?T<5rnWcHq<=)lttK&yT2rFHnO&$? z)8)n>pqiX};CDps2}2jCUZsG3`umr2!}eV8DcTAVQD+~cj-CEtrLxaUG}FcWvtJf)c-C|ZWWmYq>sC2&@-d9^;`M}(;IBDx9w^-tC`|6fGn=8XOW(6I- z*K};cFEw<#xw|_5LGv8{_eI|S4k8qC&@=|nA8O=WKxI!1cmnsfU5P(K4d>QkcNy<_ z-B%b{#mqfo+oC7vU?+_FeL6Pvp#5SbMJ#w33FJtmMCrmvSZYb+M0EMDU$G>yQy9Y; z*Cl39m5kO%fgIf15XiiHCHT8qQ~B8G)tL`@I>5Ta8K~3d)Wg9XP8sU_kh9jU|AC$B=W~O!U4$Wy1$sRZ zbLib(IX+=hRnk;sDoNIucsDj%uRNT|(fo1X%hKXQHcIME84G#Ic-fvJ;;!HHpy;=? z&I=I*eq>oOe}6)J+=L{h^Ly6bK{o7bBcT9GxM*Fcy@0A%^O5LrKpQY^X6UL`6pn?B ze;3FM?#7#iKl?PKo~rRbzf6(oU8Zp;Soq%>FL8LbhFiQOv38e$%qpRMQK3AwBtnKf zy{sCqik6?$-8y~1k&A-$R}H~t;(7g#;eukq(}NeXwoFx_hig1Yd!7=QCFLR&p4J@3 z2sfM0Vo~`@>jZ=``%IZ38vM`9pIY*t8Iq8A53A@Z%W$s6+MM`CH5oOzqs@n_i?JOL zEUQ6S%g2Wls_M^9Bs^)25urz0_Fg@*KDNyyMKHX?b27D2e zxa;NNVo^bJ@@K_$n!lBME0TP(V%F$i*a?Wj{VXga2iQz%>*djcXBGqsasj2%-|lr5jyem1nBQrl@9RpAHCFS(8Fymgq7+-}$Dt&}ssx zrOU5~=|_KNhNa~hFh!4fs;ixJ9X5rj2>R$!Nx zvPPArzEvm&{OC#iCWEbjo!~wWnZWPwV2R|Gu-H)9&X+8tA6PrI!Hn)H@~6?5It*g4 zYhft2h?hiQvc6MdDV&l~)NUq=zuFek)VQx)5`4s~S>d`UX@2mO1l}T&Ri!4|5-Fh# zXowN2Y@e<6-83VKPjcMO5Wc*w>EH>wyT_Xn*iH)QA}~kBU@%A_yON%9)48}<3-u#ZwN|)0XNM8c zX%f!AHSOd;2U;RYWhC4<2KCEKzU*gt6`;5Dz{n+e6^Q$)hCyB^Vo0u&Kun!%U<-Rv z$Rc*CJf~W;_sjifmcej`$BHPw7GK2<3X6vx)6x9>m&QByj=GaV9{In8uXkk9li?AB z#VRJ^QR(c}Z+t$67paV{YSZ2HqZ<|=cGsMmsO>D51KA8;?-;VW!rmCXh7udY@=gr| z%qZfvXvL3*e;O@yz%66mncd2uoMB4XW{gst+<7KEL(pxfrIg)h0vDo&XDO?nh#HdF z)Qr96w`n>1_(i4xfrQ96GMd@qf2~QDKYO1ym>BjBvz$jr>UwKI*#SG=f1H}8m2(Uc z!b8vPE1Xn^Pi-glEFG=5V4I}~h*WpUztH2Io~l-}nkv&)MOp0;1%mWe*m51CN&&GK z^XIM-p(PC_TTGdC>m2J?E!sB*S%Ky-Hoc+fw{jXqmAURlilfb;alR{-UM%MM~26jC;g#W4neqT?|k2N z0WHNn?LE6?XB71SrSIq)j*sV4Ku;jRlhvReMvmO$&m9;L@P;bZHJtji`zg(QZ<>Fx z=YvKuCCpR?H)7k4zpzvHv(=XS5d}xU8?0O6jk%oJNOgNV@4MFU@>LJ9k27+_MpkIoqi~YqSa$(PllABCnW#M*G}J_qbK~T~k-Y zC!{;%xu2#2?&yfR!gH{Ck@?Fz zV2$&q@SyJm@Vv)t=pJRE&5Y!YmEM~lGXL_HH_jT>oujxyU&n$BY?x&f$eNs0h&N<93D?RK22Z!mptEWbAIXv zhI&;M!Jn-qWX)20g&Dl4Bsywl=Pr7HsV1T?4I zQyhS|Y)HaF2lioy=v%lEA_Nz<^pJWZjHZ?EhaTs4NSZG4*(tO`Ov~l{EvY%qQo!FP znp0kTzP`#@l%3Pv3T7WJrR>!(`spHG2zU_I!I(UaVb1_v52qyI^Qn{zDnV136LjgBT zsmE9*~(y zT4)l=xH3rHUcww{vTLN*4_5FEimcL1O=XsCvA77ike>ZE5E5>ty_)o?O~1A#h)haY z*w!oV>V@_Qc{7Ney#scgyHX=8^;N7VRm7PR#r=wMbDm=!-BoW~z|}tq5-v2rMX6bC z-=2GB5r|xu5f|U z9(8?v%ah|i?(t^~a+3mFt#PzZ7f~nJ@YGl>cbx^&#^G@0$+8mCUYG*k$wGq(iZ6xl zk5a4EQ1H*wQOZ@{3!3MPC(`#bALtpdp2gG`^1sU*=MEh=8W-gCWjq9Ok zw9MMGJ?ur~Z_^)kR9tn+*;u_thm#)jeB-Z`7Fb)o!)+JdK71uARY!_WA677UAL#+mC$?irxT&s--ZbTA$3{ZJI9e)c z%mNe*Ep^j&E5gI>P0==vYgsiwm&E!DcUBvrSA<$s z-9_EH=I6@VG_`Q0u9osYf1BH0%0ap4A8)zbM-bCX7@Wse`v4y?v7Vz{ucx{IPk-=* zd+(6uyB9nn#ZQ6OZ5wnnboAZjwA>+@IMFEmt3L#4Vs~b>U7z_NF#ZpBcXPsKPre%B zvv)t9Wq^InZC3wwd&NtDm}q=ICy3JiZk#!f&>iK5PxI+siF)t!GS8}bFF*U<9~b6F z{`XNG^ve9_;Z1|Sf&ei_iu0W~>Ia=;)qQt(&m+g;)PJhzQFP|X>8-7*$Ef+an8VDt z)D5x!k=mtWt0hpkl#EfY?Q|xO<@a-$Y~d-G&@88KuI4& zIW4UD9a;7xiCdVo>@ZPw_@daeMX`a?Iam2%>G*T?hj!L=1i37EF<>b8r3Z@50yKq1(NZf#i>)xoDsC;)CFP(ju{L!tvfHgv zLBFIr=13$LBH~*{^TABBGlQYy{+l?2PYJcq*UPNm4VndUcRtwW&^3^vpAZJW+d)BL^6IgDzB?^Q9W?yX^f|rP-{3Q{VuSw9x&y#q}(I>5X4vr>-ZB#D^Y?=-OWv zUg>7U(xG)&(`Xd`Ie7leuavS7HrPp>M}Nb_ExB5(8A>~uGMBV3#a>%tqG!&YBHj2L z+OC*Tn0x1WzN$`j8!|;^fk)*URd3?mv#w8MT8nu=h{xgaLwtEPi(4T zEnK5yo|i4~dejYVrx`6jm^qt!&Ea~G$q+WkqLOhO6XFGL4MoC`NY<`8_^82Lctl3Wr>j2ayt@8-QdZWuU z!4r*Wr8^QUXVIPMzC(8NtSpU6D*XOq}w;WtHmRyybemyFah z5G+JD7-1Q2x_$79*mFd8%*3svbtm;$GG4mo$my=GhQN!o?P7HZx|XRQ80mX6pLadk z`P`lu4TcFfA!d8;>tsHyU;NE`Iv<2EvO+jDPSlP4z>niAoRWq}`=z7Pxbo`S_jjA_Mje(2%EetbSg z!A)iUHB<>9o13y~WVeMPbX=`)c0dQ18dhyP{>_}A^nNxbpp2vlRt+}K&idhf@m7A_ z!RLIiGkH(FIiAgd5hrvR{o2%XHTwN}oTpp5`HjtO-4^4W7~F&dM(vK?@LirgPCQtS z87t~@gEi+UHwU?MSAtkBt!u8*uBVu=?_yJBF9=e^9aMvh!#3(E#+vK)lMi19Cu3vx z{4vD*ePh;I=FzfF1&Bef0#pK!T>E>amLWK|h8*!*Iql|oE_Q~4X)?p^MX+A0czml> z?It7FWq665ZjuVoF1L-*U~(<>=iX2zql-~0k^-y5h!4H1ZPa*9{nMH&X>ZUX{k=z% zP-xLaB$Pj#>UJrm5S@9){0PV-yXeTY z{dG$EJx=R*M#~hVlt$*&?^vHc(i`!F+en)lYPFNJsyCZuKA?lA5R2@qKzdKs95)wd zp<1Ta%V=qHu0CgK^aac&~joYq&vBr_{9{ZfMjqPtXg>#Zw zMP?DS0Eb;*>HlfH$_lF^(L|me*r%&$ z?1vrT;Njq8CB?p829X(@uRZ-mW!-w9ghabjlEG3Q9oCg0Vdv*8eYk1mHoC+`r$RW# za7ST=(_e%FGaEAM;o%GnG@h?$OHA4gK6NLju5-FfcK!njw>_mg9QB)js}>SY#Gy5j zqk4JJ#jp7XC=I<)r2Zi6X-jt*#iaO7WYB1#a51UkOPz@`bHCH4d(+sBJLJV|LJ_~r z3B9ItCaJls(fN?HZ`&ra&iLape$})nZ#c`g+N3|XGe*L_<@P&@(fHRVxJc4|)h{l) zD%Os*dU01}oIqz?HFj09lVu;7|{rL%B`jC&xB6;f9+m%`4d~ z!Y>2=*rqYpQbWsR12_jA3u(P{26|FY)N(4@S(0_+?rDGm2^#V~fqsanV8Qo$^$Z7L ze71`s&&TiLYCv;%+E&NwpR-BvQlrne<~4HvU?m1=+Q^yW)$^#2l9k(=Ps{g9S4#!fUgo*+?2I!XD*q}cgqtO_K;h=E~nju3vH<)wf`K^qkn4YATcVX2mjR6K}yop0br*Z;Fb8i zNb0}rj5^&jwfB6y0CQ|K+H_nFJY4(U|3%Fa`Rw2Jbnf4pdDiqu&hl_xOR(B*afxqU zs9X6hWG{8#<0xy4J|O}&f%`E4e=GBvwg1zqZA6B>^GQ1TDO^CRktaGUvDLfzog2o|hr`nGkPp?~Qx@xf55|y&q}2&oVolVoHDC{xM-W z8o9gEej1(gyz9i-(fXDEjUnMcXf|}FnCLi^dfsgl;TzLAb0f+T+4bmnxzqZ$CvpE2 zC-|1N3q!YC8^}2hU8|mhBpdf$|HbZana_*w+;>$5u_%(I0411g(O6U~6(cBgo8&P} z2nxcTJvK`n6CnL~z8?w7%bVIppfBB(?jiq3@-LbAdn7+%7A*qleiQn{?7}K!)+K?K zuf`zzR+MDXglW4%Hx^pKG{g>^K&yl?zFl|_r9+YoYeo=V*dO{_USp9E$ECb#15UzK zbIhseL+j6c2>ZP_>?oEgB?)nruQ-=B6p>LX_tA99PnpvREa`kX?=mc!%C4K8nK6$+ z)8A?%!bfYRTM|uXhonR~XlW8eqdU}7!Y4FjrbCg!OY?~h`0wjb{`QmmJ0I%Jei~ge zo^DTjJWG=ih#CQ{0gCcHnI3sm4{0{_+F(_e3J~4G=;5rxAQa%VJC+79{%sa77*{NF znEG(x3&@}(ArlK%FIggn4zI;p4ims_ue%$YaBx$QrH;gBhn~+6_7D=`paCOHx|aRV zcqfi<&H<)8!<07yCw%QRxPcb=kp>M~bG2wu(17E{2^8bXkBCzy&HfeX;eUCPCMVZ- zR-hHTx>C3p47%f7xV+6v_RRH~%-%9MgQc%r3QQ@}KXz7tCdvCWrVY&ki&rSnuY+}2 zk~fWtn{*UvCl^(G0$rw5A4@lxlhl!Ynm|8~y%t^~_=`9Y)yH$`eldCv<)$BJ$8~*F z)pHg|>3<|{h`=H*4<`4Dy2%20>h{HG4Ubv7zm%P8`D&f%H!W9lB3FT<>X^j-1iyAAsQ=;w&RF~7c~1EFJMAEt z&g_(J3^yf@(mZTQXaRhB*g|`t-GO8HFyT5JV?t`McG+bE!CALH#hy6f6tV8_oV*DF zgyI4CrumVC9XgVYu;&{soWvZcf6Vx6NcQe11kpVjq~FEOKjx0o>2X%f6+ zJIS4}u;jk~A$THf?yaGd^?;N>CydEtY4YPoW5E+lLs+|WCv=F~^6&$dor+#qw-Rqc z=)eYc;A~aO(&=ifq%3sPrgq3VE3i=f=UyxS3sJz~US(7hkS-jD)0>>?f+k+_>bePf z`^PaFj8b?2Zh=E6#PTF+=jF*-L4% zNM*n8=91GYEG;`(yVd?r0^FP5iG22c7yO&ACXMsbQl6;J%~UAHm`$72N!8X=X7~aY znW?vH+u-{oNzcN$Si{s9vgiCq85;lCa1>U0oL=zqp~N=%vtK&xMt+>}#E(`XXLct4 zJD;r8q-#QI(EMdovIdrDq-OEiw+ZpQ-RDmYpN4&7*1EEAqry4N)i?A=EUVln?})L1 zD8<%`;2jwkdRHn}-`%y|dO}V_7`Zk@0$d#<4SZ@xVtjX9>tbQ;VGV50c2;3otT$c%;s7V43qJ@JFw@nR`sKI^$Y0jN$(x8un z5uYN;3zE!H8R1PYwz@!MB^a!?WaI;zWL^Z#udik$A5SV;t&S-X)QYu%lah8%R-Yl| znJ>K3mdz-Z#BI-_w!F9AC_zZ8or9#_-#T3v`|&?L5NG?GvTN)3wH95Dl+&9m9G6!{ z?!*dx^!BsjdGL!SkoCOrbj21UWl@O95O@2gQ(af@DIr_Nu`;ofs)KakfA8UA2NAm5 ztIiJd7re_Cu6T}T zQz3g^qLr9Ftq;k9-nGwfuJ?rhyFx!iGt}0GMT-Nw0&`3aX6P2tzhUK_*Km1rsgL+RRy}A zA zw7<1TCmPG>Di0u>mmc$cHFCRpS!+76FY^*c;ImK2%e8fZdog+@rbMIRy4*|Xk9snS zJ4Cu&jxU9kmn;t__?uH)bHoe9p@G_H4|(tTn`3VD-F`hLSfgI8|7~D17XSSOFE!HA z@0UMlxvIkB#cceisFTIolGi|>ZIM9J*qBuOuuh&Xs`|iW;oA%{B9K(0jxXXoXD(@M zM|z*iMnn+nPHx?Ud*&Xo$75DYajBBm?U{Jom-TQw%gr+GP(a<2wowI24RnhJ9y-1- zjJ7;bKMcEjZTq~dV`+D(c0Y?vhF2$;b=ja*W9iev*|>(Gx&HH`k9R=sG_DXd z30<#O&c$Wnk~6K{jPsjV_WSdAXL%?uvW2z+$=H)OFnw0I6ldA@BIrL5HlFTS{enA@%*=HM7zQ*j0$JF5)94q7*_#PQ~8yP-D$Bysj z_O|=U{W_6w*)i3iW?+}g@vr0H&bh#aWTj^BK!$B7!c|@|Gn|8kmu%JUe!Hj6 z@A-uc=e21iJ!!L?;Y}Xv9egbRz6J5^&+4s+L9#&-_gDC&MSWCNViRp6GO0K~(6ot^ z$?oB_wpr_5%`#p_L3@d`(VFq}*qa9lQ4Bv#b?ry9@$^{K3BC`iOX4`8;rRp@g3olJ z>9%WcTbr<4vgfr<7zi`ZasFiYJRZ}s)e}{-r>XZcc%t_|(`0|c{qd}*f-Z;nVxRW` zx|N{@?(3Ex7C!!JxEg6WigW*q(YzMP==V?Jd1pIpI;BBU&$brR(CdiHrfoQM$>|#7 zuLRjLpeqO6{B?2^c|iv^)!>2av}RC?&19eWpt*kHE#kdS11LW`RQ^o6l`pNm!*y1O7E&xu0`sTd7hl3uxtyO9@kNd94TlUSDO1JkQrPJ)y} zE8k)n)=#{VO^M zT0zi0FzKQkH)sQFYEIL}GOli>{l%&ElwPtKg`uY*3A3G#3bWuMUtumQ2Ffw1Qmg95 zO{NJSiI3NK!sAlKgPvZJr`kE7YN-vx$@_AqiW4m*KJvgYBS17g)a9V6gLfpeK9hK> zH{a!a(D<>S^cFQr?GAUE*hx{=2r2RtPwP7nXOG4;;Yd!I`jAJjj1)ErSu=+-eGb{L z+tH0)&}dvQCdVr3iqh!LN7xFAP+aW@4R)715;1&W#AMIL8I`*BXM z`ZXyB{N*#d`I=c97~qmqzzobi1Q|JJP;hz++`k*XENjb&;~WYS=SFJJ0b>^NxzHz| zB$^wzzFS<%bv9rz5>$-{X?08EsSPENHSOH|{O;G->cW%Do)f zpOfmYQ1cu=LiG}b?zvRMo;iFY*ecl?hrKaJ_zr6%97kcDg>mOl$J3UR!P@ipM1N9S zbvR@$Lyh5g0qn7}!W8_axPt_co~wu9+r{nJ4EyeeMWey;>W=(wlF{lGio0ch5u3X_ z+ggWLgy%aRNVgg`?e|V}9vNte2@c4^as&rwn9#bi^vw>CC00t5H5FaaB^Rs!=eOo5 ztleIMD?OpFXS?25yV7Qj2-mc{P3fe3&?uUvo4_HasM1ECI8ygD6bj(R=iAhiq1seci%CpO+cXMinFd`e%BclRDq3GYN!sGOaO=t7d`1N0 zk0{?wP^Xn@g%KYfuM0tUn7+1C_k4ICO!SUCCu(Z1hAVmJt9$#EXISfH(maOsgx~w`loz-|0Z7{atiu(3*O4knI_blt4 z2;z@VQnSkY$b-oTTC8%TV}hKe)nj}Un4rA@vkf&0C^wo!S?+DbgsqH0hH7+rH2xDE z=Yfgxbnz7vg;Y`2i0tSn3=)y#5zI8Eo=lC zUsX?qfIedw>(^;+I}EyoD$7aRsXxmea#bf@bob~6kUYB=4+E`I&g^>L}TsW15h${9I8s9~wV}%9uiGJ-R%GbMQ z|E*eD10u_6q5G*u-h4F;0~sT}`yrYlYm2`?h%34Xa1j#`Wto+3TLP=hg;mbNcZncKU+QELmN0VOdj~o3C zpkjbWv-rcS8)m^fbv@a2MV&YogNS>(IM-DBi~ zhhXEP&w6v{Zf7<)Fo)GIUvtPEl=U*SCV#kEfw~F@h;)i(p8jTKUpn+KqWj_F3xndl zcNb5*<2hytnQebsW#K2=W%&Ul4Ue95%qTk*&4Nh!*{)5s&{)hX9)ML}2_u?9nRN(# zpsg+F3tohwK^bkDGe^j*>fMibh_4sGJ(=4fQSl>MiY3ZxZGR1WAo~u1t9^K@`!@Z$h^T!QsI0RZ zab56JKyOMU$#2q`?VuDIB4Ltx_{6Xr7Y$j1sxO7UA0s^}y7;OC`2t2;L@n!7M;1Qi~}!Z@eP10lr>JkTNXV#|9ui(Ftm29=v+AF{q{Ebx5Alap!LAtXnH2O zysGVE^M)7WgzyuS&s9=RUpq<;^Z)vBm>uLQ;$dVzO_$DzG)p4tbM(yuCK zgh6tRKja#b=dEy6i$bj$BU29YbSrb4HKmJmL0UzTa*dI?ML(<>f23R#=vMZiCOSc_ zT)&oUpm~Tk>WZn9=E6S2K;ZNx6M1~aa5#Z!*ypUvnRx~s2T4PSW$Zr4e}0Fui?riC z4!o-#YQ@)IV6QQ01*i7>6yd$~Ete(;eSjWm@o%D&ZG=@euRQyfOZ*><`CL8P1G->| zIrSHZ&6~q6JQE=+pv%20F7wlB(E?+A)1`PR#P(?zD6zFrT0_7g20VQjEAM%dTd!RR3}~6<&Wh^LY2J&H^^CD=PJOCKXAhlfM+x zb+_hyTm^eGbe8)ePL$`AV$wCSifllmY}6oMDD7qi5{Z$9FBO>I8<4sA4Jp+P z`jAFxohq0Uaw!=ofzd=Ai)`E~Xgi2cELnPZdA(0eSRwu;m@$usD(>g>^6v(x^>$N= zrY**%2g0SWHSpMTtQpD1%hW!1n|(JB!n&@NlL3igCI#$NiK(iF{_%XWNfz?~$pSwiIdIV6fIbL5!Huc;_c{HE z0?R?86k>*j8%LKUf|V&PrLGVUZ4Hp(b7OTlb>VbuVm;s8n)xmTFMR?Lb%wc>BLG9& z>X)VUR{NRaw5waJi6prpY$JGVJzI=Da z?CQivkVe4?LOD^9Ci6NW2*PM0edTUI@%Zy- zfNhcP2UAdhIhjtM6DF$M$d-ifPoJgC=n6fag3zqafh>XS(wv2E@<-fS`{_(11z|;w z*X&dP#wwcuhip#BcS#AH_93cnCZZAbaCQlq%Dwxn?NwfedRnn*~QHlJ!lN{G$*3sxTZ`O*hqzcnXy?Q{z8V2~g+? z6jWP?l*r(mnp;}jWy76NGZyRL*S(9y>D*QQ+20?JU(@A>1 z_jj}pD@-Kz_owL#%n6ZvJGy5B7ZF%9zOg7wwOXs;n9VB>J=xyd(~!3UlNW(u>*|ib z&q7n1akONe9wP*c)nj(HqCg7Z~t=o^oc?NQ#sNs5o)PqkKx-Jx_zAUj02Mn zV(m|6^0M~jo5rWUi4aTBeDw!qzm3$52+~V zt_wrc#nFV9K%FcoRkV$t-hZu7%(LM!t*wnoq&Qv4CBd3YMS`0Pljj@fJLg~=_9JLS z`mm{Ac)0WX=PA76<1VRADXn*z5$-$7*v^@3+y0F&67mIsNnMUicMrRyywBkUm;a33 z&gU!#q5-^H0h8M+*46!w2RU(jDSDr9>(&TDVdaTFEp3B!SfOqwiAj2*ui=kQz9FQJVn zi&X#~Tkr3}E+Vnz5_96^WO|pQ3eu=!TU&A9XhNqd%L)5|&AAZqYU>$IIuLDv z>^DriRhZt>r>34EHg#lb^+nDA@Fn^z&sJ`-)>L}9UhbD;;aBWkwPi#LvlIJmC9eMYS0U1hIcy($4o}p zXu1TC@qkn)AGzW^Rc>P{_cWaDOhz-u5s$(c(p*=r6Qvn-SMzyWr9zJI_2145R>Nnn ztD@Bu1f-tu8!rmeuuM^#D z8kfBuu3zHt1TS8aR12iGuSQv=RnDdzKY=Hws%GokFCXKxxv6Z|4W(pZ55T5hFqO6WBS2 z(J+jdw_Ud$&li0slT)~>M_kX>PwO047vF}4ag*ors3DXwVMnt#VM}xEB?`T%3^a{l zqE+~0dKDgRZ>4fK7g|xfbn1TE%sy`#6XIBLu#wzP-)ff4rdPu%E%PJ|o8VG<_xrTk z1#%`qX0tCa&7wo?@5Ib8YA-(a(d=$0XXRZ$=+3jdosNNI$@9v725l*0)o#RJs!)iu z$SY`iGI1KH_wsM2aUO{dcTlC^;5O`Np00uU$nPIX6`&@DOEWJ;dP_zYJFyilw}!9QDAGS297ePug)8$s||BB!K z3l%+C`>zFwUmC0VyGh{wrM6!T=*<>ycX^+8Bi^say}tG()Lk(!?%2l(US%(uSXiKl zHB#`czX|NcEE*1G`L`g7yi6DUx>~!wIL`L!jLpl->)GsO>*I+Pf%o^>dc7}w}4O#5C-OqqtpiT%Q?+nBZb{X-qE#9Bl)pUESm8fm#EM#`PKkwMNZ#j+3 z+1^!G>i>a+nz?BYdHH11vMt36b7wnjbt5y@wf%=e-Fq`nyW7urIDtL27Z@VJVPrli z%EGTL#qzaRo1d59;Y2!a)R;PNX5`xl-)^tM+AjwGh!Wo5zJq^t+qAEp3wC^TnR+wU zF=@ojf~4xXQ78#N7T!00`ai$T6@->d$$p)M#pzBz07p>JoerZGxMH}O|Kh-89 z#o)LiSXHkgIaTcGUz%k$xlDUhe3nn~DhmZ(=*Rrwf+dn5n@*?J-!|vej~Ua>K6ulg zqI*uw3b`?9q2zT|yHENj=Bq_h1G^Hcws;2C*Gs@FWhm?T3$tp9YPexcBIjcm>QoEf zPi?x8F*wU}nhD&D`$*Lgn-ZyGxmS(-+8bTx%r@)b42nZOvuLTC*-afO(<2)|L4rVM9#<(mMAp?I(L5IW(iHCHj=9HdV>lf*xi%ch>3I>u#KqQGoA z@=qYQ2R(9>QJ1nKhL3xlzb{Y#!>3*oadI@5JDi-YHOtwCPf{DO`|}}(%bZPNuJ#zz zTbyKz4OH?H~T(iCxl-lCp+5LMgxl3FSo#fWO zBl==?KQwfFOHqq?y8o(_j}o;6aw=BsHpPzj9VH(YPqRi$-{EiV(p6lL=PDm$DvS2| z<^M6iB3vW*E<6j_wmwH)#I#x-0HTUxyVn8b$rgHPOj$4w=h!(j>{FyiS)6b{yE&i> z$JV1>8hvZ3@w0+pGxoiLN^hiTuX(EjNR0^+AD)tms7Li?Anm;=PerbdH4M_Z)GzlbNNam>S%PB8+BTcsFSv z;=(cR_aAHlqLE8{nLYSC6Z=li#_8pTcT`6rqlSkyGIZh!`}K=}#{5Z6Uww|d#+79x zZ&_;flSt!hnMrZe8oKo6tWmS|GAE(?Pa@8K_1-~segv9Hk+xdJMA^P0AS#4EdB}cw zCL|>tY)IQvp9ycHt7ep_>ffJ5xi|UZGwTP_=6E2S*guP9x()zZF0w^-)N@|YH1#b( z(EtuS*c09Rd%8dsTMF*H5%cfb^1uZ9YU;?jnKsSovN8ZIzG^(RSfSg~JS$|=@Qtw# z$@}m$_A{I9N03j1TjHoW6YK)57N4=0A_*RL^?-jM4=(q_`6+i6Q!F2 zckRB!&ifOtT}EC#!Ngu4hw6Gi^I4?nkZ+?Dul=Qwo+ zH7Q03!`1q>6MYB@qvadNm8FG_S5teJ8<7;B8k}|Sg4CAWZ1pMpxddu+wYOJ=LVA4v zG*$+nh;40e1!43|2wa3$6Z;a|bv@!8On{>Bwu7;i{;*cHf5JELVPDYqw)G74>9eU` zD%c<9qtSRQJRhrT(i~DV`Nj9kJQHZ-&0#0I2nMp6L&b?Ob6l$@uTl7bl6F8K!DrrQ-n zp?JrUIFB}`n&6!wwsceICay_N!sQQz+$&#J#4K@lCOlcaaJ6E;YBPi-r`rZ|?OWUF z4VtLvlL9fWBWn_D%OnZ~J=EDN8}ATH(<$dPx=>UKk1q1=#~1Cy0W`%Vr!hc&)u3@X$kQ@e)4H*z4=deE5ELA}u;pYcAsMk3#Ath6OIGaheSfmB z1yI#S$@cQYRN}qU7Ib*?#>RQfN&XfGA>k4Jl5!G=B*j$I2A8asl>)^}BkR?3ix7_g}BO5K64>s1q&A8K0 z9uIRY9xE*@ZDs1f1$2Fg=mc@2pfDO1}Jt z4ijkj8tI5w$xiJG@}$a#Y5H zTz&PQt=p{Wi8qSMJ%m8@JKLz!#ab+1+^Dziq?af($vOJ@0Xk9Dn=2v#x@=T93%Y{j z+@lcJydpRKtLQdf`-4&FL3mA-hNqeTujh63%I{TUB*$*&(hU;7FPF@NWB{50bEHKq zGxa7G0BcE8KUNgIU(~l08GjC?nj2D2L07`kucY>OYQpkq6-B3_cZkP-Qv^%oM@l`i ztE_DybsOzWh-Fh%kPJ1x}VHPXkC%-3Bd<6*rJ^A=a^7f7wO}~#%NddXgj_T zH9lL6Yk5q(K2Nq=%Myq4s$$>I;Kp+BDNX}Ak##6KJe;7;U~Si=tji#$@u>>l_gQ;W zr}>YY!3Qff`)o@KiN=QY9v5uYlE_NYCRXFmR7w3mYGPWw zHk{5lvklt86TiH^iv;Xy(zCZ&&VY?JQ#=l zJu!M)f!sdND)S+UJYIqy4krXGm7s&@jNu2K1bkLF129T%+GOxsh-}io5LU84aEj6E zrq^A20$=^XR)f7CHjn;OlsKn@mOD>X``*2_b*JlmEubvvXYL0K>xxc1O(Sb&V&A`u z>@T-GHOu@b{Cm&OC0g5h2bs6_^Y@9@tcL_kwNKgJf6Gg9BH2v_DPjXps@e#R-mX-k zPd^^79lPDTGEC0HKd$JH_$$#UWrm;lIWpU|9X0bpf0W@%`MSRl0W}xI+nqr zQP-kb4`ua>u5p0dl9YdaL=8#)x>X)Y6K1mnOyAwADct)x@40=xPLx%Pd)Wzf&3E-| z$vnr`0eV#O(mww7CjOUqpt5;=&!>5Gut}f7HBHEzCEJ4mM9A8CqqgL8ttjxW@PC{w zFeI9+W$1~!`Mlu%ubjwEdqxBPpV8;F>1c5@kZ~+m_kO0W%&yn`Jr_f~&F4r5Fbgn3 z@?@G$6yJ4`C17a}As`|&@#=*)?%3GQe6(-7->#3MV21$<9-QF9om2nQjo5m#LBWqI zKcQdk?%+ejfr`b>rNR$JePIuWll=bNM}fe zDQD>FcY`qfHS+z2TrsHjiQz^i0s&3&GZF)cRXhETZ%SZbcpb1YRJxPMd z>VP>lci(j?hE8#m-z_dw2$EtE=d3nQL zcO|it_`y5#`-s=mO3lsO6jI`=D4BL3IC{R>=C1p-GuC-zx9@RL16L9rz&zHq8^MC! z0gxTW8qP-C(KUMqn!)-h8;tmtY6)TPWpk&K6Nl77D@3a;Z(JkMVi4U~xLQ*gf9TwC zOddufye}4<4f&aWkq}2W`O`9g<;;A#-l;;|mTpH2k2>npQ_Q)Rn_#7q+zsJvi#b$U z=Z}iS_5%*eBB^V<`Hjm4cOB+S8hh>8zj5OFX!X$T{Fd|gmQx9}mETTL)Aib)|K}ylzeX@1IJ9YwH(rn)+Gqfg6Y(y zD8Q*#$(RWyo7`N@W2&kI+xr=QQPwa%|9-;{#-LVr5W{SDhBHc!**Tb#r!epQ zFbB-};YsXy3d;@4bZ)F*WNk;wu&L(~uG<;Jc^TxU>)}_oj}lLZ8RY!D`f(@RN0}jl z6Z{w6OK$CFE=G3V>~|~vMuILEoh?Vux#g?+(KT=QZ>LL+k&@_w%A++3TzR!05b774 zObQ?W7WtbdXev5;aPT!aapXtk>Q)k@XDI`eMU+onjRULRCN4_cvKZ zD1Es~a{hVXuww0Hm`ba;<5s5L0{Kei0h&rdEwZVK*btq_NmybPgrG#m*)W=ZmtSUS zMFtedpF;MYaYvVhxs)y1^*A#yCv#UHcqUzc(1AFA`I*NjrZ5j2w90>ep-<;(?J6FN zO1nmyxgHB>C{kX@KX8OAV006)8}4M;A#kPlH9W;TMg~*wF0<7auW_4cqt27frD<^A zB{_e`j!>3LrAc_bIUBm=g1#Gi79EhBzUN|{D0E}v&fL$yUqH>i9rl=_AyNUR{#{YK zY9A9b?h0S_qX;WV$=`m_{q8-~UGo=7Past|>u&-)k&l>7 zf)G#KtS8f;-&mLxeryxn#jAd!baeScI+KI25H{=Qmr{Jy(yui$Gdnd4x=FSB>DGDG z_5=+7reRXWUEW~ly|f19;mE&r(^r6vt+_(7{f@w(Pft*R_ipdR0$djj@H1@cK5(z^ z1}&NP4r_ZIeOT!tHi;5+7NcIq=8cO;X^!R$YD-Z|8G#Ysb5{BDR$syc+}cCT%0tMb z*=&~BqT6oN9=hTJd3!{c7CaB1)~X8yrl=>evM3dV?i6yML`i-pm<9&d+R|jbg|C#u zb`tBxzRFDXTa2SfHBO8toRGW;J}?TklLV^+C?DWhYUSy z8JF-i>48tT0zJ57+4+M<_+M80>uyaAy*tYh;h-VZX0QN;dv@UamGeAPedGh$AuC|b zT$rMaUrAMER9E@U-o!3FcKYGx$81s9qgRsX9^>vv=APSGx!<4C#fhj z+u>(G_Owv;I%8||kw8)*2|Y*cXfB^5T4#TT5 zSdp%ZmOtuA;;^4TmZ9r}mL^GeJoDveNQSwQZ_XZG3EF+W0ZfAN`AP6pd-czl9@yr0 znsRIe7=spD14!Fmn1#x*Ggc~E3^7;rExFJP!*jI}DHxt6!3qT}oR z+R+$8qc_K&khNw6(D*ez8lSGUL_B*FURu&e@t?qg(3vS~x-zCwKTgnQ)qUPwN-z-7 zJ#BUKWUwXwIua-@hFNQPLxzn@ncUKlbR~AWA z?nQt*sd|GKylRG=Y>u)(h=m)@!r1(~5!N0UQ&YSkz9QGGi{S3(M_%Z^-q*b+^YLGS z>JT>y7RcSfn-k!Iid8u)(${U3grit_@?`hvoMRlF5-jwQ z$L#&blT2%Q9SVYFI?Rlt2YFNS1oKEXyF%wYatufE!{l_WRQ?t(29VWfdgfD zpp}9e{X6MYG7HJJeNh_q6k}9lfwDHTcJJ?E-<%BvML;|3i~Rnxjft&zQ_s_yyyqj4 zruTE7WYGPj_rFjiJxS~zE8Sd&%l#1iOFA?J%@^Vr%>TCfx#%#Ic;*i!1KbsT!h_^y z$XYd@`$qzFH0!{EMGab+8{z6bjta$ilha(k{S6Zx;0C%ps9QLR$mrT;((mH<7Y_53 zv4TSEcztKTPP|+cXMKcy4?5zWEzmx3czzASk=Jh&E-Mw)MJS#D<8Die{}u5*3cGq$#+GDzAdE7#Ga33F6%hl?sypv`j+1D> zOI>}hiTsr{r%n$emxDWF_P&gZ&2Tl{$UHqOC+)0ir+y=k!?!X-t=}i!(_6o@eea$n zZRS_6ze4Y~yY~x&uS*P|zVLh$XS2xx z7-h}Q@Y{3=Xeu1lIsMh_>tbr2HLKIvNzKnpX^Q1HzEIFSp#cr1`1d9{{%Fj>j@0<0`wri-f$|?b}=_ zD#|pbcimR^V{N}@e;m2%XZTyJ&7lA)#7(zQ>kLu_CC7okr*_H9mK5Q4^vqe5m zn=K0Fh*wh*>)Lnqjre6RyfO{!lNRHEt1;8AyC}tJ%FV$ahX=l{X$7yJb@2Kw{`irv zTH|!5Li=tb-~v1`(=-wvHz>j5sa$;=n<(RF*@xNt`MZK@mQ=2#bMG+aX%uGd|1p$S zvSP*0Ph{E-(IAW}rSlO$kXb{rvJ@sjyM6B19n$|a?u zCc~mRoi=~4J!Y=DEi$Oy5l(G;0^T}}dE<+|r?C(@ys$cM)k#hH(>jGfw)~-iQ&dPb-gdAI}f43BC>YhV|GUWXt2xproyw(m?dj~7w&HxA3fiV0!ME$82Ofp&TTI;#5 z>yA7Ug-+cps~k_}W!}--DG7T^{%zwM=i;j~^@P8bh^k^O#s`TZzM_}2&jm6=iiGcz zWHcC&n)V|{hE^!|F}o6)?5X|A^>OF$&CL+T2)YEUxtIYuZaf0Kw(R3LdUdw#c&mM2 z3LQNrOZ*GqYWTunlgAtDpa#ifiDHcFDgV@YQ>9zUC0_jP?+Pf#6??3rpU1hb64~i- zO0d)o#WnL@lZ0q7y=IzmVRX6U_4e$vo)BP0<5^U7i*&)*d^iES`&%@+o z5Q4KdA{W1NHbraxNFqPvh~O<`8mvT&ttG> zk=l-Tqg<SYnZiE$LRk2h;K~WSehgc3v{5<-?%Q=9=9XX@Mu0EF@IU`!o99 zVOy=Jj;c!W%(q{B=M4~~g3=p)AV{Uk)_6ibS+B ze)#FB(eX%_1Y!+@v40}6a_@Jhsmhc+r1$)l@VmhiG%z^a2zFVp`?3;%Bl^MCilJGlv2aJZ2CqL|aN z=LxGPNimqP<#j=!e)P1%x)M7G?Z;c?FjF34rWWa^nuB?cwweWgiJ+ZUL;xh+XVzX4(MymikE)PY!oN z=w|ubCU@)VQII=rw(q@dI+usT_1hE^qq-nnu=g&>H;<7IVqULE6hYX1TuKU8SeGlj zIg^|U<^wX=65q?U&ESUJS8_G+3po4+AN0HB0tGq?_KUREe9PyLgf9AP zx9|Z0cnXvrZz;cZ=kWe_gM(`>Mhs~`O}m?bP5jy4Nj3b$Z`#@K5hLo>Mv!Ly4VN-# zTNA$0L~}lJb99}53h;rRxNm?v@*VH@{sVMg-#auL30+5QxsNw18Pyha$H6|^O=L$( zzW6bm?>)IGNp2TmjmkwF z|2xzV93pd5=^K;)W19sW5IBPm!tNH5pj%{&IX~1-!DJJUYrfo$Hh5K+V~x;%M~t8t z(bM0J*1{>Dkx?nGkpoVC7IOPvGl@Nqd7jzxuC%k&Ge-Y(PvDUi?T^y(fnbYqj*FUZ zBw5CB$n*K2j;SBWZ_5)3+kKVbwND!7Usa00aUdxQm5-E@#c1GaQ7$M~@$0N9DT@+g zRU~tzBQ31dLW!0oWyq{^r7_DIqc`|2QCl)QF8hOLY1tO@V{aK=k#J1ov-J`27rMM? zDIGeRPK<{bTxX7k9H7n7wz7^M^`uXX-ZcEdQ?_>d1 zbG-2B+0=!3zt^VjrDyNXOVY7)lpRMz#SM$FPfg1y^69!P1#hXiCWhYtyDvuh`m%h`PmdTD zmzclB_Iocf162##Z5TX03N6kPFOEF{?qIDJvO<~W4`~$LL|r)z=1Npq5qp1SRU2JF zY0~E;bggoF2NFg1Ey7e|4;su??n6&wD?7qrk#8o+7kde9X@4`v6zt7=lpaNa9FC(` zZsuON?zft;-hKKw4ok0=`JvYI7?(pUtQT9*zHlTG*~SP{(Qa7`LN7+^B5O^s_B2#6 zB)lo6k~JV`u=V=%(HKap&!YNswRV-?K7-n-4{zY9%0^cUz!-OVbhkPI%Qol_w)~TC zliu>-0!^Qj;N#<>#-YyW+dTe{Jc$CSX@|sswZFVkwgCU|R0hbE^In6-0t>4(?j-

    IZGy%X^9TX*IZ@Z#cg``i*F+p&dy;K&v=HZ9&X0XU+_P z3V)xiw{aK8ywHth#@yg#j9}lph<`$7iP8Ta1N|iszjqI&*Kcvz9Sf;sl3Py0!R=Xn zlgN29Ke@B6>rLgl`87bkUM+2zo@V9j#(W}Fo;_c&c4%F*IM}GV1QA{LTRJ=f^zE{K z@cfQ8e)B)U?K)K;vFF}|_o1M}tnPWzQLTcv0X7XCz{^!E;^}(*K9&#qiF_WkX8HRc z3^IrvbS-1zc~6iovh&BcbNzF%md8}P#+;E8dzG-Cw_!L3E6P>lBf`ex@BJy|e}3I{ zr<5js2gM6s)8p<#X;?i=N42?Ts4%u3UQy3x)~|_ZtZfrCEHc)#8iykYG){Bb9m^44 zZ)HbbHV*iu+INo^BcM%8{5Qa+$OdS`Hi-@;eCwA*_PpIOqz4#Kg+ga~X6$X$hPWv2 zc4Js_ovP|lTp?RnCjR%fcXLXNd%-O)Uh}34c1fymUODGG64(okW?inn>L0>GQTAS+H3&MgJvE`M&uWjm>|Zt`8A1IgFo_84 zlR6|lGa!HG^%MGn{|UiPMzB`(ZGrrDOOr1s`yyO_rFoJ*21}M35BPcDO@lRDp6ZnA z;f6=|tr)SNU{5e`p!scntxE2=!*J=sKM(Jst13iFecPJbcgxPJr*bX_rFldl!N)p; zN*SUjJ%&?)t^k0ID*T_`k)NAP28c2zjWOC)hr00i{yZle?g}xpT5hGK`IZ#8Ar_zf zPS62$a(eCim~R`$;6e{?2o$1yo1PpxS?%lelDz#`gR2DW&{J~FfBwiObWTQ0Ox!K; zND(9I3PH-TX*Koza|J*9-S2^pehYS`O$l~l0|El{`6|3S=Truo^>2$V5TVT>BwilD zLwY{c8Oor#aJv%%>XXus53M)Z97`b|rLq;!OxnRXmgBH!i+U?-ZzjY!gB!bY8l$dwTNpJLfTCeC)O~2~%)& z)6U0DqDn@mUlHdV009d?h#7oRy5hdYt|s2H;8@wei;mxQphU6cQT5`MgT71g)>dN< z1(pxej5NbKv@XiE$(?Fr4Mej&t5OOo_KXz>(Djnj26_*#_laMGwBY$*qG|X24K6y% zLh?+qXF*afc%^BOk8sj^u9c>Gh&)f@pEXM6in)fW9$)|K(?j_MJnQ3DdS5uMusb9F z$M&E&N1;EU>I^9$=W*tux!ZP=dO5*%uCa>SMq(Cwg-3M2w_YvonUdGt0dSZZpZ0GL&K=P_UyCp1T!s? zi?3mt&6mH=g7Zw=zXb&zGK<}8Yk#|pWV{X~DAud|H{w*=(=f{UTC>FOv1psK5l80s8&Po0y|1HP z@Nvws`aSfj-n}RIk6F-C+g^d&H}k{&fOvYeH{|X^JwhA5LM|?qIsZX@*Fphjo#?RF z9Wx9}EbvxW;b5Hf0v5NY{HIv4s#@$UmlhK6O|=^{`pBluUBouB=j_$R{2-MAt-HBw z+T}VU_-xA)l`?`t;+8wx{n+F8jxWSBkcynR+gDu#8Pf4jU+AFIliRP$J558?)Q~R- zQEd?>)%oy*-nI_%zt(()Az@3#Az{68)O1~wF};Uy(sRPEib}Eq<40?sNKWssfTYf_ z2d7%gg*x~HZEt>~7PkH)5+Bai^0V&*>8gpJh_-ddQKOwdM}woGr!YALDRj!raRxL0 zQwOp8|30x{#*3NJ+HP2Y*Suqy4$FS|tY)>I#)XVdg|1E)PKqOPN_XG#wEbpeY3XKp zX>Lz}oGa-x67~Xq+Us^2!gp)%gD&6axwzNHhRFw~ZSdn{d{A)5>opO1z{Z*ZU|hF; zGVG|3!`buoUE6(22RCrQd9Pzt@}~TC;p%^mNVZC&#%|E=8_0v{zq8kG+nQAk11O=8 z+UDD(yW0$Vi^}R&dD>IX&eP{qxCaY&a5yN_I#rSTzH15*b8p*9KFXr@8wXFi=679@ z<@Zlgn9}4p1k*guA3i{p3k&!}MyNMvkXl*4iWjrJ?FDIkU+@#O%8(PYZQ-iIm%aLR z(LKY@v+m@2EHsffWIDW?f-Dk)xe{^Yv)|zKcGITP=&gDSFs%NQ$1-WXg8@nrhbLVt zv8Hm2h!fN05Ov~P`d;!X%@UJ1Y|jDaL$R-~YFDdD9T-b3w&_wr*nA^u*(u_IA}=FD z`4I^dij(Ay(|NvH2QgM^dzvDsVuob8S!pvgKOzTMMh2YXXBS%-FCqp=#fhFLaT0RV;6`cByIo(F?IcduSVk(M&%`>+#{4f=tJ& zc?6ey6-k8YqSm*W@-e+C+<;g}qL`@{$fGo8Y&y)rh zrytrpE9AG46FdofU9NL%?fdLD`^7GGi^oIgaP75s zVYzq0iYJAnzhjmjx3Ff*D`0_0Cd$D6Brz+TH`m^&CRvG) zHC@D*ISPF14T(E3%im|6x6}AYs{Udl991sO(-nY}*c2ZCm7_y~2=F zCOQd4_EDN}RSJDnZ8uM%@1Dg>HI-SmcLt1Op0@deI3e0TO=w`t#6pP{1%hb#&$zZ6 z)<1H}tMECYyD_`G!W^*YdrtULiQOM_Fl}iYFAnQBB+3py1yRBeDqL}%$F4v2{s}^U zbNF)Zcs`!R_r(jn$#QOI9$@3ez1o}TyMl^^*n$f&2~~#xjK)&tIxc?h2`_ zrpE=Lv6!$U5moE9z?NXrcBJqi=ei)0wPJVGkY0%d%?BBJ&GJX6(KaQO$1Jw@L8SXzXZEAiwML^Vxr> zEPTN$5uOcrxL~Ys=Tb1CuEaqNfX{ z;-kFs>aHIx0zzMJ>9>gE|QeA1ijYsRXJ9hJl% zzer-!JHLVN*1~n$2jGs@mHW^5oMJa0?8IMbKmnWY$o=~-_9?(0eAaHZ7WPA^`U9Bb z{bSikalAyuBxmQZr7;;03tTMe%5;*{-9}`K6c4$9N>{GvCJ*P%x`ATU!4k*%3>nFm5%GK?+PmfE_yXib7~?gH#*Tf( zK#pa!gp0_UL}B?=8fy&#NoD1Uq-)FmO~v?rL5jhT4AoqS;Xn^JldlKOYV4q(e{$qk zv(pF&Kww|pYan2&$Sr_kb#*m@Y_#_%sHZ%4UM1MvnulJA3KpHX@!;w{A?u)VB7+c?RVCye3S+WP zy-1rJsQyJFJ+i~whEh&Niujjt>(71mm3bBnf!#9xul=Na;2`9Z*ztNwNX7P%LIXxu+Dg8L(vz;!hqs3++9hG$z4v zW13{H=X&<^`U4kU{{@7mgqHx;}ZceTidb1(PsVw%p@c)kW|2Ik;wf*09 ziA2X)0=ik*tfSCL$9hj-?yK0^$B>ed$kHG(GMC@DYLYuPoiB$@6K?;t4EauoU9DH_ z?N1hZEE(Ec&WroJmYzPY^q^X{s-OJ0_uRFDh)X`Z&Hl@=DNfNJC|`fZYFUKO>_kpr z%IzH;^)F!@EOylI`SXIOSh4mWI&2P^c3wdN&U-oa!-U2g9i@R}`lf-QJOT(x8e{5^ zSDB}OFu|>(z3focmepNgtvUJN@6D{z&B#YecQ94!nCvld=HJ3?AJu?0_A|jA`71x; z_!xW8H#F3MQ}i=m?^nhhcr@^Yc)@a~6#_vj4ZKPZYJbYl|7h}BY2I@s*7Phg5JwT+ zEVx=^qjvYISkrMT!e*3Q!ipJ;Ao%)F3NLA5f2D<90jc1S3Qel9gIv`T{mesz#{fxb z+L+dB?$BZ>RWw|GPhft3Xb8U+7j>G}{#j~-XC0%(;YMhYUe>fmlp~#Oke`Khb ziH8j-N?>bU_VM?C_Hy2aknlHC4tt-$hoYN9x|sL$g{XyPz$7PU6j2 zfV+~Z4JQ`rq#o)vd86iBPzODw3am)(2haew3L4+*n(ziZmcnB)XVK(|5Gzz@V<)g~)*tDdp&!;I? z&v04sC2~j`z#y&QX&NQJQ0phXa^bg>KLd3^TyHX}xE$2BG%vek?OJxzMXu;0qCdw! z`*R%f{{5oQtek=7MKhG!k|VpvThRP35c-&|%p1ouhH}Yvpd9^l39MYlc=NZRPoshD z<051WZIQfd(Z`*AhLFgEdA@Z#U?Y1x-?S}kOQ7@nmv50$H?dn9gUe&W%kvK^X~iFR zP&d{R1Kvhl#!wguEbpYcWMnvlq~y{U>+7mMDLT|1zc}HpnS{u8a5-j2xOsm|ponZE zi4@zQWXlcFjId)aAN0H)R*{PNC0`$yxm!_KPK5++)F>5R{)r;QaXxAmv0h}dq z{3?W`k(_|w;U6YtqA4Fgj*Ffgm?;8ni5&y_#I%U{)#!?4^TG!%O4^$;A*~uByv84R zd=`YAXxADOPTKBGn_j!wS;L38J#{%5Nf{cUz7}pCN1LZ4;3a>K5QPV4nqXCcs@mgr z8%Gp z<0*bQtxxBS_q|e5-BXd$w>xN14@+fr*zRAP%xZ>AnRjFgm5cp<|NgrA3h>~8pX`sS zIymM`-NdUNf85k9i8xt@rs#dz27DQSmp?=dk;ll$oiy#lEaQ9GC6w)(G22t`-U|4% zwX82Nry??p8Yz*@?+)&gAM*ehfrzQkoXyr7Gpf+IR0`aKMHh{Xvcxk~zqs3PHH0mg z4V;cIb}iG*`3qF}8HMLld&{2r4Z&qEUm>Lu7K`FMt&WPvY6bodwcP6g!4Zs0b&aYv zFy?44HKg)@JvTY0dOydr1#xBqkRYG>Z$oj~u=wtbJy3Lf&|%WuVOo4II#gnB<#iW6 zy+0R+4XEX=P{3Cm7WsIJqbJak&L3x{CDOiy9*EyW1&L47quUe3i&tN)8VT({Oi#xA zGsae$DN1Z!@VM_HE-`uUumVN(yx7b2%oqtnLNYh!y(m|U`a+9OgyoQSQ=52NiFM^M zr462rWJrC-H=pneMz~Zb=ZJ7PA;FV(G;c|jEHAUO-%f;-W|1`tlzMv}@UhC(b8pWf zo;BtDl8f(a(9Z1q9|md`VQW@mhWSuSGEOlVIXLi|Tj*KTeshBpxXA2ucmD6LA3if} z?UIO%6<9OCO^qn(3Z{0BUgd4C7ek=N$)aSu(NOqwv7sGAb+@hQ6 znkv0&aZDFV_XMfL+XSKg>`Hv@@tA+n;_IUR@@J3R{6Xx)&(|SpjYl4FebVPY52L(B z^c_H2>rV1K#i zBs2U(N%&ha@DK^^pp(c`JY<4bIbqAOm=2Ri4!@!GbhKkC^I$e6!Li96j#-S_w^x2~ zt!r>WDZ8l*tdOm#O*}>&dEiMtma+MB{%O5U?a0J}3}xhNW*qXfX?>l{dCb%YA7mZ@ zog?5xqqbypw&3uo!*dA;z)}2i?$mcc-6F5GF*{d9dj4Ye3Fz3o0}eL_)IHCcYH=NE-nQc3@vwsFxSJQ#`ChWTsP zx_PxRqb+Nu$GL4EuHe)!5sf(7K1ti;Qc$8>8)y^veKRARHM zn5e!KT=?@(WQ?fX0oKVq9ng*DOkp!wwYJ!QNp5y}j^0=U(z%yclXtptH(jy|la?)E z_FTi*D=dK*ra^_u`_c9QU2;oTVO&%(Nxtl{@8?5APtOyfKZa~Se)*S=<7UjDt3qgW-QVl-IDNU1R3J;L_^-2|H=`zW1p-JebR->RIoCJo z@>+xP#TDQ=d3yIl<>z8BRxXfjkB5Y8VvCZeB;!gkms}?~f#b7_qAeoy{+WrOlr0KO z7Ux$}WA79d7IXDUcx%dhiA?AExZ-7Mfk)^(?+?s;4)*2A#h)Vkt7jlV5m4`+XzvQh z{z>VWX!7XVRFiYRgY0Nbo1tyYpDTv9X=YRM_5f8QUek zbv=*_aQ5tw{q<6?evD^yxN*E=aYfAVt2EXBXJ?9Z6qL6GqgwHr4l3`gso@~^KCWMQ zY|Q=RtM)8V^L!7@>5fd%Gb4dlVou$!ow}-k#7LZK+8E|coY+;&JXM`L|Do(39Q0Tq z@*j1iu(vLf`bs`gf1epn6v_V%&)vJ_QK>4~P9--nV7ze<2-k2?AcoAmtpZ=NV2MnN zY{BIMK}rG(->g@j_5kO7=p5aD>~piwZ`<#~%(vFy@sEr37eWc}Jm~x-lCSkOO4c&M z-0T{-*I`unqt^5VDaPD~Wbbiy-e~k5{&!uTI4rZECE3c*b=pl`abn596~N`nDTm;1_kY2m$ol_Nw>k2PX&5LB3%I>xeHVQsZN5N- zO7TPw_uQQYz0$-`@S>18*S#c`Z9?ea-&!9`><*Mx0Kmw!;`*v7CB<+CqG1*(blW#_ zBI(Zytta?6q}5bY+sdEunB*tR?$46MiurvPWiA^)67~k+OD6fc>;c|*I*sR4=d{~P{qRv7o5o+*tFiFb_(wMe|t!*O?>E+0+&7<(q*Gl<^1jA>+0?z<1~ zUttTK@@NewnkmFeg8WkYGdfLQ(jFrB39ikRaqBWG63vK$^Qi?0jUI87eC)2l2ieX{ z&zv02oS(RV9DagylF$OD_5GNoGK|B*g+d&y-nuSRH}U&YE)Ql8n4Z!Sc*uM}z}>_{ zbT$xbs3Il6k^bYZ6kX#(!o-p}|0{-1X%!GmSJ0@LV4v92NAo8G%EL?O_mijaJLcx7 zk6+yIPtOUU-H6slv>#=X`1wvt<-Un>bUCrZT{)i6Ma4OP>Y;+v7zeKMyqS8gLw{LH7eL+?f{S9I1 zL(WNQiw($r9_Sj$&6djbL-^ygZZtRd7(Ct!L(Uz9jS+^UW;!$aa=9$GfC<`=FA?LV zp6ZOL(Y_VRxE(}6f=p(Z7qF`v&Zurd;ZM~?0^ntoH;0$FugzG12-|z=%ig9z-y5*p zEUR)T&D77yAAYS{8aHw5yo{k+qatLcZ1aPT*3$!HHQuc%qZo>s*A-_puhxzF3*;3! z#FD?`4N5vO!lr<~E%(11WGLO*heWit6|e^S#9oSRTc_pt_Q2r9NsQ!xs@=~g6apA& z*+CL5Yj(!YcRjI}x0N8oRS~$6q8@)>E0O96!CdUl;^{hy^?YF~eyhzs_Yaon+0Xx6 z$87u_G;;=?s{auO2BvRB^b8#2X8ObSUUaYUT6!w>mS~qa8f$k9NqM{lO-CfE9uZkm z-pgYtJNMuw0OAVVgRCqRjG@M^e)S};b{`zN7Q)GMlOy}Yec@29M6*-Go^qiMQy$z?MpS1W%jjkBr`Dz#9Aq#>zkxP_}nH@hoTl zDRr?Aba7J#!@44r3OoC23h}QwNWLP*S+!mSiI`_D_1fHjCE1@sUnPe!hh%sQ%4J;r z9L@Hyis!Dzy4#1N!qUtIKiN(wF|k(-*&KWn zIhWR?@le6>SoOdVk!K6?mZnFwX|Hq67K8XS$R&5h>{(7C+K7*GBO7G}2fd#z7^C7b zXc}wFiRe#AVd)i;&O+%G+IWW~SIplh);C5`udJWmgraBa!Y}q}GO}>FBLjZRszCnW z*Ih|XXot?}`Z4uNCra!3{BovT=qZoW%AL#M@d_%L%gW07AEI)W9f$&d7?LblMUU4* zC31UEsFo4CfPG%#`b9zEu&RuvSpXt8RGBbmgqJb{>!;}td){++0*{ZzPz9HeB#FYUx67M7$RzgwV?p~| z9p>J#7X)#1s?^@B+&nhMqc@x2T?q1xPcx%50_SE;Pu&1DAkK?z{@#jVTU-r zy2K6Jt@s9)n51n*%SGFsdG|!eaoGCmS6+q~t`@{a7q~eK5F(3;r#cR%fJ!(+`+nEc zX7iM&1Rv!258o(bOr`CwIpu! zKC>QWH8@0@5e-x?R@<;4Wi$d)97itm#~f@}D7?UK{==lSmH7>oe+7Ou&k_7Ssgx^# z(2nw@UE~-DU`!GK4@XIp?K_Tzk`aedy2V4Ta`yN@%HhbmPLm~LVG_bSCc9kX>dZ3D z#z7BWNS<-l%2VFBEL&LfDrr1RMhfK@!ciLGP1WWm<#@3tbS_ssdn7GM&=~UDmt=mx zFOqjLxskwO(rHN$7?r-mvOpVOK;bcP^g+jTy*N#@!uuTX=AO6477Al&YjinAHgNS5 zw0KI<$M5)iCFX~U5#aTR-3Dd4JTR2^@+9<^{10){5u?(D&PS4xEu}?qU!oLnHCIPX z2}BsQ`=%14zZ?5wao}8$k%vWX_|t2qUV?k&kJFJBe;kE%-#+iM9f~ok7V)41PKZOe z$Yc?lGU7n{0+A+*ml|`i%U8*Ru1t>@%dce`6^nnUEsvx9jEUyjYzfO!mv^Jb?FLBx zv9O9j*u}X=^E?LYNgLX~W6_%a(h3+P?Kmm9`YY1T-yEyiR8?8p^F}P2$5iT4kU5qp z%!OiIlR(6OW5sFNGH37sZ-soqlQVw|Jr5y}AwBf-D0xKLjQ7>12_L8WqMF_uolGfG za-SJ2IDqtAWa8%3qO{_39ntp`Gw-W=!Wy=hWj_YI~Fhaoi95t7P(1~_|+TC>dJE^GXE)?Y~8UZP>27hojOo!(Ab-8y6KCB?WHek zoL4|NQBgTsOoC&GnDZ~?6iy-HPP%Xe{@+_@c_AS`%&^k_n_hULhhh(_lt7J~y zOO8>7N{YNgq(6E!jj1h-)((91@o#1Kp~S4VfY3N)`(lt(PKZqT;SbR^de}^h0=V6C>uncW%{8d`V6I0!F*9?0~Ds=Xyl`0lOxLfA53y z_-=3={?MX4+S~y4fc%&P%%7v@FA{_gk_Ea!hR(pPKObslaxbM#Kkc#H{Q3T_0>;bP zg|5$jlX763l^R^o0wH-52$3R}H6)!wozg%{znANXT_&Tv+17 zNi*l6i;;B6>Xo?q;{b}HM%g1#=1hT@dJ)zqzf^}>(7hK#dDM+GEB00v$+hENMWpoi z{Da+@QZBvy7m?OaC{=V2*d1t1TX1GttkQ8rqLFexrU8-3hhxbWZt}2dt-U72=Tok~ z{j52XHgq~%FyBuU$rR^JE@Zr@AHwj=`bIEMUpKCtRhvzx@{&gO4Y+W3r3h}Gen^~0 zarBpuCwfcdFHGTdVp@dh{}elrV0I-WD@++u8rX}OLH#*BW>0fYm$7vdY+N#ICmz-}WG z-a71J@mV5%;fkv<1-xJT`FZ;LIF3TVg$0{Yx+6B2GqMx2sAKFod^4NiDV?aPNXa2dY+n4#YD&oKJiyAvewG7tKBPDz-9G zD6n36o^=wwIz={BS|kOW&wssf}eFum-Q*a$dP&*ba41&5bVX`U6MwEh)^~v#48oi9{B*N zI1I(0d-^B{j|QAiOlB;a^bK^fK^%!ydj~scL=v{n!mXF?YuptEaN%_WyHPn z2)}+{DNATJ(DqR0Y3nSg?}5qy_2l6a;X9%qvjm?`KHN0@Y0*H7s;TS)35B3jHsN1o^GK4$c%=J@wcHhTm{IH&ctI&%GxE2d=7IV#F7I&XHK#PU*EC(oe z)+w}V*dZ(LC#KE(ZoKeik$z-i=Sqy1js8N52jeICLAJ}aF3k~jH?XI;fB6hEkHxzx9-5w>y3i;OX67M;G?1mL*I2 z+M4AA+#~IWA20s$mXOWhyIZyw%dO-_^tX+k@cLIo)U7zt*PmR+7|A5W#J!%Joll^q ze?dwve8P>-Y+n4csP~+(7kKxs_$Us^!h+9l|N1p;G9G3 zsy{?%Cu`7RR#%UO&ECQS0Rn#UKkgVTts6Ev+Bk+QM$w^Sk5K*!IBsU zRaW1HeXw77Ql#1NHNr?U>q*0MJx?QUs*f#fo zCnkrNaYj(Ro+`pH=@8>QW@{w1#27B`#ZtH4#h5nthGNw2L&b~pa$XD$ z;s$PUVm{(ATxkE+7gUOY#xRnpnMAtCw0~Pim1;>p4+mk_MQJ0y()3jf5zzEDKHER= z*y7;*ES<{47k1JhN4}^0eXGbQtX1}X>((U4T36Z|gT*$VS}FZdmdf(rcZ89%(1~`D z!%(ttZ%F|Zlsnf7AJnihiA6rRGFzoyO;3dT&gIzTZv>{5z#_DZvTaZDabgX>W6alj zI+(6Nro5~!L`(Wx{ z;gR(cT{>^u>4hmXm1||6vG==Lh<~h?sms}-%|%>}4SoZ3j*;6MEYHTrwWndo&qWvt zr>*B*Qjh0?g3l<7fhR}bPG5>&M7sZ<Koy3@&iXhVk6k&ENSf(N zf6~yPLgEDa^9Bs6qjwpr_>DovVHnTiYTAXr2xx5sy{64CV-%7}M=MwxR?8j3a%P^h zV4?NlMydDc_cN8ua*bnh!n9!}XE!3!;ey7kswCAUn$BiKcDuo1cpZ-aqvtZ@Aqcwo_ zlw+mUB}=7MF?@X91uq$Bai6XYjBT6{nr^d)zt$fQ4*J;M9=QOkKW-S^ysIS--ii|; z4gy>oK0PhLfpAR7pX@eIQ3=?I!?gQiDAB(Lbk1Ipqv~+=(=MXh*M0$FUbab!idiq! zq1@mYmuBk~Soiv*#&RBWm5lVWl-ZofHWMgJ&qMGx1r(tjWFxp>OJm+{(Vg z_hv7D8v)j{``BVOdYf|;^8Q&PtU(PJOvCob*J$@fD|c4xwY zm)`}s`3vYuWiIPV;#VWtG&bL1B3{{INLEe98gtMywrjFA4(&KF^R$0yuX`w}V_+z? zJNYl|x3%|)5QX7()4Vbl=ETGJqyB7QDK>*OSNmG1S$=&~Xt9IDv_(Qs_k!C~B>~-u z0TZ+)s*UtMxsC9S_*)vc&5F9NY9KXAb3gF9p|B;#YItkisFZ!)S zedO?+NykrSx=v{&{2jr%F|9&(cnwz^+^NH71`uDJe|d=?tbZ)M{R5ZGM1aAT?Foa| z9tUkKD~LD}I~cqdYo(Tt9-~O?wJq8o;!fFaWGJsFTjy`~iQ}g`sCDT5 zJ83(C!|LT(wJmn2t4Wo?mCCo*wP8}}Fd5T@7fqQbY52_eP&0KLa;TnMUp)Q}I{X2l z$euK-rO6n4CMW<=Jt6QLUgL%cZzq>JU+~DI_Y%_7Uz7!XGt)q!IQ+z1^l|TfyRH$> z;Lt|CbEVBYt0m#2Tk$Y#rYo<@(NE<>)3L~4QPgU;mTGtsZw=%&aXou1QFdmz&-wWw zD$r^I>UvX3z|~$!!5Y+?LuEMzvpet`urp!$ELp8$0`7nz4-nRUYN@==F&V%vR?!|{ zxc+V5a6EBVSP6cVD9srqvuzZ$zE882^D=tMs^RG!XNW?tmLp_mw#zcx~BBwHt zP2A(aM6(twf@wVv0}Zea@V_+-JsT83y@UKeL7(AK>v)MM)|yVdpr&I!h z6$;+#;|qDD2!wU4Tf+O_zT2jnVuc_fl?jrjKTIj7i+Hn`0d91ET1ITC3nA`-JhA@M zhK{!^kc*x+;Y)x=2VWU+Dr&)(Q7@uVG;gKzhHjCtMbKDPGn7s<=aj|yr26Wxrs`$PPb+K?b?cLmVhi!K(xz^n{uO|YDO<)0^7mPh822A(?qY=We z)uO}AEQtBxZ^Dbg<0?ocqHvgO>cQ&m<3q6TuL@mv4!Wt{MmydX1Nu`mt@|@qcHZjU z^__a=tV19FoL?Y{n>&aLsij`NmGk5|kjfl@*$`-p5N?V;9$cDkp-Xk5@mo(09+fe| zm+HVB7)W=wwnaMyT53HIsWv-01{Fjh5wUu2i6=(M4;**yd|7gAz3?0eV zF5B#?`=WjRP1rge!|33kyyq@33-RU4F|v^Zk+?g_9rtV4C<_N`CR+E z437VYbZ~zb-}TH1-n|=4cd~f8S@d~fzoH&R7^=?Occmiw%>*=U@@jtw@Tp$u^TOVR z)jKmRzXDM)q;S5{y_zG=$@rF)JQBeXXBQ&)vnA8Pf7_qX#Ssils>!#`Yen1P3BAT* zd0HvU76qPiA8LtE`4FUr z87t)Ql9yMoaFCQ3?_IPD>K!GmZQ9Y#{eE=-n9CgTNt<8>W9+_j*bkqm8aF9UIp{4l z@5Fes^2#bU;OvqI?4>L~|Aax8+`>h-&tdt#N*XNpWZPf=Tg9qixX@EQ#w;J}hWf(t!=UekqFKsdaOMPe(>ieN^nS@q~ z)G7=5$ByRUC50H`+|sDn8mkm%B$gmdGCHi-M)C@B9P6VAEHWz(?|7r3hcR&`yFWi! z3q?odD>S9hT*L_Dlssspqj|7a@=8^4wcD^bgGK^%-x5i@U%Wy4oL-u`W*12d{ez)P zk2~Bv-S^2-fh&GwAW{e0GovJ;cynJ_2VR;#b75|Z^{SMqg%19=LvTV1k|zj=v&Zf9T~25OX$q;JNxX-b2}C4$zuxo4 zwC_#AX?~`ooNWi++hNL@1JHyP1%Gx0ZOoG7(U&-$=;=SV*uQl3uu6M!MvlYnt+U+I zX{yUC*I77#ELgJ=8n;@UTJBLxB!M29^$|zRM?cfxuz#dtd>Y$qUrj4T?pG#>!Y9tY zbv1!c@SCZ*9l-*_BXfgWFuqL&$yq+16!ofCV^cX^9Gq95%+)@}8)|oXYDBfP@11FL zD@CIY_-eXx&_`Gf7Vizkov>QTez6K%`Z!wL2h>ELj#hs!U?*q{ZOrBa?2;C9e4Pg1 zOtJYu&kp;Yhq#0aqu-U|%oEshZfLlcD@i_I#3)uzgf^aintqvy;1&(o^j|$a(|<%% zf`fRlFA&I_fAe-G1KgD@#Q&|};EG*-QX!SX&4O&7F4yt;_kLF~v2#G;I-2FHD4r;d z{O$d_p!ei!Yi@Y)<4M`Dpp~scF{6y(CCGC}jIbZXeMJpo@Zd6}8 zDVQbr4?oDYqOosmksHndm5q8;P@bjdwNdXA|LK=L7|+AWKK*9c0}s#2NkHY#Xi{Dz z(Yw3@xEsf2P_L5i3^;PF>%iZ z1&SnCD)WL>3~04-Ic=7|tmKYm_3SWBhZ~3BFlo2-8w(}X6hh&tB3nu3tCqnY*0Khi z>-RF(_tiOEU?s^Nmhn9jyizMSfe-Wpwe+lQfg^uaYGClgF!sQ71?jCMs`UGnq=sR^ z5y^7>^eL1O9Ey-Sup~wH21uRK>yF_2LrD=5(o+O+?!3xMqYZ);=yz4o4Bt0IMq;mM zW&5!CO&R<6xKD|_Dv||z_9YKPgOuLB$pz7?`PTzK0}Hw*uX_xbwE5AO#2d7Ycbzs^ zBi{b;1~$0Hy0~*v-pkiycpl=8}?r1JqK>QQ*{6% zwdnNt{8l3&CM9EN^FYITbiEcDcWS`;XA%{_dWT-S% zLMlJ--*6w^(0V{KDv9a2aU>~Si62LP!2{=Ob8?*C+NOhVe0~`ZR#v|A+3zJMX{G0f ztOSV)DjpPyNks37v?C|K=)NXtI5J4*+b7Es6|fzv-~aKL>Dy&A706spFgo?|n)k`) zn%j*Ln*`knhH?#PehGfAX_mzYU+6(YDiRXlG z+)O_i$MP6{$00eiU&QxIB9bQV5=^M~sr2PqpGT?xLt9s!#zTlb!k>T|c<`-6!jBRK z&3&wEM4N9d8Duz%&)lBi^F@gKe=P!;~5nzEbt3?r;H z!qk#gO(F07kZOVhNAeSO8B|23t>oU5%$ERnWFJ^}C8ahE!bnrSxSo+~3BD|qN`lm2 zAtoM21am@qT%PtQ`B#D{| zoDNs*)YSc%Yd0_IM%M@m>5ekB2V#5Vv*@n6riG?U%8pRxCHOoXEW4Z?A8?AZW9;7& zr1}ql>6>CFLhZsl1P!!o_$%k)%On0;MLy;V?LBj`em=>_(K{n!eC~per*vBa-rzg=B&%Jk+(IFzvx9Q4F%y7b43_`NJD>^>nXHC@e> z%HhPIf@!~WA}x}cjB;);zN3l}J{9{&m8Kecwh=P&l;pxI!N9oKwNBa3w+3%h_;k~+ zPE2Zz>oxS+JV|M}%BY|VO6RIjynkzZ2pP{3H#?HhT&OydGqB_wZX+uR7@gYb4BEX9 zx@Fnio}v+Xr|3PI!e9qXs4mbh!xRf&OnOo}x&4}PFx7Y<)ETkm6~w3h$}BX#Ck>mW z_J!^!lw{N>uCpi#+`$bPs#F$R_ltO%qd;(=A*7ozhb!3ak(d{4N7VV|{c(Id zB)3aguW`+B@?oJ*{1y1#Z+*%6--(utT5cLw5s2VN_RAH!s=ZF1^PlMFTPS$Ko2PID zrnC)`3Cj|{T313m=zlj~ko~>Z-S1{{L;qbMcFFPYmYiNts*k>vZS%vS5fPJOG!evw z*TsfT9fG)GCp<{^Ihly)wfEw*?3^Fv3^9F>c3?1=2^pRL3%mq2$z zmf(FT)xv5NL7&gX3I%j5GuhOK0tH3(;HdOP`R2v+4!`<*bu z;}-D!UkM2o^mVI`&r}x=j*aMfH;PNIUKdtB==sna(IzAh^*@XdzE(Evmxz&ypa^iG za(vw5=Dj*?0xdqDhnreP&FdlT&4XKe$)XI?X0U(mJ3C}a6&Wl3B^BBdfZXG?7 zUpPYgXW5l!FY*X%IJX3h^7*1$_U_=pZac<#Ki)#Z8j*EOh zsTp!!ln5A_UtG_6Q+|XfYC()#3B!7@pSNNKlWC?~WHNo+37O_C-9|KI|A=9AFO_9gJ z+K{ESDFZs0K?URK7U6gNwX^cr8pGPTUSX=n4#r> z8H$g3Z1jQY1c={++vYQ&QSK+ z`q|^!fjH$EJmW3(RF`GeT;_eVH7n(nd|b*~E?pzv>vFiJAL|cJ(_1x^rm36I3-N0t zv6lTd^!Td&yE&KwXM-_rWmMbZAuE!IJg)p+ihtMw!2S85R%svA040)mpTCXKqOG(T zZv@D!Q^Ds{vp+W%YYbTq()gwDQ6M5hcf06}5fpA$pfHz4&pNTD;ejf=S4~lt+ESq+ zih8d++l9*d{`e!q3FLevNTB#5t z(AC*g)N%!bxUTuos|~>5yq{Y&H(iCKVg&IU$o??go4TV#)E%J zzq!;kMh;E#>6vz18{#kUx#ePROLvy{INmuo`fkFMJX!dp^_#o!NW{!5gX_ z;|xU*MJ+CFIAhDIn%@F5eYgU(`CzTs5ngkb0qVEh!ZEB=T&m(KoQb+Tl4|53c=onPE#NYTva4^7)#R zQIa0(kCF90AfNgE4_BLajH&%MQBXsA}?GHDHW;^+15O!cB zBx?IcwA1_1rFo}OT=c#pme+D{iy1+;OUmM*E244pZ?ZiTBKSa(TZBs)aOWuey(`A- z?mX^pZW{AT=}QNRvW3f~^%c!nSeKM;)2fukIk3u3|~?}QQZ zR2{!bMEZ4k{G7ZMct%;`(aJ|az)I7kwU2Bk6^xcS=8^!imc1?UZ)P!8;;L4I+F{Lf zP&G~MwLIs2Jo?zi&-5?eJwBfU94==pc{4scnXhYV>ChjwS966+VOex46(%KdF7EF) zOZ6QZoXorTSMU6G^^m$|FPf(Bw5uJt1EFDMXZAy3ru6=PDa&qqcA|TELUdHibudOa zSHf3~-9dM%;*sC5kP+F?`NT+{fOk$N{#m`L7B246Wj?` zwzRkU<+q-pTUur8*t22DSNYRP2{*uRI$Zl8an=aia)~;sDs(P)DEAVIm&0lIh z1Z8+ZLOHKz)M@>`5)DcnG3K4IT9s~R){&f)sZYHjgit#$r-kG*Zi?w3I37QQUH-0&f|y)6QOY`tDLEh`6bao#n^`r zkBv`!B2c>L(ta!MNil2 zG;HT;^0=S*4PY*mIg&E^rgIC*Y|p(PHNgf!zt5}-#$hJ4KS42c%2=m*{_0F9Ahhs& zy6?T=xqcHa^s+JI1V@o<5Nbakt8Cp{{AHi7zn`OjRs3iz8f8c}UG$mt#DxmWKpEM? zHa!enY(nSQp>1dZTYq<#Wq&_kmd$zgS`l1Z*e*25-)hRh3p-shq0&np!Du^IH$! zbo{h4bi0>EUlk-1TCXaXGQC}(ed|Xn8p6TvaIN%`;wds3DJj!s`mROgA)h^jvA0^W zZX-#*)?qR%>2vI9;W(b5sQSqJf{DZi^qm*$%vO1D@XzD2`sKI7TU)ZX?`q#SSohk! zK_MkGC;;04W<19E-T}w#tO@!@5jCY-}`~vK8@yrrLLgzC+`|CK_`CBop=fEtAG&* zydwNz@ojEEuG(gHa^vUazI%)bTJuv?_62l{JskF|0lI)i~}U4rof?2F_M8b46qvgW|tsVAdeb%XcBJ`)An2 zO4-PpUW6(h!7~je(~VSJG^K%macn`71MzvGY3_;?+{ zRNP&6Z$qkT=_M~m)@$a`g`~mNo*oG=>%XRN^w^i(UIzEw9D=r; z2Ij{|zB*PlA^v#jdVqM0`?7paAx2^W&%SFun@{@w;FylZ`Iqci;b@os?a&wM*6?0a50s_OSCEM3M&g2D^S@)#a*g2#KJRhNK89qG#1X_SZ%xAT` z8!1|9pOi9SoOeM%rf1^6++ux(V3<2*KkmY(fBnxy z*=rM!n8sYA484prr5W*=7k>|be-n>eX z2s|wzy~UpZP&c1iXzNJQq%7!4;vE0-)fG3*XGXayi7Q9*b0B~}6bvKKK{H%&0(;f8 z-P@2QDdcwwZ?7Y(do?u(StMFwCrKd{MNJmak@i(9R6M=~UToKzYRBuyKyqWV7&+1A@MZVs zG$;8q7ve1PcG#_2Zv<9RoAw1lu$Sno`hMhN`pKI=mf$%Z1K~&;^`TRN#U@)%L1&Fd zx$!SCQTxK=jri%aSsZVPrjN^g^6LHo1qEI$%ByI^6txaSiBJBHB5wE;FtU>tnK&PH z0-q!(%33Gf^H#WLe>FtU<=z5xEv9&v%&jpo>hYe&cDKg33Yt|iT$^%_g44>}M=V>= zRBIYX^cN}W2<|9XMQ=qGmnkh{E!C0o(3$tJ1O@9l)rF$>VEFFu6=Wqtd8YT?L4z8` zAI+iGdFA5Lul^QG=vgF{P}Iop5%a!(C$A+IoX#zWo+iN!Sgk(647vC?l*t0O4@iL+ zGDYNxE(^w6cCvi)l=fP-6L0euTV)6r*V3h2sZoG^g`<7y+lp8!;Zvb-^m{Y49K4em zfa^|8mc)_PuJnPMUaef!dp{1lFUlogG*=JQn)HMWGFEUs zQEV*FyMU0s(_Xo%*$$!D@9exqFx+y2x{3p;T31T--5YdjAVn~3iR3I~BdU6Yi^yU^ zaIyb68&2te7euqQVCW=`uYKPA1QP>)-`msUG4FkiP&`;z#`g&cG>Q+@E-i1ibp5q| zN02r_Kc@gB6a4nB^ZPKJh{yW~dUf|2U-Ll)7=Jpu$Sm~43uBm{TcgyPEAC6IM`YtF zPfBbL!$n&Q*Q!L=1N*9(oDsB_x-MGd zSio@|kWsP(u&*_6H631j^(RKW4geesPq0-zvNw2#ZJ{e4US^dN#=>f;3sgWGvyy!( z@^jJ;vS0Z}%kP=!5*V3+=Af5skV4@KLoNKQ<=i*tMv<5*yY;WPUB@c}A-5W`$*6QLSA1;HfH z>Ow}86J-;(W6LRS`}Jjd@uHo+`^@F{f3`vZl?vSfAng8qwCJUoN?Lub+eStKmy>uX zdu21Ft~j_agl0_}k|6mfZ0Q!^4D$YFx;L1pQVne$GT|E%A8Ry!RO^&B6OD9RP&6kM zOW6Jq=70a;8~;nINGRJIr&=|H5x3pQY^aN!X2Tjp2~U)*o)7gz#xIRqqWH4-eh~*t z)EH*u?Br0kQQ2F+c`{^0VuLfDxcOUBP4dt)g#k`RCN@XI;VCHRggp?)r;NR`tb6Ng z$E*MH3Z@3dS+iW~%m^?U5|GaxBHEtbH_E4yVR{Anpyy$56FPE79$?TaMXGC98!Goy z!u1zg@KViY&!R)vP1HK<35aDB4dD4~xI~>o2UAX$YwGW%JeHRwYh-BhJ2D;RE3>N1 zmy$3x3`|IFfV_c!T60?zN)>0jL0kef>S(D4TKlFaHN>8^?8VyE0BZ@-RtEp%Yb5cf zU9Bb8pvM-Ae8e5K37_K*Y ziEJ_@P|rPjQ)~OaD|F^LXCa@(1wioSiE;i<4J{~Qk|-#zNkzf`@2U70q~~TC=_}@E@llzZ7P%LE+d+$!E|+2U}%^3_PUb`_D9ZVSaqdq*4lc zb}6eKrXTT^BLnY2)(J^E~?(qP6#Ax~@p)7RNNi>G}^dmiR=AZ6}LX zj9&)HRv7rQN1Er1`v)WE8qXR$(2kjWB(vwE9mh za;@-*CGSispYBxlh`ZLr1$RCQTw;5xJ5R6{lkl67%+wFUb|qoFdEfAl!jR<#L@s7@ zZDP1k!8e~$j$u`+XB~M0@AkApRw$)#<#@>4iG5THaotlP_++Xa?Lzrs_6XMGq0@s5OVVm zx*0&TDg1+^hFdtjxT@Wl}GrzJfizvCGcPL z(L5iRvLT#Yyv_fP%D$A%Z~UbGzd=keKpUP@5d7H{Q7Bs@$*kK}_kYl$ybDG-|9P`u z=$Z%@+U;U$mc0@r{99U1dD-T>;41|{!M7dE%tvG8V32>;`iYWax`@R-0&Ohi$^G}o z+Tx61ejCQVyKeP~f&I1<3S!iP?s#FfWv+Lh_v!OUiENeyLf(IkmWR2U6(jvflB-&7 zch!;Nm*km_cXVjricGVkxYiw$+tp%|(>{E%_e>dEUi&b8+#+dNz{T)As=&o~zY~{5 zPl1~6UnCg1xMnVw&bVm6L=rW%&^{+8#bgSCy5jz+$S1}_s%UYo8c9|9l`YNr*HVV* zt!=Wr4U1^;(OQ*PR^NwLBX)W^7zwn4R%qx}JvJmfVm8{&O-5%)`CdbN%SL!%GT4~( z_QD|GH?gpU&-)Ild23`iqie+-k>P;3T=~I4CRZ}n^gspmx4~9;!mjH49*fj2&)zg~ zn&lB>ZOxg&MeWWSE^{=X3IZy#eC8;SMt;bi0cR$OBfPCnh33JYkhk)*K_iyyl{8|R z)y%>m(CR_x?p0~NC4w*$PqUs^fgUM&Y)(BB=C0Ja_{ph^rk#AN1Lw*TevLT&%AGJde0qI`XuN*)XZENb zHhwd}L27MGKKEzxDh13Dt`AnICy}q|>ma^$+BAaHZtS{)F5>7K6gg+s4=O0_KA~Nt zmMDkMZO`*bHQ%-?6-G{aFa{43jJ^Fx_eR?B_TaJ6J1^7A8hI>AzG{CE2X!<3a`!C1 z)3=#$?EcffzXJ&Er)E6skK!#qLy!4UI|nDQ|+TuhL4^AOmS z>ZNs~1^^YI)%l%~2%ocee=0DVj34bkO#Sm^a+lfT-NoGvPFMehsTjdfz)e{SkGVK~ zzsLtEI7eQRW(e*=YHNRHP;R8>dJNKj3$qUrqzF(z+dR^=!@fzeOU7M10vlH>XOZFt z&sc%#ua>2uzJpH_nOZIUF-gbt_D5M&H`>-+<4#E3o*PLxL2#1eVGqM=3-4%4DVZuK zcqL8>;0Eaj+I)4Y^1Fp)Ahk!Iuq|AAnUjElR_phHX>H{Nnfptog9M=BnxpNLrWy)Y zSy*gIahCFt;zb6YggS)>{w*NQbcITN3`2>cWJLlC<7weq?&4PoVKX z?>La0FI|Q#pEy4ZrD){;L9qV?*RKFw)3>DWsq_`4*;kIkR_vI;_IMNO@X>Jj{&3Zg z#6(pykOV4i`jeXxs+!^e3`NQpi4F&6XN+VMFDf;0wUo@)UdIEeeq$@kKYa={3bOea zuv$%GkK3~k27llD+P3)0pDL6@XT}r$B{?VnKR$}D~Z4VW6cIG z6+ft#e(JPw-?#{W_2|;;`O^l%HlaTvC+H6KD0y7*1mq-Y@wbe4Udmb17;7!^)_EDw zcOgIOo#H=45-+6cPo7#1MGnSrsRR<_VSMJXwzoG2?5RNSdt#~vcumC^7S)jMdJ$go zu>5El(xGDK=+WhDztdn$cIC2w52b#Xqgg=SY`@71hyfQVChd;m@30RN1$ zi>q_?1##wlp(x*rKX%{khiJgKXEj>+;Z?o2`^GB&S@|z2vN0ne3z@{n_n;Z>3?E}F zJWOM&n_rdGPwBU^48&b}TKj*;&q5BO3vF1Sj6}^I=o;f*ruAcLx!H-G<02_Qw;j`w z8eG>Hf?HZX6*P;kIL5$}4th>?-$2(&2b@Wr{Z}^?s8$O+*p;0%+a-1-S`C^1Jma6Y zQbh_)IsZM!I?ycETyU0dYF`Vk&VGngnScK=^$O@_>4j8c%6nt*C$m5dpfya}-q!X_ z7?x32(}%mdbKV!zPvO-5b4N`WxoOjbf;!-YSAZM`8#nx=UU#6m#`64uV&t(>x2*Z0 zNNTX4hI%S3>55-I4+d#6)c@##9%DzFT4aZ(9*8-F!4TT0cSPR!2Rv8x5k9wGWQa0y z=PDJ}%XHKb=Ne&7kF_ph;jV7<8LEY5|ByQ;lM?8d`=t$dOEh1e2zmlM77JbQJ!jMzO|t_e>0Z!QipKHthsg0ev|@Z@^d zjO_NM$X`^0XK5*XIyOOeP-gl4y;tU|q^qAEVzIq3;xDw@6bH>TG9vfZrshcq^OBB) z0rq&tgJ`!Gd9i0Re>zXgnj4A8KKiyevEEByuYtm|C9|wMINu)Tt)1?2U9p6m1vC zi7b-7q6qN3`>Y)i%ozME+kiQ8{$~k$v{c1|vUALw%IsmSEdQ%Hjm8<1{1QxTw3KdV z!b>Lo&qMX7d*!k5f@60%lHj;tB%5zGZ)xw9RD~BTBlF69*|U@*<{7oN;@l?N9Ga6^ z4i7&PI<(tK-`Y@GP`xd=u{EG!hROG9I0h(fzAV*IY)Z$8M2&}QCtd3>*^Tiv7<$Zl z>^~`f*|p=aIAO6eZS?s3Gy9!?R-B^0p*up3Qg&R`(@L|XPbQ8i7F`Va;drjOys5pH z$pCT>o-Os{-*tXwjzNT}|2H~RS-)}Rn=(QoI(ZN6L8tx>9_`wPNO?YQIKod6bJyc` z;9eJn0V0^W?D8s#ZBC@}qYG`g5t{hl=pcAbQ3ppyy)BT;P{eLXSCuVwx04Gbl z!jO!*j5BMs?4JlgY80b(K1@LpmYP`uW^VFCQ00lbWXk3h>Xv(H&b(|e=TAD4fvQTx zNXuK8w=hFP^&t?#4Xam zp^Qh>6GWtJkvxPR_TY*^!`^UrlG>?cMQ*OS5h7%#O*If^sh{(DhNy|70XU5{37Q*P z%g+onJq+sN(b+HmzMizb_}@X7f!n#EG=tZv{1=0Eh)KDzXHPCV%?~`sdTJ!I-d`8Z zP!c`M{Ae};QoB3VX1OOoCbTOVoj4rf#DguMAT6ELrmNj8=5m7n*XepINK zR({AUt8o>!#=>Vpp>&;4OZQ2R;TSB6M|fSsd_b&lh?552!M0Rb-{o1r;GBhxHuo&l zF<{SxyPMBZ!5bV{ZYPo`miIhM66fD5D?>qGd*r zw`4uzt=HcQzNN+R8CuIP%Nbd{TGfsS3rRtzVElFr8OmJkb9pZtQ4}{3`9T?~4*EP+ zMLCA65zJgy_9jk z-h*963)?)H(sRJJ-|<%!-zvVX-l6g`vN&N)>8Ce&s_UQTkbhG{E8jLS9)G-2eKXo; zFPOnB*-qIO$Guve=WXBb!lkst<5tX(r7E4@r7Afij#^~sF>1}ILfKMmDEV9`D!q1eE@u z9T&uE7~PiirczK;_sW4n7x`6ILxT%YB5os_vubO(g~h3F*le`%G#oU&#iS%2JtGop znO9vb2*t6EhA24LxFX;%rC(^4MFat__++Cm9`cQ`KoC);ii?b6tX3w(_XIU|)U zRL_P!UhnS2F#CijuNWqD- zlaXkg@Sqt)g=Ca%hMI@%cOy7tg0J!wqGT{~DsG#_6vKS?&g#c*>={JsSI@*>Mwa3L z0NOh3dC)Rnvk?C2+=7b*yhUC1D8)~c8a1J=uZ~h7WG~O!4e5A}Z}EbFvqm}1qG+m8 zLzfj`{u%7K#;Z9PK4}-UdC5^!PS5vDq@_3Zz2~Z=HsN*Rnt^!cBA-866!9`B|AK+V zVp7FmBO5S1MJC;d(N~@56Cz@2mrdb8bVGLM=w)?sbcs>{R5q{>FX@7xbl+hvMY~Zo zLyjp+BMz!ZSrGo3uSwZC6IQP6T4DH6R%$MHkKw85bOAyf?@VI(JW1(fIA|@@9QyoA zB61+}XrKIb3&*Dbj1xy&>ZkX%MxZC0yS|F1!z;&!0q&YH#0psBYQTG7_En^mO4gyg zRVMA%bsjr5Z(|dgravG%&@}eFh>|MOA>jn*ti>n%HZcckA#L{-Gi9ZplhSKAt9d^p zVcB-SyydPZCLkn&y92qZO9UmL3&O@hX|r<(+3bDJetL&b?lZ8ft|zueo7+NokQ(4z z>UPT0!gUC$1RfP=%##o5{606@5|1yo9HShnJN<&Z!@dVB@BaMTON|e7QyX(KPR(;3w1xb_3yJQTd)AXUc!S& zl1`R!X0WJ5_%e-mIAGDRs1g@+TuDctcl+y2#>AjZI<5I2)wdS2nS`6ZdKZU`Z9o5= zsToSxc5T8?@Zulf)pi3)foFYLS^nj`8FYP+#k-t>hyJ5kI<^Cyy*!f^HtM)07Fr%L zsrcoTLZD3}^9hKvl7I5KH&B=3Q+9+Yl0SFWW*$Xz)$aJvaSmzgMFjmG(DHS=L!19w zF|%$TZH+H$$n-vJ-$Q^uXKwZskNIm3T!hZ@ztgb*$721Yrcef^@^k4%X11see9S^M zfFkUo@d5LZf#Q(Ytau%!*rg9-I}>1Pr-p-OMM_zId1-b}(36lJw(Ur*&UKu!q>@(v zin03*_rWq)BsIEuXuTv+VOxaQ-E0%E9D`9Hc5*1@zO*R*V&@5d6(a^yk7M2c5@s#F z-T0nLDCxf3PHQ1Mx6#^W&)or1zIRhbeMqX^orkpR@?Rvc@ZC zOtov@a2PNpexQ4(gW9u>+mF;QZkjD2=SN#BZ%YPM*_oQeSF4+||6Ttpow;&?N7IyR ztUg%h+mn>3n0?;-QL*xydmymKV5s9;=#tQ^+M(+ed!6Ap=VdaYL%hohd^_kE?ZuU6Oaw4}CP1 z$?(YsnBEjGUI2bnRMb6i@mZdzIe^_C)|ENubwXiV5T`)?|9ly|PfcA`VwT+goOtHB z>IYjrC4-+dnV#R{b-g^D>fT0&HzaMmWJLs?EtDI`YH4W+dJ4>ZVu(6vCbc|pck<#_ zWy^FO{S3}o%(^*qyNVLx)SP(c6S?c|D4E4YaW?U|4R+g^fHL;Hv~6k~AS+76tBE`% zy4?us+(w9nQ~F&`*Uzf4vu_4G^}?nXJ1-OEi>FVyf$ayIY@6%OeNk>RpP8q7&}n4O ze22trtgWpHzTGyF*l}k19;IGC(L4*>Jv+k012^2BSD#r8RLE_2xf6w>53Ad#WndlP zj*|Hq+R*>`zxR+hwIW$a=dDhUr_j0^f5%?MnSKHJ(|zs1I2CTN&cBvoy3J1tr00^> zOmY>N4;U?xv2XNqz3^L#7?L%d9i1g?n8P9ysOWzPE>P2Nf(WRhZDgofM<{fEX@s!o zeR295Ee#il2vZ<7AZkIsj^2grh0UW@t2>MAkeeH~GDr0eGgF~f9p|C8R0aFxznvz^ zvktLh)r*6LWyQC*vVC)#AXyHLWk+7 z{^}_>w7`|8J=k7&FiY_p-VkhvE{f#giX~z+@f4L)m-VNx%_!3gio`I}-yM;PvU%0X zSc#32c%X_Go=;~O3hPlXTqwyEGlcRg+6;~}#gfpya*n(z)Sy=2@xCDN5Bqq{A(K*+ z83(g?ihvFah+&~-3kLE;^$foCnZtY_%>xBlbn4L)Dh?f(V`v4>T)$1sTkX65EwVp7 zJ0AE$(Kdm136%rkQ&M3>By39kywXN11#$&O3&iHLB$kra4X3`GI4CroJ9Si9l7*uI ze_cISZEhTQO3$CWS`$q}q)6hLA=cWf<%k{^**J(=(Uh3ZyDIHi7mWVtJJpeX!u9K0 z>}mI2wEU%y&BkWZGTJ<(DopQ(wrI=iEYLnhp5X;E?Eo%$HcVwDK=WuY5X`a{m}2^e)CPtfP=JtjVNK$RmY@Qaw+y~zrRbw32@GdeG4Cv>MYf?6>by8#$djsTKBFL2p@iDK; zUH{FKc7DV3NcC@-?R(a)tn_lA?e$Qg-PY{EJBIY3KZA%E(mc#&c2seu(^iOpk3W0p@TdM75yZ7HBW?(|^P+RJOHW z?v#@g=(-T4XUm1?P`jC5{d0G;a9|h??&04(rc@yrSBUFpIC*;j@T}N{hD4)|CH`C= z@Mx;uwK0@gPH5j)JZYuKyie1c9a9(4sm#bu&xds#%RFq!pZ!&oynXH6V_P)yj7~}W zVT&g)3U4)e+Fm9g$c^BU;}+HG>Uh10Q5VYrXwXq*5im23zS4+#nK`!M1I|1o=|WR# zXOrK)O#n*5+*&WkXbN=8esQ3X>n*c>Or}sc!}>=xUi@+A>Wtm6?01+A1DuaI#9w79 z*@5`n+?pIK%u7aCz_K&%X}o!F#*PN+W!2 zE#UW~{O<)RkQOqAY>Dbdb6Ju9JX;=7=sL7zR?EW~2J>POqeY81qdRbxer+jn>qzQ# zW=Z3R?e~0Tnx}63fI8#46D2g$)%B?2z0(4jsmto&C@iB8!iQg2+G?3aa4R&JM2w|IL!U=fE0>E6M!n{niWeW2?$W_n^f)zjrDcW-+L% zm=9~M7nQZ1HU65CMw>B_oxpwE$Z>tj+1GGY@k8rjKCzP)$#6p>p*z~15J{_!7{xcT zM0EoE?ELo8^kB9dIT}bge_l%5{gmv#Hk_xfBuLgtMH-nQZ-dIZWcJHkxch^zsTdHBLTn-~w)h_%%+?5!*r~$)0+MP=3jO?2SuqY&-y$> zbA|D+#F#v_P+{oQ1hgf?4ZR+T@ynQ$JTBjYUtG9&_plZ{&S$M60^K8M)H3^8uJsIs z=F7dmFy+i&nkQ0;^Du_PL~b9EJul<)!_DlqmkrP5+~dm5eWB~ts|~nb<%FsFLiuv$ z^VQQ?p<>o$`xczW7t-c-S_N$`e~)j8?YH%>Y1VMVm-@6}F^i$Z{IQ`PCa;6M$Y_vwu{BIt1& z^?!Xj{4ccyySE&=t7U!4?H%o6c%|w}ShjZin-)OU5J*cd_m?#!=+o2}I+>EGuzZJI zoL-4m$7WAT4Ci1{nK`=7Gi3pDQdJ2ojk*0L%6f_}2AQT_zFwGZ;aj-~W*2=mlcN}w z8epsf@%;7tmrJftY@%X*`c^(^*%H@?Y4r=_``KZkwPPF$oLuf`5lk96eA>rmLmshW^C^tiW19%l@ zbEAoE9?VI%BSeI#89OE1KbcS+0<~XT?$s&8hVDdu*d5dy{Dhn{V=YqZn#Mq4$fj=j zHaIR)7N=oWB>3-Sr>ZEhX^riZ=;T<~u&Nvn1H>p7{}Ic4EKL=^-a-nhHblzt;S|!v$4^}w%NvNFfk`a zV;hZav(dz6W4FnKlSy{I|6gnGZ|$@9_&vCvYh6U34Xu>i0uWHg)8jWAa=Zu^kw@Ur zNQ6kI+Z_ZiY8|iH3_y*q#1C4?S3{y{VwINn;ouR#TG>GrD7U1oT29()KikP_nhfca zxm_L6>AK|db@eQH4MQlJ=KiRU>tN@Vg}4m!;H;q3m8n@PI;D9b7chwmONrAWn0G447&g-7~!9e)CQhhDnlBASRd{={}GbCdC% zpDIB~+T_dh@ZJjba)*VRdeH+zg2che~>&divB(c9@ zzPoT&iY1A366X^2ZxbD%*+}I2qcP$y8O7hGDFCx~f#^v)JDoRQpO;g0=VMfa<0t~a zbKoMv-oz{4Rr6BcE}=~qXdJlZ4Z;k)$+ADmvac=VRjynyED(SF=WEdGC&*^p8=J;v zC^wnTQEok%z7TCa`R(>-+GDF_Wgb7>RONiRrmFdFFlJl2>#8Zez?X`RukKJpJ?urT zUK2jG(i1ojkw{C)%sO)LrpG0au7$fF4HkmHn8RCP%zXZwum{jM+yk^B4j@{zWA2gs z+}uPhO|7~mnh}w1!JKuD6~6^`X~=&AjO1$%`P6071&7=mumX-DOGV|=s zh7QCTEJkUZx16G-c`Lr`doIXt@U^P@Uy524FL_7JWDANMF-vHhSVExI?fvo)bW=xYx2I zaK-J_)8x9z=x8YaiMR8mIKLp%^Ssnpyz@`CN1d=}77&s1AMLpY9TQ`aUG97-+#Uw%Q-5&$|S+Fe8tHc+oL{5caz`WawpC_3h zZ|461z=GSI02u#kh&UCSuF(^l@2F;;6lLeZM{Zt1aDJ2p$&?V7YuVnmgY;Utb=uQy zzeREKj?V*ocI1RG%iSNHAMYPbt5k_NzNlp8U?eT0A*ZKZgkfuVf#rPsc{DP$cQ%({pl&5vJ?Ep{zBYpir(ybMr4XOx>O_T;_%swx_-_h z$}e|7)2V7Ih*-{^r6g)=$8j|H(Qjq8Q#q>-JD_ zffcRQN94+9cXqrIr|N*dh7f|k#KElBGn zjy1$MR$7;g`h`*++A`W4HjYM5@AMjAeS-bbC{<4;o9NS?_~|>i4g_`bdp^MpjM-uzYtfUtfM#GX3X>$R!BfuO{_zu(HmUi{CMIzCo{5 zO`qq}fk4Jd?#_!w&}_q)XauJ4Bew5cW%)dSqZ8N*t#O|7>boWH+oOmrU0AinQ}!GT^lXx$+{cN??uamd{}Rit~M-N%_$sCC3Fmk#|Rj zn8oADG{FrOg1JKNi#Mk&!H0=Yr*os1-YZ{XQTR+R5Iq13k}<{`LZDe?!YZ!OkZgmk z#8ccNOuW@;$SoOn{{3KsDyeI3@P3ZGM2{k6fT+oNBQj!A>d(w%2q2>(B4!!h^&ibt zB(CFgQCn7-GKuF&4}vE{uB5-nc7fTZh&b!nepZbK*p^>(v2l{L z1quA|MAqwyF`l@cOo#p18i1dy{v;lYEhEnQj*qPoGeU*4$WVV_&vA!gGU6 ziDx>97_G9kYdx&ZDI#wriZhh3txe!}^cOX0!`eY?;mBqQ*qFDR#oyUPBVtb~-YR*LdX zN)J1d$a2eDF;Jd9w2)}PX0;>7DVpHhPdi}J z=0}LYw^zjeGQQ7BNMHIR&L(nJNPU(A!ENW)OJ0_zrF6+$T0_{YdwIj;8n)y)$v3wx8 zmDZxj+2Jd?{n2$issp^C6CBq2@p9-^cWj!1dKRKv**lt?LmuQZ#RFJi! zYe`h6!F!2onUUOM%d?LH>OiyZ+~?vEV8o=7SeQ;;ct*kx`k@+!-wN~>5Q8FywjqbdPs8<&C)T!bvp;0@K)t(@oTH^#GFS zmoMkgM(GNojRKwoJOtJ^@3BWxe6{U8c=DJzpGm(CaNoKEQ`U6>x54Y_-uK9YUa;zV zO)Ei_F*w6+t=7#>*^|U=QJt1y67P*#9Ox9E(Qe9pTKfWf=Pnfi%Iu}I@gm8+&{NP? zb@9Gu=1inEZU*(hSwg(;x_Yk0{=r6L#FXc_vG`@zd8yMpHvkI(8k)vSqcyHPAE+MbFOg=hGl~bu-<*1p;P86WWoWkMcX^@?;NI>)iY58|eYh3m$G` z^Wn$&w71mP9Esu1QR-mwfMqr~fi^SJ>gmmAIn(RqKRzPY7aytm!hI0o3u43G5s5%N z!{G%VVm@9Ig_U8Tw2TabcopB(QCBd<{@n*1*pm+VopZBy=TBV$(kmF?ae3XX+~6~b zeizMk1~e@^SlAW(ASyfnDD`TY(WLsL8{*{Z&S2|t4{w?fpu}+hLwO(8%hB^U*q9O{ z6XQ3JAH8H|hW()0w04j(%&nFE!5J*?Z8wVNsheDJ`M->VB&2nn?hkBZ!$BSNMK74E zxVsFox6^*P7Bvg#L|SUr4@jL^nJC>g)gayIIL+dp_C%%&we~SwuBEENX~e?yn0!Ts zwEi)3y@vJbN_$is)g)8z!z$YA7^23fP2*d%c79V0N$9O~5Grsur_b%3>wGCAIo8C@ z6y6K^c3_Y<(!Ok@f4kPu4}oH$+%CbUQnT98ceb7Dtv{NFsPo$Etk%sltB1>hV3n-F zR5G2hjCp9P;xa0i*mwIwv*uYdZ=Zgk1zrK(_4Ws`{7?OH^^ zYlr#r&t6M_q-pt2NGGKEI@#0NelsqY-08+a9HmK?b3eHr{+|hD=ImH?W_bS(+FmCU z21^GPj6a9%niD}zMuT^w-8r$+eRt1OcjV8NV=I@UV)#Z#&uO!RAH~v6w^8^*2hBaR2NowLCzU zD+&N*dx0@R*Uy>uN_g@|yiF`Ffuj?L-_qeTQ(shfekCi$^$Mn#?&{DS2P5JuJ+pBe zdH(l9W18^%wy|CFrL1>PT14rCJ6|Ezr=5#Jq-#~hvU0oIG~%bH3Fm<9*Sm&<8*3KI zc!5qemG<;kHfwp2+y4h+{`YHh#_hbO4I$s_-M8Dho9kTeZ1RNh{4b$bb&N950igl1 zl5CkEoEG4Ar|t0%vZIRSzd981Qk0!?c5(tj>>ixO>@cN*M z$2Yd{AB5-3FsV~rK$!1I?|(3_y08fL_`Bg;+Y@i{Sd`AHG3`VtxZiBMu!Sj3_jncZ z;1|Z;cLRyhD(*8%yKL6{`xHMV(^%~GXdXFV{sw{aic$1xL1sUt&nwRO4v{BMTX2q_ zu%y4Gr~pO)r%9?JF%&N+JSVU6?=32>C&qTyaPbBHnC-UMtCBKcpe2P$u1a~?@PA9# zYp(C0P^S(PL}1uI z>>j3DR$>pq`^m{mRkBsyIo?lYp(NX={kI=>LAq8V_w5^Rw7|y$vP3%(0+D<-`)?t| z@CbVxK-(GnQES?0qPYlQ(BjjD$XOmvgNTl0nmewIVM!Lh)QtRtk7OdE#83j#-Ad6h z;~7C^w~x^E4~JNp0x8Z&x@#eMeVUJk*v~-L1P?1S4DL=c&x-BLaU;z;>8Lc?ABN+f z7=Q@jh%n6gV=m~P67U{#b#l4}F&?5@3XP{DTvJg`sGX9{2 zoQJMcvnbd2CL=M(y^hq$d29?1Ne7`!TMl=&TWwvXXEe{mp1=TfuW+$eBQGYx60vCSGB=^E;9i;C+Ss;pF6>5J9}-2>r@Q z6LS+B&6ReFH$r&zq%I)(%U95%5=OMPS=YOnjxngyHledkh4)Mzqr{eDa_HP1vS13= zmF!@rs~BJ)XZh5B2f6uEM1MvaVfTPhr`&nEZkXl{x2@F`9s2AwD^V*sTUm-f;C zVWTR z%|_PaqX3y^d}4a=R+xkhc{)l)XTgu3b06DrPlm^GCC;4;6%&N6xIC7$br)K#e0{Sh zUJ8)r2at9$-}fS@zIzGrGNP`xo5MM7w+Hh(Z7ProIFu-p3e3MZNZJiWV0f&fsTPM~ ziX^It{F7OCeuI`)S6huq9pB-jX&}_lJlEL7p+x^&5e9u(#jYn0E^#8(H20BYw>8hx zxDU70MTQV)PZzG>yRkQ{;+!su|dNjZI7_5 ztJlfn19e(eHzDERcminKIy8K)p{jt#K~0nL8@(pt zSRE%A|8mlUT|+tfI`R+`&%eEV+$S+vMqgBX1u%Wi=LpPH`TY7Aa&PGRkwCV}dw3{<_!G3>q|;Zu_b zf1*2IG2}mj?fX(2ZP;fl2OhJ2#_GF!z8G6QbHiRy z;@6o#bvfaf5L@;%jSQ(C#10y&z5JXvlM@;wWYhGp~;$*CS@S!{__*(+Jcm) zYvL{w(#`4`$l&caP-`*zaDCqc+N$Bt4^I`F?mTb!}w9A)Age@!?k4W++fuoy$wmJ&`)l76Q&U` z)k0~DVbFOTZ- z0Vd{1tGB@e1E+z4$8|i2%5r4ow+YNLu)%oqF!qmaqF5Y$6SsiP4R!-lEPSUC$fh&GPBHuH4nJ0~g-Rs?0ntCn-97WbgF%B)R;G(4Jpb|5pSG}O{zx|P zr{mH-z=tBj4Acm+PRwtYj#l^P> zP(uZFZI9<`Mcgm(T4~wgrZE1^o{|kgEexG5@t=(*pzRIU&%Qpp z`FQl0x@{%<;EqS)pfFow8hh3J_Xq_XL{es?|5S?8taOU#z!9Qmdg{(|Mk9ptgSZKk zXh_IDC)3H>Ir=B@1Ofl8=+x!}#6A1i8Oc_ZYIhA2W&v+xR@T9CU~C#N6?P+EV@aP7 zhR$Yt%WQc~di0InJp#0}THZl<;4pmN(leY}6LxBOm0n9`Qvz$R^Y=CM7Fxdh-?5PnK^t(l0b%q?*Xb1kf=73z|V+w|_^!AruPv}Qjla>gELrp+$ za*U&<5YNbg$FJt~#A62(G)1LE;*N&Qrn+S_*qN&n5u+Vq-q%TtJG4xqbe)aU`21IaPraG`*d?Vuj*U@Pk zSLJ@}fJw=d|FkL=iTY7T?hLZwZijX$c|h>-~?L8 zkNk(3goR<5bd$f}z^Cjb>&c_(<^ZVgy64jOXr|)dzkgqDYvLi~-v4nw`SbSjm=9*P zMDIDocvFE~b%F8?xBeUD;ha7|Jm#BB@%#k%mrIARguRo#gUcs(lwFX>07Ac1jxk%8 zX-(YBuME!m*l6?MVBqx|_s2b~>@# z)t4^qK&w{lu;A1Rq3|FnV@5044-?cs`=~AD%Q7hB1oh{}B%1f!6MmA8F+Hd=ZG23U zS3&{EInL0>{W+{qv{w|Y^)U)g(R7!U5BdGzT_*KVn6ns$<8sI-E3US}GeP>q^9hUbULuR3_VlXJPrkW@KgHkxqO1qCU|4*k9eAW%c*18fw2fzXhz zBA7)O8{0Pfs-S5SWa-r>cNgqwDWXy_yTF0v9^#wAgm5|ND>2MYO`<0c|2;+N)N5)& zNp|y;Y6vE9VV_b+;`!{Q3+RHdfru^mr1PeKOsuVdCLXC~D#LEU7H+SQh@fGD_g*dk z8#S7Yy1IM$GY$E;3hfi?oso=(6%ZKf&Fa=m;ed)m{GI2SMwVipgwAVXt|`MlV`bJh zd4*scl?n-e-RPR_FtAO#q*mUav4raUYj-^}&6FU%b=8e-u_Pyo)rF_h*cKS$C>vzhJDUJxG)r5|Ey$yLjvKoaNKr1%JMb(pi_By7x!NptS z#6>Di=0daJN2knLxuE!5%}#=bo5r-DXPE3{&dkNn$&*6zJhc@@vxCf35I)+RVznLF z#yV*!I@VQz8*|m0Dp)3@9|@>MzZm5L5^Q$^IQg~L0zEuD=-$$dzy|rJJ-r{ds@Ej3 zeG)*7rstFD=aQfywVx1G>U~Kl;3%te-D22Tc(f?EJ7r@%UGD4J=qiO}O(SS;fV-f(uP+d5Aw6G>G|w`GJ@;)_9iH zXel1Vmsl1?24fv*nC|+rNSIeB+a9ow5m*pO=??oo0+|sX4rEl;d|nwNpS+hQUIO;2 zyGy_GSm|T>BtltBsFr5ca#`H#BoK+kvYz|Z@Lr#%^wYr@F$oTlOReW+uU>iit?K}A zbGcY5=I6f>AkHyQ;8H+MCJ@{K9~#my+&jx5ccRy_s3FHjBPz6ho;34os~cokiMvyF z-?Zx8W0?w7lda}~`*5L3g+iPf31zE1QDexgmw5Kaz6aWjlf=lS7wYGUkff_e|H2q- zF1z2&nApe9qxDx_4F2K}^7nxeRsYj$+K=gHXZeN;=xTnU=0`aZx%pab!C!olHn^2J zj&ZhV)NcOlf>kV+9z~$2JGe6qJb<2?pUVP0?ftFtn9CR!L1dp;}szwb=8AQoCY@USS8rBl=d(y9VG*cjd}_a z?qACl=dzCg6L&Yva+km%lDtVId;%(SFP2k=X$O3U%7U6I2gkE+L~=L}Dz)*E0*fwR ztK##G!jWzdQ&mo8@hhR_pwGGLptG<*VjN*C9T+*!8A60RM->LKhn*81(--mQ`&8Yp z8?U#?9J9B1o;2icR2NBEqNX#HQx^j#vL}%_Qo{nzONpuOT=#^;ZBn_`hieXXET+>c z#sY{9Tw3Tu9cD37S7StV3LEgG&g8Vgx^%U9Y$juR?w`vu5Ns}X-G1_4KWvWaW75N$ z^mW_{1SyZCIk1?E6APtyz@`nflB(u#SWn+M?{OOkT2IveyDgO;69Fr9K9bwce*pz5 zC&nLMth_O7LD?Y-a1<`4PW>MrA~!nhC#NT~5uK(B#tPqT##`LK50?J>Ryt+Ye=h!& z#-J^L7P=xYF82mGbe{FF&da;sLZAZ=YB=B+pvB_X zmIjz)`7b+5XpzYlW^U|2X~u;JGDfu-=L{s z0$EPU2mvDdhuJEr=@IyuY-3FTEXcU`6~ryX1&qHZEy&L<%tG~@o2nLuKR)<}H9q<= zpAQ-{SPs1MF5(k;P9pnWaJkyH)YlEDGWH6LNF4YJ8MNna9($K@A{%aF?ZH|6E^?Lp zZfGNj^MlexK0}}u({lEqySC=BR|gws&S4LlFC&BKE3s-d_{tWObJwB7;y}3R2X1J@ zkNkc;eMokE21|gMeH(xcc?}cA;rSSdaz2kT8K7KGVGcsFloz&AoI`&7rFB_==Sy?} z5+QWs%y8@ZiP@z1SA^3~j1-f(BOXyQi0hvCB2)`D%GUJekEpnJW|aR8n-&~JQ<17M{ys7x4GEA zXf%)xBdu+3wSv-I)*M&dvv(K`?ofbLufq7c_$lvv2-Tm3)dA8h+&)R?o9z5S8ag>KTZE~zu(^gBEPpp zr0vN0VV9niYyBtX8liNKh?gz+{|!$3U;Q!erFddO0earBWBTCFt?c zMc#_PG4w;eX*95(SG8<=9I?A54UWGuVZPB zm-$V-u9I4|hdy%)BzvBmbS#mYC*0d zr{8J}sKvCF+G*-@E)v!%!(aY(gb3BjL;+IN3YX(;aQnMe7IV(s4^HVRY!9?@q?%df z=dxhuO3D6xS3D_4g+-rBVdhpZtY$eglr35wBDAS@!r6Q@!TI*F2j>kr$E7BV!p?XvNanE6tUrp)?6f@&U?L6dqQ80`jjaDx%CVtmYwO$R8^ge zpRH}|!7!&M{pzmK__)l4?U}ql?m5y+*oyRY5{@FFhwm_gxxfghLGv%%%Zuq5J4VIS z|D+KfUGPyl4vgWJsC$$0{iq(OB6t@LZpt^t_v?8JfXr2y`ty_Day&5bd_`K7tT1h7 z)kUyYg{#pXGonF;lS2uvl%COwRlu=HFgilNFBPNR5>lgP7GS+k!(8zR@~^OQ{65N* z!xnKcrav3Oo5eJDeJ)YhNop>PrkGJ+Mg$YBA&69SA!x&C4PWG&D`xFdjZjKA?05AIPaX1m+GLFOj)TfPwTbO>xDT@9HX<65PdK=!+F5Ll-V zu`|@qpS%+xDm6!RAhlBWo(VefjDCeF&Z>8KG)SqEzzK&KXBxvTUH{tL+NK!jF&Dn; zFofXqCX=93=&K`7R9DS@YY>|B?kUrA#Ir!>kH{H2AUobbK}^+iE=D{}ZQo{->qKo# zT@gNaEl7@0I)z#7s%Kjb@N!%z7JIHHkS)H{qV4E;5<#)@M({bBFl3xw)2qP;PT5-8 zaiW97)j!7cWPSJW%)Klap$k!Kks#&1A+?q?ovx3+bcG7X0_hIAS1z?WrpK)E;nv%* zfBgE?btwfBx<3LoNvWB>NyxO1p$DUCm9@P&yIZ>{ipwe1!1sVZkwUfv0or z+8_1CRPtTH@rz2pSooc~ee9wxZdgD6<#I}+t2KEbkiw-8kbdF$(B1HvK03W!9!qMB zZN^wLR*p9```vV7=n|a%ZKW_)IcBc18+1JZP>Rf_wlkhWd+faNN$3U{33F+p>*2Kb z45rQL4_*EU#?8ygSRpB{V|&YEb0}jFdlmzq?-ThrnIwl24P|25n9~eyz*%8#@4E(T5I@n`!;!LpTSN z()e*33+bWko6AXiPjp^h)dkS<`OF-3N5?Y~rWb9#}D@$JF{2RsECs?59kJ@&*&dsgE?w zPp}R0vJ@yH^%6H6>X(tGq?g8o&GPVD%06Ob?bk^}j?Yq@T{YR*eUv5syqS zo7guxx+wi+gPNN1$Opeg!hnpV99G+(<3iKo6{9^|+I<|5^5#6F! zUU-K1uYdO%j9mi>;pdxUt@cAUCiV+8xs~IAedpM1?J({HOGk@fHx0x`Y+ zs%4|CC7$B?=|i}%>Fp@c6q^R_D$w*s4aw?7equq5;6kzW6wGaJ9}kIn4nEZjuayr$ zQtF58Mg#WqWo3Bt0rXyAarmW?&nmlK+q7oFzT{IE0e<2{p=ddx@QGYI$tJyD2xJFZ+JWR{8f%*-UZPsov8Pp)|?3_J6s>-euNtQ}y20ySjxj zvHOPIF8_zE^xpfHx>;HZ*Qt~K`2XdqUdxhMj^f{E=WDwyCqiEjO__HbSKFrG zJ#8Xt-xtn8um5v5*fbmoOs@c-#LKFk6_t}drFTguf6hPNcXhl!UTCe^$NulrX!i2_ ztqL?@x9bN>DVFhH3!(cHzz+MJ!+&Q(9DlpNUY63r3E%5AvG~7g621$uZxGA5tvpsC zcchYJv~+7cO<0;zJ|UDq1Tf80vXsoKnwpro9af?odHnKxM92zjX0MdEW~4(r+AZ*1sk3o3Z=*-X}CW!QMl&) z);cylLZuDSeuncrL3_7a9VKmSktWoV$4@hKdBA{&ko>FgRdbPy^Qo6L*G?VVvopC#B*m`I7=g zT!dh-ot=!8)Wwe?rU4STBUB?NDR|Vi!i3F~#{FHB=^vWr(n*b|0dg4(D#z6nY0rXD zi!VoFs-7MtGEk|=cA(J-ooSI=%lpZh8Tm$YY6-e2A|j%jAarN=V;gbX9j z)9CD@9ml@nskdA_B(LG@bRbp6;^kJWoP`T-$@>KN?jd9SK;1}ujtkTSS>ZK0AP;)T zz}JCviM}F8qFA8varj}5xNJY*k!asg%86wK(A@jTOg>n=m}yFIe5rLaXWxS=a6N1N zu|)W`ly5rVLa^Ep;>K4C95NVv_48oF$% z{9n((JWb}hG#_c%BK!N|2~%xb9Q+2SkDys9`%-!@8*6ktA&KLb=CjV|Op4Sibs^On z(**PtMhE=6+FL!hEjog{a~-`t4z!`HYS1jj?GkzJen)x6eg7cU1=4LIh@ zyPZ{NKb9wCvaG!Pv`_pWib1Cbia4!|03bz1{nwaX8t(LgIaoyP@_x#wer4#DTT9NU z!#<*Zk5<%6u2^uGzTdXA&fbME?=TS^L%Cf*f)_wr0=grBXCo?qe5gbP5M+pLGBofp zp%9@@dM4f$XIZSCH*k0^n*6%OC^!2Xq^vQs6735AQ-|C_j*I_9EaDMbD_0)d%%&FT z8HoZ4bJNjwph|7}P|5eT^#0>}=zsfy1p>QY0jkH&am6KFKgEyWA}IFIw4#yxo5oH< zlhDYnZ_ijhlR%bD|JmEj?Ye`rE@q6b@pg$tbpzj@<1#dJ|KX{f)Kg)OM=!;9EguOS?a6 zCIz30_R))2#k}fpz!fp2Yf1agp zrtK+hk~G!vJmwGxXU}KEA{(otGXlrwu*w-k?kYX%Als1vZcq5&N8Eg>K2IUz)9mSN zr{ev$JN@Q>p|agq_ctgLc()j{@bg@E6RX((>_UtfkGzTafmx|lR7X!9G0!HCRe&4u zXWb}qsh9A-lnqdnY-)FRE>#_!dm+xR1Mevl%NpcZB>Rq6v*vz%9L%8~F5(j-$AxSM zI~9b>OfR4QvZ0;Bf-LJlcxGJU{P>b%G3`nkvEsfp=s6$H5YER<+4=8ONsvDXb%D%J z-{QRM$@H&5On3*9t?er^BI%=2(8LTs(AhDT7@1dk`bYR0Z&+}=5&1w`7_=(e5jAg% z1JxWHqY*$~d{szVh&D~eLzY5p1N_=+cSUIPJmUL%H3EoGYT7EaDlp0DQ9G0yNZgh`d5m<&st&MVc(^8B>AhbK zs3kuW#e{FAAi_RC*D@2Q=>-|LX$IKOQch|Jq@&q?Bi~R*fTWb8=G7(H?^KVxl5*>y z?>>gBM(%`yGoUtZ<)J{J-AsFjz)XPW#V7tuJjgSD?a(hE@egi+d?cPeuS+7^drHsV zMF5J212bB7COb`eDE~iijyOZ!>EPxzj2smhhW8Y12J%cMp)G*ywQjw@5uEKcGs=Sc_}?if(F*Agq4bH(%4nj*cPdPzJs_gy4!DjJG5lR3- zlkXlG+jXEz049{lH;=WVX={dSgW$6zhV8Fcr3=G#TTQ|JzwIGO@M5+;SS3UvF60?m z7itsrW-F5|reAWMAKb)z?SBn8)$M!s z{JT|UdP}k%_y+ZAkA25q3}9ytZ|XZ=(e-+BTH`<{Mw=Jk|L{hyr`8pj!t;UXNAb=9 z`=sC#B#{iK_mS(+Ys%+DB#kQ1F}vdTW|I5+_hyo-fCJIWg_`%ed8BkwClghBXEf8@ z?QeZOZ-3mJjl0rRL|(bTznVF0^?WKkKPefPl7qM zV>pJHQj`WF3m+s06?c2#YSUjngU{sqkg+^`x}=Qrzii zxm#*(vQi;7i#&9yT=J>4$7Eq<@|k~5rnvCKkv$-@^b~QQ?qpLqjz7ZvilX%%gTD;r zQnPFOy2u+qDN68~{=Mt3tx92!5K|&NYUzYy6i&r&DC#I~0)4cV*!dIkoPtzry6wd- zn&2QsX?d)J-maeQK_1KJPB+&3I~jNz#Ui+&AL#f7V)$WFhJoruc z&92q7nmBBcc3!8J_{z3}R@*UGXgKmx!~2u1NnPQC zeq%Q!AN-YKp7W+P#AfIkJG`}(jGQ%$&Frs47^73u4|pL8kd}I4DsW^DS>p|TdUZev ztMn-Z`8%i#yNK_*x<4!aauf|&b|kpAsQ+=(Y$~ zR?Av^ZmuTwOk-a+33X8PqMW}tTc(bb#r{LmNM(FRnqZk) zlMo&qKSrU>lG@-3MLgiV5%|Nk-)+M{!@@|AiJP3xkGU?%3=2RPipbyMqb~9ea;>c< zt#hW~Z?YhV8m9B8HN;RQ3lcO-v?EdfA!W>DbkaU@wvWA?;4Y$qBMDPSOD-qS#8JRq zUPyxs@!Xo#R<^47)Sj*G6&*oH?qS$G=jMu+pD^W=AjtLyrHh^#LhXZQTD~T1q`-Nu z)US&pdMh#&x*W$!xvlYfuv*{9e9m47O z0+0kgM%c?uvZ3Ck7eRI8sxUxnNx=NdV)~c*V%-age|C z^m&mnp?E$LjvM|r)c|mDLFblJn+KvAq+Y6rcn0HIALEJatD|XzwiqxBbTp9`{z04L zA`tHC{Ikq3+Al1h72@2TuOAJ(wysYy69QrHe5rqK4iCfk+@#jge3gR#jJ-$UdAg3U ztW3qC%h{+$Buqf|OW<6v#Co2D;X6|p2d&h=4GrUM>@lBO7LmM7GjPEg-1i2teqPgF zf%oir>&uhq%iNIi%KmYGHN)m*Y7SjP8V8SuRjXK@8ByP+9_LXFq?-Q5n_@1=AlEo+ zF5dY7^Wno68A+`hTZRq9pD@=^p@t#%isFf+fmDGH{kUzNKkjWd{mq1ehQCFoaTv;t zX7u;D4k_D1x6NBPCYI`IR*CjP$w7oC?)h&txxVX)cF>4K{RrU6>=0(LmysV%&9=QN zi2+OXXVN37w_yx{+R}fANLp76H0!pYC2(3({@0rLb3?&M#vjPUaFT7zl-RXAI3rIs zS9o4J2tOn3D9uM0L(ZR1*=1y!`&;4fKUUZHEOQaR#Y54BZ0+-YlO#grN?ixVK0W*N z$C6J`r~~*9_$A2Au##@ZQ9}_{d)z#iBPpEmos_N7vW@Y_qyc2lF8q{;`5TQ$;l4vD zdXYk!UG50Fe+ABz4F4V0$BiMoOir=3Qz(3Y>=x3DSB6I?)&f_>;wRnogscUrAAiZt z|HMk&edUABz07CW0SzG4?|b4y@*;oE#FuL^+f206s#0Ravj^Xb1{-K0h}m_@6t8?tgRJYi^q0G;8Xq)a!V^8q`YLWGpEx!e5``)%Fk^iGs56j=Khg z%;9dXkyp3RgrMvf#NGV6Uf6TDlB-eD09^+pQq7zS9<&Fafz}Th1N|p3bi&Ide#9Dt zd75S$sU0Jigu=7Ft;Rd?i9Q1xwHF}%tIYE!!xQAGX|4bmPDC3l3*oVL{)<6`oh@?heJFxO;JTr?@*5DDLi1+={ye z55=9!Z~xD|d*5@;i;S#{k$1@&-)B8@&d+R8$Z+l(^d$PGpb^~#`jm< zFD8AEOb){Kq&E3=kpq1t)0yoU-y>Aci+<@kUuzlFM3gq`KxIs80I-mKpd2bNym+%-03p z=InMc>$lFSefdUa89>d8Ms%M6t3!z;Dj$!LC%uooKx56Z|50&tgs}X!)WGb?Y%Rl2tt( z$CzeR(0z~+q*U;9!F$x@6z~dr*B_ZL7D?m>hCe}E4b#^=f|yON*57Ixee(0GTBAju zx%0BsqjUsMlD5Eyq33!kL#_I6Rrj2(E?lS}*DLb(zY}G5|6-LZ$`oima*uibqtpFg zBI|eov+q@h@AdNwp_5T*_GUoi$pCRQdusMLrx4u45B^qEgwa|5mHL(eD z2sW+pDuAkQ92ZAa98()*9E^-nuQpbCINVg&!O<%bg+&Ll{%w*+X*`jU$-%Dmq~}tZ z5Gpz}dr5~Gz}gP0_fX6uauvK7E*K6FRz+4d6hN5zqSIt$f}(XJTOE*PaX1Ynqi}BR z)wsuZ&YzJfOQE9#y*Fm9`4M%}Pl86@tIZ|P6+(jk?nX47KI~wd6`%@JEJ6LqU)<9{ z$EFXrp9U>kqBzYDut(^U%?g*|;=rz0*MYq(AB26dO}ET&c8BkEL;%E*#}J&Jp$Ij=??l@Ec}2I3Sb~0rLWVPso*j zMsvv;Yq>6ejbf^-3Oy-A5nMvG$g3hHmCj9VAO#UGxgum?sA({FEZjEo&Y|dh;shLk zX?{^WUP2U93#qP3xq^Kjow%xV0dGK^IQX6T8yiGR>bZe-l*EPuZbxmF!QpGRfIb-} zzK5#jqrATg^=yGqtR4rJTQX@ZU$+q_j@q7^YaqY->NFE&8#~T3PmWRc|Xf^Dp!FO16d?xI$c+ zkJ3j-3IqV-s|(!!9Sr`QnQpRV@ioB*yeZwK9WRrW+#SK6>*|7^nrP1t02xO~_m3g5 zJ3mwhBJ~c=>iJ8bDO+8hi5;EduC}4oqEAN^hB#q>rDlL!F}kn05gUeA*7MUj$ty)2Lw}{CP)4;?_nV^pt(bJ-;o&M5(yopTUg1ePv=J*W3epvSTrE~Qf& z+B{94%;muK2yOELU}uLZ&5L}@!igKsZ1(<{aBwfCG@^)%^i|DI1oKH4Ety#c*Ku^O zIyRxW#Kc7r-#YR}$zO;w&bQqU{Rv#DuM)&@-T%sAb1>qHlodNu5v0)%Hq%fIcTl}I z^}9(Tg)=%KA1oJKW>&QKRC7R|<7IbSAfHlV^!X-b;c!nvFMlM_Z&`WJ0#y4T%gkt~ zb6jiNxSgQ+sy`UK6+V6_f6w)o5kZ)P>l3#VqX*3+oh}g7Ds}_>iR?Rz%QFTbHdTm+ z(+h!&3FJ6pv4*j@q%mMd!AqU{T1Ky=-tpw~Hfn(4&g($p=|hsC=UVu-%e?Q2hzv2c zNFMc+Jy>4%##tWYqmqi!{N`IXl+Q)OhVtE>SDv`<#c|R{#4NtpSt$8mUCWEP7Xw}A zAG1LLbjN?r1yYaPNs?+Bms1Tz&Zd|nqMrm>_1C+u!~OLr^XLDx0bZZ=?dvvK0Fu3$#DLZxyEdwV`>a32oUJ~*_AdKQGhfh zxY%(1>LIDqCYKv-irNrw57XvT4zv7T*_SNV;H>h;T&Io7v(lAp7+D}=4BtQ3p=TRg z@Dk6&{tqmjJ#I{qZqwqPVoq~~7QRWhbLUrSya>4nI7|AySK?)_GW&trzqz6A&t?==OfYBJ028AJ_@B=ICT#)Q`8xz!$eX~72i7`16<3G0Bg zZ=O0?)H_~b%50h5m41vvPJpH|YjrX!$Dh8A69%7xHnANOq4hxzXj#CA2Sug-%d(F( zs}6w{`r`q)I3w>O6$0N?afUa;y@YPgULXFnT5o*r1!MAi(W;ZBNqEnHToL87xC?~` z<1Rbb?vTiw!Jd!3KuZ8?JM`TaqP6rPr-`J#p=-v(zF(|#*(8=C8-h@F=QL|CNzZ&$ zIXFMVr4IdPN8#MB;Wb}leb7U2nL}HGK#X@2=18NJnNyCkgeNbgHmzxuiGSgYbZf+E z?|vjBOnxUhf(KbS+6QR3X@E5TU zcgJEHU#F+(Un@j%;0`(K?jT3AsO+qu@aVSO65jLv^#a&Ol-dByHXJ%{on%1z<=xi^_uqtZR0v(XfQVSX9`F3)JVW%C z=ZT!2=b?X+EX};NZJ8@^bQH|8I>dzU8s6a7-aq-=`oR;4V3$qSWFUlP)jwE?6@BDt zBh2T-1WJiAR&*c0>gO!4s?&?A%&^wK+k**fOLaeo)zXRb%e57I0vt@-u%29Hxciy$ zfGmFO`$6PShX`J8my;WDa_ylCdlPGcoaM zTw=JTIZgBMmb47Yzql1dxHDN$CT0M^>qr80OR-t^6(fN$Y4ZFi7M*>BC}J zKzbC-D_=b2{kc(b3RGm!JO+cyr@Cy9vpVaa`hO4hvI@};2$#HCK*kUuXXH_vLj_`n z9?jj!`h3%*+9g7+%Fnn!@`#o>)I=XZ7+_1yz|5GF5P@m$F?kQAi4Zq%kLJ=K(U()hHV0%-ZfK zExV~O7%aN=e}j}d37EvCnUjd0Z>9KTk4HzGHg-3B8YHeE%p75mtQan3fZN;km?r%# z*~|=%AN6q@ioMhXC2Ll2Oq!Z!Hl7|rohB(ZZFli}6QYM?dlGYfZMN!N^h+$XUPL5S zO74dk+l3##ej|-NE39|FH+Y30ii>OJQ0vIM+5X%(u;Nzgt%1c*PDxDs^3li8p7A~g zujR7#)_Cub;U?Yu+sP zH}?}m5c=}(#yWb~_BtVS^rHpf=E_lYT?kKA!rJvw4C}Xh#M~;F1V1YclZ!)YsilAP zOiQs|#BsC(EAuC}G0IXM+^BhW`|hb3Qax3-r5IrVabNUn##@6BEiufz$VZ*!^a4Q*st79GEKQQZL%YcN;ga9ndZHYZw0{NH3sFb z1)pF1Rwo&xf59NRKhQj``a2Wj{#iL>t(2Ky-h6<2&wn4<{W2=%PG)|r?^9zgu5O?| z5YDhbS6l^+E6j8@Grt%Vzbb@$SM^#k&J-oqI74)K@_zp!<`_!j8eCr$j$@L%u3nF{ zAcJJ|n}0{+8Wpw`#Aw8bj~TB`ZteW`7Jc%_&bxvLP~!m~B^!vfD-|7h8I5%iEQJL5 zSqa=~SuNUq9;7X#c69K+>=eAkq42-4eVbtwFVAcxveD&YgA~vTskBJw=)(w_b8&xc zZHio}>nL7|xb9Y7|2eRtee9hW@NaTwE>=a9^2r~#E9h*&nM zqamK2KKL+ZU}^YT54)X{Ao{`AdABfmxb3@n;Jj{Ak#_g>#SCT9Bb&QbnKx0}HVuAO zv+i3aB$18n*zBm;QGUPn`OFpcAJOr*IDLmRW{)-QC!=ewCQDFphP{Ey`}L9oAa)2d zeiK;MfMRtpbR*S2>$@2}&u^rg!nsDUzbNXpzi$t*$n+l8Qmp4bp zO%T=@*WCIrA40F#U;Fc#y%Tr!BM-6mQP&Tk92wZ?cQ2!3CAe2ISPLA8^#jTjPg%Cc zww#7Ip+f?hK-5R?GYkSg4l3lAVyUhsVG#Ck{gJPdV37h~iNN@vtCt)<9|RO6G25Si zWyeD9u)5pE6_mi*b-gqnobtOc8SdTJ4F}(UDtya(o?e0Nqt*~~^VhQiLyEFG-Tkgd zdzP!$Krb)OG3O1}t5{gqF*{;Bhdfy&M2RGlstF<6G=797hsD$3a&vSW`npu!J}z!Yw-GlcUvL{Rf56Y2 zcL?u645hI1t~e)TCy~4OukUBP5mHp(w%!@O?(uF&E#!v<3_lA!{!*ue%IFfX-xC?` zMc&yA$(3}bQ3a~JV`Ksc`0|HD3LUXxNfOgo5edM7=y9}nLKh0FAF?}fP2iqw>;cP} zPd~Tb^>3$O-wu`gl*#+W7DkFtPBO0j;oO}DZjCCi??sB7S+laT|QMB`t0 zhkwYmm6{N}d?otFWGMQcVDDSVd%36zsudy`fBGccfo*^M&)%cc_i`R9}1w=Pd+C8g$*{}}&2e|yWPgf%`3id=g9|JFXT zUyg<6@v%O%dOT$%<++B$AJ-JiClAR|k002}+9w9l(wv@X9hKhzkKR5jsu3Y%HKBS5 zIMahc1YnolVjZRAe@f86SGAXP)p6ze2IeIhLg1P3N!~GvOT})nlIhtU+P|6(?wY82 z70`G~N_NGCsZ(#;3n+mRt!RfC%6loUZs;AE3=fFSum$k|HSpl@KJtYr8MaxC$TeiN zcbGFtfD(b06F?hLp7`L`Zo9xQq~Y3@oYS4z1`UC>(NUfB*>C0VG5fVIc9t$DV#j*!8H%xo8lK= zze(fo4-NEDtu-Ebov-5YEp>CMbzYczHaz)5lB-4eswg%J%v%CGH9Wr*H1?Brx_GEc=6LPJJ57v#mDc^}hITM}9s=u)eu)6^&|lO5?vwbT5)3VQc&WOq zZ-IGaEL>XV$i>75l@-A?_|2SFk?Ao=;mXA8x!;I7)-jfFimkA-8$)=ccOt6g7hgh? z;pw#BE;HLX5-qMYDPqy7r{){du;96w@Fnt&kKxWXNwO3#j8&b-m)M zRk6-R_ZXrFYtGr=HX1`qQf>-#5{_&5X9-m)yqm8mVVEr*_9_u^fD)r zQW?yn6Lr%m>|OvTo7f5x3iG#!4x|*91LdU}w80Rw!yyYnfL{y5-P--T;D@AmLQFda zq2N&N@i(|Ua56Uv8{?q84fC$bOiJ!?k7+RVoNv}pzS5Vyjh9ijsxNW{D~(APVr$<> zbr!TwI!e&rJuV2Tf1QLl$Yequ?LP93440hHGM*xb1v62{_@ALr)D>9RRKkUXkv)VhfVZbYWPaqgOo5A z6|BGR^3k!;alC`%o$v)W@M@f6S!FCt|7%g!(x3S@CyN;Y$|Sdo1%^tveUCj~6#zm* zJB*c4Nfsq6@!~ZOh{fn`#+UdWrz77#D5>L$1GPJ~+!=JYZuD$}%?vJ*RGkwqBnzD_ z4ofVTF44^Kp&)XrsHy6I>imFvu{rlyHAm5O|9y*gbg-wS% z{ZhW-pej`$f^2O4P>11Lr1BdVM}6$D=p80xe_zzYK9YE?&z}LOZ-tQyV@{vpFV&qv zXHQe`wgOg3C}y5O>Z?)FtqG^u2X2F8H-ZKMh*{Pb;h4W?R8d4Gov4b7*vk_R%|e^g zRaRd9N>Y4vS|^!uyNgxGHmy!I`{ zDI`kXhtLXU&h+*<;uW>>m3@(N$O>yR4!Q7!6lc%d6`N9D+>QAX>Rw1aIP8$0Yv`z@ zowSP&Q*H0Kn%zCocQds2ZiH2yaAkY=mqdFaFbmzoluf~{sp)Ry^NsBNx@)Jz`2CIG zn{QJfKWW}t1*_jf6IYWrVxGrxmizv^uLjYWM2QoB*+jX}qdlq#Hj@b_-4-0+M{D#k z(D&}o>^rLen}+dMH0ZgcA6)@(msug2rYx}MQtRv9N#gi9#a0rZ8v)G-K5ogDy6PPn z1nUr3-Gvp7mRxaK03KZ-VRz)&5Q-mGFU|IKXo<3p5U6)b_~ESfH2}qcQHBw>HPz-s zv|I&=me}F>(7s^0xX!gPv<9MI$|Gf5EQ5z#Rc3KG{MeHZX87G(eS8p21^I%u3bcg> z-BJVhl^Lta+c;-TinDPDjeMkR-{A*MUXQ^mmRqIqXAE=#5F8I80_L>P5rZ&Rtn|%DI8K z%9#lrnqnc2K9`&$h1=!L7T=M+g(jr1Fo@%g{fqe?LG4(2pNqV`0yxr*Kto>|m}o_&1bJMx0b<2u=G1%7%*C z%4G#%7PcRLg+Mgl(HHF>SsVe2ZR$>VpNtN~dQiHi1>-kx;_BkfKc+LflE|#kW#vcC~w=@!R?# z!_7U8>j1d-(AjNVWErH)YCemUQhelDQWuefMbUrjL8R=bY#Z1Q4i|YGR}*zf0$nhm zlcWR-{LjC@e^GDktw~j|=eo<}vsQ9_A-Cc3{-qCSrfg<{hPoFiHhnV7W!hYPv<+JW zz5p6Uq3(bVkjvo$sW{2q=~v^C6xL*4a%$&G?x>Z;i7&cGFMZxd_+<|b z@%NrjwD*$jjnkfz!6=d98icO;8_cYLCx1hj(1`$T;guqUn}o9Al$#M&V|2~)ume(Q z^R1;8anUAB$rJ_Rg-6mG!S6?xcVJFv9w|9x#iM}2EtlP^FsJ2Y2 zT6^9hXL+%S)SGC_^&OdV(tGiICM{4D0kDS7ZB6ZF3z< zdVXdjdz*p1>XPN|@IM04v=0HFYB}&yr6?V}N~h%U8D#3QQys@^cPUSVCWj$NgV3Tg z7`XIVtT)+;WhPon@P)`+;Kc!lw;}RW|`+`XBsgaaLAqt?YCcJdzn8F8(9Ct~;vz~Z>)*0Xc;u=H6CXCS4AMEpvsGbUz@jJW>6~va z2-RI!cvODN%CY^uL{W-8NIc|(Ji*4YVHK;PMtN$TALxKrQrfT*)G z8QplR35W0{bv1^t;TmcS=byfhQ2;!4_LXFH1)

    lIe97tg02OgWfCH8sH!NC+yGiQg>&s+UcSB7tf>1P*%qF#+1 zx-)OHwqxNE@!P?W1W2EO6LgZlL>Vle{Z-|n@Lw-h;9t`z$m4lnR73ym5QN8}yietf z!Ua?hg(dV*mb`HLgIR3%u-4TUfTFP%?03*I7b!qS2;OAmidmHdoyYTE(V|iMSfFf6T+wxmOYr zZ#bf}J0J&tBK`3VOEp`f*N5@1#|GNW`>CCp2i#_lbJPvQf~W&>aSyw9Il{rareiEd z{u;0f1*)-eq(4_CAi1i0F-D@jGShjw-0Bl+G%#qM>Ixx5I;EH}s4Q6c!|3xmdU^qK zWJLv?zqW>@+{)vJu~`%)?jxe=b+R-pK&5pZJ!W6KHfB|lrZ}U(A&1m1jCCsSFhpRi8m zj9Ts41V~knYlBExJOqmwMXUp*$$UdD$}^-_KYM-_42d6fB0cc?{4_I2(i%(`pmW(`6zDqG4v+H_g-Ul8FB8qn1m8 z7IJz1R=8u4*21Tgd;F!%Nx&9OMdDvk^8s{rmGl5m?-`vd)RtYvV%SFC$>QRR}=*6gP2CZ%+3R4H1Fz zHmYv~l87b#$Bw9~Pe$>>evh5_my{7`P^8B^-{<}lvxTx~gKLaZ341r#XGVEy_`^x_|OGp@1-QTb%@iZds`{jI)3|2&P z*96+`k-hsVYuuxCN0$Ur42Gt0D77Ja_pl(!5(_UsavMruWn82QB#Vd2`bHwPTBx>) zOCTn5)SIls;19K{mC&QgBMqf;x0Lkp4{yh%xGYF#U}T-U{JJwGWOl@CYjSL()gnRH z#$Xl!Cz&e8X6B7G`_#R8o3x7L2xY5o+Z&^<<{ETO)qQoC5|D6gxhu@|AD4IyotjD# zPi7eOfPU<<*8F&^4IAe;TwQtMvoOYa@>{yo+Isb=Q zTk!Yc_$vF~Z>Z^B=H5i}IgM6HL%=CTrToXEmCOR+dd?i*5gM+w6pdd}D zLtv;vZG>qJ+*Bf;b;vjyNHH1rhyGz1Z-~7-3$-jNhl9e@K$Te(R72u8NQpkRfnF!* zw#ZJS2*oxMwGRILNwLEa4#b-4e2ZBBaOtlFmFrAggv5A-;%*%&^>!X&7#eumx?nqT7 zwn64cNwL`V+gKpi#g6fjafv;=;^e0Q z@4Tflr}JI?34g*c)N%s-_e_u~fI{w4^Y^tiidv>@_^Yd@1$cj1Mevnof6uhFTE}7oBV#Sa+m_YihAf zk~oWym_th^+z|QOm1;=7#jC!kyh}_gN-p+X*!`%nr-J>XR0f??S}}T1!OxrGlngIW zt0h;OY@04qhdQOU$6Qg>^A+@RqggNadFKVDFI>Rdr5|`y34ztsMxDk|htPdu@F{^F+ z2tTV0FEr_Z|I@MTsHG(zc|!k*(Jr@aD%p|d$kHcZEmx3Zxpv5m=+IM0djQ?X?DMwz zF`;tA(9xm@Wf_Kf8&W-&%h!m1I2zc@$1X!%@E6@?*fWGK%TWP=3-wnU<}aR&kDK(@ ze#+Uq2$PpeHl!*1s&BhVQQS`53-)tF{hqj{zJD-y{TAKd#$@hhhVM51h<#dk>G0ki zx?ZXU7bl(uB5d4(10m<4Hn$%huD&2om+P-fyuKt-)T)a>A=gu99W>KXJ;)O;$~|H- zXA#9yA%nr7?V<`he?a>cr9@E_J@MUhd(S}8uq!PtH{#;)*v9X z>%ZZa()e2atMA(s-JgmRwWIFf7xo8OwBIb`Hju^Xwc_Ro{$eu?Q{=Os@Xg4^(ZLod z`ag7_aqHb76Yk=mZq;M3i-v(`(02WY9f41^Ka^QL?{sEv!6Hq zsys(UdFk((+OYN0^MJ9o9NrDK10GTUi_G;BRBFYXSwX*^C44r-z}#MM3FEhV@?9-P zjX(|gR*O5Kjxy1|4?0&AN>uO*pAH5Na@f`Id_IKLY%^;{7{G+rorRBXTeL{e82$l@ ztr7Yk-*i7w1I;r+@L)R|v?Fqjb;`V=7mY>xU$b;!D43&)q=b5J_a2>qUxBYGF@iQ! zAx{rX1Q+Q9RgxVK43GI;=$iP2ooFibfjpujy3OW?QoN`AoKFAN)swv7nMCbpz#RZ& zia5qhZ#DlM~k;hw?+BCekYkGTFByfsrDUH9hff*nu(iMyi#$Cox{zpV$Q zCTW>@Vr~x3e}hQXA2C%gYHmq&MSDSO9;FqLDI`c7t5xIkLIBVo<(${AeX*aAimGC_ z`6jJs_2e8DJlfXMj4e=^)lc*807E;S+1i=3STxe+y-0If25l%Dx7&z!Li>r`CIp#- zRPsHUf|CaqNn0GM_N3w!C`wcGc;uZRrHRoTCWh5AQ;~nf6Ch+u)*+p@zzs30GBYnW zDXsF@^#_ifew@j}EOQRN{dX|QJ7nS=pwyA`QEd({Cr4s!TTq>TCkXsmOFoTL zHgcgzx}F{e*qW!ED_jFWLP_zYwwZMhG*Tu63qHCNBz7u62bv@^bzo}R561#M?X_;M&mA4BTj*&E*JM{Y{*X1;6;-p)$c6#~t z!Wc7x&Q#xUkSCrxd=}0&$FE->M6bgDw!A_Cqn2nuJacM1F`?cCaM=p;A8e@#YSl75 z=?aa7_{t~VcM0|tr+R9#8qiQmME|mMtQ0&Il7c52G7w?Ub^FrW?iyj}gWI)-+^T3k z^z2{2CAXNp91KH&eW*EDMH^93sX-}m?jY|ymood(Zk}a>y@0eQGK~=S;}@9hI;lx= zmwH~0%lQkxGh6Ksjl`I-V4=2D(urA7!B_vrOKWPiuBLbvK&Gt)vIR{kNa7hzmZHFL zwq`ABC}x^Q>2o>$7?W)g?=RQr-zNo$khrdLQ>rl>t1#c7fnt$@qjsjTrgyxJBQRw| zd`>02TAd~QkGh!k0IAjBs=lHUtaWJHDvwjDBm7YSD=%GULlB=6>zaHuuz(1wHbhn5 zXPdRmsa6APqn*aywE!HRG!y~m&is7#LCGNQhO&|EJ4divd6o*b5m2)o7;3hRLwAiz6r1~|D@Flcit&O)H>_y%q&ctK1X3LOU~-IqJl_iCGur0xi1K6PV_6kH zzy!QbP_~!j*egY8wTGA>%6WNOD~6vcQkq8TOgkWRx1XMi#36n-?txayVxW^P)B=4T=M49Zlr1U9f} zM&x|5a^v4s#S^WqQYZ}X8FPG&viFzl>K#Ou5&SB z>_m@wO4)TPw4qeFu{R#`b^h`2`C=s{WST-A?}{xQcR=)B%&GS$i|uD44h(j(WME+6 z{^WIZVt+^J)xhD#2mJ8r1RA`XSH#$iy@Y)Cp)Q?zy`vNJCvSe7m$x$F1~FmSJ;$tG zu8UOR^K5jhqivX3-^;4|^3O*cADXkK$Jd$U;XFxnB14O-r3A6)0Kd(xY*iQg2pw{6MS39!T*^IT_kEqZS(nx;$2rOEBPd9e_F(+JjT zSziU@khrczuk&W=DW`13wLlxHM^hXI{{7-T<0rUzWDTbF48&3x5#u167T2c0-W6z5m^*eYJJ>a#!t%i6U@TDD`b7 z#dJ#&>-EIT9m+RQWZd{lpn4)eo6^ugO7U+7OBiD2b5nRU(Gqx>2Q^uJ&Cq40dA^>P zwmWC$0dB>|CCtHNiK=yM25^{yHtOAvvDnK5`k;8%FQ9%Udx^rrik*B8{10N|4>9&eQMrVGi%XI}p*x&< z_%U@0hSsR%B6LrnI%0(2HyaI36{o`(i-xw$5CI9D@sfrdl9H*pwF?U)ndXvMEzw>h zb6_bozi-J{A;?n$U0+vdwPfteK0?=>jA^N}BtXK97Zr{$iNPbM9v!k-G8NQ4>5t^` zi-@HEk(IZaPH(fL7^1i$$r_Y{uVijlDdeKg7*uQ@pMQ1BDElh7B%kRL3EwI|Ukz|+ zB^cC%0Ho^Uu0r;3cvR)IJ*NdU@2JhPI?MOUqPt~#XJ1PG9ocKym z!hus;zhNGG>VOt+LOs_gsHZ%S)2PNE?3X6)g@HL=ZK?Cxt31+TRuAG8SxfZzx1@#E zE30T{J&QFgboFaZJHiMxk-E{&XNW=Q&kJQ6zs<&ieG6=rRRf|-PhrI0AIc-Rn4{0omJ#aenOvZR9!zK4VVob(3YD z@Vwac@z?cdW#IwD0)#@ai|k9ZDS@=4JP3cP5vLa|yTAzXn>{pw`g_ueFaKZ`8x$_O zd&2KePgp|56=BB-4^%YD?2zh)x7`W{o5+3LG5UET<{+b5dYaD>;89RmY$Q+RMDh>? zaTT{%aM22kkn2kP3kz>!m?(eqfRe5cbyCAW0W$yHFO2VB(j|OKb6475q+u61Xy@uD z+?{_n7u33g0ri$$1kkI-op3jDkyz#}1$tp-dwzFTe;bljvTlF{Qgy`h4@@8WwUppG zDQJ&#>RSZ-$_oWdMo;ieHjFok*`f8tuaV~vCVJh_l#W#0lkD*pR`nnp^EB2^=Qew5*9phbtlumZ0PP z5NEET_sz%%k&c&qBHi|t#6w&2gSqnipHN^*yJeit+Y0q3%UWgGt3IV-;ZbJ!d(tNY zS+v3^&&GKcDL~ZvMawl3jl!it;+4huyK4Bra&jmA^jO$PLf?7bE^@sI=ze{x)uK;0 z-VW}`Uy|TwUZ`JG2@=qXy@5lACogk(D$o|5310&d0Z*jzw$m=;pfb&-Bw?q0b?|Ab zaE>>_;feFxY#5D86EoKSydC0M{t=n?OHl+&k`AWm!@k5z{W5)k7LJ< zq53FaIY?t=ap02&nj&`8MRRhX5{#Cs?%^w?Enn&_w&u0#{a{|`qsoDv`CHH0C5rCv zv3;&FjTKb)E`DSa-nHO0r4Gt$-ubkvbx!U|kN63WH$22`95Gdc&W%;I`G|n2+k-Tm zt|=PhG17YjvjvYvLD;sk%d>Hjh;orhg*+8RmtRS{(9Tv`IfU6G|Hnt`guNJR2SA6w zofuaR$Y8vR&WuPqf!C3k+Z*WD-)+IIG$1Heo6mOUR7koB7-!|<<@d3q}FOd95p#SnYYqrX7{C;hPgx@aNL zXk50_zzp8vpdTdd5NE{m<1YheE(Nj)xf` zqS=t}*!|{4a+s;_+@kU92|t$_d-_XK1&tzmAZU88T=eTLAnYG@r{C25iC51>e$F8) ze)VY&^%B?S*)D%75q`F$HrMs0Z_y!jXJz@$eqp}4v&cNe3hXiGmiW8b!M|jpK!I#N zo*kB(8~OISX(tCRm;Ct>TQQ2_6T3uB-S(!w57i?fCM!p$Z!y5#ah%8Z#T5xDwIDIO zUp6LGii$nxK3wdodrfFBHuN44J}g%b$PU0@P55C1W116#*CIiW=SVwj$EqlQP0UW8 z%VG@Z5wg-hf9fjzADM>b%;&rAlEDV->1}DG+_MLz#q|HM^;S`B1zfvsv0}xoxD=;Y zarfeG#amK}6(|G`R$Pj^6Wl2jcZvik5FCmY4Q|1Lo_zm4^1LZ1wTH0N*RJsw{0yu!Am}Auz9?5$A<0YcpaVMVO`5^G0e*2xaKn1>gfq%_;1V~WK8(YnIw*6&*U==j$Ta; zTh`)2BD;c{PM+hEBcWPSPGEK+dE>OA!6n}#rB{5)O-K3U9E?d^j?+P_IP2zXXqmN{ zGw+ZZE%AK%*3!%wJzx#y_sJ&HVh*#mtEO#P6~^xyE1+N_=j!n zY#WA!e&_RBU%*fk-Bu|2fc$2g=hoWy{R=^MT5ZQWi>BJVW*93ajPuInCid%+1F|g?Vo@ z2wDLAdpyp8SjAm#;?bz+ETRYC^qHDlI*;?qkf$ieAu?{IM!uCkIf7_nV4brrTe|^v zTf^W$h+X)s0@b@E+M7%mof>scrsPW&NJ zVq0`U-+zyT4JsG$A#9V@{NE$KLwHJdS2du7IgEw7G}NGUE0W!}#!DbY>wPY^o085> z`g^02@2JyMB2Kv5O-mcUsge(tx%?u%F^`t-c60nj|ya)K9&?(7Su zZW+fVmnE+2^MH4ptH#A|H3{#2T(DBbUE$}H%DlsJ?rc8xA z>$A&|rt3kA-^J;^W(a#LC*NxmPCAxl21ukYzf>VJni|_O%Qhy`VBROt%nQGz%Mg&w zo4%~l5}xp<0cIymS_9+j2cKCBU-^{T52a!GPeh0Zjl3~*iNN%9v*WYa~x zY%Pu3?ES*}t?kkQw+kavq0iKdGO1x3{wwiZNM)k8#;zNfBr5m?DMq^W?56m_`EZkc zRyZO5^p6XvZzCAjD-&wWrlviwMv4tHCh=zhCIO}h5)2!%}0ywp#RH>V;og#N@-|s z@gsX!QZ>tFsbTg8Z$34bQ1onx-m&Q&HVHRh9dOOv&$5k2)y@6YeeN=kN5{~OX>NLq zNbHrk9fn3NHj_HG!*l@2&We^dc-#$4xl$jizZu@ zn6r7%4YB+H7tqPck##`F(E6D6M%kO&9yh7z(1a*?ci^%|gh*8OAqnAa`*=I+&gFTY zR;Q;{SCFJq2B98#IjF$krL)9OjEeG}T5*dR+s}C9m&9PdJr{4QZ5Wd~p>8X{()u@c zow}q>0}cmCE$7=Pvu!}N0z!yezG{0%G9e>8r!E(!%1larfi&~J*2=zyyL8&)Lv~l~4nH`?3oUX+f zip7!a@+V3-=v;X;!u9nb?hmwG0+p|1_6iJpWC`>QkIFNAZnI5Mv<_2Hsr{B*D6UHE zc;;0m3n6$VS~S?r=VWoS;xY2~#ldxc^KqNyGLnw);VAh6WXqWO62L1XkU%Uq*|>$y z< znjk$pi9bp+BhTua)|BhjWAtzctA?i9$cIGIMAdRC{Ss$V-G)4}rZ*8w{DHU!I?uJK ztKmbZ0&*ytCe#563`Y@zCld8*9dCWvM)P8tbg~8-cO86Sac#iTnP8=mhKRc35d`l3 zQNG3d{HI0G3Wx=p7M|+A2Mz4V&hr~3=GGF2%bVIouw5@Y09A70(7NTkYKFb8ZWHtrWQpZvgxX% zT(71*;q5t3{&0u*u4^N*xfZ$aKb`jOID}mwzRLe)7c^%;^Zwxdi5TBsdXA<3v&%fe zy^kl|YH34Jmy%Zj)ic-LR)SY6jx2b3K(2NP3b~$m2sgYowK3~rSs^3CD^;z|C(qYP zRiF2#6=BPP0d^F=gtwLW(LewF3S06i|L1^=mzbFL`~Ub>pYt*IFX^A=+pceu1GVmt ze@S4FDgAy+1wT{6%_^BJU$wOvDer_3DQm4Q<2`22;@bYNLkeuX$k?UzJMid8U&~Cj zjpM)R&lEjUXM6Id!bjmI?Ek|GT!K3Zl-^9WJ3>9;Xx)0Br)U>)>TTa;@5s{WwS}b{`JR|9zH*` z*rq;-bYGY(9bILMLIOP2l-!>%C}gTG)wp6K?dA->OnELlBRPm(5_gMhr$vD7YuCGR zXwOcL!1Ml~dw*<8O)*U^4N{GPAO9l)JEbt|D|nwWOMY_XvWd@}nk!zB5LXaearUUt z8LG!$z;q+3Nv5EIMJ9?mC=*!M^|W>f?_hG7^PAXNhE>`rKlW>nm+nmXtaBO z^%DF*|2Lj&go&{QIpoQIRyBVY;|#NScojyNSZJ|$ha)9|H*3#Z)sQIS&%I=-o?^%| zQpCY2nr<|?gE{TFl&7AfVrVv%f*pD0-eoxWW|HQGP2p}E4*q)8HoH{$o5?q4uVitj z++RJ&N!j_nGC11na8k3+n3+FbPIuJk`<8bM(9qrpUois2lui^_nm)A1Ma|k1eNr0Y zR{A2>CN&pvkwSk0eP~IHy61FKUd8pta;lyfJyx{9OvOy`0O&ZYmn1Hrv`y}7_c7}-X zfuhRh=5BxKQYlqWQUXp_m^C>zj|v}oJ`H+t@onAz_|}_4Ei)cy;(;G6A2v5+9#C#Z zT~s{~QdWEF`{ZlMaDM!8P8wL}iB4d6gHd8}dgN0%^IgOvk%c>k6)744ZJH|S#K=&W zxSJWkG#oZsJq?zw|j$ z<2W}$1pkR1>X^MVWTJb=FF|4?CDcY&n0c59mn)cx#&LF>CSMMj{o_=gS=p8p4Y!D~F5z*ya$h{+c7e&y-0mF8uF)!Pk&>wS} zjUUFdu34k3lqYObxv1Aig~IrS38}Q787L+*iugC|D7~!*W~(^vE(}a>B#V1>4_k+9C2yK8NAOfwvC>nv3hjcjaiF&YVGjf+FhYgg> zauch+Ze*{MH?qLkarv$ny&%x^B$VT3M7qI3uB5u!Mg#v~B$ZO8-{xR`ztU=+6^uHE ze~@&Db`=Z)OaD5r5z>4C>=}>gFq#}K7t|}}zY33q@kXEjsH|F&fT-e@&Y6mZiN;e# zLLHYG0-@WiYKYEgJ#!yv`%#6s6-EqpomH3}5zluu(k=1aBQM{)p@&K=dOk`w8`?rU zreRz=682;(a0-;DK}COR=_dt>8##$lF%J;OYx4KY@r{9Ffb&o$R>fUF!Q6~aZ%Ed_ zsW^v>G4H>Nf;^qTfI+eKt)}!DVmbA`EX=C{Z1SuGu12g>iMR#1SjOxTT{J02MA zT!2_Fk%{lq&+#c`Vu=%#fUArkH;n77uTT5|K=BXxEu`P8lRx3r*;C3d>Q#O=u^Mt% zGje3|Uj@L^#q(if2SszcT3)q@pz@|_X=nH}A~#UYNcKl|B^fb{^@B@~U`>H!GI18G zM7JcYTGkp;+}t@EgT03v=5hITn2M4s3mC0J_ZOrszs-KP#?{N9d8v=LYb*gGn zC`cYU35a50fJI>;iTrgu?QzaMaJek8uK0N~TaP8z4w1=AWQ^g0+UiElcWOA|UmvV0jSi^L=h7aFissU)CUR(rHEBg9Ojo_#E zf75G9XJgrUNy|yMqevL|?MCIzb{LgRkCaL6Y;+;owF^X~mEyJSby&3CFR?b_l;`3k z7SOa5?z}y}yDVcy;`&gbx*}92a);fizn-ruIKS4(*mM!l{CebjQrfR$gY!M?{<}kR z;-ughOt#6(O4Z0(i6Uwyh4XCX*XvhLaffJXYHrXDW#r$dCW}pyJPyA|O2xCL*|m1zM3etnLcmNTfFC$bsH2 zQwi<3#K!7CIjbZVOnE;U*+&2NO3d~gH$?B}BDZYRc?d>FMMLxxaJDT*@Ew9_M%w;v z89nx||0)yC6E3G2=T~_1q9mx0a(>3tx~bz&4XIP)T-K*lt5*mYnv5`(Yp|? z2tYoo`pU(j=(JAjg>pPVl8w8lmyk1BkF9K~GN)UY()+yC@Qpn7hs+@u6_vbK_PwKE z2;6DB?&bz%{vxTx#_K zY4y)TTn*5Ws7SKZigKHbq=54hZl062LEHA@)mCtRFzd9><-OqnrWm&{6qGtCSr(Qv+qFq>Irc~}c__R#kV5T0V*3ugY-R^@b{nRDEa3ftHr)VX zMYa^IXU_2D;PXqHXnKRUJF=POM{nkCn4RmZo+iW3t%g20%cyoR+Hkg$vqn>w|N7QV zFA7m|{q3Yx#RIR0rVoz+pwJKarf++g^LXe>(FFk__GXAywB-U6Dx%aYgqBm6Bubp4lNKF)r zgdha7I1WFvmwe+j`QvKpO)h4Qna#dL;)PcZVW^UHN;(lJ2PGK0h5j_q*It29wG8FB zKgiCo6`)q_DIG}*#o2(e4Hag9Jl@k$-hPh_t6Vzb{jl8*;C|H=hz}Q38 znP+_AWz-ce9TaaDe{y4aFmIxk2=8JEWH0PfOu=4NcEhom+ni^Qv;}1u-)Sjq zDTGalPhw7UJgo_MP^p}ET{L$#{($*H@tF%@Ib}@1g_1w1d1E5CV}ZaPcy*+(0iW@K zhxcZ;OYhqzq5)q(`ty06`&~TmpVi~cun_mZ^v`OTM^`*Pn7;jv3$7DB7Zrn-+XCu# zHA3?RHVQ?Tm%%J;?vatm3&iCyNq`0$Juo3z+8f%X{c1&ALv(@Vz(aO8PC&wdJEVQhjYu^p&LZ<5>e%X4gVB|i!o2Y#9iY23LjEWxJ zu~T`o%^GTQ{Y`FVpgoVf{Bl^L{fJ|0)ldb@EhggV`o-n1x$XQUc25fy*oJAdVoCoG zk-C)9l+HabgJl0@yScvDc6+OmCCXoedD|S!V(>^t*tWq$^6E^Vgkk!_l{dk}&1#zk zOvKr%)N2GrLmMdr7l&C$d{tKVgDbnO28XHdpLCp-Y6_d?3;OG2S0`?lE*9-|(tn5L zS^=shDPsPY%*qm`OgrzDHjqr(HqU%o;=db7n%S&g)$gk6{*C>QYAfpaKZ>a`eBOW? zOL5U7zknUC-RVYYk>Y+U7om+5dcRv$SnIXY9RguIiHwy@olEDzrd+M=%YjD`Wqwh@ z>t92#;m>{<$SU^GO}FBszZ6NH*Tud}i8@cuwaMxh2ojh(A6Rn84m_wRn@TmdxT>D! zsu6Tz%XM`=Stzkj=3Aa*6s&Fes8#BSIzplRtl>(4!*bBOE(vb{T%o9W*OPSZ{+!O~?PUtYhu+A+B zA2~5s-qke{wH;G4=>${4?5rF|<;-TCjcWp5m8{lBx=<2p7UeK9)AF8By$UX;tniQu z>n!%G@hMp!*}{NJKG9LYtEHocRwbeHB%xz!_ZY=Hn)LL#KG6u@gxYPnL{z23XpC`( z9>Jn=`RY@iI)PP;;{vNYtq{12^w5fBnW3?Oy}SpHt4rHgok2N_zozUS!PkHLca-P~ z7W1b7?1Z}MKK4Pz?6WOPGeXo0agpqwvtBB?gn#UVT0-WG2Ji``K3fu})~Fw`H+Zgk z^R-Y{e;F$`KeGs%`8qd_n^HbO#;rIb$P5*YJC_~AAXU;%IA;z=#evsH1F%RcRsK~@ z{i~1Zl;1Jfw|00*!iWrpQmoUZ{7`{Rg<>}7C7rJqy!*FX(|7!RpSr^6E0OM~KLfTc z7-!6CKg0BV-UMU1-{X}-%4A^>)0S9U1Z44?xs<RMqBlIxs0H85hId|3C5Fty5>5%sS(XcYtPb zoCtiGcwVu>0063CTo?=V{$ zR&T%l%V+I81k!dgCWT5yZ3ICl^YEP`7K{9PA8z_Sg|gs{QkPE?T8nXJM2a|B3JM{u zY3zjBE63Uc2m$!%OsTJKU!&L#r`6a}4uwf}6;Z`aAs~`WUQY1(P#1<*>L=U92#=rC zdn;Cz9Mw)H-#GS=s-JJ~8_I0GL|)GLvt0P&Q~zOt^F%*iIgMX8gqbPgx2^!tWWZzX zHUB6_dGhaQ`zG9v*>*cqLVIZ zX~rTmOu>3uD5^l6+?=WFa{-iH(t)XJ04XCnR-sXr_nVLEHI%x}QQ+9-g|(=pdh%|0`T<^Np`SPPk_G$v{|udzyM15A{3)C!@|(u+g`?e7mveFyo!PHLv-GD; zNrGh8RMv}^O>47DS;yJA&7yMm|Cq_b-j%(dw|58=I51xyVw?rz&-sp_Sp?6XC!7X( z&G(|Un zVdo=X`!cM7p@g3d%hF(IrXD71k-ocV z>HT?1?XmkQ6NoM^P`3@Y0yh&5gvYA^^fuq4fB|ihwE8UHszsM##`ME>yj0cS(O0cY zeUw+zh`N5<<`)!{H1c+VA~UQZV;$q7PHlhh+;^^?Q-HO%GctP$9X&Bm5`kc#Qx>tu zT&19;Evik4g9~ZeB7h4XP4bk~5`@=AFg~9Dc(Ng7qkvk1%jk9u_bY{E{ zkEmE}riR}KY;MiY0LWxjlP06s^1I(PeE9E>3a_bI>*8ad%k_`{Gu&|ZtopCNX@rB; z-ff7Pb@6LYEV_1R=0XG8H}Fl{zh)R^O9*(KXq~Q%BK#g>pNsuvJZOZnk5}3NTeSk4 z{~CzePk38q=C+Ti&vpk=c)6&W%3^0p=+zCE%5a}jP*_+VyCfCl^IxA^J*iilb|%z8F864)s@Y+i%jHNG)#@Q+>0kRG~RL3l1g@4D&__ z%hP$^03Cd&iOKrIjrmq%71~|STlU@$MT|-Hs7dZ0r>N>uq*&Pki77r`^$*$< z8qQnqYhb%2Cqg;bbQE{grB>{jKP9iq> zj&?yEvzfi{Vtf{@cMC7>1V|@m{(^{|I1*l;mHKfv>Vj)pca*vckK>SNPvDco=uE3J zkXYRQQ;W_lX7AWYSovYk;n+HI5LP zg-u>>NLRKIq>6S~+M9w~d9iO%hYZO+we2p|vk+tn%2L}icIO_l;Lha#dZ}%$JH%-n zf$W>xW`z%kY;PUEBzb$wTkEo5k}LitRapFqZu;x2MZDKG%OLX1$T?Y?Eua-s0` zZYR1$Q|E#}R`i8nxIOQub8;isDDY-n`^&MgYAW@jzeY8K;SWj$AjMxfr$9~-_8qWk z2vg0wWxsH@R|it;jSpFuC?;=Qt*Ex$UWKmYHx=@Ers}SOM-D~X6hn;00JVB>V}C0w ziPcXs6n+&T0^Q7a;F56%1Mt@j=`CZ#ujmprx(R)U)?&F?VN$HkZlwdt$a5d>_ZHPZ zlM$~0j(f!Tg!$r|m-Ke8S=`47LKlA`Q84t(sR=W5Tj!K;!#`eKzpoajwz6aYd{3}f zl{$I9`|VfX{@^>F1+n8#WCFj2oOqnL-40P^%O*9=RLGrx0lW+P8X9sqnjJ1>*y9P! z13McaZzp|`8rP?{vsm2}3!i2o0A?A8!Bn!2`s-(y!EI(jt`%R4ay9GV?T@C3;IxV= zADS{Ucmq;K+MFP+m)?qTr)C_!{gP^qSc}>*HB`a(|c*B z+dEBr=*_8o$H|5CQsC)xk^aP;P+tI|3cf$we^dOos`}FfU1k9O(km8CHO@mqqABZl z{*Ev8No0Z3Ta+nmk(96Ay;%BXq;c?}BCRq(XGvHMhc z6EcT|7#YyT!Xkw=y^_3}y#vexCW+nOTFfRVXuq==OCl4e+(p+M?_DIC|Ioz+&oH|w zvwuiUUFYuyqcq(gdSPMM(?t6=s6!Z@vZj*brK6mnM>z zSME3p(8ckBl1^=wK4jR3=y@`}W495X9vhF+;R+7Npo&12qCQONe(7A;*%VH)=4OBk z_r!xW--XqVy7RH$urv!9Ag#V4&;tC=`DCMi9ACFIuq%WSo#!#AsC$7AdZYPm2NXtZ1^HEIVFtNIlohJTrnUlMfU8h3H zR>Tmo$hqu^4y@uDKbmWe(K_H{S}|%}otfOX11$=}so-C(!BWjrqRO0EtrUa*^7S^Y z+0gsdv{|^KrTz`&VsqGePhs4`>Y?}!$)i4u;#7JOWCVzEhUGDxj3Dfasi zp{_p`?#yMJbSbuSGaz||t6zV(s0QGr6~zzcF4!xD`$w+$8qB}ny%mT_6I+L7&gGls z?|XT5O=S7zU8un-mM+$+_I#zvB-7pDIOi1L<`xF+eRLT(CJrup`-Lynny99$ zGiU6ktRvHJeU zW6qluDVZAyXsyJlY)snQA}HO{@$b%64`|@tB(Hx&ci?oo8{E>_zi?@dO}d-c!s{{C z9YJ54CQ#e)#43*1p4kgp9Va-?P?roS>&t6cTgR96K3nw~666b%i%i>mNoI17$(_FU z*XX{p>B&%xfCntMMy7C+i?VM@g{-|{2h-H+smw%A*xv}BWut4_WpP@sa~wTHwrNI2 zI%qQuchigep!gGhj-D96u{!oRgK{|s_@$N}S)4!R-_@X8CBKQqW(kEKA>3!ypZ0TR zmS;4T>CasySnckzSPzg1!M$nPW9dY7UAep4?#TH&`Nf!Tp1l7ZP?=T(*)MKAKl1vY z^oHymXS=9UZb4N)zbE`u|4$xsQoYho$6HC(4tk%Mb3Y4@`OCR;-LoP@^pWh zc9WvNK7W*!wR)T`LuO!LFbgdiiJOZZtz{&D=%8*ias*R4)Kj%j(@|*nEjh z;WNu_e{4Wo$GNHiWcQ=4bA2T#3?jQ?m0?5nCeT^X9n!)t{8BqVv*UYfp+J@@`}*jd zFc1czF^b6=iY8l?y1MiXESG(7F60I{Z;1rA_+|M|M?WX(dczH;QF>PzoIgCw!!0A% z{M#}HPO2gb%u_}KaZF56T2`dZe!l;FE@&z@`|P@ZkIaIFU+l*PaU0a$U_;pr8Y9&Z_ZMN!3pC9O zgt~!)v(UoX2fG_V(Dp>^E-{fV3qReGPE8r-OW{P9EoNYrPMGm*tI%&~eM>Vr5s?Q} zxbi*wt8~dHFJL??Xt{w?ih_dRb3&_0dHYN7n>dR)7;_L?-9^$asIQ}0%z8BU+rvOm zg}|q|oOD)xp)_t*WT$A^dvP8i@X8gDke+ZTZp~62i&?(e0HAX*W z={4QypCmm@)TnLgDbG!T325w*1cXH{GoCQI$18GmH*zi*Iuf@GBAK7nyGe`F8ml9| zQcBga`0td~kBo^D{ZE8xXUHAv zOK`?fRKpKDPcorVg!L3xnL}q6XExjj`=VxOdwCt?$j|Yz{BbM(QU2 z-f}hqC-0eRBUqf^IUpZNS&QHo)7kCEAI)o^q5n28XQ?yzo%unh&yN#}@s7k@-Q-N)C>)0Fr`CyyI}Uv^Q=l2n)lQ)9feiGS_=2 z^))js%7@pLi2>dCR{|CnSc3$k^m7?Jgl(L(nkR%;(R!}MrU;zA8cwsUp_FFtuds`q zNLoCB)8G{*clJET(C`hw*jI`D0=i<2VTC2;&2wBp*yOvE1}woJq4(sw8=ka|4D5BS z{@y%ON?gt}txD1@`U4g`SuPbpV+?TdIxpsHeltQ3qn(yR1wC7`fxkS=p0e05&K5X2 zv8CBJ|6y~cM(*@hrmK*43cZM0v(Di!c_>4kn%{=UTon~7PUmYHGZn{)Ob5*s zp=X>3&+56MHh+PdNW9e^Uw zUyh#qpl`{u0svZrtrK6a8))QhXL%mocq})MfTFT+&<7IiroP55x>exD@%{ zT4>i;C4Go1*&j#|$lN0F%9+TGI2{>R_c9Kn%1n5vo{sk?>%~@UbHy|d>l?rAZtczA zSCHstCMFZeX&!vsUFHKm-cKqBTMI={Y5mGg0FS-q`@Rno7qvR=qtOhWq{U(;7Q4Gk zWS=k)#^~k*_cooF3B{UsFpHZRq>KUbM+CI$xVH)HQC39KRMbJe600pemqd5)@teuB z$W@@^wtCt0-!i|yV$WdZ-5DzQmnkI~sid{%sWdt<6y$HUVrdg@0HRT8iOEi%QornOp zYmUYl&`rX(x5z(j%zzEdONbSnyrGct$5V39ME4QiS+{G8Nkm8`101cV4)6p|Qd}dr z^72-OJPBXF{V9#vM0`)=eM7+Iy|8ohny~1cjuTj$A9y;hYj7ojA_m*{e+{yLA=+om z9i!3?axKR>7I}N~B90W;)#4_8t=j&yL8_6KF6sQuF&_t5{2UroS&j+?HTVBP=@tHj zudFxWFM1i5(k^-0JL~cB*hhQfP>G&;!Pmk{lb5>UQlaA?5MoFo?*H6@{@)K!)L(sV zX;sXlZb%A^1ZxB6>2VKdMEk#omM)sFu|6}o(0Hg-dy<xyDw}|_cvP!rNoPMKWB{*vCAvTla!%B9u;@OhNn>zj4F$r87O7LYX6vR zJDO}AHVoi1rJw04PW?o98m1K}4pgo&X?>yOA}aV@C)YH-5Qe0xUZ7q47Bx`yqq#&U z->YS$HJMsc4pIFKnkJx04RI6J(u+3e z8BYQ>u+{_4>B&NPc1C{~B-831^cW@S$jrQzotKqRdICQjZ+h0o9 zjJHd^8vC`k0r1WP_>H%+cPo@VSRD9C5|<|~6YYtY{(}l0YWc1nZ8!|mo-qsCV|G`` zqprT(GD)5rC3D5~NVS$uVLte47p(jys)V3meY0cO3v$@5*Iu}yqc5`P*f6-DUH8pT zG7cMAV9C7`V_{f&y<{sA{>ljcJ?=%SZ>(VrAZG&gddwo%o@8Jfzp5MnUA1G~Z*6xy zXl86wx@;ENu(Iksp6VHd@XH%ZQIG@1Y7Kb0gQjDK|ZyD9b zu1Km$%C3(q6c{WQmJ?r@s=5AYO#Qi3cY!yUUR655HG;FLE>MB`nNT;bUXKgUSCK!h zA*qX2B!L@<{miDvzgpomeeq#1ivP6%<-_slC4gz!%fA8eGVM9D?z0Y$p}|fw>!VqU}R;<5k(B z^8fw$z`r7#9ewCgV?P0x+}{FJ>(PVps*~AK|3?3Ft~TQlVKfM#SJ=&}o-1e`AfMg zbNZqsUal5cO|U6g5>Q*!XQR*vJ)tkQ&GU_Jn4g(ZxTp7KIBvel+rCFx1+7cD4-WPe zIo932!NY4y!CcI$8Xujc5ar5QC=ieN0>6zB`e_s%jYA7yjn|g{`*9G39o5&5@qz;qcy9}L5j`+RV970QR2lHJq-15;SEqh0AI~deicQe*4E)plPV+7(rN zMHetfP}hr_F|hRtuyuJs#Z*5^tzIxyX(uGZg%iq>eFjJWE7#;$p&X??b{>N)$SB~> zc}M3lz}gNyO}u;v0S!F!f^XDyW&}|_^qKLd6#BkOk~ABvR(7^P zfWLT46~hvOGVpqDTxSF5V&1y?P2;Zc#Vo5{U(Mub7<#myK}Qi8!6W+)M;0 zb++eHJPT^B)nr0W%#2l@`386v1z+bEu-iTp6X#Rld;)_jG`&xaoRm&~?ZGmNbk(E^ zY6YN#DVRq$-USDxCrx7d1~MaD(?e9~r`eKm#q}(DDi)+GXbV_>lOX<@#X1fEvjAw@ zg#Q;cOrG@W=O(-&ZXH^t`F_uvXs<*lvnX<`7#fIjIA`25^07O) zAG&+)|NrlPKC-vo(kEL<4w!VX$Mc!yk?EoewI_T|9J10J9L;$(EFtid_WOV(`-R8-Rf+D@II#;I-yL!I9xi5AL~YL#)E&Nlnh58)pUG_;eE-Y- zhE_NUa2)QFe`cy-g2B?hd*W~4`{PZ)q(JC|BqwaYe$DuFkgt<#I8SM=gdY&s3#vTo ztMc;yP}ZhOpA0={W2*6dca(x_q&PTTOqfi5%6-wD$Qp9s&j<#n(gT;1iZL&m@{$Dx zh&4NUCA2#1*FGUJl-O-W)=$b) zQU+XzFHTNQoD@43>hfA(H9&t|fJLK+=Rj-+ac=4op zbC8p%|G1FdvR7wFoDBSx@7YGgekUASCglmjMACBFFGoF$N<8LG!#w?Oj4pHQPCMLi z{x1#;ue_Q6910Amdh(xnDQzJy;EU^qm$qbEP&dfb^_J*A@t1@8{L=2ViH(AfH*3#U zh-YE5t~8$8eO-g9c9|#X^I0a}gG?4daii0{#q)4YD<%3z6@H9F^`j;wdONdSVpBayPHTC!%(F zL(z@g8XT(!GO5Qd(2J|rU)Z?-)sgmW&dV9^hs<6%tRxt0X4Up)+Q}pEDsckecLnX~ zoO9~^CUi$hwQ3YkQCWYHdqUn$&SfS~>#f37 zoVJimO47{bQ(T0H&%xObIl%-aUF5a;}Ejw`+5A<33g0iII`vc@NaEIYZ~qh7iS}ev0S} zWRoRl18lYr!)iYps8BShBrbn^m6lpphy@zhorsN$y^@l$#*$9Yk#CYk*Qf;`9z99jh zJu(&%mw*C`emB>7grHO%g2AV_?ey4u;_$0UzfJ1swdk(USWU+Il6>aB&IVK}(o7V( zo_vQaX%VWH5w`eU83eOM!1-y;L$_O($KG#vfX2p;{aF>_ObI!~7%oHKLoJEv`?ddU z-teS==|HJrOtB_(BA+WrlnT?L{`5Pj?res+vF_j{8Tf0EHH93KKq&G5Q1zBkZGCaK zZh=zVp%j%P^+!I~=*py^?N-wo z>lmirg2SE49?yKRVhDE8yGNgKWd6Q#QhzTus8*#<0hi&i4#Pcul;JFs$oRUD{!2LY zF{y2EiE^H$T@ikwPp!g*NDEY4u%|$eL|tp3oWcez{yid4GqVRX4i=x<+RZe4?>`cE z4?Iq%SKfXyO*wGOtBX z8n|>n-|+A03k6EPRMEND$4v1gz8WO-8oXIn+&uc^6J$l|qfFM3t8n8g!o z3{7?O9J$O+S~1tk65Vs05UhVz9Z&p%k|M5tFX)=vlxZlmxpq_H>*cb4AVi!X$R!15 zig0viU_tE|SCrAThPzWz5J{qZrdyFEaBlo&JmCeW*cLCsl`ek+O{%i~HRK{EA5r5g zI;Z+;M3!8QI$0s(2Rp$`AJo4>Jz@X3>vG$T|CMr@=ET1p$OO1{)0EtyqjJZ^6wIv^ z6ws7J%J>UVgpJU$D(4cnH$3nk^H~&2SFRMgy3j23fAi&k_-9ZiZ`!^5$KA*@*2{Gq zB&?VR-JuV)T8HEfW&16**f$UuI8O2$!9DrM9>-2zgy>`mPfLsNokimwvWZQhg>n!0 z??5wJ*8!{8uD-`7C#BH=FJHZ%6v==Hq`pO_s_(mb30;?1uhw0^P9aV~_G4n3mQ~lm zW$>IEXj8IV_{rsVz1A<`{;gMO|H-=$kSB1C;kx1UCcPj>=QI099cNcLdrcVr%~0I} z=v)n&lqf<{Jtt+~V7U_7NEWk4uk@P)KmMVx-VxGJD-#m=b0gEEO54)X6DDM>Oz-C9 z;zPYOHhF5g)N1(h368R)M3laM#7wiXIU;K}baSLZxtq^23jba&gmm)a9OD)2?|sV# zDzMC|<}NrWD_{v<@V{nUpSNv39O~{!sCnq)Y6KaQ0sTbottcs@fDs!vHD`xaYF$z< zFV?l#X7*DPaBfq39qg&Ct-pp40yee)&(!{IRKZW=rt%^K-qD>IPfAzUbP-YLuj;;g9~D%9Kqryv5OVJ3sApA@%X~KVLG( ze@RdjxFX`TSiD{HfV_g8ydk+m)0K+`srUYGi<2I2{$^M?j+5_khP&^z<7bi3Xy+l} zw*@uJBXX4d*%aos2RZMT#r7FvBf-p?*7woh%Q)FcYqe%HbJwlhSb`hau5$zDN=D& ziTF~-$uH~7k0YIDY-zv`$HBCmADMaP8L_+PnaHlG`q8B>BZ>{rT0IqIY%03)q^253 z+YLRGqi8T`io0sCD}VlwfGW2rTZk6~I#Wo}%$NSwh6$}=t0tzd-w)Zrv7W7nx{%gxeA>#3-O!sx&-S;JhAfaf=n)$> zq`$F6Zjt$Z2f-Z146F6~J1nF-_RDn!q-+$cAfMzSwz%a2D>XG8k2K|!vcHsH{y?td zI$lsGb2nw!^$D8hKQ%xczwUjw!~dRi!3m&Jmkf6rt$e{&xk~4}A`Wk@mU)w0FK3X> zEfRT9X3V-^Q{2*trLCLa|KO7@x4O-F@Q0Q%<@8JV@A}zyP!DuNzwor{^H#*m9(1{^qe7?_{~p2 zc^sOAdQ79TpW%JY_S2EHgaaT&r>uZUzJGK)u-lwoRfP3sa#k?qc3!tbVufNZHlO}- zZr=LEm*B4HdN{PSysKmw<;zdg6Q}3yIxH|p-8IedG;d>X`wCBAYm27os!zLHnJKNf z4r=_VqF3s5Jp#AMd!b)wvl{cO-ih7<9~tsjPwO#67;1ByAgCG{Zqpy?h(B`jD%5*k zpZlM1QVA;|9arHBhFb>c@3mKB{w>AxtWE_yA1I=b(#b?lKEQnVAS)@RjT-u69$Q{ic#p|68$+(j-; z_2$mvrMC};l9JF&JTjqK{M@D-8qL>869s?w*mD=|-QyY8p*7#eGg1ZTWY1H&KCy2& zzN;MZI=;yReO?xPjlJv$xp_+0SF@8q`Y}ni#g5jTHLr-%_BhIzLwR~R=`nqLVkgN& zS>ck0sL{1A1RsLD5I?hs*^MWOC{iAMOd^RK!=}tK??qgB&zcyW3cn%>-@{%c(}a{N zv&cyzu7=63Rhf0&jyWe$#sAZUOD3~lJpYeu>qnBxlzq3+zg{zB7Vu~(+^lp?9jNQ? z0&~JTU32~Y0H`^q_9|c;CZYkk34PW!rZo%II}XcDf#RsQ;y!sv<5KS~v2x`qRa^#C zu8#rFW+FZIchPwkFt;=-EQHWV}>#AC$~*@ zcg>%B0`FI0F!ab$EB855dOg-EhBE}463h`)2uyWT#&j0Cde<@Z3}njd&*R@&k`sUY zrUdS?wjC_ zuR2nXTjJxv$H}NTcS_}cq#5kvbX@hxq)?MJKsFX1Nc>I`#cnk5Qdc3rIibcNH5(FA z_sy0W{oZ5S@);iE`zwx z7?3@$&Z2l>fKTpI>(N%m$RqR3IBrm1rfTV^kP6wM#Is^BSYFw9aqWbYj{-0ZS?{E) zGT2MiG{6aTrY=HHvu+ivuu)n(-Anl@js6WN`H!<;;GDB!mjw5fe-kONYsFV{`m#x`I8lY&kf6Xr?H)tjq9|!&h z7D_25x!+3LRDTCA3@Q?x&XcEu|ku&#&L^RMg4Q?l`(;D?HdzPt#{gA59EwPHVi)0e9p&rl@lu(m^%crmO4 zazQg*S+NKh^k`V}xj?$n%$7*3P@=@1rS z*{4-2+hCzs7#f{6QultE72fQ3Va(|ww%8>CreH5d=COFhR-x8mf=e@yn zhT?02Vs^4=B|%Xe~KgP`9elrd64qROMeeIpAzQ=$dQ3mk?aw{|CyP zwTo@i`_faNAzL~K|EEBAX}kD`v2nn-M*!+Eiotjdx=Zp;V!3g*X5$}o-R0+zXg<8N zTL@_SdN=yGJpPD*g;KbGf?;}7F8wGsOZnf(&T8o4*qww0-nIgt`{-f8g@El0ii<_B zetv5O6fchKS}ay|Fj_mFH()o07SR=+B` zsw8v@)my(kXws*1ypbDDaP#yOwQa1kT#`Fr(usus(^RV%(%nC3SPte-M**qTp7?+= zJwdIa0;hXy{_mYK?$k7$R)Tz-bCR>{t8 z+T@|ngG5ytQ9+Q3PuO1|@(i|bm1YkkqZ%37c;9AdHHpQP5S`t6u zbi{L6O>)O{OcAiY407=*H(p8h2=ErB2mlvv>OKAAlXbZ$v@?mnwx2z~2ocrn@P`a@2>H<;wGFm^oMSx2K zEK!9(e0AmG-gh^?d6zWDNuR~duXLjWwj{Go4lHzItvNMo=KwrwTPfRi0;l9Il;;$B zHr9Sgi;Jv~L;s`*c9veITZh*~0wP2!+dhk!Z;q3p@QLLoSLRB*Rwiq6M62F~4n6vM;z7cR?Uo%md`*UK_+S1w#J3Wq(Z`cG`p*=M(4h1sH3wmk?C(O! zg5lX1TqxtIpF5P9p;)2a7%s--ju*dg@Q-+;@yBk0k1R0hh|9(u(m{#Ms=$YkY1H#U zjL}(ZRFySn9;N3WZE6_T!Co-j8pn~fX;3*kD%tcb#4O#e7#7PX$Hu^~u_;6+xSc_< zd?_Tfu%s7k$9<9TSq?dW8XgqilvDp9G48agy*ZW#NPY%;+aoMkO!^L> zR!am)c$Dm(jn7l);kJqQqyz9K`%$WIV7M`;sXmp)Liyc5u-v|1YUP(^RB~zKa~_8f zSat(<%9|2G7-tG_wtgi2jl*esw#09KYkMI)6DYgY4v*Bg#3z+EQ$yaycO)F-;_3gz z>-yEk@jz-RD`vR2Z>3`-D}QbBPMh$b6VbNJ)YKzG;=e>7ZY zMr8WL(fZiuOf;+0sE0d{*WwV&T#}baq=n9KE$`9KXSXYUpSqyj&hf zZq_`2(qwuvMBOW49}?I&9YHpc&g_28&6Ir&3jQoS^D&3{C5{*1>?!a-xoHJRWXMJ9 zw-AVxDHNR!C!4Wk2#mLH$)3n`(eyB3y>k-P+wCDY#_xa|Q~YOqW9So+tt9^IHSUVd zTc6c7aPl^92+T6x5xx78?=t%z1L=3CXMulkn||@~%YDrsB~y64>V=J_y7?IO@!#aC zqLspRis|Vdx9nyLyuwfP$x!bMPvMPAu3F$`w#VmAiUZkf5?k zdh_)_%d&R8r4(}EK_&EjsOCKR&S5r!C`3wX{k(l12kEs`I*q|MQ^X!zFMDM6+Jm2ray+hXGC|vS=)Nt+U-+f_XLNd_y* zw@j;`St^&a4z9xI&eB;u^@{g*Tlt6e5fG)_yqjpvkX`VceS|lM+qL$tAZ3g+<+fbR z!kji8hWL~+Rdk3Gq1bV=k1~}biF&ovi^LP;F<0wLFP%E+023cT&1tP0(KLnCj#BQj zY8-O*o=P6h;EX2usnN8O;k$0lF`-*cUJsoQSsblXoao$uwejtRA%?N;H~=&3!wz$q zE1PD1=X;BtB-Uhfy9NW7EE&``Vzza{s?`*>7s*QBPmMmrxJ~Hf0&c3%|NcHC(^I9g zXWx0~S4GH;Bi^1*V$ZTsNr9YNsGZHBDn7qzd)2 zQ(@H-D~0yp$?VzbeYdM)%gvE57iPiS5dsgG7+tNBZumaZCr(EwfhmL=r&u0MO@$n8 ziew#QqQ%0ZnW~2~W#TD-kXX64h>JzxhX|Xf0a32S;OH@P4Vl`tP(5)QO(?0OGvYcY zjJQ=#MOjqm4KTt2eEXh$Rae!ou=|_ZeGm66zef}rU6*X6In516@)(Mrr||1Cu=u8l zzlHt=vzMAn=*B=JHu8|DQ||2duW2eLIql9>6DhVq37VB3w$+bajao6(hov{3Dc|J5>C0&3YBj!>*E#q`Av-_D$$nEK$a0Q+2-hCe+ zC%t=!uY+8LdC>^<=qGP{*z_5{?fGjg!Fo82kSdH{V5qjMsg}q@S;pn1FB1e4-oiUS zfw|`8{!#)GUtzr)0y1knI1`w%NoNX9pbAtbN%5b7%+S6X1^w&UIO$s(6y3|j%4~A^ zwmqwVjK63R+m9wstKRWXRg+U?G?T9-Yv^Nw_36s;q{Q-6x(`)~!T5I8ba?9vf*8w0 zI$Msnx3{J1d>jGKnxoKJ^Q5k%nXGF5!T1mAUj~d>Q{sr24gs1!E_pX3za(f1NqbE< zhM#)Mr{tilv8rRoNn%BFx^vSJ_OU}yVbkV+z>5**is6*|k~4c54DLfDc=ljtuQ~!% zDcBzrNv!6K6(oys+V+!L!=|F)3(uR)T^w5M@A&wOe$`0Cn!#&)0CDlfL~AZJW);rc zU$2#@%i^K8f4$*5IQw&uO;+hnx&I40HV^4QEaQz(q7~D=3mAqtfGqy?S=thZixyCp zsZ4f_j_M}wj{0+;0p0v76hy&g_6l1U2v*INz%ro{#n}Vbf_>z$D{ZmGy7m06N>`{@ z;L`&#hMMugd=TF_8Riti{>`ZzLv6$`HMYFasDTWN;ZP6+LD{ zdH|cz6=C7}7x!yh&@BHmoO4D39Z+?ggDz0@ZzZd>GPo3R2remj6T1A7Aw?N;k)1&g zkYD!qF0d|1RBakmfLbNf2S{yWl2;Ko8X_b;(;xOaw9&r#D3yZM#FJV$P&`DAVJ+DpTH>tu&2waF-7Vqy40X&IT6*Uac8lE!hH zUmF-PNIcf4T(kN>d|*pb>iJLx&vD3@;HFN|q1D9%^Qh5PCIz-PQdzwoI>dHsN`fpM z8kGpm!Rrd~-{Bj=!v6F!9G4{g@=9c-9>U}mW^Rpsw@ zp$ZhFoiiOl{sRtXzt-<-u=Ej=p-aZ(SNDX(Z6^AR7Aar=8hYCU(f9YYEPs6GIco)n zTTKaxC3IEly5dFr{$VRF(nY}#94G@tt61fjo{CqHx^S&!a zo9;gMmx(iQJKH=OHVu%G_H)|Qr?UiLrE-7G@qoW0C?z@c(jq#@o9A;FBH0wP z$nhukd(LN3@T}CT>4s66X7O252?*6Zq5bbkW(kQWe*YA!LeA%xmcg&rkZ`L?P_-h@ z+YJ(Jj>zUc@7eS6=4^||le_m2cP+OLOnD^4n>F|AJaWXOmq{752l=-zy+J*qkzqjz z47k>SC3T<0oZ$qkb9{p4wa3-s#*uc`l{jxL$*d&DZyo3@dAouGk}?}PvN+Ij%_CPOj29JTR$j`$Yh;(B);a^SaMQ zD<2aHaRF_fuB5a-^n>?B`-XzC9XA%&(S)9zoDtvioNGT%*H^HS^Kz=|btKyH@$qQY zix>{UG~Ql{5!lUK`+oxz|3h#$Z{MuW9#8on?Z_)*^X_Z+3zOjX zw;Nm`;cgep(EkOhmM?MZbtg{p@6$b=x1g8UHLp32f=ImkkLE4^#{GJ?2o~IS7K$Va zK=v-)!kF7`!`80Sg8j_m4Gz;C{EIK2f`y$vkT_qK^814}LGssXWTAvZ)ZmF zbDvJeORT9c%9_oVc}ZO7&2O8MYk_rFkvRzRPn-2znppsl(WB7g%t+pQBlxgB`C&n&OfmL;!Zb<2RS1!)psY-zmS=`0f>YtHRm^f9#vLY9F7V zIPSL0y*Oe0o_HTFaf+s+WoHw(YEqUP`lZsj6ZiIuhsr^vg$esOvIpIQnx=2L=L(hx zh_&ZoKTekQHd;G2$4+I(4NGjD{3>xfvN|On=zQWdJG(e`$J6V!q9FE;MGR6KTz#^@ z3FSE7pKzTO*VBnL?da3f915O4{?d;W!efukBgLh6l4Du}-wJ-E*!Ug-WjnXU;jEgpS!g(IsPea@v6!+T+5XJu1fml~tn5$ECTt(uy^Vnc(qAA@u&=e{*m1 z9U<1y14h7+r4^25-hbNj1 z^XN*;9#XF^`)=nJmmYX`+ke)kCQSD;WJv{cY+b9) zm`SRC4d){x2@#2xHIKB*E$X-kokdy1$8y48NvA?#gs9%2lS+lle5w+fp(#B1HK^8s z>Uj0@d;S=0WTPof(~q3q0G#*{&d%)8BE|O;kIB~bF}4=zb&l~?;4@fwj(F$#N}vEY z0SP0W(JL+%7IU06k7wTtKdS_p4!&JezsK_%AVYG2m`k0NVk^X8*2A~>d9Pybls z1f^Wo8l_^7l5Bo7zg@P686AmIkwZ8X*5Phk;Nqf@22h!*nwneyz#z zJN~>cIHDYTUGTFB{tlCoOhP#pkX-Smn_p{7@pomo(rR3;nSbN>v;00sai@6S8>mBU z$SU-uD#f4f*WJ**G393fB%h~KV({#&i06oSbYn}DPJqQ@r^2IT4u8XiUlVmETY;w& z`X@!jqbibZ=_w&%DYav=w!MvX4H#!OZ_M4xwFaC3o62QIO-W!N)))D9NKdXBW zWHubS5mhIXy>|ZVVmUZaPTOKNzm~JlHS$-DO*ma-d((SmkIU#dq(>1 zv?P9)?yh~+Z^f1N-hn1}5@%lBZ}{up;N9D?WJpPC!xK|$@S8V-zQXTJ$`lmgcNV|- zm*3euc(cc|Ti{^R$@=Bz8%v7?(}~AxXY!_=3n$%Fnv0`@yw|i1MGTU!@CuMdztTNe z2$rH}%TK_5UnX4!=i>63dFbh58ON!{HV^74)*<8zKzKw|!{pSeF>})6&lNRFL>W&*1L+xxEsd&|RPw4ASq$_7adh!Ibe+O*x)bHu`{1UKr?NUis+_RVtA4T2( z*Vo~UwS@-m%c*A7;9$(7FvlL33OPs8G zZ#&q8Y=EG@{;xH5txTxC-cO4s0zm!+Io$iI_FD_i`nwh0T8gdgKLTG zJP}~!CLhPL2iODV^I#n6@|wqL1m*hh4mGd46Y-xvT+A*led*lIxd67z}ma{svzAF@ToO*08w=EviNZ}VU0YYy$w{bkxS?yqnc&37u zb0_{BmznBDTTgcI^MA(VH^{jMZ(eQdy*E+jGxg8+2Epws5zkX{&$c_=NXAyxc4wtUS*p1$qG`>)RMfx#h~&6 z+L&j4mo4oAgkXYCW1BB{L%DowaemHYQm2UsB{TIShRrX&_0=<)v3 zgu+LXqw4bqvZx~!r!3&2y{=9QO62jkNceurDER!hVwD^Bb8-yI{qmKcppOG4?{}m< zSUSB}olL|Q)A{fO6#ap!X&OwU+!HK0y~bKz0JWiSzoFAy!RdR8?b(N8vPSad_>ycTeK=n!5@a#KIAFJE2uSwp;x@ z0sfg30W=u=UrZ|1-N&0YN(!Rl~XL={*$z>*NlHstFavDVt1a-X$LCqL|#et{(ntcwj#q_LC_pG%O$U(s2c&r zHKXLp9@yW(1q3e6%HUQ%AlVe+3>f)*AGa#3>_K5vMu@;eyap#mo}8oT z)s<&cSC;PX*LA|p%NL(N7#x14c<1eN!OR)EVnd>Y@}9ouC<=VmRWx?jKb9oh5A-yg zM~%RR8pHKmLkI&69kYGM9x8EE+1!FB023jYGNtnOr~@Yq293Ev%2&f$EHydmSnUG^ zkL{Ua4X?c#EXVb~^Kb{IH>f_85?bYvs`K+;<#uWaX&PU0B+-|ad43cZv+Q48AJ~y_ zm>>9fJPngwk!;{^{1zbelBoKML^mGaY;-ygzJhtLe?d_jiQ^aUlu7<8ERb`#_}dyI z+;Bl|7sq7t=}{&?)zdlGK_2SN#7C?xxd-{#XN~S5jt8FUtK3>^F)XmQc{AWHSH%>sqbjx&`Q5KEl98`iE$e;gRts4L{KPPw&6lBe~ zNoD5;O>!*`<*nIC(MUlR4rM zxRWMBh)*@K#uK`g53fXDc`IaoRJ1?#0AgK0MdzHkCKrof+%&~hh`-X$Fvq|K~bzplxlN?cw#xD0JylccqyOO~9_k-Yk^3lu+yIMRo(8KwI zri{k)1>=gOapqZNzKNxCIQK%XS1c_RzMzK>2anRD4nK6OcJ`OAO$B1W{hxz0J!`2d zMY>IkI&_#w>ujZ?Pn}8M*HFyRmYi>X)fSVTaF5?*FS9TKB@f*y=H03e>Du&L>!_Yk z`70G&G$kXk^b{#YOP`UA!%ou2h!!6VBjsmgS4v``1*7dL`17~`%hAPJf$t?S@gj%d z>f*^x*-oXU*UCfsdRky4SC^khvU4FBBOY7w;%T#Xk2kgSOJ?>*Wy!ngU8ilYo_-se zrMT6uOy-Uqqibk$dtN?t3U#(a;}i0Q9VpOxW8jgI2kbuS()>+I9m7wbvG}aBRCA;J z%VL-}8pWs`;POvK`Zr^@9%sv6w8bcb@{j*q>X7K92MrNbr4s>?8g#mFid@}OCFp5+ z*M9`V{HOq@m)FTw2y@-stwVM8sVSjFQCGyf+`}Z#=eUZ6`GXfG&ovhGvFA_oyGp{z zV+)&a&^%ce;rF(c+eL_9;_FrE+&jJa^Qqzj|04c#(t_r6gVSur&WeoM=J2zj$DkE$ zA{csmW0d_KqqZr0AaHO!U)cR}_FKPRLZZ+05fUHfh%NqXVvsGwrjGcqqAnBUI$*aIU80s*PlfXo8!>ZItY2iKDi}!|uuYvEM#xdgbO5 zUDXx8{-8;kznr9a_Wjqn&$_1h-a)P-W;;LU_{gk+=pwm_m9xzAKKV4eSMOhCqNxR@ zea&`12Q86Vw(8BdUK{7$6xkVLwC^;woEj}l4|k3cj$sLKN)h{rE-=%w{NaqGk7<7( z^yo}tz0x^u{=t;sqjmjfXu5t0(* zJ@2KZ!;6WQ<~??hBDZf#F1)X8%?WW1-;RNgE~YN!$jFMY+yT4Gy|)p4(TLzoKe# z3+^6{E-{lCeQvEdFP5chJKy&_D5Q?r63~pGTx=SZXOL5Mz9wW)m5v)1k6_G$2w+np zm3B7Tb<}YEt`Tg*E+eZTSn_G(gr6sx zL>Mp@j10{UdX|-BrZd1hgIjN%h*}EKm;3mLkWwdukF18YpGqSx_8}4YDPC|vG4b;# z==bZ1=7-Tp5ppk4u{ib#M8^3(2FIe%j``(E$HobDL@A9P$gpXS(4+MpL;uxZ6&hrj zy|mp5-$L{A+W~wTgj3@;!&}py-_-MP4F^0P2;A!>Mt;RDdw^~Z!J(A{yKi&!{<=RwTFYE0^ z#`u%(VNVi~)qZ@~c!qD4D3i(c``Nj`we}n(hv9S@BhHBz8dDx0nCcj*TJ2Q0l zaesV(gcjS4b-tH7%xi!TA91Ki|SP!P)J7WTmywI6}IG z-$yoT+F8hF{-J_7Ot1#cUVB}%x*Qf<Sv+bQszY>Dyu7uYV>a0CAL&*#NJfv3 zo1zO1)5Ew95dG#fgd0Qjr8}*@#h1fsv@R3q_oIr~3^tKQ=f{%6h_>xE(zOkyC5YJ6x8Jag8e9$DF5fn z-8k6l+4l1Jj-z)|rMQaZ;I@Qqn{vmUHB);(B&XCh}N?+o3O&);baBWu}pk z(c4h&ojDVKOTN5a&GiE849>MHLet_Lkh;j0U^CIr=g*W#lPszQrOoI0+13*DJM9eU zk$q9uzv!slM9x#lIFUuF_&`{U4yb^h;!JJ$vP!`jHdnFh2nj7Q*tw4hWm#aC);z%G zEW37UuNzoHCzbvqQQC9?y=D^VAIZ=ehg9dSidJ#n1=rr5bfufwS-uR3RoLTfd51Dj zlb^?V497fr70jL&aNPzkcbZ#)uzE4UrxrO`m*Hg+s`>J}09EvE(o_tYGTQr9^GE5T zXTJWnAc29W(5$%6Rx^UPf?H;G^3OpHp1d5qb9M{5J1qu3nHla7MYf;argB)qrTHlZ z_MYo7-r}Q;L7dr>vMerB4lGVLy_{4kWdLPTXttxC4SMD`%};-`Gf$^8wv2~PI9}@w?>JOB`g@aAPO~@8wH~D}kCMFt* zO$7!tGzf6;xIrM&c@T9_`FX~&xw*>I<=paWOS`jHPKDluS~gA5GD=KsyhVR)@0TCp zM_$W56uS)PYO zsuwKeLXoZ`_M-b7S1}La81z;5;)U()iLUW`y z2+R^X^Ww`ka$%G-xJmC}k-uL;-{@ayrrISs+6t46hQQch4?2ADen3~;_%}g245ydYWJ8zO^-!nZ0D8Vq+emnbZq-r-M_|@htQxWT5H2trd9g zNhuj)E{j`d3+p6s^yGmlO_SbD&5Xcl=ORo^}u#XE#_&hOsKf+ z18u>M$%w3eFtV7?IDA=k5Yu{T^EF-8wW*SXdqHahzYb+z*mu6btUz}+3+1O?g#lUY zk+W13EGb6br_cWql#C{fiP=F*Cx@nbI(iK4=Z~ghhjSJp?PNKwv*kDu4kq{WE+|Ly zHx9*WQ{Ab(|4Q#bhC$v=ayOq*1t940?pAphhJaH43%JIC6D>Co@^hf~S8jSH<@pHD z8FDPFyOrgK)$H7Ah}&nT#`9#bmtu4i2>}Kd+BnKY2Q0=3pH(K&s>>&DbL9r`U+--8 z*L0!RVnc=Z^gc=WC`2RtOu9kwT&hPRZZW? zE6s1Hl~w+HE{?1XBd>VQ7&df}nOoA4o;iSGb?(~f%~^GKDA&9^%l>;O zj~EoGH@KD#7do{hu$b)96pc1ItXzJDwdh%dp-p#eTYF0i=nQO4L6ice=RH;k!s^|6t-S;8OuR6qL>j*2bQFBTzXMziM2xojx zYZ~pFiLb2Ebi=*Z)fZT4m;fsMQ^Y&!bM&D)<#~c-CNw@s)K{%N!jv>+(+7-RoTy*f zb+##+hz;?12B;cvw6)dlkX<2ZtNu14%+N3cLuFywy|Hsh9MFRN8{Ha=ES3saDbexX zaKp*JWOrB%j?wTb1qilETOVgrY2e7ym9jb{CQ{rxX;39x!%wvWkC z4hP;T*8klktJXjz4d|+w7p~G}R`rsQ3Nz>eG91)@H27+3W**Ua|NShdE#m7j(Vv}P za%z)HC=-fyr{}5br$$Rp|AkS0 zM$?&GJy@Su=gX$igB}hgtHH$1toU4JD?Mg-J{cODyyKKf7cAz>kajiF9O0rf7o}%ACilisk(<*Mo>d zGuz${Ap>)$yJ|gCbqj(MA6^6oNhgVhnZ(G=Km5|?Y3e(U5=qiNTD)avqi{F#n?cN* z6E01@3+FL(VKVTzTUh++bJm34`NHH-Fc2lKnP%{GO0?L}Rlt$dgmdd6{s+;v7zP*l z5Z`9PYPRK0x7k{PP7JfqCN(#^a&j|yy|zFk7%F$S1r3V9dt93h+!goNheuAMuOv>& zt(K(M;JI|^ychRsq3UdPQEy;fO52i_vW;bvmOaqi`^;HagV&l1HsjW|SY3*x-EjN3vRyd-)^CheYpcPgyI1E&}_c zR=AJE_#sN>rzM-9p^KsQOG6be+!Y>wQd(ICl-rU+{O*I7)SweORnyp7w!pYu%47AD zJ}hj;i8&|IzS4-mZrLQX{vnJzw(lWe4Pk#we+FJ2?>G)N_vF)=W?n`WUNf-wB2hx# zQB&PkDge0~Z&wxBI->J0(=B;5D?GtKyo_2$h~O`aoq=PWgm39U7+r#@-P|DiP_lG> zd8tSYWK@V6akE$LQ`LnKMM*)7Mk{?W$*_&#h7n3BHWpU2@U^vJgo zJBrw#yKFBqXZ28alU)BH(M!xEjPtpRXhoD)VD`->n_FVv)Jjgl^)W={T4B7+pTiaBVIz<=8oGLN;`C>lF5KxjSmtn9%&oTQ=lOe# zNjSgEp1dk%b^0?sE-C8CWNvX3jj|;FNf9Y5C*Wr~@ZQ1Z?w=yf%B*YD{QVpFcTmp5 zeLWXWA$?0s(z~Yl_G7d;;8`0s3o&DNA2HZ+WYhuZ6t{}s*?*Z<%!DHsWi4+V!{#Df zLKm2wKj?KW!EqlQ-Csm~uLS$rHTd?lM!Tuhe%3+qa!}HK_)fu=j3k#%3vARQ+0_jY z)u#-Q@Lvl&%@Tk=lU+GZy~NM@c)SoA_-(T_zCUEsT`@CW^`7wSN$sC@$IjQOS`6hP zbp{qeAZc)}AHQA0nSkXC+|!H*c{=NTpue!N&JDUTZ;iSz354C&%BQ{_5vN7Orr>d1w) zwrc*Y&OU{!)`bD$SWLHhSzbfn)JmV0U&u_*t~plzOdgkT4f>-Am*kPe$N^l_6&A^3 zhzc{5rldr+$=|%p{N7h$MGg?B8Pd<)Jy>|A&cc5Wf@2w@=lV}>yj4u3b4R*wLYZzmga6}; zw!Stl57yRqy)AI@#6zEh;{~NIUZ=GV%S+|C>E6Ouyicm-XyTNp z*9Y)_iladbwY6`m95Zqh3DavOY~zM{iVhn-7iq!mIdpV}rGPmw9c!-(J;i%t8dmkh zCI1?)jKpF0Om8gD{6fNyADsmF?NnL4S}tc~GwtlQ9PIvT-5XVB5*1a0_ zVxjfjlr{7NrN2ZAPL)~dV1?a-4q|>>{7~9((9C0$;yC{J^mJVHd%9weswKHwv;Hfd zIXL;eG@YT(=_^_t1I8LY5=`)FD;RujT#Ay1)oky>sxQ4HM7OW;w=CIrlXLw4A?vN8 z>Wa3l%_KMkx8Uw>!QI{6g1ftH++8;A?(PsYIDw731b25fQk+|@s?M$dZN04ZG~1fJ z_3@3~d2z0h3Irrl8V%$8_L&_&b_|wY2&J_G-DiXk;o2Y;I#wFRi?8rg{;iVLnedHds z1XJd1g3+wyIp=7EIUYox5fR;<{;3~ErQ#UbBvPjHl_I;AW{+h~as`F@>1@ut&dACN zIdefTWONe&9@JSbhvzPD^(HvHrn~kJ+!2`vG0$HtcANzXSHzID;z7_7t0-(^{5@qDt-bYHN1HnOgO7yarM*f9O8W_X(&qO-1M5d(&iwLYqT9`o zbA{cDc@a=xyG+FrC!vbl0+0D30ivx_dxgZ*EMj0^(55rd{2NnBmCx2CbWL<3U^p1l z?7CCW^)3(j@*Uv#x0J@17lWAxkMU#elEi~gJH~)%ol1UZ?0qd}o-Ig`TvSFc=>+joVrs4mR>v*Etk9rOs0)F8iNd@Rx z%!6%W0)=u5Tmyiy!gjhXBLBg}y8#!FwiWnYS#tKS0zyn$^2K*rL`edWFX-0v*v?I% zdhkzA;m+phkbFxD?{RnWv5P~|Ar9*c2XP$zYgEOsi&F^ z!c#wB+KKgyWL)L+)7xZYUq8Ne;-#kZFQMKF^(wu`MkOot>PXXGN(#JH7nN&=fn;;OP5gV4y4_mExmGc0Jtoig|4XIpzo$dNH}N==Tu zm*&4~7Jeti>NBjaL@^w}K^~JNt-wJVNXGpkXvd8aTJ z%JNKU>U(kjpULr`Cswnzk7M)Bf7oTZORYZOwap*FiEO`}UyN97a9HRr5&C$4{ls)(Xy5@nJGZ(sNV;o~ z@!Ypqa{@5A!bJ?4zm>Gj>WN{G5c1lN33_|B+wMUA0%|vdbf8q;4zdzkklYF*deI$c zSDddnCi9_MoP6`qKJ^c;WYF#xmRCJ*6>c&%FjD0ZYaxvL!^SIhu)luBhd}8xI&arT2_jHN3eLnea>RZXI-0bJtGH>~} zhm`}wl38}Ar~GgKxmaMaL1*$0`H-# z%v)2?RMYLs?FeO)n2a#~VJ_!!%bXzEW3`3MLOZQsXN@&HLUj(M1ZJ#$L-n47zznqT z*->N=Z|jBVY;l}1b$P)rqn*%0I4ALBG4}g!_mQo^T{8x#KUZx@-_kY~(JI&!Hz_13 zE`;6wx}uOM2Z)L35n75qxKOYB7`IH2)7toM#*uhTrc`(PY>>c>OJ7mxB%qt!`@^Ww z*+0Row-gSzQE0u+d}vgw3%47>Ao& z4Hn~`aN3RO`8_L9WuLD$yb@T?-n!7ve1;b1X~Cox*^Pv;(2Cmuy>FJpRe2U^+EbT|JvjY&a$k~2IB~iF^zpBCvN#$M*(BO@Z=QL zV6;Nd#QSQLWG?bML_2Z+zAJNB@z%FD>`aWP2uKMDRijaLkWw!uW<|81 z)zVOIlU&F=?8Z{M*vR^ai*b-;*0qb?5=AGWC=Ry{07`DB4A19yUS#u*nfJq=4e$;J zturcLE{LP97mhXoAf(fjHqM-I@8hp=wt~%D(^GyBuYC7db$w|VnV}hZjhM}L&~;!x z@)6_e8G;$4~l- zk)l**{P*9LQy%xn{9u(jfe6Ca#8O_%p-2Jj!`6XsehQ=HNe&u{^-N#IWh^{%Lny{k z<&A@QC|V~J2A}~DA@JP01n?$Zze7wp!aCLIOz*TRFxm2}g*uWdJCEJC;(L_GLMp3+ zaOwQ#yAr8sqr7U(OEAfUb5+tqqbO%=Cg}+3LfeDh<&=e#dWAz+AO%7-oKmR^_zhnx z)uzLh)NlD@ffGL$WKY-@_lwH8etZNOV@>JfvrfBQU1G2ty=~X|%`!UOSploxKdl{whI4=HQyRB(;Nm zsQqs@S;(s}@c>Uve0T|#2GN73RY?i27a5%6=MHb$Qkx`;Iyz60X*I}rR`0NR?FN|+ z>Kuh${e#`Kw-gUWR9kW;Zs+jQV3ee+mXTT%7U6{NXJ`N3(PTnIx2^!!RMM*Kvbpk7 z_amc(^TP%WTk2x<>3kK(o01ni5iTGuNMwRCCqw7YoTc6$@%052#NtL+p}eKi>F9GO zacPRj7bAuAKvZt_1|zupKR@VH?z4tNY%6F;>M2M>=@Dk3XL=E|qrJY(_-x%%^4u)n zTK4D5>rgiy8m3FGUEXPTo)qTI;%(NWjzqEy8Fb;N{pPIQAFm7f&BDkb9v_KBGF_&_ zP;8j%$7g^K9Qz5H-%@SnfPsEIJF*T%P-ML&f?HOwg#NuB(mYS~2uDwU9#RGBc$1zV#-udj z2`F6x)@RZ)C+UPHhaUrVn8J;aDwg^i!Zrv9*n2ui^~QZsUDR5j=}slqb0>M=LAs(@ ztC0qTF8*~#Z`g%8*SKNp!&ZYY(UkcEPVIMLXL{?9wKTXeh5r(r>zCFwqS*{-R!VXE}EH+R~$WH{8949-wy#5HS{yy_U!>|)sk{%g2UAH&d9T$jh9CII{waC z-P;@g@H}s@*m677n{ecLLGec{o)u{Z$_`=tJ23h1 z8AzN%`%w<@c`lQMbd~I2j7WP69Fhf^X|d0Z*(2q7#JLmC<1OP6^Y-0Ybg|8VF$Wq3hqEhFGTdskAvtNG0bSQt{IEbT;%BpFHpRGm zj|4y>@CUP%kbBd~?K4_=#5wNwlObNW{c78mQ?5wIMb;XjPTP3TtQ{%4E{}6Dw2tPRps;aXd5x|@PdqQ?}q++InXt;`b&&A;=F!jca`|kPf`$S9E7o-DzZU)$)8MT z{dKjtfTG}GI_(YvQ6&DY;D+I9-F1^g5M{Z!c(7^2m~ZKjb8K!LZo%K^H=^p3gp6aBlDHq4B#V{Su^laip1vw9hBC6%SutFRm5ipo zCo16bU){`A;ZX6pyu(JEz{|>8G2-7{cRsQ{W3kkzP^SkZ1*6b`0gKDg+2Q8A)jpY( zjcTr|c83|@S89}`b~*t5i&!~c^9u?kJi;BQP%D5G7eJdAe6j*4yX)>z){78PdiGU{ zwh>*;SGp!EjjsQU&b;!t=(?*DM6Wuha11klHz$;R5N;^nd2C|gr~=hKa!&p2oGP{zO{ zarx%wCc{rw04*_Ilwvg`A9!Aune6t1{Qkt-IBA@ zmW(n8iexUn{qJDe0M6ErHvCx?5d6}U8XUJ;;5R6InA!_bZm5UH%vWM-@Osy2`H}Ll z+}`#zSAaL6|NeK!7|mDvg1i95FPP5xvlmNxX2qpl5%8$**hi-87R+BEZleOMaD)No z4|=)`gCup`BsbnT*Gw1nos3IM+Rt+5J;Q%FnFS|Ku+NEiJ~h`Dksp6AqJ0>o%j`kF z;JxL5EUw>uzPkap_2N8xeRNvPQIXUDbWu5L!=qE-)0oppFK3s+R*rke1)}DTQ+S$? z2swh`w4^6`wY<|N*)B)uoJjI6G^)rxL%TTz*5y26?zdKncZ=tDDX4_)wh#s8<;~@f zo64OC`|N4N$}hC=aX$_wT~^mzgW2m_Lfmq7ci%w=V<&4P7b z-pk~2cZ3&&#Pc~=W%;0HIKt98F`*NE3}e(o!E+oM51brTKq@O+WXrAuIK?A*cc%k+ z4A+4XR&fDhJnk?>3B4>T?BEw4m8FIuBzy#t%qzyac^xO0zWA@?gL@6MG|y%T*0}X zCr}+f+Z|EQulg0|%nk`8b?v&{uqZb@3c3SXYesIJHpiUcn7HYDU}Pc*Ui|VdI#2Ca z-vM2~5F({bri~}Wx{^CN(wWq{4R!`!Pep-=tKc(B=9_R`fU#KSdZBRNYk631b`9=is)DoJ?Q!zGMH`d(ZCGf4qu zB(R}wG53moTpVO}x2h4uGw|ZC_FIFrSNcxJT>py=qy8EdmS85JRPpkbh3BC?xqNMZ zt9;dt=lqWC=ydBz!=2~_3sf8J>zDR__!VdVi&#iVv)F|7CBSZP}c+%3fH`(|!E@WH)^FGY-A(YU(b8yUyBc+yIs{upB2GtFA?w z+Dq6Bgrx)?V7|ADVZq+)F};I_D!RIo?QPV<7eb=7oYvKw*s1$?KU0sFGm8hh@2BaP zenA@APOItHfJX~X<+3b0SS|l^%%k>8z~GC3w93ms8>nm~_Z39UqV{mJ_PLxa)OFEV zJUUUe%~ui#;LT9-!;hyLs(f9e@>n%_U8 zRnWCgYoOF|b<3Jqkj`Iaxd?T}bmE3YZYD@!;-1KzC>iUz5wa9sO(Gvd6>E2UC%eTzSuja6szS~L9vgi%pAQ# zXV@HTH|AfnYgehEF{32MEx>hpY5$XnyU5kOau3yQYhgTiAJ=@;1EGer_tnll#XE1X zo4S>TD_CwWeW=TbqZ_suVx@upkj}y?X79Ucwq5!V&W&~`2Pmx-LM5^SJ9{N&WBR1p zu%wVP?0rToE(=}(VOYj=&g95} z%ERMG`vw!^$n&(ziF)>}oh6HTIsHeWzNy^Q^wrSv!P4{Tzjab`YgIlxnl{$0Ob~a3 zo)O9?ygaxtG^bWB*B|oPgGJJ5*sMGVy?iygL&l86b)5Q~6!$&khG&aC&mb#c@913@ zp>A0CC$Vi&g-lk?dEWt)F`@w^s4!)@8@ay} z^&}_)A%<)GHe;suZ@zfd=3yeJw(|+a-wd_wHK-(4v~PaKEmOsmC3D9N;cF%VQHtB^ zY(rMw^6?{OA%zeJ%uK8&OfsOqe$@pHwThnrFkEyp1wjNbVvTG+Y9{dk2bVHc4nxehc$d@`^XWOmbV( z&tGD;IeFgr-4C>p2e0v$->x;U7}wpDfm(-^bZ#^>FOp+o->jl>`L>Q0=O5iW^7)iL zyu7hik&dXC%XRV9E90*_O+qVnYZY}^qN^rNBx?c1GS9e{W{H(dBftUeHVZEWj2Uj@ z06rVK)SL!1EVJS;sQ_3$B*%=MoaE~+?#c1w=8q}IxQDNyiTa=HQ%wY$f}a>*a+foH z&C0qr_@Ek0F|+!l(aC?Jv$Wxo8Ey4_FO+*hzu!Xx_&V^%!EM;$r#wXfw~UscN6lIG zmGOS^9fhwyu(gK06m_XihoOF0?a5XoO!W;Y8d*OkXE5M2Kr~nBEdEfx+DqB|uP%FE zPj2qZxzxO+Qohsj`L>q^VX^<;rNnIMy8WBo`QBOtV7vmIB_j1HAB|X541bDYaq`4G zJ#xN3BW2eQnXpo)KIi8VexwTMW<2puYJdmOS?8wD6^f*0-&abGWnogklt_g&R9YTdI7aT00VPW*SE0qk!si;1d zS;>7~Alg;p1-lT|m~XQ>e)(t4Fi1`j`n>)6a(qa#(AWJZxX zi9D@-3g5fpd@TaTuzby8zBh>h4fSdQ*vGBBgb9{} zzI!yQ?{>P=x@{{V_OEj}&H&eDaUb8EFE3!brMWXq&23lOJQih>;dawB)9lM=xexcs zP0h0RNtc>b9U>E-`>Lffd!`Z;`{0;B0RpeV$D@m`%L2hI=c@a^Y)mn;4ovTicJ-5A zOK9|&=L=zSe-v}pwez5UDwPYnBOArkpVPDCZrZ*CUy{OYG- z?VITW6P<)@k~LuAwlkxOxRG_fngP|*6*LWOF>8C9=sYsgLLI>oI3nabUL@7IR{n>H zZ(F%bc#2WGab}Y(m2dgjx%+Iu&$xtnfjp4H=G`LVF97%jmy1R~D zCTFh0-I;o*lnH*pQbz5l&xgkM0^q`FX1m#G7SFcoa`Z-vQ|=uJ0e@n*e-7n0FsCHv zG7CA#vUoDZC2$;-_W3&t>;$)Qi85JvYxmZVx<_oTyplhZVnyxvb3jZNhp(XX72X6q zUm~?516-JYB5W>_(f7ai%D)z)%(w3K6PUI`{lw2a?*_`n(e!+M- zj4G5#%VSYo#>HmJnFrviCo?qbgw=_{F@aH9aP|ycV<(=7T3?db4hg-}ab4fDgled7{u*FRjz5fe2E;c6x*Qx&UC=?)dHfD z?IJwsY$aifW;)wC7vR@PElD#$G`xot9xUM2yp-&L@V3PKxXbpmZ&;AJkauB&>!}^W zGk^m*CR#!9jc{A2?8+sWZXiUkAX3}=Z$QCi&cuC;pFwg~Gh&H={Dbo2Z}6J3g5$@2 zwtk&D;I|E*%Ca=KWY4kQB}eE{v zKk=E}vkf%9&W7nopC&wW`Xq;;Njw9Nrt#dSP{X78ExsWL6n(^93p}!{1c0YllY#{v zF#+!DcHG5L_BIK-&0w*OksuzUVWq~Kf8|6RffZ|LQ82KwI&XpPAqRs?UDf3?A@OPv zWNuGVN{Kom;GV`$45NqC z%gaEi(qPs!nmFX7ES;I$;=uTK7%r^eMmjuyiDg)n&cTYzj1Fj%<&Gk!V5E_~-WeW< z_>6bGJOOZ=wc+-Qm}--z?CtfGEOtT zzh#Ru+#~d;?xu!(HEb6u$m>Na6gQ@u9=zGjWhA@K>rhlC4^tVB3>mXRDzhN+^1bq| z-W*4Ne8R~)l>d|vr3C>(#c%XX%DZhpIV7poTB>e0DUh;md6w1{b&V#abS+HmVd02GQPw(TS_Q_A!?(h!8@ zDt~emYQTB>1EdNQ>A7DSgB>yKT_&@f;IF`dd zWKUPynQu?#@@Fv$oWgp8P9}H}5YVb+ZH&cy7aX*c-d|V^7f+O0{fo za}Q@gepg=JXJlJNU_xJ&+O4NF{~3#qOI9B54~7bDJba7+xGv3m{oLbWhgv2|BW!+Y>O7HDDs{`7Y$6BVALCov@HR=&*DrC=a-y9*}` z@nFt#lx10CX}?QBz9~SsPBdVzaLF)Y^qHS{yCbF=J76%L{c61*8{Ye;V!z&qs$Z6< z$^Ez8+j0)tj0u#wER%Q4?+KD(MC(>C|1mEps%>gJ!jPUfANRD?Ywe#`lI`_%UldG5)@EzOmN3@$x!|uBgO=lyBs<=1k== zXw{*UJ6a-ViOH*Mpl**hpQ)3A80uSHD`~Bn$rObtdIgKw-@m-v1&>}b6+TpW1riX& z(b2SeK#o$UZ?17gNkv~V7YyMjPdFC^3)uMf0L`i-G^ci0gR=Z@P zq}JXRdQ*%8+azKmxti*EO_LF?0Gg#9L65;_eh`^( zTeszZ9zA_Q)jAvST~I+tX@5vJ5!>@~xJi^D4$sUNLA>lRUYt8~MJo!U@6?~C-a7B* z@>7gS{*V$B_~UyB7#$FP&fmyi=ev-@W|21~jv%+4XHt7OAGFWY$4o7HAzcS?8|?!} z?!Mi4TF_sv*Ln$1^lUgBw^G29ojrsF)!L5G$+@eFY6EFnNflJnzQGGAPd`RQG0_zb z_npzFanUK}5XI+I2uGs*$aBKdpHHW+Tw$Ew{_WGcG!CrES$}pFGxw<0XoKZ|u)sK& zLi;O66ZqDr3_s?zjjypzu>UHQGx^4QDYQ6F3Q(}CD{?<6rL^O*?~u$hi_Tf*l2uHp z@Fk*d$;>9mU9gj3xT$wZr};W|q`Jc3+fEL2rCCkoVcIAWKjj&PDOxZ4mxKT1I7mGkwX7dOh*2aJs~*6AgNtXRq;(*6dyb)cqWKUib*K#^{cod)}A?Eb{z! zrYQ^rD~3~Snz((!d5D`e^Zk(dG@!ow7VINKkyF}K?~%y(T3_E60{<5)cAx!}Q09ed zi%VIuNrCWODo8XMyx;zMOl(ixF#FKs($wgy>+!)dES*<4r#$nhM^rv!(KH7ST6sw{ zbHUKP=(#oQfx#qiY1t0ryS^@&DOk$N0Rs6z96uE9LFgTed+jQB5&}Bv zP=kEwm9fBXd(2)w*rkM7KG;0&q<9H^SldA;eU)ft+>^?#=QNy_2;0OkfBw)uC*2nX z)l1|;$U;qkXs-BMJf_1BvNUMo|Nh=7xcjxBkVM1~g*LmM_b649 zzs<%?sGa8ThH#TY_udj(euX3|+wzuhUYHop(aI5J0MJv%N6DG^!Y>d;I7g{d;)N)E zD`ap2SSLZ1hxFL1b^;PQFYq`j9?UEo-Ixw`MHH{pU(EbUq~Tg;HD1DB!D`-=-AJl4 zRR8LZA2ng&h_8+OG}-d*d5d;|&cbWE*OQ*=*?E00o5xh_@}xL!TV7>~Lv9J$p7_x% z{yHK&Gft`^nJ+sDltOR$qdkhVrE5TIT?((8*>siZ80K&oVN?OT;uFcN(!rzl7U>M| ztU+jg;fbOz@k69&F{+EWAJcC&Fxrvm#3GTe2odT1$g`BsnMMDIW)CXH=jGlMgBG}> z_06vQ@5%z7_v_&qn8RrD_SOn0A1PMd@`^sOP?PfwWKco8 z|DiX}an@zNx;e5EirFsPqIQU5b^<;*>&#ajGd>hK+K$xf^R0C!hxAmZlTXkCB zhy#Lny4t*&>5*xhc( z$&<*&d}W`sHpRdo|KR^Wf^p_Qj`cUs0q}lA=rb+zx#gOzqWZsFk65$wxbChZtQ5O| zivQ>pAG1~6)=w#OOX9p+1>vXkZn#_sG>0(Xnj!_Va+i zrjggc5+xhDF2t3DJ@Slc%1<)qX%7zZ%d?11F&_#wRhxQYy9iiFFEZOiUL#w4=$o50 zXTu!nv$|DK5C$X`QP;T45p})2J!i=@B!*W}I9o{q(>$UjliqfuZ|ffkLNp7}o@hom zb^gd&k=cQLG>a{vA^3gf2trF4DP0_nnMXc2RmfE()xsXw6JtNl`K|~qbWEi0mAlKL z9hpCaJ&5jU??*sEj5EpZHf`ZpX?A}eYaEs0dc2wVKon(nw?DUssv!p+%iFMZGYUU1 zT{>{j(7x(ng2u0sBNu~KStmllr4zI5+UcVr=|K70K8qaanlF-GB#6W<^u{<)Jw*Q= zn5)%lilpU|pLnUW5br}z!zVlw!09xFu;HsP@R-!gURcT=w<%|iCA%!FFX%kO97 z)%au8(f6}*EFYD3$0jADqcm{G-?hSF(0iXlzh`XD!4KJidCGW_-^v#*YgVMYj@7V&1`qOAxx5X_duyCT3 zXpR+H@We7Y;X>deY{nj05+h2d`k!>b}erfy$ zkwa>kQz-Dp?mlBnd6JT~PmCtA=~veSFRd#%5|LGMlT>g7UtQ&{Pvwa02`G3)Hc$*qsfE={;PbiT$sI%ln<@pMZc|>AKo9!X<+o|#mxTAZ4@RQ9A zdR!%krEAt+Orf~Q&0qJ3{Jw)@D(#}sG%A|^=m=IAjLmz%^+VbzjT>ORWl9w;0J7c7 zm%B3y$n)zoWz1$}gv+vlv)wUYk~Ft$wo(~>`ii9Z{noY4o|EyAFLxRKGg$KV*O|!{ zmHF1_w2_=PDo%>`iCgul7N%4?=)^e_Z>IX}XXfs7wh-X@6_swvxrBb?Yg7*9A@}&0 z9yfP0>|Z)h`?-nDiK#36xIdI`0pG~q2&7~MrrtFKzwAX~%?g~q;)#4FqI;pAK$62~ zv{kq>2{e?g=5ug1S^-q@h_M1rb}KwY%hA%wVwat`&NnF~P3`3(XLX;ob4%fmUa%X( zGf$tnS))@*nK@yGe3Kl6KSJ+Fkbp?=3XW7+2o;A#JJ^` zXjD!QX$YS!%#e0<^HkxI0Y2)(NEuQs8@_&K-jK5_C*qbQCph7Q-zt$-S0O?2pbd-J z0In!7N_7>~4N@hS+X~qv%i{OW4)9D`b9JxM>%btg>bYKW<@m_6GkPO*rDie{rkjiO zKZqn9K z(#B$mTLS;iDp*2p8$RAoG{-K9ke|eCNb6)mbA*nrVUC?Zllsz=)W}#=`kNf_5ptq< zev2N#L3&nAqWBk`6`Or_JMuf{u!@qjR}aoZHg^t*9rbv1%%NFx<5?#g6Z+Py--Y^V zoqTWaiN)OvphvXr^`4N5%BH#p9^hNI0l$k@iOtA+i$eSU8}cCGrul=yBwv09tuTdr zsyVpLz75f??fl+#VWGD)FUOO0b6`= zJf#0o(v94atH5u}={+}WqZE4>f0TCb&8+|ddzw*)I&W7N%wc@qMnkAHY#C`P9IlUX@l7aMa+NIMX7eZ39ZMm|Zt1+LnN19fkc8qs$u-fBZz zYzY?$CJ%%%gLalY6+l20c~UyE=m(xZA~Y_3bxMBL0dCF3;-GHkG=ZB2h-&6w;(T=t zotgVN{%S!pK=k3M-}Zi!8UKss{~mF47uIanbAD^qS;(mRZ-3%Jh9hmgAts*W|CS3G zwg-1Kt_#m2(``uWS(^{3la}!tTauE1%P*ZbNvg+0{@s}x>HIccR8ARLhhELQalF%Y zb-kInekX9N#}Gu07af=HFjiS@=W?8{7aBZZY)K^)j1S+963BRy|@jIvYN`RJ?{p`35ip?z;=(+u47R(vrJFR>lHnjIX*izMsN; z3;&mZ1B>F-D5?mou5i1ww9WImq|1a{MT>x4X27Z9LJ^p%l57aeRg^F3v8OYJ+nM#T z`ds-iABA9!f^rIf#j!&SiFQYugRQc=Mk213YPpT8v?#Zm31jDnc^1tHQb;VWNX{b! zzP@trx&9RI$Odd}JL3#@jV8*4@8s-egSwCloUtmAa7dA7<6W6lni1yvzn>wAYu^|) zBTRET0=zfguTO?%Y!6N8E@obm8wo0?O^Ts_*u~;6GV1Z|MCVZC@oM)Jb6VTAx0y_2 zz#8hl0>bbc-Km910ZExe(-*=G+H?Iy=}eiAAQDF|w|YIJAh zEd~_CycvfP#*O{IWevKhs8~?g_c!Bjp&G?^Tnft94Ms2yC>(#i0DqLO8ly5(j+dQc z^c%Lhx{S>Zt5!p+Qs#28_ewezyJ^~Vspsz*jdCuop5N*0DD-<3RV)1QfNEnir#eyQso<@3$l0Yao$cM{R5N%v1pG%Q z6>GY{o1sW&jIbH}3xu$zt(38s3oAxRC~90D(yCjk# zsg?0a@+`cFkGW0M(`lkJ7ZgmXVVM~zsyZF!1vM{2zyV#g_3K84QZt1|7!9iMK%jBS zfZPbJ!~%TuNVIpD=*mT2NPPz5d%N&{r@Q zjeRUu(49vag4N^DiC)9#CHI_;1lcO!nz6AK0f)p2$1IQM5q}Skq*7dmMMDEgN#&aB zz=F0O6{(Cw<_T9BcE1$xz@w%qU1t|sV#Q)fwAu#AP)aYq_Y5WQCA|Oqr+xS58lnD0 z+CA6~R(##P7%}imK8=W1BUBlcbhU*ZZA>9kzJc&l-}=`#?JZY7{C7G~EMEeaNderi z?o%8 z`-uFQ#Slaq(Ogp21e$s=$<$uNo90xTm&w!hu^>CWwgowy zrHy*zb}rquA%dCYx(_QxOo~XYQO0o`^86C=^MVnfmTELHNjEC|nUf#HnOtO*AI2+JIali117XTfWhHt63+Rfm|?B zfo2@}^ZYUel{Gb$Cy8^H?rG5ik!zYf!56pW?|e5;97REp);0zzE542hj+Y63qa==- z@Z-!Lg0_?;*RG22I4qQ`kD)TY+0Wc9b+jq53p!mv#|zcGw5Vlf1HTbCm%``Vgp>qo zQ+%e#ITF94hXFs~(GUok&)m&ST9DU%H^Lc<$Y>y%Cd+b=$0W>1wk2;gdG^FG4ZzWz=0HarhMv>(-likq1|` zDV8*l^lX2rHME-P*3XAPY>txUV;=g3MlSx8x%ytql;b*dJ{!B2!J0NtG{OWukFMd} zIPR-|?`P}}!5=R^XFnUI9wC(LO9SFxCJez7pct1S0nu5!68i}QWLUgpZpLc<0F3k! zFcnp{JTm_cq*d4J%|zrZBjAD!JWZKpOTHh-A;V+_zXw^q{lQo@O7P!$;WMHFXyq)~ zrJue-;D^qMH+xtmw2`e{qE&mH_iO> zTQm#{uAa%IkAuN)F`FHeHb-DXaK!D~3iey-!}E>P{KXI9A>f!i&u+Q?k-l!7#YR>s z4FC+2HjO%BXoV_B3%+dY-MR%0oPHL?Pxb!$zt}p4I%eIB$F2xB_XmEFTcc(~k zLUFg??i6=|LveRn+%-V);ts_j*zmkF#~ky{%rD57e7LW3ue~To;nrQndAufu)Qm$A5$6NXVW^Znx3-;2LLqEo-4=t&#}^kV4A zt!O4A7ZEK&D5NgXzX&1s=@fnF58>PtF4N$OEE?Pma6F=~Y84q#BUSwR;q-$*RWz#B zavch;4OBK!z*RtOt+-s_PBgtPfy|bRZcChqJ2YxjZzD~O@E`Hb$H0gPh^`dBKonKM zTv$(^szG^kI?Kw_4Xyj^?lLiSp|5ici{2a(1YC~phg_C>+(r>+XsF%6EuE&x0}_Qk z;}H(#aeD0+Pm2j=xvK4Z)U(tg1LkxmRy3Za4j%B>oj=@RTq;ZYVrV^6+LxtbQ_W6m z3lKc=Dn;`taS&%vS!ox_NmTZSi7#0x4pw5>0Tu3|*A%-xScst6+4`}pyq%0uj>@WF zcu;>9u6~?s5&Cn$htUlmr}3UXpGacrbXf{RDr0i+%)`ZC|DcSs0rQ%cim>;RL3Nx-h&T3e44xW@w_? zl6f{9IK6n=r&78-5E1NEDH^!t+GhS?H|YY;}YNjSt96 z%#B|`g0*Zirx8V9J~1O0rQQ<%bSNEUaWtsRk0UG`jtZkt`kYr-fhoCq)d~o9OMYd` zT3IlAa}yT4RulYOo?dLGD+%O{_IU>mgyN>v3)iS;70S|Sd|z>skW)x*M^4lF45k^h zBcHmzg0gNM6LQ#^h7&Ee^(Z@=(PTJHe~Fi_lgk^!F@t}K3u}DrZ)aYT7Uu0C+=rNQ zYknrb6TDZ4Bv>UFcPhYL8d;^mxd*{%YY$c~5XE)uES~!9LBatgv;#X{luer7!XM@teVqSGu=Q8W-#mh zs>py=b&J*h4hAX~op}bw_bl#b?&S>_Ul$ulyqu<)>pjpzhO@SlBhD?-=2 zD!MSmQOW6J`lZgird)7i0~3r1}z71Pw_m6>Q*+ZBvpza&X3$b z34ZzOr)bS5+W~x(;XSSVz<9~UnGy7?2zhHD2Rv&}Bl1nOzutiK=p!*<3Cq5nn$9Ef~?Y?Zn|ht>@U^^ zSfuJtJ(PPClppBSm5vsRLCF&n=#=AUi=)G&;`0|L7bM%ly~j=A*$@8M$m4NilX17@ zJMFX{W&a{0N|c(wa}TKDOx_=785{|e&KW5im||1Zgum5KQuI0TB^Lf`Gq{&pjM`Rx zUg0f@k6hv{ibFO|le};8E&F#6ac6wF5={VEnD`y^W6dWo`N29N)4wq#688GLcBXIv(1)6 z7K4KqqjrpQ78S6ti--VrBnV*9Eq~*x&Mp|8J3s5?A0lj_rCX{r*E#BDZEk9kdgHFF zMz1h&#*}{^!v`Q!`tr}kwKwr`=KaRY$APxk0G%~Q0d!{4f;sM(-h{by{#c_e%!axiebO>iW_X}bBiulpk}C^uj^&gxzUKj1$} zEB(FAC?IW-uHwAEBnj33c^>3xKb7&%VAhVPp7&AZJ~qF~ny#$IqYKPHMFm?^@-JrZ zxUOe7jCyNOIDofB`44_RzS%zq{l8XtI4=GFxCkv0+i&3R{qxuS4C35#UXm^D+llZq&d7|8!V`IcuLM6!?jeQzi>OP(_Bc6PN7%(1Xv!&~@JUVB46# z+?7Ml;?=PE)k;`e$IzE*!v7_Z?fm%0`?~8KAeFc5;D2;8%6;42+59v@29i!d!qXDN zMK{k&*xyAWDePQSMY0lGTH`gec$w*-8+8)ntk%-d;SC5E>5of z(x1NlKb$1sD4dV6AC41q5XzDv%lJ(!GPpm!+lludH9p(?@yezIrkIjefwX}StkK#|P9qNg3tWq5cmAnG- zON^)4bJIgdjTve=eUoQC5UWx;^^SuTIjW=c-{es3B@fTV<%)F}+)m$mT^!}dJ8I0b zLg{xENDM^kNe+~aj)V;biWdui8}mJ{eT}=ovAPNi2fh9~)mRf`s!isW{?LkB;%bH#DF| zz}fgpr8v6VASONVt^Y=AG8I&$L_$|RzAy&bG6*DY$wq2+^zdFLInhca*?4OY5xT!wKY0r<9<)z%toBX4uMbNs8ga7{L}>mJO~$d zHfUA)F@0B|%j-MwqL%daE2EXUx$2nN?M5&&mWj=1JatOnX8&E=B5kbghuW6S`U#R}JPeO@odqW*8@y@-Er{t?{VDd4IiFRg+T^Ug9IkGs zqFN^glIg`Nz|*HfV*U+1@wSOCPT~_`!t~3wI|wcY1F061R|O)4akusGtUyHaV&PkN zxG%Klm3iaJR0k~LKB&6LQM5q3qDmI@Ct(!MuE>pIpoN=-!!Qbod{%z9`t2e~{^`b* zy}sQ9*J1e(Ag$B)Zn_V&;?b1_d#ZdTMykI+-+onHEpEP#UoyVeQ|gXuW*e=DYqDX$ z!B(ph(XiNlQjyRIikhMoO7C6(VouAv;RWEhLdG0I>Td<@*C)cCz~=dMvNmXe>imbX zKc+^np?-EWm918H_}#QDDc6vExui3qX{IV1EJ;FWtBz;zVWI+kf8H6{7g~vFjV?4bwOmBrAL}ub z4MyLnn+lPW1PR)NQisHMQqA%47;50(ccs*a(6jgee#BjFfLB!T#SxMhtouMiS|3m# zzi9D2Benf57bi1rU_!u=ugeUr1P=>#a^8klm2QSTJrz%)kVH;cwdp*iz@H+NjBS*U z_7QnzZd7UIm7|UiG`T`DM?Yln%*~`jZgUR+w0~Cu71d6z6u|V((WEsPm+8hVs_x6* z@ySgX2Ly^>NTuhybqG6Bsw}w0qD_}2DH8{I0t<`c+`pdGP`DU;^2Vm%v_g*;k15X# zvg24BNcLRc+hDxVO|6-ODWQ_Y4eGLz$tW~o9N5Y!_1FAFo3(we9BfiJ4AjLLz|$9% zxfRt7xmCAkOtSE4-Wm6kZuR8pidT|&`b|XaiA)oRvx5O-bd1>9Mmjxk3SHiu`H53C zqy4Ey9QeHdx?$PFH4rJ#B@d$`CX>q4oAU~1Hav3dHgM?X_aMxE^|)2_L|}RG^gHu! ziBK+W@1e%JGr&+O$@xp>FjL(VWcR7aZ}^dnj!3Ft~X z$>KNQw7;kjr6}tHmfW)XMKCKIEM&?5+_jyzwM+fjVCHvbbZVSDFQvzq2o0j~K9LUcsZ7aq{%F5meLG-nC!^Yhley=|@~nplKa8kMPqeJlc)ae$As!wZucip! zZnn(6-w;e8_q+#v(V;H1YrohNI4pP@L3>!$8w>4lJ_x-*i+BFr@$Rp^6ge-!@iXsA*rV?0zVlM#MZMa~W~zbf zZ(joz^krRNTz8?>JD>qk;F?CDXK${ynpWaxo4c3dJ8!8E0E0I-}!>^=N`2}u4 zxmB0Rv~}Y(XiIoIl2XK*u18HoZ7RiU({AaWHBTw_0*(s`ssP>Sq^2m^M<&ISs&J*u zs~s|EA%f8TG_~s-;8k(x<=ULsfY*G$X;Ug~CXA#zwY&4RTaP!8xioDm^>Giw&Qd=V z;M_0;c?o5)^4^B8JQ;Gbbx!~VFXK^8WoeP$>0y#EO}{^kC2-HA_1279k1z>UcUBB1 zRY>}Tm{|eXb0fVB7VHV=6HJg=HEF^rpc^lp9jXYql&Iu4g3ls!G!G+XG8sZniY3Yp zBYq7a^CoxG;yQYwVBSy1u^)4D;#%%M97y-`xvB1Loa18gxHekO`Yb;cQTi*QZ=pd4Gwa<^=VYjm z&(>O2s`XwtEtE7*jt3&UoQlS7M8-vE6}P;hGTn-lO%QRw7?ljd1D>gg-Vy#`{Yne; zi!V+$0F*v@9{E(;XAyH;`&9S-e&**H0e}XpA(%6=??7jWjplNFyEV5u#j=y-41v>( z*Drs9NPk>J7QLu1`{e`?I;5|+!P=CHUU@%*EApn+ktw6cVN>3f^E>dm3^qYWeMfqi z&%b5C4xpS)ZT(k$@oxb2^QDAO!Ka)+;YmIrF8g@$Wq6j7(A9GabQjOLUdHu=O^_r* zcp5#P6Xvrq=R`Y8G;R`z1cEly%S$slGsvdkcy{ltx-IqTo6byp=xu@0(SCOIZ32Fp zd&u>NrmNsjpUhf)|M6IJRy_*lJB~mj8O4E;KN-#A8~*Gtwl|FI5$@`if*6Nwp8|H$ zzL}9FMLLi4B42(+U6vh>=m5r`JH?b>*f?s-ty!@+mezk8ar>P`YQ7aZlJK_?H+XhW z2mYL`IO8f#_`-{sda08pYxQ2{1pb-AfmMA7jF%C!S%LZMPkPw^TR)B2`+eN!hkV=H z;ZaZe*8>U>*Ui@W)dGIMfZnh~zkBvhYuJ{xHhJHw{k}6_rNMbaZdNaN$~KN>(>$CjBf(H=?CvlsO!uVh0AtfUzJN?l-ew*WASU=9C=TK!y+Y(;cszE#sH*xjoPjIi-;W`u*zWxMv8;Fj4$Per%_s-3z{U<(N9*C zm}>nm(Y|A;UZ&?s^y!2Ed0HKS0Kl?K&V-XjHKU=G$}IiI9+BVQ8#n2xHI+f7!D${t z&zosp#fvzxh8yRbM$CiS()#`P&D5OjTBx*93{TV6wm|>RF!!y1#qT*IonMv{Gp%NW zqPCZ+PW-4{-q?EE?z;^rh(kOH4tp6-i?*|LUu#d7og$V*RL6Kr;!{OE#Y8rNs4VlR znCFmc)D)4YVY)>-W4YR;PQEFAKbfOWi*QJlxrAE9lY7?)f9z)XPr#6~lj(2#=zLF| z+JC5uwM$Tl|8=u}E!GYjw|igB)N6VP&r(EB_Lae2*QkT$V-pgc(&&3E0qwC<^TFN!qxE=o-%>#WD?c-p7LBwFDfQb?}|?AUKE zI^u&HT6AOEmXhV3tX`)EX}Ikr@a4VClk=IzRXgBGfRbE_JA7x&;umbRjpMgDONYQ)oIfHbK5{E-28BFc-;9d=Mfc<1$C)J-rZJE^$+j|8SevM;a#R z;N6f>6Zeftpc?+AVjM!2*RUOPx+P<3y9noHaMHqWMrU;XV*(dMJaVV0f4@_)>9mA3bvR3PR>qH)@Skq?sq+P%8-Q{Naf6*-0khU)F-otVe~FfNT}tN; zWlj`-R&i?if^ChU$Nlx>iT}KP3XNf)rEhA^=D;}PnW0h;o1z&7ZUNP5E>5o~UZzO( z>iLkG>!|UdU|)t#S|bye%wYPl?ZWj0vG&oDDNzxJ=CL^|->nK+$Cm9ZqkwlxOidwE z%Jrp#RlQN~i!OE13j%g1*RpBS;`Aqy8kI-Cr(oA{6=le_vS88=17c-?XIt3pd*jbj zSHotIjmugKw)+1|y!|Y*GzoZNt$IDu`MMa#w-up-W?J`uE`89CRjS2XtM_z)+|Hh) zXgvNWCei%4+4}8Zb3hV4YxCVs_N&945p22|P>9asO%49Gzjkoua|YI%V+W~=fRF!9 zeO-KU+9-4#(c;m-g1=ts+K?zEdz9L5yIC;Gh=|}`ceHDVt)5ShGAK55`yVZNHTIE* z&vd=|&bGhgw!AI7yv?yHW!X4<2A`C-H6Fj;p8;d`)H2p9sLJwy$2t(`-uKI5HA&kQvtgBSY4>kCzJnv$^1gY7dTj@Gbv zXM3$U%O#8#Im6gTWm(b)X6DKY#q{Od9Ppks(-FF~aEG{DJ-=uv#dUlukeYQFCRN?d zEToR|P@wd0hde}~z2_1h)j3K~>{cQX>t_FcY2W4^e;n4ZGBoNBc9mV4Vb7$(e(3fM zceQ~@VfMoT5$n{1v~DU#F?UlO9|M07AJK`V<dJD&3($&2cBkBdD}l8eq4hK zNzJ?_x>aLit)dP8pdV*K`N$h0_`48y#ZF;%pd*Z+j=l@B{Hm$l+=3%8kr-a+Xq$;q znO35HS7yxjA)t>qU>N5?OXi7JAWKRtIH!EK;{Y-mIjty^AZUxveK{A&K2)76+=mIw zu2io<$q1b{O#E96F4V`_;ocbSkpJjF@+TP!oJXrubYr^fYP4IG_!=ZWgrODc8+?=8 zuK)EzQ-pACXn-@Ofd%^|<qOkK5m9!N(|A1|g{IHSP&UepluYe;nPW2f z*U&Sk_;?QS?zwM25=ceU->D&kseH4RCMpF#ZR+BXYwW@FbO{w?ct=z2k@AJlUK3=l zj|-g?sh`dt?6h~Km-@nXe&b=SI~fI61kR^BpTySsTe_U!QAMrk%Dj!NYd>=ubFXVKXR|2W#>EpRl#hnn;WXBWF~ zlwi?|T#2VZ5K@BiTJZv1JhYQSI5|RvJX{-apy z!k>8UfOwf2+7q*+9%USY@e$5L(X7fi@($C{A=3eV8GW-Mv}NRIB{(uzfBcO6D(Qrb ziqVv!IO(prlZ#btc}HhRi>XzM&^w39)h)I-)PYBY%q-73P^L;twtl2S!TAdHSe(Tp zh1hTNX3>7k6R}fXV{;t;bf64`$C?6;vg8%xP4p$nPdJj&O>lEJKM%B+EASV=7<0^+ z5BxHpl4{vR)bm0m3$Vv>h4}+3rzS7R<96}9}xYkKtb^ot%uJ zV-sFv`enTr9Vp_+$*)wlRr#7SeNLIuNxFmWyiH@BXG><0e4m=rPbi13f7J}lu;+PRZ3RT%eXAdh=csK zTG}{43O#dLLkthufWiu$q1uC6RB`-$-0s>#A3e4MQY(oPyO9~*D9-@zE$SCXK%6(S zI~}xx>`ZFmHC4d zBlgA!Z47?#2BmuhPWm%5@LO?}iQqo}EIfe{_3w^jp7F=w{+#r`R`E#=RTP${Qlp&m z#s@f0>tEW0@m_B=mmU9oL_wJ&q%4Lj$+x{;6@$FJZI8UtK4cg|28WVk9o`I$GOfP zzsu!+(X62+q|a2k7Zn;}TCs}is3Lt(BCD#1-_sDrdzx(h4mdxO;Z~BITDdZ-1VF79 zV&odF|6FglAj*)L{%K8~q-ms~3`}1CT2IcFTMRPr?i~GL=f=hyhi!bb};GdW()z} zMqYBy@9o-3uD1!x1mp9GtO9>N&}drH+&)7==u=5!NPisL(O}Ha{BsM7`p4Y7;Py+i%b7Inipd|cVc<_=*LguQI>-E7E9xDkh#{>xMyifob%6P0WS4t!UXC-Z&~?O8 zgwybh)m#kG+~Lp{BgvLk_q?Qq2_F4;{x$OX@7P^7Yx9T8S3L><*pAwD%}`#F&))b~ zS-|~0w(BNKx5QWb3w^ypukAgOS@g7*2>11?tn2Frmx<@A@Iy1>t^mK=95T>YtWKH} zs(;h&5QXuZ=HP@`^(Lj|}<3y|wE&7w2K{FzGq*4G2b?bzk0^ zf2=a0p#T(pXmapCADwJ*`CL36Xj|X*WYl}b>k`8fr{K1j!g--xGjgy2?DxPjrSI9_ z%DX$z@aYWieqIC2OjAh&c|DEnTR)>4sem}UT1AZz{$DzxsLNr9?C^%9>vy~RmYPWZ zcjk8U|7&fGo}oTt&Q9xQ^}#G!?SE1fBpaWY^bK0;;JEqulj8!pp{y66)7Ku zR^vOcrNGgc&O-oY9L|JFwE@TR#l8_UX-FVgSC-|HP0GERGVTO$rH9;}fd5O@Ok~m< zhZuEB%`FLaEV(krNR84Z9g-Zc#2bhrdWn4bag?hiiRAF0q8_gvX-gHorxZkd#8=dG z8d#Ay&mkH%LBXksS5_`Yf8mG$Vv~|;iLMta&ZIR<6uke5u;^+-P~3@Ys2%M=*IbRe z{GLH=8T4(JL-SN9ABx2VRfZ9ollxJ|whMAd644ct%95iZcVIUhQ*JD7Zp3*bU4O{CTjeV@C#& zYK5egb5*_Lk;m=i@Z4Xi=<>knFeceE{KX~r0jfk}L(6v_e@u0#+Ixfh&wi5}!omj4 z_4Ui2uBo$$)K_ttRQQ3U5DZ()pQW+6F#ebnbtQ z^Zsb#R2D0BSPBilUoT5nOO+30m8OfRlHhl zK$cp>Q;IQFx-l0;XOA35ET7|ru}elnUEe`u;}ePdtURGDyl4NH^|V&ZRWyq%l_Os- zc5+|ENA4@qrvmAKsOF?ob(Q0v*w{0Sla@(+y5paNo7L^irg&g?-qxi=MY$pB#5>*n zI?!Cz2Y{W_xcDp2uk(^y4R3as^K!oaO=;Hg8&Z=WC|olE`oOgni zQil3WpU3d;3dw`tUnFn5!@!Cj2?n~}4(_rSz7ZG79Pjv5JeI)mw~g#C+FKH+J9rbr z&SSFF|0!WeRIEbm27W;dGrAyK-d={w5A!WQGn6GyOiMBB?|3S0sJ~lM)=s@@Rs7=O zw;7(FMZU?6XgUPc&=gG)&*o^W^f6e7-X>@ImRJ4T4#DY{IE#XRLv9W@(uruB49)Iz zC7evUqsw1L-i7R*645t7!({}3#C($y5dr+$6G3;` z;NjvbRw9j@r6;nOo!Rv>nU-_Z2L1Y{S`zZ-)uDV5m7AsAqZpTw7Ihxe@d(`;Y+wpJ zwwi}0YqSI%<8?-DiT@t)PK{VBmK1O@LI;%k=xmmnK(e!{cYPx`Xtw$ZKcb5M*2Z1G z@JFl3!&@Nohb{^y$Df<12D_Q~Ymu+HZ_6C<0*p*5vZQEOcX-NGCVDDu=q|rk(hXN8 zwfQ7I?Ndw!u9oj8*}xb|!M`_H!d+byoLqmnY*UCA_05~Fa>WEUkA8{@16L46n z36cF%b)WJ62S>N+QNTPsSn6?fDzvC6^3`rG%T#qza z0157==L%XKPh2pGs!;+Q>D=r$`L=4x-YD&>j)o{HO?k=iP6Vf9*l=oNWW=GfxTj=B z*>&&V6@DmkH3=;=;&8wC`AgU!?O=#TrJJkK4$u}Qh7M3%CMC>=agC8X9;hFt4O~kw zy3nYwRobERH0E=R!-dD37w%-05#(H~Y#qgWnzc+Y_u~O$(bfrPt_9pgB(C1i6a1=C zwHUI1i&C3?3!`Ui>Wpz0ypn?AJrug`C>B|}nB^wC&4M`?kC9dplYcD8%YrWTu!XSR zfTMAhP!_S|t9j@C1}cdSYoPTTMItPgfv-j*xZ%O=A^7?v2nnK?6n)6jC79{nR;WV` zuWRg&>t`yhzdrUE$kRAoiIjNo7%MOs-Ds-=kq+WG4~AGM{{u!pZh*FDKKXi}k)|Qk zHy-NjvSSCyfb>UY$!IL9M>Iez1pabFe6DFu)oDVQ0RF(9)K;fnkB?fU$4u!W3dS1J zi0Vn7l}PHg)Qd3Q$}e?hqyW$s6mNsfp_6!?_!$rW{Q%8(BuPmX!Hds1zN1rzB*p^2 z+U+C$v<9@R-JFHCv8%bTakT_v-gMt}2fyi>Qm#8hR<$#k?c#+m1Wzy80JAb8jbvp0 zJ6533C62>U7tG>#Fw7d}Vy_|l-GxncJDAE>fy>{b(&RmmBr;WTH3#ZG*z9;BU=0s|RH{Y2Drl56`)| zxLv;5{~gGztbf&8an7nb?2sV?86LZJTu-Q+D8#>9{oYNk_-|fKMu(qOf_*UGMX(BE zY4U&FQ=ad1yNpV277XXw-FG&_rx8dxN88=!058|@EH=L7NgC(1}mjv zCT=gcO^63g+HrHc@BK2CxT6C%rM z5_-oN2%TnLQbMLFF1tZR0$SjuU5s{9cN|1)BUN_(YF9sZ7Z@c_k(m+Ytd&TUsF;n| zf8=A5m5`ysQf(=eV;nUH8XiaKQtrnvut)kw`_uTY4IZP+Sr${n75@Y2$>6 zXnF1eds7pj;izy-6G3|q{)jAAU7PCd2kwcbv4>2yPnIWT!vgyBy!H7s+_`gvgE309 zRzW|~$r@E@E-HWr}N{i*eKfp!Mu^q0j1yq-PRw~(g~-_ zw$#d2ICK^#Yj zmsI%yuct-EX!KloIYwLDnaGP2aWGGEPE~#q63;6L*}VWii{${DJ+4$Xg)Nw2gu8hG zC$pPV6z*sq(fIvS@s{T*X&^Ymi?2A8)xY+fNZv!>Z}_Q4mj}um?#S9>rE*m|&K~K! zpDuQ`s`DM9m0>T_b`gc-amNgjwz`4MP zV|*|Z13r+aTT00jrauY(U;Jf;~MqJA-@Q!I4|)GU^-Cgo&GylCq}m}h~IJu>GLo2I~lwtPlVBCXcl z$lXU&aqmuf4Ii}YKQ3a=rUrw&p-!tVnqr074nR9Yi^Q^N&0enOQMT~ZZ)shn&;`1s z^Uu4ZVi7d*;EI$Z42vSo=RyA&5!38(+>iep{LcByFAwnodo_rO!1hh6;f>U<&sS8}W;We;!^DcW1ka!J-&<21 zY*1GXYJzHIX}7GL^dj}6YdG5Ap1+b+Fv%=UoihxoCUcePsHXqk8VIwr^p_B`b9VY8 z*zDb{)R43?jR2nC3(*|tZYEF-Xw$cQA3pHb>6yAquK9w(hxNYxPTp(ky_7)| zZ%B6Oh@hPEOf0J{RrD8V0?x-4T2Z7;iAC)4^YC5rDU!dOj^5i+?u4Y_$vL!o`S!!g z9c;z5&D~SoKQDYU--g@sGiXir&<3JR-ZoK1*F}#g^90o^#+~pVkJ?LqS+f^hLjs?> z@Jo%>l`D_Sjgn_*p%Rk-W9huC3a45YB%r)!b-15QuQ+=H`;dY069=WWQ$~<|(IXSL zN)@Jzk)n|RwM_z**6`a7v!g88zxSMVaL2eOuWm98!VDtZsGAxC# zPV>Wq*-s=@a;4d3-LESm=S_A_E4#vwKWyv7vWc-W+DT9N z)4~zMz&tWmH7EViEs~W%DyN-lm539zbq)pDf4Wp8=Sx4%hYIOq6gGkRR>?nPQdv)& zA~6-rwy^|AFVipaEmoBWW%IhLk9&%80=#Liu$20Ltx~!7$~NT@e~V4sA4wESbD3*} zw-B1S@aQdBv2CAk2_}jB{rJ*7y{|I*GfnAD)k2XD?J(eC*%e4(G zxKT>(9oq{6enW`3tp~>u3|$VVtna#dqH3EnED$6U(}=Q)Q0i2hM~EQAGl*`CZf zzOY?ozvG+s@yACS?3~9diG#Pxi0n|VD;s+JpX~*j9+=R40dbDwjujB;!%-e*ir>pN z%k*N8KXH!N1=B)TY4)8}W=Fo1$WxDD`QY(ZX2t2p#D-5ZrW%++8fWRzpkstDc07G5PO5DtSWT;<7i&YA%aV=Vn|viUT@~fN}0P4a)TkfZct?dOxO?Ge`1OeSAJEhALfgZt-@6e{-q0v3 zq%MSZHaMY36;yEFgx%)oK8Us__88eFF?70@1YLZuOwfL~KB<5t2+GyamVZ`H^@Qid z7N`=pR=Z9UrHi~6-K8UmZntCZI9YzzgIYHAhWVMBh=Gl_dkub_`v>PaKyqAiEjdQS zmfvT1Xf5&`xj84F79_vOF0(Ln@hN%6mr5y82Y4fP1eR};N2WE!es?mi@rO~ggJ;?&2x;a6o+tfjdK&d0EBa_^Hjn^87W znwsti<4T0zuNr$ug)y2;i#q}sHYG%cQ!*T$L;0ep)Cj}0yPIG`Cz|_Do0a#I`Prpt zW9PRM?EM0Y&`oI(=gi zTl)$0BeO(m+}eD1KFZDk)o-bjn_->rRHL=(_T$??IU)7T@846sx&htT7D6qEQL^rU z1GywALt)d`(8FdK^CKOS19NMJIGB7n71XUUTy8Y{sF+S|_>FQMSoeLKcPkaA^mKBGzfBW74K7e6x0&^@ZtpV%J6Wo%LjR`cT&+-?v{6y8I zlvre~?bFN%yszKDkd0_!jnjlJN{J(D0w2Y=ir`1OYpML8d}A5o9fo!U*w)iSN$$`# zz9u@EcDF)Lw98^jg}yZv_hz4}RI<8}9*zgujeI$=TCwq9oM_8;0lQ10(YsZy!V0M% z-ne|p&0#+oBRcWq!k@#uRg;owY_p7L5jZ^|!={?TiovD;2-mf*dk<9tA(xeDG4j-Y zO3UQK4ANL%XUH~f7Q2aWL}dAV5r&fwR=PL#?=+=orz3P53TUJ-b?eE=$_ES1NmuUY z@3)AO^rk5)c#3wg7C4HC~6p;P+yWK??{VA?#Bmm<2PO!IdGJ*r6NF^zJx z5kJomCs)VIeq)C#q2R*IO>y6wksE0E#y0}z3oqurtH0z2!FvUvWZa(L$75uK><6*m z)y?}AER461giu0}iuLhCsK-QLU9}~LtWSxJOJ?-5qJ99iUstrZGi2)7>RJ4Qpx}B% z+}%;AzjpehK*8mCQF#%@KMas*L80kcn{YI(EpLBVf453W>a7YsvPE|T&HT93M3>H_m8=Qow#&)CXhHVdg*u~dgZPz09o6B(`4N&ln zb_pW6pLX^0&7=&dVX^AyKH{wXcl}%|vDRh(8_oZXQ7O}P6_>^*Ijq#WT03(PVyPI@ z&$i&g&!&L02#-t-`w~&tySPydBdYHqASosPB0dFFSZuvxkzt^Ax1KYTpA7#u_z)`_ zlwZ%SvFbqLY=GRvspPzkw&2YA<(Ji2nkPClR)wC=31D{MW(k*d@wLp{5NujF~#)+UhntgibU2gJ&tpAYFZGUuS_02Ye?*$NV;y| zj3FF;?IK-g^WRl90r@PaE!0)4-}%Wr|2*Igl`y~jTMR$w%i~K_=D);kJmX(F|0v#y zl4XgWdwvI3`4c$H@rb0$lhA}E1?RLUyV^Cz?`lulF9-m48fCHOol-dYubKfQo&=13 zl4VSR>g2d}{Z+QAGm^@O%tYS&*A|1b14XB+S z!SBY9R$-LC(t9lhW>A=A!Omc=)Hc zfwL#9wfn~nmeS<^qkHo9G2*apPIi1{eCF-@@VV$eV8}_&FaiOFUY>C_M;^KNj??k7 z`-=TDdx^F0SBIQ=)SB~)&6c@pz#nblQjmPhx?hb;-uj>SJ|~g;2SOeGY0TY6Q_glR zWE-+pPu9jZ&{L&w50QU(c1?4TYX~?FVr=M7(zF7*&mDb9I`#0sCz&w*)V%a zOuX`+q~)LBTYDeMz^X(u{I$~heXZ;%2Gj(B#>6J*<5rkAhjL5>{>D7i{+rS(mOA3O z-uFo7Mo%89EhJA5M&aOkv2IG{5SoVo6t@0FRcN4V{u|K2fphK}R8T+PP>#-VtP~ZR zyQybFD^K_HE_KW0>pZ?@1@MdEMoxAw&c@-@OhsLpp z%NB$d;bTGhBng?8Tj4Rlj!DftJoz=KBvAy8cpkDMpX6~zooCIU>OPinm(TXkg-dxJ z2r*>+9jr)H2y`V1MENkP@D(~>@anrjpX`#~qqt=l><02#O&w`;P@)ZtA~Zo%t&43) zB04Rc1aaB1)(K~Ms@phrDVq*lxvp36h2x)x^^0qF*N)*lh%e`NruR#rCWj4g*J8ah zjiwMcKO(Rw8Y4ExZDeYki5oIUEynq=)?41iu&Q7tF9nA06}uo>r%0i&yEGEDFgB*? z033Y@L6y?R6wqrp_5+8VpbRao{n102uhO3PZp$fs=-FEpC&E?Aa0&ntqG!Y zh7G}#Sb5TyjBEKxl24%?BM*miRtZ-tYIlp+UhZ8T0du@S5I&dN#`sAF61}JF-Gl9A zJAXqqx+Es0Z~q^s_=fS6E!L52MV3?f4C{eyte=$<_8j0R#eNf>=NP~{eheUqYAGbI zUjVsaXeVGSS3Q%sU@Cay_i2XcYyAj5>oub4#nzfc-UV9A%=~f2^89y(6Z=^lE-o0- z4qSg6UzG`i{iz4E^)+*}h}dgGApiTHZX8(GW`7!6Rk^Q((hd9KOzEE&#=%wMyOCH& zn^a?aGwJu)8Zr?ub^lUnb9+q6JpAvz2={IS8Xr^>V0^1;+GS|B{K)~FL2E~a@mBsM zKXI^UPOQ1Hu_yMQFkCk3(y#bpuiCWzwLPrA{zDc$A>3n^F>Kt*y+>)t(({9PJ@vMJ z0&cL~{B&$lAbZ>CclclvCYyq|^A2)m-G|vM2A=_>~EE`C?r3^~Eo38~_Vu z1pE^*mA*jht=W5G+CX=lMeui|BN^Gi{2QB4CxZKP+!2 z8`V-(D11_?Zm0BK5VnlY5gGYk)mX@o4pN0q1yES zv!Rf86dHGg;9>xog7hG)#ij%hYVfz+1a(4;u`Y`;{b@l*l_V6(*ZAyri<FG1Y8f)9jUQsd;VM-B2m`a<63HVSuqtZWSO}~BD4cZ4 zUjmd{-M)teE1WoCavHVH*5EHCSA zn3hMgz}6Y}Mn-J^Cg;W?!Rwf1$}u9Fgt9?CaK2^0dPG~kWJ*OXr->|*9nhzkbCSU^ z(WnH4>Z~>AfrGtSlIu}VYU3Tc?4Y#Pms)5|mJ@;nGySG*_5L9~l~tz}Xu71F!>d4s z=tg`Yn~Fh%9UzlYD)_?R2=L3@*bhh#m>oIDUK@x0fCTf`q+7_# z*?H051#!Y7`@6e2cQBROuDeV078f71+x4XgZ$+FIZiD?l8Fgnp)3yfR4> z>}X8JT60hU7_e8zgDRN#x9i(;hQc{jZziX_E@}@QxSZr?sMTqYy-s@4%WxjsXl7;@ zj!4HFzs8ug0^XKy%QW@fTJIzHlJWpUYj@4_HZ$1&gRQrUYBS*0bz3OKiaQi2Rve1E zySrO)hvE(`?(VJuin~LB0!4$nTks&kf}Q+pt#S6*d!M^p#}*Y|LRpnBW5t$%gj=e>`ZXnw&9|GdL| z48#(+Ub^{uDgx#4nC=&AJkOaTYFaW1{$t!Vuslzc1^QbyYT|43fm!K^Km5mPi}2gQ zU6AgMbHj>s^J2EWYIgiWd9UFAwQL!Sq*K!1zo~9IW?t)^TwQ*j!{eOq#$x}!O)7WS z%OpJdb5Oz6li7_o|JH}Y|6arlw`@J%N&7-=yU2z1b4&`G0s}uyW(kNuH^YI2$se`O2%y;jjm!|U}jVR z-lmB2D<-Mei6sQeEFuqn+y))(jyo56cAY)+jTs-`o!#7ZZUb^0+H=)+S7H3`KX6j? zKaV+PqyDLNo}L~~=mx=%FV%_^1+<^tlXpqO_2cH$nq%8VvC?^7dIU27`x*OGFpz;f zTj|tKFv<*N45~coG;g7CoOj0D?)u}62NKHuXEVR03Sk{%4W3nopf7OV@+}w&|5D$- z0^}DYp+p@bPfWPD+LbtrxP`0p<2ODu*slms35j{jJB|vk(el-X1)MNv?R5C+6UEeQ zq5^ne$~QlTPr8QKh_xr3ND7*!Qvw;|GBIb|v*0z;lYu#Rywaj_TpJ;}J)u$}TO_H+ zPi0;zEzYSiiTrQ|Y;n{>A6od9f_|^%7qJyGon*_3-T5pppGPMacZ-M1;>xPl6~&1S zIi2t|&9u`xB3MRvy{A}`yxr?1d3@?z-|phwN{ey)CL`5wtrGsvY!iX5-BDdC0eOrXM`v7w+@H!`F{NF@5!KamvlKzGS-sDn$i?4+W(q=d)4a8O=8}sscQ`F|w zjkhoU;U5UJ&R7XJC#}7&T^e%zM1RQ#?e-8@FD;+?PE8qL_1zpAzQ{r42L4J}yjTp1 z{Fm#qmfQFn?~@IVtssmuq~>OKr&pQL;I?!Fx6*);rrnl9AC%nMW1u_#rMD(kE)B(C zs#sz%)mIXCuUDlgcfqM@bX;>!6@BQ$LCCSp&Ptnw;+5%SH*dt!`f&^fUG2|uFe9b{ zw;sBleFCNk+7ipolD5w7#(AN8Ok0o8M71DuEhDgY?d)L{|G+jo5RqRQS-j@ZhuiE3 zr=xG;y$no%oVmjPLIEG5DnhgH=PSaS6gw2Jj89()!tX^=pJQD=b1T^pnyV-FEX93f zM9H!dlT=SvQVt3^^+TT{-=6z>HuncnoY%N)M*pwfXVLyRj(zo4bPlrDiKerFqbi* zd)byu$;|HSy3CKaXt$vNrv$XS@7lXwhzA@AYl=oKIFi3k=}rSLnyh%5dU=&`P8wCp zqr8}%>eRm9%Xp)1M+BpDgl+jTnYzz~1_nYfjv?ePp<`z^hyiKRv%0gD0YUTNPhV5M zOm*af(Vv1tau$Cs)Pv-RG&<_o6sA;(>3egRc5n1%6)Y+L~M1vM%pKGSqNLird? z6g3BQze7Q02eQ>ui3_+`v@D|`0U}96jD>fyX(Au7@u0dw!eTzKqBiLsBUA1bVsHa? za@1E(8+I)sON6Xrj|huy6g`@l#(T>N$!v!O98J~-)NAbla6rY0=tv%*0hZ6Ga{Y@v z4|_}m%CiBY$W@CEPTuV`BuXJg8|U!uCbo;V%xb<@>)JUxtH1PP+!!OcsC=BoTtb6# z#HM9$hZ+tEJ?wCE(W%t^t*5l0KhVj@xsJvQgEL3J#@;`op6zorp%V^_6&9~dOikD> z{cs~kjxs8DhsI|~2|cZy==0A1drOQ&k50{Z;4Hu(Rpxq2e0SeY1|h(iH?@@orZeX~og~q!uUl4uJpSq;4EZ^gU zz39I~_4KlQ#nMG6{xvuwcV_G>9jqxxhmnc#7Z_?63#UD17PCPMS$^`{S8_i|;hf{RleqeYm3XB<52pEF%>EI2s`S zF1F*$C;z=x869O_yMumg(e@XIixA7tIe(;e=Be5!Sjm{5g$n|eCo;Fj*J)+sl3Wmu z;gQiCe^;gEMYr!B-)mJcwig6cF5XC{PBsl~O@q;gOZ251Vql=N+=uFCucDYQUC+H# zee@a<(h>O?E_mQx3cuf;RZusGeP&lY%S2=(lFDwV2>KS4%*Uv-6GGu4vJoy*G^^=0MY!}c87`&Xe^0XGW6jQ- z+ORr?I-s}xRsJ8@9+pVxX1Ejf>-S{drC}Na#ms57yMT*qx$hTUQ4t;PA24o79R%4U zzFI5KNAKb@G8&LtXQj>XqU7h)Uw2zDF)QRyCL^CSnh>mdVKp5ST`bq#$aw0H&CqjiyV($_f! z+et0JEIN2SGct0~*c{);?eNuofYrG)rmkbK$otRhj~h}*1dI=!#Q~Tl>*~q4d%pB+ zzF4jL*V%GW5cW6hG3;6;xqULoC$868quF8_0#8(cBZbVie_n0asdzPO{~pS*OV=d= zCivon$@k4UI+Wk04lylG#TRDbn9W{1dF-vmIA+66|GmY9DPzsM_IG~IL#)oE?n3;M(Ec<%q% z>o9O=4!?b5^%Cm!$Nn!l8cc~+qo#w@I2dtPV$~#8@B}elyd?>|dCFka!)b(>%~0xC z37hLw3b?ZZy&p1;fIPX%gTO6Cf?St*?4g@7aK41h=OCJmyZaHkc${qRvVvFePtv3K z(_R%3f#<#E1$>fJVF9nrFQk6EKF!Al_a&sBZy$|3Cz z=Zshq`Va1U?PQn4?DQgFIQVOW;f-!B_kcjI=%7ugp2o>_tw04QR_dnI2b!b|4Q5N9 z{+ICH_v$j`p%t@(K27qbG1Xd7$>%jR5(1da||Eh;$;wX9tuqJze& z%5RrKvnbez6Z7SHL52~l%u_Rx`o)V)3$rN;%7PmJ-&y74f$M2I-sX-O>U6Iy6-PXh zd5;PZzNTMwD_$B&!4ntdigj3ND$Wnwm0K3t+PsqRZKFhvj-p15<1nA!Xz4Qvn8wM= zkK{<=6fEV;bGWHBp zE5Y_D0E?wTCM7OFu+4Rt+604H#OnJqL@w)c7Dezp-Ym*gzlL>w4)4ou`&Gcjy0MH> z^{}}bZrWKhUW+S=)HhG$HI~#;`p8)EE=46K>W_4Cn9()tchh5{cR%F|^XRJ!3sX6Y zN9lIe={{we;kfpS&9YJFGfjPD#CrY<5SZNU{<&L5j~n9((h3g#5r!XzpZ&Le`=3Vpax@H<57aTfq;f$a z;5gnq4RJ_-XI62HWM8lBFkg-#!m1AQ#HO4rkK$Niv-xUz?kHM|Xw(TTzJb%u{cCvl zUNinU@%HnuU+j>Ci;m`!Y)Cg_LyhjwWISN86DQj5wB=ClWu~v+MIU>4FVtx#F8X=7 zFpJM!-b((tpbBJl7U3g&CV1ldlEOy1Ur`c|1&8b~Uej9Kg8@ZxY`xWPHk&W(sP@ih zdeQMT$w~WmnFa{`!nLwgV7K;YFafqWv)=2EevjkxAOvoWm>?<|Q!LO?V%IX`whX zX6?e*N2k+q?>N9C4R0P&M@tK5aXj3F;$Zt9+vTy4rG{VQ!fD_l0m^B$xeC@G8u7wT;KU5SeA;hflMg zkih4y3G*&?va4*|0Yvnymg;)w-^yz2%d?R?T{@^8%JCYlJHRxt^DfL6C z!4pa02lqzK9S3_z2fIBMf?x%aYG&|4QX-~giM{&cA1?A^SCVHdK8!#ZW<$~C2&Ly~yq3XDf zc+^Pwb>9UQI}gd|QLBSeE2>}}KUfe5J1u0@}E6GrsSF@i8BLt}e!k~<5Ka5<6 zV)Pg8<4lUTu;{YH*sjIWG>|>SRICLTkhktmMBtfQMm!08X zC^DI(ruSe2Y0(AyQT4JHT$E3Wmq8Ju1qzCL0KUH!#r9@anATN3C7 zXuucAC$z{V+fR+r(p?wa6mYpP=@4nozhdM%H@11*T@6P5EIl_UQvgyiU9&bhEfr$B0iyi=ejFhr|z zrFsr=tBWtdV(ETnFhN)WWE_Z^=eKjq>D;es${W;q>ja&v@tTW0PC5Pq42Zm2*%M;l zw4*HCPZ_Z%s`EBS3KaPU0gf6fx;Vv!2id#I!sKWP{A_)EmuQCj;^JC=Sst)^KKJ0c z9PxE0VvHZHe479++ zh@vg{DlZT&tJ7$8?fU=2|Eig@n=yHFZ}M>L1Nw*8_FnL>I-J&|YyAHI@+A>5tt@D$Oil-Kd^Vav( zRg(7TyX=8*Z?iAdq={$nQ!CP7{cNcNHEC8CItqq3RS+ujpC&o9qzdb1?2)fXx~2@4 zqu;wM%Sagf=mKf!TfPpY0wZTAj5A-d`At-6QDCz3#LOf zibE3F-za^2nJgCG11Em008H}Tcn{a=lp-9Y|I-8huA%3b;!%=(jIMuzdt$PY@M$>h zLJTWZ4<1akYQ7irK=6Bqg}CZGJ^oGMJvjYySfgHGzXzI~!9dG8! zq<0;VE>K%AzjrVLf4^P%rINWngZdE5cQMk!u3u#uI!^(*TC4^|8Q#`Bj@#Ll7Th>a z^^Fgp+sWGk6jKH1=byOTUh`KE1oN|L)dZ4^q)PMf_sj6P-B7-eR;Xi&(<5qS_*+@h zSG5Ul7O_~{puP{+`svFfh=s)nZ|+n;SOBQFf+<@HM-_#S;75OMk#=j?yv@1bQ?wm` z`27K9@la|`+Quz=hzSx7*yGDFo=T=|oa*6xjaP?;S+;Rtz^1!UNz-^|h#pK%UkSA(g0_*yn#B$}(K?UH@G_GfFkl zp4{xjH55;PThNE0FE%n*xE%6CvW9h+m6Vu*sdgXF%X45?D3*zfI{%UvGJ&QpJ*kkg zBwx1BXlmgPdXIUd(f7~GB-Dkb%Ts9nA2mf*`_H7)0iDq#xBQsWkc6j;Vy6oxJ>bpJ zwzGepI#7@&`)_g}jIZw3yk_+;0Cg;LBy>~|T`Ar%8}AvXJH+^O^sHgIf^51iW&rL) zpn?#0QmzW~7RJmM(8T?Ga&|*GXI&>#jU*P77k}OUNvlJhnFt#vwKv!OQ^ana47LhZ z96f4P$$;hyXOV8=$F9B%ko?qlnntZ+y>0FWcozz0mnH3iQ@7o)qtbi=nz9FrC^em` zM%*&SRl*h9X}O`s|Iuj(8ozhA8|EST#y!fFn<8!hOh&$cADY-CnK#T-=(bQus6J znut@OVBsYBkUGtsFI440T5x!Qmfl2<@r~`Z$T%$2wCc69V*)BjfXo|P`+%3b_0PCn zA?xS1$I6z~L0~ZwSMj^Fd$Z~xVW~uk9?C5F0Oa5Vid8~(GVAEK-=32P=}-@~@nE>% z-kFS{Cv2KJ?N2C5q6HFL^&)P3Wg7_M$S_Fs8AR^Ajh*<4AliMx>iwxt9v}jDs7Thg ztT~q5H&m6OJxFoh%zt&9KCsAJhNPcxnf)Grh~kl*Gx5wPCvs<0?=CmoJ2cse+hlqy zkVE?S^C0Jy*mcM=gB0^W=6soaef@Oz0&gD8 zEs9V|;(w>u(aAlNWq3sc0kY3lJ?BJ-jutv8+;RQQeh^xDNgxNtiyxy{q zF%O~9gZmv{c`XflV6^HSZUg*_aUB=6{P)be!B{w5tPWfuxKqr0optMhk+HS|!4C7o z=0JVtNj=Erp4h@TdziBF1u{l)VwXQ` zJhHb<;iyi-k+JyY^Z#RnKSm;}Z>~5`gZ{vo1bXk(9|unA4=x;Ek31UedNJX3l!SV+ zso{w0RXx#dr^T-mg(AkvE9oD` zfmfYk$pB8vVH8La#>Zw7l35uZKRt|Sp?=Hyf*U4d>_=H2YLjv9Uv2VH_)Wlz~h|B zMOi=}#GL=0AR_1X`(s@Hg`!|VkohsNHv!t%fn$?@RJFHRQRH#ui@lgI8@d*NEL7r9 zJ(K#WTEjzz;QC%s3rv#8MN1J)*9TW1mNwMB;AmMeECng_cZ9g-Y;r= z_~=+cqIUXw!$!5i>>giJsgT=!R+5gb2iHywU|Zq;(;?{x&0{6S4-24Wc(zR)q+`JH`XxvEu@%Zo+FLIO=YD0lr!gxQ0s9xV2XPYG?Aq{qnJe#%%>UW$cMWK zs0dXjTe*+XtAz7SEc@dUNxDp0t*KDG8Z$khr^bQ1*L{A*t{)&#*)2Jf=#HYP=TLn) z%%YxKgo<;3peZOH7PHYbXjnYG>es|Iq?yx7Q^o+%G-qZs4qOL8#aRco@ z4`8{JAa}4aR}7GyAgrk`lWH<`dak6}IHDb6z~r37RMGf7%7FL3F^xHl(pXWRt27MS zJ+>h7-=49c8GLwPdC>Dh?uwEmWSFN%&R;3E#wB}}S_+p#lVb~N52d1u%iJzLVuP1k zl-@%inrcEbn}hj&9Zly{V|A6H0)G#pOsOXE1p9ghwM{ZOHe26z(=lENUp|rUPnk(= zAub>?-8u}}6i!)X)Tf=Or-uDfH#>4$zOj>XVr-bs=U+4_A*|7}#e}D4%d};5_;03Xt1yyUZyu^ z8aD0~K{~t2@t?y_tP{OmDy*=Ps;J;a<}ed4HB-IR`O45bP$=u!bGkt z%;cqdDJm|tEtcS!JnRcW{djytl~8yCe4z1g#UnWgaE%@3Qn|%t8Ozb*OlC&91^!57{iMGOZW`f zFayTDTxwA5(>kCTJ}n(?z^)ql{BaYDE8a)7Uf|YXm&2uJ*y8ESV?JSJRM>ADoNwk_ zOO^c?Awh$VQ{^2D7wj~SER+8tN_4p(@RaPDv6}a8twASrz|ZV)YC(YNcb|%xL>ko3 z1RzMKf)9q-8c#j4ztYp(=2sG|MZUWq`5dE8lN0Sqc2QqoZ!N2l6Qr^4v#nwP85J zl~}N5(aMAmv5d*FO198pBZ}9;To*B8_L{V)xP8!ncpcQ|7Vz)FW6Y5K)s=WN?6jNm*6dFol z>NQ8EM=@V*r1AdKOqm}UZA-R@wnx1XrC~7I&JC!%(wkLX@9Uv9i;$4d{@xQ+_x--# z>hoN$2a5{MD}CG+p6FRRZs)5eTata9Cp?hycOwQX7~z3*w0UUPI4+JKY`D1dvOUTKg1 zNCYY)l3ly4UbdXhOo4+0&bxrLwot{pUoRgQo<7=SY}}O^d9I~6bzVtBkMauuVG_dx zf{zddn-;F664vv+y5<_qG3#W~BFai4lO&MK)wf46Zs#F>jA2)$u4(h!YUFJ{49>*# zTYn~ZmtJzs@gY>pi3+ZAmshTqF~H1lG)nPO1O$q5G2)FL;|BxOqz7oX+=Xr#TI3_> z3kUjDtn{V)-N5fL&p|tzB<*{p3ILIqObAn7=%0^UDsU8Q|F4``)*%&1RM7>bEzT7kfTA2g>NY?8aJV{x2ZgMC-DR(&^1^ z68FYaV$4+Cf7~%o&JArn9tI%&@|2hM=QR@NqRFa;4J=Na@3 zL;ddq-ipif$)1jiGY0>=wyQRSmj$j6aedP^c!HDCmozST^9D}kM*1ea55T^J^sL%SE<2;pcxp$XBW zc3H;#m2F~3D_;LkjIXm;`rw<7g54LCtbvNj7u9=N!xi{K`6ECi6kV`bc1o{x4P~r* z$oAJP$`}t_P8YpqwWqGjK60}>6#2^zzOAZf@yOj0N> z;j`oZHSdQV3~=BDhBqRs2}ZJ_+Q$Re2?2wmRYzG8O2#QwD4NAO-GB%G6#;nF4BcS0 zhO!9~AuskxiZpcadNV?sFAmVqtPmQ<9dai;Klxb<(>GD1A|25iao=pSfgTj$mi+|h zN6T@1VE7NQ8fbN^Fc$&G)i!8Qa5Vus^6hZ|ae4Mdv+L%SF?PZHY8BDG zto0&@d{Ie(AH(yi)wXaMm1{8PQLt4R47ondc1B`vPOdSo(8O|zX&KuqPwvK^)pJRe zXik3eQ2CC#h5k$O;vk{{mG@6am^?xmP*%DC89|GMIX+qn7sn#vy@x>#>A<&fFZ~?L zkx3A+oEL~=$6K*X%xFn&ppu9v70S=E`M~l&Ooteo#v-RLq<}r<1shTmGhKkZ6B2-& z_`5ytlV_usgDW!$qUip>OHZ!KulgC8;~ne_mDq(T zc zk|K-x7All+t72-nxRXexRJGJC^r-nK=6h2=ExsqNaH zJ8HbGwD-|}cin)u=-0F$^_k0Xx1bNzo1P7W>U;&r^s!lPZQ9)00@RecsKw2a;UB(Y zM{yAGsA{Reuy8N`zBVmHP2#_-N5vTk-jqHD^fDy7|3%B&r4Tga>w3)9Q`= zd}Q@|I%4glQ7?TdWD`2=4~P#u*=L3Gw~iFfzi%^hIW@~sEouIudow8Fh%M7sVfE0F zfc>3&^XP43RB3k~6D1uP+^~dz+s{}2O-qfSkJt#qLfZYv%*q1|z{vZ5<+!PlHvF*Y zeNcMkYpdDRB*Cs|-jyJ#UCTWB$W4G62LG5_jXyI73kE$8rZIL-{yyTm&ae1`g$Zgh z0ABo8Yysx%e{9ODpVER~T!9>xlqtWGpQ)6DGc#0a+=!PW7_D(<%txKK`u7(awIe5rB)+t(09K9`K`oVys%`FU{%wNjW%f_ifWuI zfqFPm*3ai=^BUu3^ZqU1;lj@%scz|b*0kW6Qz?rt#JFwfuz9#@vAp*+@HfxN{MMGr ztyPMhsVv5ppNNnvxTkr?Drd&&>0CmCRwb|_rtp78rq{i5Sc+u(pA;!x29_c@wLN41 zcZ%f92}|5{GV)Z&*AJ*5(}P1Kf{~FIe%q$20d+6OtK%vq(j~^eTR|l~eotPrb#=TU z!jJ~wTny_J$&K;WeO?@C3av_U2*!GWwvh+kDHMIJFKbHhiUe}Z`j#kdQ2WDmRx|~l z*^?)=S)ZBzxK!h~c`i`S@1g-Q;DFJjm}|)}B{EXDdpzPO%Q_!s3VOmk&Ef#^mvc^< z63)gFaoAMBY7EZ)H#yDyA4y`fM#hXg!%Rh;w+>s}kGaAh{HDT|ta1d>OPKE`Nmq;m z33Q60NE_5s_Up2Mcgj84Znb$mHwyXW5$RYUTn1Sk%B}%y4^~EJ%~es+KNpo3yj>9< ztJjtvERm=DFjBGc<%yN*|Gbc@%`OXU6v~Ira;vO}`iA?^;#a_-d9(_pCeq>2N%`$1 zDkp}^(kJL!;MV^d8=*s78L<@=F0#T#pfgDjGKdQ0q&!!32}2P0;1Z}KV8zAej0*AA zDGCJCtiHzus2&4;3v$L4UOY-$hSl@-do<#GT;-%0RMmDcL##lnREPO~W#A0~#q8-f zkxkY~2L5&9Wa&s!!K0Y(Pgb;}e(kE>1W2UI7Aj87ZrnB+c}$xXJ4~+Jt-w6;ohPiG zwCF$D2rzN~<~IX}__#Qed=S5i<;maGs$&WQ8pJDfDXa$O7L~E-*Hkx8VIahK9Lx1x zsJ*KvT2#oMItqWc20RL4D!8eTJ>#YoCnvl~-J~@H3rwiFdMMPghF(EYv4p>rZX>_j z&xA3A|AJii(6H^o~H9yEFv_ZCgplxPSofku^;(b8+>v8CrOF)HURFZJ&^ zJ%ZQqg=ZP{%u);7YK*|%MPkOrfGVR{T-tT5X~kNhaR@YNvSN3F#e7ANNzHyJOdYmz zg^%?Wr&qpwy?jhljVhb;2vw(gJhgD_t84gJ{@1DQTZN-#tmd8$YwHuk#{I-`>p@TM zSU_>EQO&SyY8HjY^2tUmBrkfIYENQOSX%Y&^_|B<56%Mfh>+0V6=3VCcc7U{JKWZV zKrH3zlygQ(bRg@&+x1duA9mOmz2lE!_SDCE_x&<|0#e#*r}e}AxQ~l7zCs-Q{&sR_ zA%9#YmwJ`diVnm~MU%pbevvaYe(sGKYZ|^%N`@Xc1aVOY>McfWKkN66r`TTH7k_)| z1sGezs+ii%nd6r$0>s~r5IMQ_@6#PlMyaJ7ezJa8;2Rc5CGh_fwCPuHyWX|g(^Ezv zE~q5(yjy+BF1>6tc$9M&YC30rwAkK)I`1O3v1l&ZQLcoA|PTx7CGMzR%EGPNe2R|<{x07LDr9^4dPUF zm!h5OO1a{h44>T4t2WRD_gU*lh<7<N1B*)L+iqqmXy$4UI44V9eL+YG-KYHNy`<<(Kb$e8J@O*7WB`b zo5;nlzl+f5`<@;o(96b^g(gv~ch-F%Ap5#<5fi@vm(FFU$8(svbuLJ*;O&m*Sg7kl z9GBAM&X6>~B6oF9QztEBW@RN6rAXZ8S1I|TRw}tt`G+FpQ=}9))i+Vw=8^MO6qS*T zzP7;tf2D#hv>?=Im59Rd`+*B`Ns0=5A8dKcO$rM-3}N`EyVOSwV^G+cv??W~w&2I- z-jFF!g_c-RFHlX5d+sSTxCEu1s-4K(u8H5N-hkE3A|N;xv-@~U zh-KaA@5CdDUp`=$ofPD9(ifv1z7w01Gg%$qToYuTM`@z&Q)i)5m->cXkPIa6J`&aOa{w^vrmI%%nm2d zxTooSUFu^+yJl_MkSY+`emjDsB;e0V^!i)G&L6u))_r^OOBijal&{i586R7EZcRH3 zMC^CAThTRL+)Y$DcgD>?TqN~f2YakUEL$`&AJy@g$cXSl3Hi^@Oh#w8R=b0J#-^t! zp~Gz$B?>Hk>485WNf5xKi303?#GK!;bM)){d58H3;^vDq|K4Blv< zlwQrP7}nw{&kN*#xt`##ZQJlFuM51bYTksN&JmzLpa?xe+}y`TX$b&o26am%1jgRK zV8jph&fRFnBR7)=LDInGo&BfY`7#g>o#jbDO%MVlyScBQi}XEQe@P@^TAI>#p@>tP zCEdLxbK7Mbb=~npSFAdzQ84}&_wi0R4elg-DQ0HBjQYEUL$1$7PV-QDB-G)?b)oAF zkf5nh!z?i)l=lpN&DjYy65W6?rj5KrWN!~~^^NbqSCao*|J~D0>|gz<$U1=Ai>dbc>xV&F__E;<#zrMee3?nz+^LR;9FtMW;;b*;Oq|M-hic~ z=WaP};G6qE+#ZbVaBsNkFsEb-?ztf!b^Axr41$RzT*&r#0eeBdpI;!$<{1ww zW9bu{y$klXX?W-6@yY_H*DyTK#D5Who6H%Ve>H(ix5=!n(_UhQRY(N7%_0m1<~5+l&OKlAMMKD zbB2eLt7E%ni8+oR*WBnqkOXK9h%}|8X8g|0)Rr%uy1EDOn~~IHEeoD>nd^?}SDTBu z)os}=9STPBRqdv*K3Wl==uWHus3rOP$&LcuHiWoMk!ArXLisaM)IyfDf;?5tz;Y>3 zw|-5%s!4SJ=gBzPPt4EPir9xE2rVBe>y2rgYH_<@|SgYmVB(3y)dEsYI&c4cEn^0`4+= zgQQx@v*?BL!1MJ~x5hBeL3t7nSQGsxG1}wj!(K#Fd)K%?fkGtmICHhhHoRY!`hV^0 z1Bbf>a=G~ua-QJP5pz-eG1x`l;fZLP+Um?N{1ee!I+DBH4)r}{7kcMTR8cSRJKrVG zH$+l0^-i>#5Ku7{&2_^CRE{Mc>LyDgro0S5o{O~08$A*z`T zTP-8qGqm;ds?L22=N{5?)9kfniq5bI1+F+*9!EiGU9t@kSd*#_*?j831oDym8EtFC z$q+jUXaf$x z@Cf#(H}9~YWn^HMI-Ks>A;n{7FV-~HmqTjN*i879t(f}LVci)au+@M#0BO@twTtq= zJoekwJM-~+)zEE&%qy%n80r9JJ~cl;n7%?rh{(X9SUcec!Q`hACh-u<)%-<>T|Vov z82L0_UvTxaN5E_TQ%Ag=LSYz}_><7|4&{Eovchw1r9TVR@HHT#Z%$X|R%gz;5itnu zg>+b`l+@#0wmgrLP5Zd@B#$GxbOH~&We#4mU0NE~(bE2evu#!N^rfriZyTL}Y6tun zy@2Wpo^Si(m?{o%q)6myPTi?&d3%!joKoh>Jg4Aq$~(f-#j476J}=>rwe=aMB_lUx zQqPASO>+@~{S@PDRmoOz1Q974Yrb9bBnlUwb48EO^Z%h6>KY_&00U@`h*hNu$!~UL zQ$I2b^N>;lA}g_u82xI_gqEyf9e_#Tu7!BYSJoo(7(czB$X5X*lG?GN&}NE*nXzj; z#x(d(A28J7uR)shjC;xok)+wK6fYgbRL`pPAw_hvWAY0Z0k5cb+L+ulCa}HwdZ1K8 zfnG>~*rTW;tDS32tBhA247M&DNgO6wab_GSot9=%B`L;WS)IzgDkxH1uU+jb7I5Hn z*w>1(W=`K+7pbprYmobn>m&b(5NTT{x`@#N__ptnIpC!*zf$b{7p?ORu*|&f5I7;MFebu(I z*{_-9HoTJBPQc3zwxyA0`6I%pbB>|2I=PtH4^mvL=Vg+}PgKHV*v+h%0+|r&3WBq_ zy#TFO-LX=JqVM!W71I{8=t(+l7gyfh=kZo{97=jWquz)beq#mLRICap`i~g;xO8fK zmG{}vRPFgK*2HSlW9U}4Qj2^jB z651ifN~_fWA-c7b2x(srMHzS@tRSZ2*}ACe@m_w|6Sn$Sh>-gH^UI5VNWq|arpA|Z zBY_ `2_zPAoOx_+bWnUA0m}K7y9Sux_*3!fTju1u|_tHTCtpA#=+JH=9qZWB@ZY zaUyO3F+j`6D2)FlmZjZpkRo&0qHW|i%2IC?*vb_})4%!+0s>#xke0G_p^_xE_5Gb1VWRlp(Vk`a(dm~Vx zZ)dH@^oz;fH2bjp+$=V){G9Gx_b2OKw>slXk7pqA9JfvEsR9Y$)p)4LF?S1xiGYN=X4)+c784YLCif|)fw(n5w=p*Q1O)@4 zerg0Y)eRo9nIU;XBr=+2OgVTY8YJu+mBjSMt8t7Nkin3)x{7jE)ze(4e*&!85)idn zzw@yf4C<2}9K1;SA}J{XJ+_5Bss!a`D=GbDh)TJ6ub-8Xnp$+1W}b71|C4`evL}P; z*wl#0<&Zm@ta!x4&k$qf?E_vfbDxowi^$Ue9RH=k$ba>ZBO|jX`D+?yqnnSvlFXL% zbxy5Rs96vgJu`ORb#6@$S(7WEqQ)CN4!`f3^F6J$Jp|{goYr>X=ea*8LE`Gj6-D^& zKJee1-BfI}jvCu$&st^}NoN>&5=p5^Hdz75c$h6c*UsB+e6?zwf0@v{N@!~fz2?yA zw>P~4wThNJL#One@r@I*H7o*9PhTqafg|;?oF3BWE#wrA^l)hpxR)l<_yV?xErg9r z_4_<{)wlJ|XG@J}B)7lLd?#>?dYte-2nV=>YK}5fYSSNBI?s3@S3<(F8Jb6Cd_dGHPn(w z&zEQGqv|y$fXVZ*7o?1wR0{lh)V3${+p*REPbS7N?(ov0Q!gy?3duc4O@<9)u>Iqf z=c(|4x1z@nOWe3+BVEpRZ~;5R(G#%@BA?0&rLQxu@cp|R5pc@$6*nJ z(t`vsvPO|9A>dtV_t)(6N_+-b(RFhj<>M_eGUh{sLLZu)3gSp)(!`%^*|fA;{+LK+DOi>2Hhp?3d{gZ4v*S;%3Kpw+%&YEu?;1{(pa z?S8Rlyhb)TG-bnJu6h@LMw3!(a(o2r^VWT)=oJU*fr&jI{x}1A7KtM zrPmz6MIh!JV(Li7t0eRvqp|DfRamWI?kg9YUvqBAMYc+20;p$c+_MyT2G>^N$*}7I zE|Zf#v!ovx`EZWe5wA#@h7*kKQshW8IzKh0NUQ0$wB9}w@X!KRENgB8yt|^NgZr`g@nFtH zguE2ORV~|5nR)2ktGwAzBzZlKc)j<>@(shng<;RQS8%4-CPyKF%E>Fu^*}OaY!=3! zl*!g1Hd-l1ZeYOSvN~mv?ig~>_NGW|0<5J(`+SFV2_cy4sO#4Wr=6G-krX3ldVCEM zEZV>XUN--ozv<&3=J8F)i@F?Vp@~E}yOO4FI#&Msh}NiHxUl#rzNCKSb%A44RqFLN! zkFZcRm^YNG>=rh9JBPS0M=K;W*mIR=VDqz{{#Xf3DK~{9%zet8fRPK$W-OUFjyN5j zP|43s(!a+#65#NY5{3s-vY;8J!qT$&e8O6HB_bmxJzLVAR?L*79c4QGn)gSjo`^+= zz3_0{pas4))xh98d7sgrVn<=)wGv-)SpX=0pkdiMl?z)gc}v0HXiUPC5~?{}ZzuRm zjG+|m`6ucrd<(4l3-h@%-g?A5M)SCju_PB0g869*DG{u`4DqE(R7({394-K}gMi0L zH08ZSmkctDtluF=+;{5-E?u0ND>}Hl>miIqg2yN5SI7kY7|Zfw!1E|0{oH!Nv(yG; zB%bVI5zba&->2m+p6Gv$iCID5iHRFGVC_+;^Zj9G(RN2L-N^qr%b-{O$6QbQj`-tt zAi}+JuAF_KyQ7|C0>%Q9eVw>{O)EYt@NS{W3ZFAd!Ej@f0#9(V2c6^-gsnzU zMfKtPrC|A*Km*-VeunA7A7KVi3Yw)cBFc{>U~0sU_C9OCV37Ul1_}wiz)3Up3dch~ zPsA(EhAE>C(m0u)w9EM7ob-fxw#8hTfz}iUhpkj*BKO6&P2NFj=zI12{G)z7tW{nk z!hVT26kK9%#zv>XSm;Rfv~ui1YGJF`Qj68~V}Tn-;>n~lT#Yg-L6{-Q$gH9L zp>YYyk{-2NWST2%lCPJQ~2?+`sLgnvLHZUAOSvN zPqs&ahd(J+teYz9xu1=)>0(`;n%-D@jhXPPq^+1wN)$~;7r5hiK41?Nx6b#WLqkQy zJ#BAj+MQ(10*m?c-%8k%a5lRQAH58=u%3Z!wCqhg$7_#Hr)6dVcIgpm(F|284L-Py z%Z!bD5gP7kBVl#5 zqOuI5P`<72AL-o>qKbjq25uW2r~e9)+sCLG%hJ9o{NIxgLhO-``#DIpST947+2?ow zVR0zv9kQ!l_3@alP0U`V3g9p7`aca4clUFhz=NO1!-{O>s*XJe8>NPRPphLO3r|5E zKhXgX40=|h$Pp*-hoA~S=dQe2z~9e+JFws4sJ(yma?rcoT?Cm|IZhHpWXpY~^==mo z(pV3{@BZJ-yls3w+EnNA`tR~RjH@z;I`6i*A!dU5hUcy)}5*H9=g zqg;DzA5K&sUZnylcDoRGaFNfCYZ>CZ6Y!xk&1#Q5+^5MB*Fj>T#Qd(M z2Dx^r#TTJLz`q?oB^yrp(1kNDhMG)1IbJQJSn>@t_5ZRsj(q8Q5z~Q@_-5?=3`G^g zg(LpZ8INm|L?Qvv7nHW)T8w~ff89`JJU_k>x(-)fd*kDB#goXh^mb>f{g#mp)7ImPML!Y?$+ zZ`b}|DMKdu^Ga;GDK`~5!mzcYee?BsbNgEkn4co*Q+B;O`4KH&PQ55>yS~&{HPHSE zJTHZDQ`A`f65MvkcXWQ_w)4cPE?T1ddAW5M)Mg$MZ9~W+zc9cs60_vQC-A`A5hGY7uM^hSs6q|?NvVj>%{I!ohGKhuKi{IS@s#XT;& z9%T+z7`sQxpTVXg9SOu6Z+&E_nH%8WZ;HD6u@Wab7T=+6Q`GHDj6%@yd3TBL)^z@) zA}J5&pa$Hz3# zU)?g1U_78VA{ld5Tnndwy~M)Xit!D9fff0g-6o)@ESJ}JE#t66(Sy$b_dfOs@V#%| z!@teTI|$^mB!Al|n-CK&-e44fqm@oYT}*u5Hf%YU>XIZ7byTU4Vaf$%2MvaqQ}#$K zGCDaWSPGl=*yN*Zx$Z(+lrrxxtY!|t5y(3kYp^;*p04~!uHk8JQwKH}?3CT&>!D;= z>aH?xk)0lfqC=UE30>-EE>%AvWXIC^Sre8(DJA2s=u)_81HsIht#A7ZP8mElany5bT%Yvr;|>a_gZ^C(2-?90+p(pHZPAeRnv{7NXO6p-)xM*F`H3UwC4pJ3+@z?Jv(sY?l- zL*Ulm;vjP+L4mY_)IHDssn*CV z-SjSnOqzBr7|9J~dg4A-Zgt>*JUCrGAVSk#Kj;gW4%<+~xGehWBoBDn+DjvQ63_Qz z;&7AJ;Y8Hd`I!m2Bd4SX2E_Ox;0>W+Awk6ZZN57Eo(FkwTm@rko@`d-cwo4xXr3Ky znE0#NIQCgxAeq*?sb`99@yZ?&y z5JWI)Wm>KDgXM>|hgKQQcz!;JuVwb}#zjh8F+GTyzC*1e0{c0gw)#MtWHhfJ5$bqY1k%wIm$KYwy`sxkcdUa8sUms{!!AZ6Y#^LGQe?Q2o0ujXm(stMO?&)pA= zla4F0(^HYiO^-(kTX3xxdw0;qefxrS+Y5`~^M;$vM@6&IEWftIq_5kE?I+}9CtjZm z8G0Kg7b}=Qd-d!!G|(>`e2Pq45QkhU&XiBR0l}Umi?X6k_+)Pv1rx1N6h863^mmKP z`i=_puP-b>5s%5w*^RrXRZDbpA93|?j=&k~lQ&UOwIv~Uh#|)OW{7(BcJ0GoN7$N% z52aI4J^39T;LjAo!ix|1xZvIQhZQJWn%jD&cGi7Qo0f6j!=|(7sje9l+OYDPzH~49j*>5p5zP||Qw@>daDK&c8{ZE>YqbZU zK@=MGd09%X@JY8?c~TGA>fa|JsZ6yeA9;}F%OIQI&zJeVr097> z0!_WqkPfJn&jRICuU%99uJ_6=Hk_j|ikKbo14vI5y^%foj!R<5C~WBcS@=$v;UeAk zpB`Y)1nbACuzOz_tuZd9ar!YMYloy;m!RW!zQYqLn?I>VG?t65#@`~aeDUZXfOcf~ z8-9JW=`|*u0uGknOGMlMwuhm;u8S1qoMNZAgkU}!$q-!I?iZ4K4J8wF_oG4b z%b%fDw=TJbJs3+k<0LN4Rt6Lb{=Z^jp2-|S|B}be8_TyJoovlD4uw1$qDM^JAjPLo z#q_6@WRYsnxhdD={oZ+n66pJ+@;p+qu)2ra^2MZKo~!Wf{;GUq-f ztDTTQ^)!jpwEoTg>f2*08gDf5gT$Bga+30J2J=siSbHu;zl|hwHn8{e~IQO@8xl~Ew3mqiuC{orO#T-&8!(y*QHZ3BZ{1%k; zO<|?MNVOG`i}De0sKKv{&2mZXE`*Gkc^|1X7p<7PBK_UX41g~bEo(-Tdag$rPF!?( z%`!qdVb1F2HKEhB9w1{XpU_={AShE##)wB(I`s=Z_|24+=&zSGlyf27^$WUn640MQ zMOX2g(krLzfGxC}b_Ru7+{z?WJ~KU-Fbs?|H|!fxU-<@mA$VvoO|^V7@7U*yy@&`z zPtF7}Ptt|TdE+A`^ta{jiu^o#(XS6Gaf!2oA~o53k{A3o*~p#&3#FIMcDSBjJ#aU#ifrVOt$ z5^@uwEWCf;g=PhaaJbo1OHsL>|CnPs^dUlE!u?R?PW#JAto8>wNwyizo4ZCE?KMr0 zH5K8H;tMW(*T3oj&)$t2Qxv1jHjzLG3W7pr0k7`Q>nj))Ko_OdhREvx2-W`m$SAVF?M;e68|R(!)I5V3NbXb(M-AO~E*eq;b*{Nf4EKhRba-2Qy11 z#t^Xxb*Gy~CejFWfCc(bn9kT#dyzTn>opI-lJM1{GKkF3|BBO;eY zXOmGOW)IC?P#zcM(oh_liWCkOLBp!B_AW@*SALBwhHpKg@uBl-sH-YkSvDZ#bg_~$ z+#JemtOWYJ{(RJ?NIy&3JgwiqP~lZwei4`2_aP7X zQ$kUM;eSVS_j50?yvwO0#tFF3^$ZDkOv(zVE34vAsMP)A9Z=${xz-ye=KspV?rcZH};vAI2gANT{46$YlS+jO(X zonvS}*hP}15m!)6 zD#F*ItphT^PSS*7UDuE^%$fd3;IgMG&x-2=ZGQ`_g>dOkvqE9p~XqJj-+DiTLdT9>RpaSHDCkSWB zBdF9`!o=Rp-!gsdD-!w6a=xgZC4Pm_xr8%+fBqC)JEzNhgv!}wbjc0pgA3661JyG| znZtI}UG$?L>#GC&mMmZse>QH5d-efcpsx2z?2o^BZlhG+l0tox_`8*565r<;$rgp~ z+M{g4RGk$cH+^W7bGTx_o}h`^5{CB!u*<52?K7O!bzQ3G2ai>#;0TTNwDkM8WX8+oO3VDH z{+x#6pba(_Zw_nc!zjdNC&1G*Z~Z>v)cL$!xcE)}^)97#g|1lWm*81uK{+EUA!+G{ z0YWyP2a``eJ<#kO5SmOa&*F!*FHmL|ERVGzL7ZFydUbgSd(_=P%?F%~2Vyk%oLX;e zgz~YVyt9308}a(F{HqeC*LHZg5NJiCVYR;xMB(+0YSo(5H)B(1g*lhs z6&TqYv>V}gcI&Bd)ZX>~;3$j-L1Mk&SMw7|3d0Sc=V4{VLN^eMpCs`g+@fOSe`EQ4 zqN-X*=D8X1VJ(RUuz7!VOC-xbnioL``Z?Qo;;VNPaHX%5-RpC5dQzn9wVk5Uxr_b0 z1@k!Xr7HTl)tTeD{@om4_Rc=92oim#Sm^lQ?cgV|G+T(W zc4_vxT};Pa8VODQ&qDXqex-FjiFPM}v47U=7jXA`4VSZey1o-pmF6uvEP;VPjbrnf zwry7}pKG1{7&>*au;R!xihlo(|wh?6Vio zq&^uW^RVfGlRc-6D(Bm^Pd2n!L`$GRJ=FPWSVT%yu}pK7^Q&o`iB%#lDA;nKR8laQoOqmsLxg9ITL39@EvzRqIBob( zw0}Y95}Jmot_m_HE|XB4OnoR&Y%u5AqeWnqVfS-U^!%X50iMB9`d~}34o&c^<9I;o z4yAP*Cay`RT({<2DKMFXT!-2KKVD4jmkEK3hg2r^6w_WC%4tWTrTUTy&+)F5N4XZo z#h?CYE*_fj2eLFydJtZ7hs~)!N!v&*`zr1n+qEj3p-5@|C#30VT6vZltk7OK2??s< z7Pdh-X!p+WbklvePj@hWw4u8K$7OcA>AbR>@r8lM6iZ!)UE@TB0%8Lw_38uAl{Y`U zvp+EniNO%tkV_cuCrU&2u=wG~)F|>Pg{V(fA}jxWJo%kMl`Ux2O=f*Z3>wB?O?XnL z1hYbK63QOaQ0|2^`p3je|$OcZH%ucJ~oQT6Qdl$Dh*N?9DGrJccf#;ZFCHrT~q zL#is5>_cJsSFE}4{6r-X<)XQ#{EM(=n#Fe5zWb0`=@_iF*=OVGVmdn8X>o@G!~^IjcCUPk?WI(RvwWzhmY@L>h)SQa z(Wldp+LGVq9D-peX$3e?*5UU2p!>$g5kBD2Q8oouWWc?_u-9-Kj>|Fc63u}ZD&Mvy zx++v;_9}?umo(MuU1_S#UQ#{%MWe|Zbmz;0qD}qlLy3C6HH=J)8Jbjp33uVlk!>Vn zL?>LVzS;l;dUt3)Z9bHI+arTjErT4qHbX0`8U1 zv9?vSR1-XT<>-Im?kJWqN#sWsVk&aAxQAK^Pso_lam0(LdD5+dPA{HY|b?Awf};VvCgB+sgQVgm76~u5AqcM(DW@_ zSfHIyo#tNy%}H?_TY7+wpsw%J!EuLiz6{0lDgd`4ra8mOON)|< z9woU;pe;Hg&7dP=`igov>HNM@kzJa^sf*XK<6?Q=kG#xQp4H8%6F|RQgtiVv<^WX@W7veE7B-7lyKJ2 zd55AkAx*lP9furJ2l6xYRJV2d3xz`P+Mv%g4Ja z5SYy99~d0K+ec74oOfgqE8VAHv$zCQF_J##L`ulE&{g6pPM$gDCkc$3ugUg+E7o;i z80Lybgw7ZdFI^>=`JPdkB(<}d6<6M}`i!o6UosSRWDW)|%z)COSiA^LKYE3P_2HVc=PLltw$y!BC$p>YVrWniscTjk2OB<7~1^y zFkZxN7t5^C8*~Z22z1l&s2EB3iDl*f2>Y#YXP%+pEK}+N5dVEI@RyN*e$)n^H7E~3MO9T4EU^J_=-cwV8{icg^jY`x0r_c_ z3Oe`YKfLXR++`q;PYbu7LbM#Xt@O>j4j$JIlj?iDjvJ;+XYpE(Z7^5Oc~hlkh0Qrm zn`>9%u4xSY>k1`GS~5Sph|B&cZM%VlT$RFn4Q*(e@EIV`&~!~|5<@vcyd}V%y46AI zoQm9UcoAkA@qmY%>^0-N@K+i7ZRg-`?Y{|&CvceE59Om4os5Ob8|s{+(x%D&UlNFs zrMk8k@BzW_&9*WJLb^sul|L9F2`L^94!MnsYu(Q4!MIWP^Htv!!{ghoH?jXdUgr`| z=X&pa*{SJFaT13f+JF;5*#-FwBc&CQ2M)vA{(!qMFsNJ@DQpkHLDVF z*}x}C-^-JVF+-#5a_pv}|E*fKW14oH&eV-ne*+&9D2(Kj-XXt7O4a}4sV2K5MS5=N zNAow>&-zQb*7G@~Ow+1+Qtd zqVJo7{?^kv`A$723V@5!J)0a>{-OlYR&>YifwAK#Su!!>RrBh1IYR}Xqe=Xn7YZ_; z@gw|d^s6t2=yBLC^Xu}5pT@X6I-&sC(vl^vJS2$2qo!T~hRLGIWW5>Ufwwd%-rs;e zNW`*5rY)2Og|CiS>|micG}!aNmWbXHV)G&K1_t}f2!4AN8w}KqMCNB# z{f!Y#-5~wB3UxnKg1(o{fklWCelLBg9{YK=SxnA@+YA|%WzJDz#KCHgz_(i;w_cF# zPm9()%mfN~V7gD4u$wHq-zijCY#Uq^v(^d~%DVU6L-ixcagAEu-`F<`UD2j{NkMT0 z%6udnY%6O*tXWMl56w>cY!6g!=Z+$?bMDN|9_Z7qlt85F^Ohju5r)}c7%D=0q{>ETvyW6 z)WrFZ-IeTqi-S3u+ZB;%wjTiFzGzV^tG18}n9q zo8g&Q^Vfk<+lgHjIp3>Vm`qhS+qg`nqk~;he4m*3rGG5v$mFSRF za_|DP^r7rwwVLdq(Sfl?T+Z*RmB27)hWE!vNf1O(_-T0WF65iG+g-u%gF8k&6wPGf zs5hP`(av-N4jxIG46~pEsY#FNeh3Fcr2rhZ$30F;9>_w3UCa`>I?~QwH2O2^9abp= z2$X=8u?8)NT$*GTkeJwORN-Cccysm3${o~XiDWnAwgELwHfT0tZ+hNkACX}W<+^i3 z8aod@AhSW8@pOU{id0LliBTr_9qRex$*3`Ye-b`;48`>o2N8l)pF*5aZ7g6&5p%yu zp7PU@wAry}&Q?H4ZXhaf1iKjPcV*Ol9oaL6<*shcy~s>HcShTWExYgaKwm3+M)Ze6 zoQmw*F~VJCZQJK!)M{%eb6HPNW@+6;6^IBhF<2g?0jkV=)V0!}-n(wxcb!^DE6XVM z;9L6$9gvZgIi7Tzt=M_pbtj$;ueEK}yIV9*EGc+D6{4Qx@NWVnq+jezv2|wlzF%Ca zwQ7CX<^_Ine18H>taIaBQ8ypl!ABQ4=8ts3LHv1Q+~Siv2;jx|ZUsjUFpy{{3ovY! z%5qg1AhSbRAzS)p${B9x@si|jI%j%K2%g4ua_hNYoC~oxnqMqaqCAC~`#s^pqBvBq z{a4KJ`By8mk;Eet6L0DsVh>I0*soA-H&djfGzXlmFz;MhBvyl(*sy8x-jPX=+7;K6 z?bSip2F6C*-tUELaj|m;#7GSBz6*hMd6%t+Q+<{NxHqK4_#8+lfy0}OzZS&{YhL#ZGiMd}q{KvK(pGPMKDbb6i9 zW<(7mjgz8pin!tM!)+{KLDyYU`fN)~&<3>HokJz3mWD+H`ribqS+V)0qLL>|j6Oh@ zH5E@WnaMMlJs*!(&4oA)O_=9?R7CEf?j}wAJ`AJiSu0~!pm73AU5^b>e)ND?orLg| zQpqmdrs{5_v4h4{)o5hLSj|JPwv-=qy7}M-kRCjSCCn^52WyP+ZRuu40#46AUcUm{S8&ceoQ*{T zAknY3U92k=s?Lya*@Dhu3!85cKil@awqjXw2!GGpv(ma_`_?gutbd*_y>fZlI9J_7 zl%PFCN?pqDs)zof=P`djT`qA6^^W7N}vFH~Mv)d&}=UfV9L!uR`O?gPL+$}8o_N&|zYNk8qvoxI0@ zDoul^aC_(Ou#cHEJKwjHs1sRHp?o^!?3PR(mV+&p-m69Rs36bApYMsLm!I@XqYYuA z6IpiYFfI7!N@aGJ?rjzr@}!dS_e+N;5(?$H$iHTj!4&IC^5jUdwK<=j(usr@lVnog zzVTIf#l$? zcoLHSJK+0jW(;;yF-k8~TZ=xSvg~&M-~;g!QP#^ETL zNpY{6azGK!TH-(f1n|>igP{vU2O@{ccrDMb1HO(sit7MRsK@~Zb8(~?bJfxLgX4pi z`sp}cZ{k_%yTxCaZmFA(O0J6AT2=0OQe1xR^oclaAc^UG52$5h%bDcx!$a1IdxzrT zU+-O$fcUmZW!!Y+4lB`M4`qWt74M{@wNc0{$Fwn?IV`B|_lZ5AR4bGRip@_kkNi$T zIjm-j@B}k@=F5VMk~o6oQMZ)*jW(7pHw$~@;hyNW7cW=EHw}-8-H50jyC^41zliSn z#6My%l*U$*B&&;E@fsZ+akO_#lL~;e-pI!AU!w zd#<3LS?tuU=~nkdXx>iRZg!<}H_><>X~FXYug0r$;S-bT+haG%*wWysESp_zL|Km-|SHx=uKCW|NRwnVag^m z#h_l!yK0Y&@$ODU{s1>TqoqWPQTMPndbxcJLu)^YQy_f_HNfYXZgty}i7czU6ryR1 zci#Sa;y~lW8un6iZNLl`nB4U|HQ$0!U_cC5_pHb}j@MBA>7McSECIMo7Ot$XmtAMF zCB$|8Y2L2=O~{+$1~tSd0wrhs0wJA7Ze`d~hd#BJlSf}CixNu|CKA1rEFWCsM&UqA zY)ZvKCAL|aS*rUv(rTtZm!Iq1vOPbOTj0v>3OkqJF+?IBc|Bx>u>jX@4a|KO*QKT9 zTmI#lAI~1qJ*Jb+Wo@oWI8bC@_I zc8TE7kV%w(!YrR+ky911s?<4k1mvEmVg1;8?^fl$h|foz24l8ypW@OKB(s1&Ib^2I z3PH5p?_5NSii%9>b1F3|7V7J|Lq!@!5iCWj={Z{$T4>B=1smg&_i&~w8=LF_oG!~4A#{GezWrwv(w80IpgO6&bv9`pi(%ui`BPzuRhtMO?R^2-ubnkQgtiN>~VW* z(oXxHt)X(2E}KYB$AV{a9rUbuBtN;D+O$JV=h6lA7K1h!4q1jrYsi2{MdGeNhf4Ne0ws-*=%_=6Wx7=47$<@ZFU3x{RJ~Oy{aAaLZ zlO~Y@4SNHJY5TkiK4f%O&?tB`#a5RMo~`p(XU6CQ81VI!Q)&|XBUZwn1oOf1S9Kr( zo=U6vWVf*4iH#;p-09jt)}UIs!qhu2@o7Kto2J@}%KK@n%z3YIA1!Xi3F3ZF#@YE$ zYWLQ+Jj$ewKm7|e3_M<^1WAgM+HB!Ij?+nKi5WE{b?J{Q!Zyy$5y|g zSO~#6mOz@wws?>1HTWuWb&;()u3Mw~-Qh7GP&|wJf*D{v#<88E!x?qrrO>2_ni+W9 zsYwkghlW;F!scG?Z>YgT*c4#s`5Oz}cl*0;(YH_?2$+tGF`N2SG z&d@6lk0D17kD^~W-4{pr1<(+H%c-}&kTmdBZ9G;%5gW6@<2b=N)qpdtNr-!7TEE#?Sz@=dIU6(yB+DAblrG zc%g^vLG6jYD4@vJH1*{T18a#rR}=-I||C>_hKXS9`|CDk45+UPxC8Hl_YxOyzae7_Mg5PaJ1TZ$2B$Q(Y`4` zp|ckjC*S2&ai@|i)~_%@^(OPU?W(_Vca-aoJmY`S+V+?qaBJMMW8GFNSl`Ka z^rHU1LgF}$s-MYxM$X@k*3Y;JSm+%8=l-^E)8OsY!U_h2%kcXw=wsySy|~AGtV`2# z{c^EoLgoyxQrB_Y`9EM23$gC`G?|Fcp{O`V$G9A&M8r3$d)ITMlF)7YpoYR8r*LKH zeC4d}7`e@?^#g)vBPSLCO1KnlwcwXm z!6F<_!?x5uAs?3S(8d7D5((*`5IR_H=w!`t)j2k}?UJ=>*54F_%iei8w1qdTu()S2xXc@)_vBA^lZ50Cree)KA&0hRq~x(?O;}bvNrl`=dIj4pdXzZfG5JXzE!YlrT=T%lcppNzSxhxnp(GeaV}i7T znfYE4D^#f-#he1bf3brVy$?$W)MN2crQAlC0Y=a$NgsF?ugnu$~ zHp`<5JD&2MF>0tD@S?EK&p&a;ME_dn&svWuY)<`rbbvfDG6%C4dD}nI1#iJGr0M2j z|I=dXw^1L^yXzlHnhFBQC;=J@TXBwBd41+SxZ7U4syV7P?%kPjF6tVtd7fAw67_>X z{c^LpW|t z17+P%t-Q-6oGa{vS=UoqM8GP4Xr{xY0YWJhp&-YRX~HK-1VZNTWMB`>djU&iSCt~N zrYGJ=Yv3~@53SmAt4N}3)v`#8V^XWS8u=l+P9dy%&Vvj0k_9=wlvKNTpdVFc@$~&X z+#!tn985U73%S_Mcw)pmPp-eTETuVPBPxvu=pyvH*Gk?myJsM$*(#CQLU3 z%-4K-l6C0djwbu>r`nbCHgiby1>&MlYZFU0`xogywe(`xI-eEvJf99M>##@w73zTn z;%UYR(4Z1Rx5@?~R$_!YsVl*shC7$trkfy^(!_+%1B zLd%@aEB013Ez}J?-+Pc)zOms<{Ytvec*G`mfFcl&z6o)`^v<7tvKcac6CQjxLF_xC zu~WhAl4?|&oDIGZDkvD*UTK<_N;4s+Ul|SIIp_{wp^+geH~ICc6UQOYp2XN1_B zF#oAfNLa(o5*cPeAvzc1Ft2sGpIw!(^!z165jCRLbxYn|D8K=s85nIT(^1HbjOH0D z(h6MnI{l}&+ptsE&&=m;fg7uL=?9~wIpcl0vNcqplpqe1<9p=S@K(d*f)}|u!|~r# zW6Ow|73Epd*%Nn*r#`0qi#0ZD)-C9{RwuM{fED8c-u@Gx0;q6~^M>J8@F;fF z%{dvhb4*N07%o?Gw={f`!COais~>>Voy8SCsRnnoH*a#`oLC?->Zaj?ifSxDgGABf zEyp{En8bXFgFVzAaV=Y+oyp!ZWjTZhRu_o)jYwwCQ$rlI{%zsP=+0!Saj>l`AmFDB z2C|#@**)g+(1JV!(lcEi{OSq>liVCDynR(ah_{US-f13w(fF#qEMDmZD&4;R@%R=7 ziK_#*8ivWX-R5;%Pl_k0{6-Sb)kALgGqT4RruHotf|&yTC_0tVc53s*Q4^H(D?gnm zc_d>*6Y!AdbVJkrHr{K-irbp?wEz)qR#zH?ysm63dPIZ)_W^?+V@52onN@&3#<0ua z_NRzsU)N5G5PR|!sdT*A5BvwEU}vmcf_l!GXXpwgKbgV4#!=WOpzH?MAY$|lQ^3#; zQ|Zi>vr2>U0cj+t!AzvyCZVeNrCohnAYJZ1uIB#^7EKDu^}0>ZFRdH_%(h;l`sORV zL?X%G9~I-|tG_BdU8N$tJ@Y^Rt7t2{qIgejOC9r$BT+J6xN+Ix$7!dj!04=uFX$F^L==fQ zjMaXOkkDXy#i|eilqlKZrI9VpofTtJXEfolVdR5iU3)3S5c1*4fuAGgqw6>mjX2_U zMYKHfzZPOA5Y@?NG~t``fkuNa{jA2u8u1k^%}Y^yidI;~CA+ zojLhtk1zLkn10*R&n3?^aOXJL^A^-4?<`+&FIIk7pH+#Z94*V5#WEhIt2Cx^UIy6u z1+tL@WkI}xg_o)(xeZI=Li`hO<7AYn!bgC~J;*7bZY{~t%m&))9`cb7I267Zc{^gx zG<+T{U>bXIy0QMBO5isTsC5mIreETIR*oqD49I*gy4fc7GTA*4L~pgqNW5DqTZ2)d zyxzBhouSW}R9ItlP)T&FDfpwahFZku4xUCRh2Ko`v2t~G&ZQJj@II=JkRW;_iZjj& zP43#3H%uy4sKB=ab)T1T!+VuZ)vHb_2r_AorQTQy-lLxF-pI@#gkrhsh0Oka z4JRlY>x(jP2<^CvDIC?_}%hf0C1vFUqMPBo{`hOAq1Ei|&45{IWB3@x_B zvp|Q~bsNp}039XUw13fMsp#5}potqA3+KSH)-g;q7>Du$nKRZ9w|JPjMrCjMPm$9o zt`0UVR^{ZV5MNY5wU5XL2vX0jh;w+u-~#DV#N;`A{MA62b##+h9B-7*j^D4JZBUL# zO>JD8Wg2MO&AS zr&wrloAR;5vPwco&8l#h0LdH`{4BSS#uwta83s(z#eL)5=@zWN?dC0NBdtCy=Avn% zf`uCZ^FJ4_#GOc98ON{zjvy_smr?4~mPeV^#@=CVFPk^+ul499k3H7%SsMM+^Srjw zY>O?6U*m(}QDQA!cY)aP=K?^RNSGBG z)ZnKxjaZ<3@&cPg6K|(jGB)ol3RV12wr*c%S7s$FgYay1nHt4!r=cpWJD`1e;))%S zDi^e%L6QGmcHxG6b5Z~75Fk=OcU`2}=wAxa86HRccfl4J1cRQZH3r^SEebAkjE^Y| z_hJc7WXbRHo2;E}Op|M}?V8LTr^&W$+va2&ld-eix9{h<55D*J zoUOxkuyFk^B5b4gS8u!|Mle*Ga3MogLtCp-2HHU$ej0Ix@G*-u9}PmsZ=VYjS!j_b z`cFMDaBy%)jC^$$c)v-pXhOZd&Q4O#pI6|I`q&;pjjGl!;h-$sVu+DG`)FY|eyj>L zKhSKo*yvI!0h(vm4GIW9qAo-3DROWBaNqF2s(L>`wF*7De7$U2mT97QwGVC9e#_jq<60p_@U!$^18h!OL;_CInTNNb4cct3ro_?<4E$%M3ow)q^5`T-- z2j*^27Sw+W7E;(U_dnsX5q;lqsIP2)p@BV<*39`GERM#GJph|3?;|_{wF^&pjYu?- zmEt7#&5*;2@s~?v`-a>1ca(9J=vm0ynsNJh)^P&O+Wd>6&H97qKRSOZk(2-Sy%9H0 z#2S{00I{J%vX3Z9L6|uwe|JJ7+F{r5Xu;ybxu6ke05M`=#rP>z=n_7NxP{zB{N)#$ z!I7)q9as_$&lqk-%8!)KTN>3pd!Ti?K(^NE5zh}A|Cs<1KO*#P$Of>xaiG2u*diE> zRay?CH9c4-GgPMaL#@`I*)Xo5&4cGu&9)mW6C~_X#WK#dgl`#*s3M7YaZ&I2%^$i` zZy$!>svG5NzRB;2b^Kr-VdAw5ynRjLKAfNVmF4BVzLIA83U`^G$qD1U|Ij)|L_oH1dGFz)~W(N@YShF?%hst2UCDS2+^y zzt(TI$K{`uDPnqZG zhb9V1I^Bgv@7ZN>WvVjYuZniAE%E%l`z z&6uh;h&j>Oe%FZBT$@eaeQ%x*nK`hwe+?^O$9+a49qUJM0`$gg&~2kUQg14mVNFoC zk4DX$mbaxFv4JC8+D)Gqw9^PpRkNr^lHxT)$m@UHqW4#Rvry}#Idm2Ncd2VE3nOZuRmaz8fgUGhNZo}vK}K^vm_c*= zr_0|=qo!s0(JnT=t>FKULcmx+gzIC0&t=jgO5+`gCPO%$5l1f)-t-f$YWaAHkQw+C(s~hOIIMm*?SVci)qBYG zu5QaFEO7NY>kG+zC0+Lrr=Tczb)StZ$rO>!cBGd#uwT^`y5BK+*+eXv@g?#}zi5AW zX`bTyFG9M>Nq+3HkU@fe{Yx2*<#jcCG~$u}zqkEqE;FCl4ZM??`Y3E^$!K0P-Ww`a zqbus<{0~2Z5oT1N$qgYeL815yeVNgCUALulyl$3%EoWXoB@#(eAS_=Wj6=IuWP2oJi&hrCJ;V0cCAUvC4h~IRZZTh+ zWh7$yQG_ChcYGbeGae{pv7dX?_RjpB@ZlfG1YLAL#&J&Wi_x)VX|w3+sB=K1B;EmR z+Q@hQ3Bgf3Kakx(MSK(y=&>>G*rrjuCh~hrod>TjzDOiq!&O19 z=1jP{`~dFl5CMWh23eK>E_8KU1y}Y`e$n{YbN|jt4Cg)u75605GhZ3;D*o_H$^9v3#yXGXA2s}p`Q}|<^-ix7$5Y3 z@TM)BF%&7=k%udzvoZ{U2>B?d)jP-jZWw=X^hlS;4PiPZyV?NK0dv#CuEPz1f$+wa zD!r$HJnP*N-LjPPF}rO!tw4>vN7xAtXKmCGd$RgV3HN_XSRl+TgSX*Kc3zZkiWIMC zMre>&SThxfMI04YcbP*bu#AN5%d8<&7L6BQBJ~F{*&;?9Q(KR9j+<>4BmFu*?>X`i zJClusQtrdXzs9+-nvHg%ERb^(+4}cQ5XRvgw@RcCGzf~OMgXk{q+7R>+yz_)J0pdV z1l)ZG-lFb*uzOIInKg*}jrvSBqo*eR>CMi^*JAA{5+VHY)$3!E<2GyXdkTP`u~fq7 z_Fugh_BgU_kun){a9DWU4326JWF(-Pbj3Juu#;5J7rn&B+&HJbS((KFj{4`s3KlNo zk~eb!nbg7tFVmp*02jsC)gL3pd`mXF+F6U^n8!-loc9g=yP53|@@b*>4Uo&JC--a| zyq&BBE+*n{7W}z(@f1!N6dWA$MudvusM%~o@H|Kgb(c-w1_YwEE^b56KVJ-jL7Tx# zsuikU6!KdxC3KwH@Kh(lfripK&aitU{s=z}uv)VW@Z%;p4`>R)rdVK0Ze=7=*64Yb zhAT^o2t(T8WgqG6qv!z!;~#<@MCNvb?bM^&wgLu`u2SX5`C55n4 zLeduBHO-2XQ5w|9MqNyoNbH!+a4W}oi_t4NG3uPzCIm%NtZ@DFpXLYw)YGUHuM@RH zTq^8gC!-EN2~$r8EM-}hZHMVDr{HD^o2n+dd+!?;LLqB$kSA!4YPMJX) zOmS)cX4J|3Rl4b5GwM3Ipu=auZdVw=&%uPd4HHG46OzwygOlr?x1=|+Tq?I|Om*FN zXpS{&8GVpL9dA{`tdz?T@6nuDNtS4VIP?0%M2bNkDd`hu`$eF^ystDcWUFO~Fs(hE ztIBA*QuE}lyP$j}>&BZrghCeDY$-K{=Uu(Q_^)@U} zHvp`>4lY#%GL~!Rr@k|{;LY8qJlqta(z+4Q$G4Xmn0Vmg^?7Tv+F20;Qf}U&oI^N% z-ts>r%i!{s;v@MC1|2m{)CZ&=r+gezw)3CZxwKK&j zWK|fV|GvdOci~gA}_sq6!?4wrz^@qKdh1z?Qi}A80w*t6-J^TXipa}GB+CM0w20>*|H^sKGD-Aj>y`Rjha1TU$+IZOY)Y{?IJE&DdJlV%VLcrQV7vd#B;(XZG` zT-7-l3{fO+ZW9#kK`Y8t>L)8EF-82B?Ga1bHdQSKb)S%FJZ5af&L_c*TUk}o(y)4j5)73v$BZp5EUCh<&^c8;sQJ{b)%}GK z6PM%6JuEfM)j4#&=i`$|2!Kusm2cA}2UQ;fxR}HaHPPv@59;NqtZ<*hUdS(EDiQ{F z)XY{rK!@(Cu~c?yW3?7fX)jL-z;GBK^sk8O7@9LeW`0O2%$laeFGHHf$C{7HQT=bo-)GB@RM|Ps~j@gP--+u z0y%r!m@J0Sc0tYbtu;ND{E#qwONV+8C147DB~D zH_$jzfvSptFU@*!P1GLf3Tsy$r!C*9llX(+rn^qRuA(?Hs5sK0)}J+=m1CJ9@^|$w z8;#Lps6p=7OK~gNtze@iyht|aFF7lrHCs5WuKzsKf;v;z``*q~zt1UcC5Q;f0~C~N zW|KW1hF(~YW_`;@klOMv56G%J+~FNYGW!;Xw^oHk;0G?7CUr4n z++-4!5Pg5d9IBrGp-RA@j5JJqOXS#cO(M>%Wx}JUwu^Rakh`=aKVeC3O-|i>QlSnM z!RXjco1o=1I>YDe2?#yOZpX&NWWwhJLJp&SFLI|iI`UeC!|^E5K17}jhm>f{4PGt4 zaRmKSv)Q|OnQ|TMNm%L2%<;_*Wvi=Rg~qcZDzo5F4GG8DAYhtvxgnp%yF5z7G!{t` zAGE+i%t@paYM({87{vDJ^32$gwA>xc_Ewlk65_X`7^bn^b+Fp*XvSi%=Im!5-=* zt^qb8)Z?!+|8z`-3NDN@(&2=XXB#DnXK!d55~UD8LnIgZu*H+9R$24+FRaVJqiSHP z0}&D|=rf4iijZ}<^n`4?ALMK;f`p2 zbQzuGx}aGUz0>!v%0RZ7i$Bb1KV1oQ&Ne_(eacWZ-l_@=@HfZh?lSQhJ!Mw6^PMn>?>JNGc_3?9egFWA*N1DKJLW|7Ng$_8_SE^nB?fW3^bs4gp&*1wZtIg^? zmFn3EsPzw=m07@Q8*Lj!w+#E1Qb?)rglO0EnC)^1*e;R&xI5aob=UR9+4LTmY}`wE zEXe9(!IVW$qJ`;sol1Rp_b(O~Z;2Zpco8^ZQ={Pudd(H5OR zGCqPdr^JARKNHr23;p^7(>tF7Cz*GjlQ9I%{NfU6@?{@A4RCzh4x`RsWNk@rjud=7MgzbuLbcV&irFN6f&8Kiz~a3n#TSD8J8?;VSy9{bS0%4B^SBB?C8KOKHER9@fMzsMXc^F0RMgPDu_gKhXGA%!_=y z?{Fzk8TlKw~SmihRo- z{Az@0qS0I#X@3vPqEvy_3>N(^RS!y2EZTn&@T-`$c!%#&t@|R6^W=@AcrXP-x1YJ0 zxqaD0laK)USi-&>IGkO=lL7hCuiX=Cf)x+? znM%v2kftDW`j%dx*A!hqM8X_tsR)njJNyVOKf2kmspw{)TRkGzkp*UPQ2%j z8<#}?VfDOy+`OfWu-zJ`8QX1<|7}=G*pd|_G`T0C9)z0bTTK)09X*JZ{V0OF60;4) zS{=yEpKN|nr%`b8)oE6*+^&rSnCq<%+y;K7Ol}4{6m%r}EmWU`;*qd&UbCRvdjDAG zoy_x$e4v_Z6rv)h@-rsX5$XWW_%nmsY_0TXNA6oh|Bu-t(Q&N2qWTA@<`wg34LU}k zC$$j_F5F`}pZbySin<=-uB;>^z(Hd~n(Xh~n=V*gk6o8~k!$Y`VMm^KC!6j?^A~MF z-A-eQR*Nydg{*c}6%|(`rU#3{A~d=WIxkzRCG+v)N9c+mN=lbmFrR@Z+y;2xIkn01 zSpF}cQD1ssLaH4{eb=^A#N4ry_rLKWFmj1deb_L;TXRWJ3p1uuPtStfV3qB)7%szhnSyH{)`mOd{$d7^*;d1bmGMf$Dj>7&#>0lSP^mX5o*G zQUn7AGYmX^ueXu#iLeKdW9gIs2B$#^5b@)EO1*i6omXOT8roaZ!VsLo4x)Y_^`ji; z^2CsVey@qW?Ub|PHd-Y0xmxK2Gln~#8{Wc13?eL})7=dQ2uQ7Fpq(!kxT;>_3Xtds z{CN#j8lz@q`33Av2c);2A%TU$8$5*5@<{I%xv0YEd8&}LxjwGg?dU1)_VMUfLUSoR-EO6_ZUluiV6jy+Ml0 z>l94mD_e>8CvGK6@kj}J<110VyD(P7T8llUjWO~)CE+(^dExUqUd0F^d$;66oa}R4(wZ42@ILY zRxI=nsyG2#AEA_QFxMCU6fAdg%gfK)v0H=ziIVydOlXQaItCZqAH=i{hIF!di{KM- zmJe06y9#B_s<2P6KlULi4M}s?Wm9A{T{g6JzeZg@@{9x4uGSsWJr?Hd&urDJk0R^$ zF#M#=A(Bb_OOHnk7LuO6!60|UN&*iH;LSea653#W&2@vOA4fh82;F5^7x@oGt{NbV zrqkT8I&0o`lsz?t6<%WO)nG}WIgS2J2zqC|BvsaBRlA)Hp)y&lD%Uqq8bTH(^+=Q3 z2z~cu%FfC5moJ(d%9^k5z=lC_MN0X`A>ry4Q9FE^5NN6BP;Nz?jy*|@A}BG*6ZIHj z!aa35P{=3(A1?p*c&H)2qn70WVN8T*lYW3?;VOq~Z);pTYar~k2slpcMdmPZu8FL1 za?bs-WWaTj>PflJiHX9PcWmtf!cC;WklculbdzWE0$LZypJcy&(BFF;*QG7=+@*7{ zCaeMu3Efa8ctv1-bh-*2Jw;Ac_8cc>NzJppw4RaFh43b_+fs zW&Ken^z`R%h#zu=gtMc2`SA(+WVM zNnO3OZ&1a~Lok_++Qu50$U1{L#8CI{aE9Vq3Z$M(2d%eM>~6QsGPT5lb<;7jw@>!r zgrgQkG~a2*UD5kv0S_mw-C02hM?v~p2oAvQ`28dwyV4|Uikh#^I`X}9=4;-A{#?6@hw5ucNdbwkFSszN7+tkn(AAmL&(sF)}QJrq2Tlj7q?y1nX zMkrx84}qL64RYwF12YtGE{knP^T8!=fl_er#9ijyH}vnvxj`rQM+UiFX#6~Q!h}Z% zOUygBP*%j5-1r0sk3`GoAA|%w{Zy{EY(+(|JvQ+Yzs%ZQ|fHS!)4!6t@gKnWD(lZOO~DTiywat{JD0E0IKu z;&uK^*!TpklDl*j*asQIM1-z9cCWD)R>=_=*1+Q;rreKRj4!kg+SRP0J9|UyFK14s zz>i|7%QV&W>q~V}I%U#&6E7ymW=6~FwCKLxzC^ix2Qk^w>l965D)G!$qLA`!=8cA)gn7K3z^G66*Bi~3k^uPV8 zR2hY-TC#L3GL`NBo8KP)9B#@;9rH7* z1Os!i9MkSI4Sg0i>Bc+ObeLx}PN1x=%8$Wy6z-QBkL!W$`QO0%- zmiy&rr&oif8X`7wG_hKtk}bQoJ75oOAg?j*~s3 zc8Tr}7Yo&m4Yr!iT?g`XE_Ba>UHfs12A`SF<4VFw6u0>-dJKR3+eEhJ&)Z-2=Dq{j zIf}LO6Kl$luitD#2ic;EB`^I6WkcaGC{%*HguisT@8+bV&P*Ri0pYU~9YVr`r&KL| z^1?Z2jZhx>c5rI02vIB#&pSh9_qD*R2pG~PG=Lj`B`>()J%vnc{;fkz@%BpDr0F1Q zN(26MCaVhbq7QKs6%Fhr{vYkcjC|jO^fmSN_43mrodo!{`aeJR&f5cY_YG0>&vS>V zCZ?8+cr@X+tN&zmwUf0xzpa5vDNiO|FAE(q-(~rp8nI47oqgM8o`06BtsRUT+8jmsnbf zNYz%2(s2+xkas~+_hdP#x*U-@Zm;-VFD+6FFk}1`p{@m?u6@S{&7lDLljGfdVZt+c zQ_RM56Zn>hCn{SSzA3Cv6zB7u!!PBX%6{W~kl+4c4t)nD(*C@+Lr6!o#T?GChM>il z;)F0m*J~H>$Vl_)m4Yl}!iVu6y$hoqT=_zksg%ewovR48-V6JV@~O3*`Tm(UiCiSO zMp=ku6&Yl*Vy~5w$aIea!53Bxz6O>ExraxQPT&7W+vEMd_!!J9If8{2K=rQ?zh|c7 zui5J3ill-`YtT+Ub2jKuF}`^25T8iT5Z36jO<9V8$#7q@!FtTvX+t4u?N4uolK`Vm zsUIy#bYy6U(ogl83~dWfGKb$c)YZsfT8q&?S%gDeWptcP@&dO z74)rKLQxP!)b4`h%#MS`>2R{bXv<42jm6;cI+C}ix*177sc(&E^$FZ76nniT0qhIY z88;sONj8V{t$x9X%n<$Iq;H53w;gK7Fo8`dh`Io=3qf(Aw|gAEeRY-ORZ=&o6RkrT zy(QWk`K?`$Y}cxKS9Rb4LEigV^RKTGIg65GgJ7`jvSX3*ALFx8a@1hR-?y^CU7qu=b-PK=pA+wuGFq0j zx%?@>%|!L{0e+=hO1n6U24I4yv}ieRH24T33u`-r0wuf0)N2Y5cEjvXT`tzxX#2Si zr6+((V;F6;PrC$&;6W_w+j`lIyt4bU_GbiT?R!*`nKzA-)`jL=bAg#&K$OukJXsci zMYyAvjDV6Pr`EAAmt*wgE**L}+9Cv%9)gk0=8@u#t%Mmvh{TDR4~gA>G&)#nerH^U~i&+S(5!GREgwJKmMdlhBn z-Kc=M$(oZNJCRE8{8)M)LS`%oOFhjo{HSJ$meLAmPRzErm(A#V7iazVq2G&VWPxwL z&uQf3I)|Hesi`BVb{#L&ZbJ3Q(mDG>G5bd;VGOnNer7~A!_MJ?aF?hn^R)OOAK$(M z;yg5=d9x~%t3Qm{uh)WHiAIfCb%Hr5SblAbN4&vMkXqWjxner_j2L>nt# zq|fpiK7nh=pt|ujuI?3JWORT%i*Kzapt|4Em!0hv%5bOY^Z6PY-5xnLg{&Q;nfK&` z@-@7UlKEF`Zz}>vKVJhuy(d^)c&l`EpO@y^61jQ!xjmX5e%}c(#BH_q^JOf?S0v;7 zS_^E*-#dwWy-4v6y2YD8Ruq)K)G@=MQLMH5(Noc+%pQP5PQAjj0Lid5Kit<5XUP7VR>!R|mBo{ayKTNZcLbwIe94B|6CE$f2h4Wwni6 zX#zvD;CEBNBbp;HS#ZE;30)YbAl7CN**U0IfHTX*Fy~68p&FL$^E#7JPWmZhg)I@) z;9O_!?Q6Q#$KiunXG%a9;WZ(+a1%)spB-d?Wh0lsh)7UE{w;ly9@e=yqPdAZIN}tW zcvVVnFKWH~;4d-@<6cDw(eSuOCspka#69 zZ+DAEWU5|IEc`hSBB_RezxL8Tq#F+kFys~B3^RV5=Do1!`gH=Lj8FUyn@z7EM8o@1 z<*rXHIZryrLu*dg>wVjD?_T5R2uRaag*7F1#$z2KN9~1$LIOL@MBg0MLLupD%iy3q zEvfRUFhL^erpeqj<}krV7G76S5J{s)GmJKg#HNrOs~|^BFsxH|f{bzEbwN!*gCaj}7p+GP5eazGpal{COHhWJDy0JnXK}vg`H! z;GQzq(&LQM_0g_1zNBSA%=h_rYZ;8A9{aNlFCOpK)3tnCmBiagzkF;?ZaD4HB1Owsif1Si{Lm|yN!7uMG)`7DROpWzq_ zxev5d3JLX;Dkb+pI}@ELsF7}7wniz&pJvo$cip-F8?5`cz>V8ov?@~BHF-2__=M|* z%Iadd4n6U$(%D>|SzB6V=%HvHbQ@0*$0>et9OsZWblnBz=VKtci&}@t96q$Y?@fDplly&B$qgDr~}B|!`{&Aj*Dl79%qC2m~~5P@`3 zp*#$5vi=sW5n|d1StwUyCzHsvztC1H(C(`OnI!5=dvDQ!5Y2Hs@cuRvgBb5<{aX?Du5#a-{wBeap&Rv9obUvfJXq>(k7q(gHYbzMM;_=Lw}6Y6V?i zio9b;$5N#QmEnA2oCKvn#gcI8aAE8y4x$$gMHQP0z&w7XNkg9N(S(=q=|VA6FkP|u z%6t#sk5QKhd*6SIfb-bA-0;p=Nu7=p@(I=S?Ti^QmV?;~^}W@9-6(OyP%^JaX*pIv zCZY~7xYsIs?>NoL|tJ3%QK8fu(<^kpF-O%Uo{LpFUd5aLg-a}u_-w7_YA)XRFzo;E_Hi2EvibyaGYR4TiBtY zUAkuf2~_6DRrN&ah79$g!es8XCKjh_&44jkHC^k$$O&>Zdi!>dR2Ko;2@B*E24ARx zi2#MlA!nYvvJsK9Deu(b`xn?g0dTp-73*QV{dKBCPx6FK+p!v}+rRg7^KXQ7n!UYT zMyGIl-S>^;Tzv;<1(l}-_@Eov7j%@ENIpjXHGcw(|OYqMm25$Y0 zc}r(yg(N44>!5~10tyD)!|kmxnund7a7`;7bO$mA{zjWjmGoIPO|?EiG>X;B_!(8VrxGPN}mdsvz-YJctd4n_w!jHN{I0FLg+!6&|21sAfpfKUQ!lpG&L2q5 zc1C|6pdohy#i5IPkNDeR?3kL>0h?UHbz|)-AJhH1kTd~R^ii8zh$IpF4e|ZHdtjix zLfMit-F&C2kZ0q~4+hmD6~|Kh9FI3dZTGGi)qkG#)W<#b@IU2Z19MeSNRSz>d9vpx z#GbNBeHYFM9pq#3v+l(XBY;{NUU2n)I&Qg#&PQkt@4rUmMH6gEfjb@_tD4|8{l@K_ z&bKn`i&2|bBHO*a|7v(UrM*3s2uk}`Ynp7-1ME41f)(BD@tQ@8C?D>iCol;5q7gT) zqu8V!sT22R#j<2v=3Wh}f@%SQL&{4^iDeJr;Q&k^PvL`6UW!uCHn0(K3SOz$Og?}) z7ZxhvD@MHSdXUjy)rT^4bBOf^Ly-krYJAz+acZ@cUADdl81LlQ2Clz!s!Ca~-eHAS zj~Kk``sdsT;EZ3;DrN8JZ&yaxeqpyqn0oy}yUaxxAgFrTsHxh;`M$*)iZjJOO8g8m zS2Qhbs^;*}U{*0zbA?M}YZUqYs6xn}d4t&yISk~iIh5L;u8mbEHQEqW2m1Ct*?yA_ zMU6X|4(rBFVQ?1{%iUlDWrpa6efG|Z#t4nd%&;yx9ACzw$!Nx+b@DgrTV+xppIT+Y ziY8(O!I&x6aNP4By?h~obvg#)X4!FVf+J1l+K0VP2ntF@r+Gx3K;xTUSA#6YMl8La z1!n@DK@w$I2@F?Wv)ry;745-)anz34oZ>k~B5Y>%?8AV2AAHZC zBGn?RY|vLtz~A90W0ZrP$-rkKAG7{|g2pzn`10*13Gv9Trkj^?h8v-Ygz)=Gn9E(= zHVsAodGmU^9rLu{<5#=xAzYV%N$&Z0S!Nk!&4Qn)y(`tdX?)1|v%g_0LzxC|PbM0s z3wi{u$S^VAPd<8P_&!@8-}gsG<{=H%6jz$kWhjpB3|EF{w(XAWEXNY*?pC|d^LC7E z_=P?Od#v{4-E)JwZ*X4vu16vS8EgqN?SG!x3xP~J+|tT<70amP%SxIEejoN%<(#o> zI{c@J-^&4hy?wKGX}vc&Tcq!0YAqp z&s2emnT^>wp0LX~iX#3P4nt(QpZ$l)D^_Glf9W6RlwP}1Vfnd9i| z(p&EBYj%cx-nsCd^2gxxP2RTYcISvtK6O^$lsH&V$xP3VjRlog9+ouUzrcSl249 zOBHn(@~p;EZpk#M@^bl%@^k-RBvx%Gcvs11ScOTavI^YsgL81P_SuXS+h+e}*~ZKQ zi7g;o95uKL@P}UO1>=QA?^GBs-M!J}7U1WiQG-Hhzi+r>xDN2QNWi%GMiA1iZ5F1X zW4jLRYh44T0BLOT8S4dqFQ-7Lo$!a;!_-?$WXCTA!6aTn^1FW14sF+k05tL7er#-# zCIh0onYoYCn+r1mk#83q#(RNXe=wy*JoXUpqR1xbBdg6_lob2}q2|TE=ukD}xUIrAtVk~GQ#A;!b^faZ<;UGzzb!c6AoJnb#9-R^E=XG; z3}!@F-r-|fP9{OI_+Brt+zhf9evlcgsn^U8gb&$#dCD!6yVXTkbT@pBbB)$*tN>a} zZri>+&|wN(_gN2I;OqGZ<=G>P>RRtL$?39cR z&C-|I?4mr0+x_$MWe>>PU+ve$Ey#jP0F=K6rN8l`4-}s$$oqAVny;9Wz|$gw0Eg=0Ko(PD`XYYAi zPW9CMH;V1jXUmQEalq?kkuytc?356Q(d!}2qO_tSLS5eq&aQLkrST@-6+By&THSR3 z`Et9R<9wQ2EYZ_(LEgUe_*ppc@QQNYHa}ukHS`tdS&bq$1lDZ)I7VV9V`w7}->b7& z)IZRyV7OVtWK{Puy*1J^diy`@#+)Vfw_0H6)f$2Nf&iR4rqq@PI9NtujVd^$wp+&+ zfF8uU{IW?<9Q8+)bV4J^J}9&dz1xUHbV5}$sP((m4bSDw@xPJ=9>462wyb&6RDs-q zw)0+HiqS8kqz(3yuy*U>rvoSDSgCY*b^f=Uer8pu6k5pSPpHM1!j*iF$_}D4T$^1= z7;QGN+2|@MX$)aRTp?TTFti{EcnE+ip%@lV^Mdp@Z;n;bt&(gkX3Htz)L- za~FJ1=*^##b6%6`ULjr8kr#<2#Eo%?bURWnSU+WDt)-mw{`4z4nlP=Eg3US^)vYI&PQbo@ zwC{HlI>*x68+|h9#6dG=uW|`IA!th|B$8(YZJ^@@wCOG^Q-MnItisS8)6N5|S$q_d zmw^0NSUJ))FS&%>PFI3pTLk2=f~o4VKoW!RhjCw#zz0__4YmcalS$`r+*Vb#!;Dh> zt1%%Uoa0V29Q~DFO4-Qp2d$yV^&6KbP89iO^N*iGR?_Rdd1CMPEx&&rL~*K|2H#3` zx%ayIGJAK;^RSc)8|1LN`yWobKlZ4W$QLZ@`$e7Jzj@v#C<@7ds$L!WUY0 z>gsZt5rv-}h!GHD>AdC>{Cu%-`4n89eT?A^Y~HrDTeDbJoV!%mjF@`av2T2vOK_S- zM_D{MPN*)DcM}em|kKXTj+~62oF7H}x*YxgoHyZ=A1z7zASDP&^ zefTx@J*+o(;f(+`-3}HTk4m@;YA)N*If>4R)BdXknHiZ?EhZP)37ywVzdx+OpUl$8 zE|vbzJqITR1i-f0pbI@2>WrX`j$W~Xx2DA{Q;6LQm-UHAE2L<&jDioWj*cUI?&>TxB&vL<{>O+SHeE7`jx7ueAx z+;4%I0omOPBa*;ehxD@-9MAKvBY&p=W|EJhy?J#iJPg!l_QaOa_YeS$UDb?SY+DwWYU-$0rRy!e7!j2G*WgIg!`H6JrNvABImN^7+Pxt*aTGS=j$#bG^f0(U z$63?g*uz^A3ft5Ap@Ck|^)Sa|p36RtDUD~}imL*CDlFhbpCWGMZAO|yWc)UlC->m_ zbJ*T!G8gX$jT!}D>ou_r7i&S#&Q#_nr_}9$DZ1c)mMHJ(gZSdr%tQ?ri2!4op&R2w zB)tYaVm-Y38qo#~4Ed1mn5v4IOQgqG8SBs*`c^Fkhq<9-7qgAbb6MTn7&LL|vCNaR zL|YItm)eh15Y;;elc+y0`4~!L4f#cSw_Q>MO$$buKcK~$zmNUOcE~_%Lf}A>)M>&1 zq=vltR|dT>?g+LmwQ2A>ZEjpZ=&-|i0P=f4<~94ZMFV&|6tXId+qto&Hu^E;Z}6vo zaDP=zbxCbJ%w}SY^0`VklGqHiEZ&$30@4s}d_*2oDU^s2CSA$~g3vg3Ign6r-J9e&L8T z2C-~a7{m(o+OY02jUdl_bH_;AO7~RKmqos4I6JSU>e*u*I|jp#zBe2r5_1|q>0R%!- z8aRXCaFk3oi`joK1Ma)6YmF|D=;|EZ9A>Cdy6xwFEud^}$&+HBEDQGdUZqGFWJb40l| zMH0!>mJ&i52+{dWOq1@fwz9i~mdU(n58)lM>14JgP30E{m)C)!5-0uoaN{kg`pufd zI#yO;OAVHqfP((zl7bTrYEoV$QOjYY`OH9pnCFZ%&0eV;D;KsiP8KR-msb6pip@Xw z(6sAh)&{~io1MXJzt_3Q@DMYjw&?-Kad;1=k;ZY&M{ITW;u-iUjwD%u@7D-?F)Ye8jH}Icpw0WldN*RqjojP=LuIA-4b7AD{N^WJi-aWK_DP?7LglCSx6k@UM5U|s2KieD2mg*rb*yM`V znr8a;_$t9qD&w-7NDuhX=CaMyqP!;c$?wpa#w@TW_wuHJMm3gu8~S|?BuEN#>(_q? zIrVK~ZRbL!&f@-wpz0E)S+!O@Fb~>fe2rzJ#wQ!QjBK_0NG8*EmF0?b68{e;)+O}V zuy;UZFc3PDI^3u+o_D>Mzfh|qeM}w2R550|mIWDOkT$`OQg|Ag-I~#^e44^mf%(=X zW-Vs=sI3l?Um}&n|6!l6$)kt!F0#zsB(%J1qJGq^CB=jU(gyBxP&@ZrLrWHeeN$lh zYua+Ng3Ly4>O?l29G0jWK&O;bbh=7rer2w1jYv}OT^Fxw`EP9nLIfPUYom)G(kX)t z1-nYXGo}8&!sYvi0+Js=%tz^Gdiw&Rc|SQ#;PK@QEy|znz(i8DS(b66Uhk`QttH1~ zI`tWz7b&CNq?ySCU9%c*htc}LnsBnR%0pl0$C$qQ`A(gEh*~Xw{~9~E3&@~)2%l-@$M9$k#%s{1A*5hUA-#aFc-b?>u6FPLJ@*l^*FsLJ)JSF@`=E! z!@E8U?XLqh zLZlL2P1Pn<6F%}(;AsF$nbKdj*i9kUpuRjzuLh^}s+PHSP^EZD=8N;1SB4$!+$Qze zngDuGAGBNM8k7$xLwW|h8LY3l<$07T{wXc6Ex!%TzbB3Fs=4(?C9}AB%3kelix?OCVU5-g%$j1W zl)SkV5cllsNHTc__|;yF^CCx-tPBOAaV_|%{9g}ji>UQtX!py|9Dy1vBaQky7yO~r(mhNDa3|;V!HOqH+b*gFe;Y~n3UBL5y017tv z4TJ`7!V|kr5x87zy3e@1o=G-USL?PL z9;1HY3)V@m;F5dw`+eNeNx9BPw+A-;e=M?|DP!PH6X_=HD(8NvG=2*-pzq`KEadBD z4)SGv?aeR|JAd)>W6y_VhFvRW*2k?X-O0by+CG}k>F4);WR^KT8(?TO&)v+%R$-zF zZVRK^Vf5%NF)nn(6W4Mhp2|N@-iHVZYzUY(ygYsmWbruDIR5lLlTKRP9`3(B4BdK`K{D}y23-M#}Wy)wDvhy2A=5(L4_Z+5>{e|>8 znSm|&+=CESEDMDC-d>o!u?-pfPnVke|0C-xgW?FgZ5=$gTX0BlcXtc!?(PHzcXx;2 z?!n#N-Q5EO8wPj1^BuW=&hM`3uCD5;-tSs_JqrzO@Z>lEovNNRXZk(e)n1wQ9LI`a ziQpyfiVp+cYgVG3T%RfJ&#{7t(lg_k-}gA0>unVDCQpb3lQVFCy^J@%(y9;?k*af4>6&&zxN~N z#A3Tm`kOiGT?e!7FX!P*C??aP8TsU)2EMv4&uSz-!MD!yykD7_PDtJ~s?gC0f@fk8 z)9$UXVNpU&8mO_kIDZxLtNkD`EDHHGR>QC1YuM$Th^xdOcfqU}gF^3IEG{Hs3 z!T6KqXbR`Xy!V5+Rg&U>YsXU3Y#b0E_!_ZlhYlE+BM*kQ|I-x`oq&xo*uDSJBE$F0 zPLtnZa$OHhlZ@#w2d-UU{=KhSNy(muz@gu5y^JG8^q4UPVBTh!kN#R^nli-*CjfnQ zbl$zQ6QuvX0V_(!GD2ZH}iL?W`00&)Os#{3=D;NM(99xdNqn2 z>WXb=pU8J;T*dtYO8UM8ryF;hJAQc!q&OndZnsGI$V9eFn3U{P!%ouL^h|vYK6bjX z3K)I99fZw^Y#G+FDZSt)p?m#yAICnRpu|=q<)08_&4@*STYuc|W$a{139*7u#j>5e zdz`{yOq1sa=pVT&@xCae=yB(L2hIB@r*bQtbYxwW0v$TCT6UZs1ybv zs)2X7Qt?A59GZ;AE4?{7YGWbhaEa-)t;q&GG;|oFLt4>mku@CX!U@b4W_NkA*)q~I zH^v`9jK?oQ8RDX&L4ncPuR+2qVI+p}4QIwp@0yj2Fa<(#JfFC>ub6PZiD zqGEC7%;)doEFnuqUyEjrlR6SYDX80{dDVCU5%*o6Xu=9Vi_PlOvf~Z~F>!IhH8j(@ z)SH)_;)Yc9{@S%L#rg)s*@Q*E@vHh}d(zYSuCT&4%T`NGlCuBQn z^2FqTgcGxdD$+K73ER1js?mS>UD@S>4A@-EBn+S>Xc&L)Ux^5@?>pO6akI#u7}>AL z@DB6r4xhm+grD(^f3)2>i53@UpYX1rw?PG0JxdhD8@mUQDY9q@qd^h7G0yIeX=>ru z)xacfSJv5MiBI7F#G^gpP0K0*M2uZ<)oBj&&=0KC2ABu&?=~nwgd%q)uFHpDi@7+h zbMRZ#B)MoEW8W5^w+0!^OKniY9!O6zP_(uxgOVyW_w^Kue%2`X{b&?2F=KiNMPK1w z+eK(+dapiXdPj1DuWDz^g7xgFY839$d6*ue(S10ts7kRPABi|6n2b1W@PsAPataWR z9r|j;@w5)xix@b$R-pWt*)PTxV39Gz3%{W=L%aX*=xQJ_l`+?x(9ND4f2iL_n?H+v zb&l(qSV64LUhvU!wAQ(f*?(xx(Gr;;r=dzfHr z>uaGpv}er5Ky6RI0-1B{p)JUJ!g>Ti?EjvGtuHjJ73hgXZfUpVU;gH5&2$MxB4%Gpw`7dmf0e*4BhOp&e|Nz9!)rN=!QZe zH60Hc-|+BUas@um0Vj#QLUOoU8OgtcpOF0 z8h3g489u*3;^lALVO!h#Td?6_!{xLj?KeGfNf+H=gd?$p(8uY|cBo!_Hj6pQEW3vE zCfa{F)R>g_i^f7P5Y>M45ZHqho-&do|R=3@XqWxbto!C6+>xVF!cBljsJ3scL znHDU-i!W*Ve)m}mrtkNwMZYz?X4@8b0mu4H{h!scTn6s?o3C^;T7flPhk80*r#}FX z#6Eiv8^)E@R$!sVQD#*|MtkW$Vhn~1O#yp&CK!uv=cBVlCs`IlG|8zPKOA&Mu(?RD zkgT`0tx&km!A>ECd15QV(Lrob9xO%tV*zB>heR zqpfhh@=6pobPN$%zSxFqMqC+EcVb!XoPxv@BSqvZ;Y&l3wJCY@#m@z`J9+ib(6fx( zKKC=Yqi9BEim$S;>9lypMd*teuzkJ#lq?g}GWd_@*s$z!vC0CG$Ay)FUuu%0k3_nj z=R-z`kGaSq#!P3Y8AB)RtsLb@ArQ3``@YQogZK$7^we=W)2pS5%y*jCNW6>_BF%q+ z#BIh$uz@!($14mfcT+x3JXYoM-iURsGHjYQIzLjr-TJ_U{ptY7kFpik8USJ(z))kX z*yn4vvKah^wjrFgEKavLoVThawm8H;bgQCk!cxjeyxeR_dR%cogJ)Wh-e-V1g!6VbMY(#HNTdV!l??Jz63H#P$W_S_`N7dpKu_gd+xetRtuQ)HX$QHeJW`+4}tEF}}0S)*C5R;u9UQyawq%>akxMh5Z>U zw^^0Y8+@r!K1@F@pg2#S*NApy;hi$Jz`S#)G$J&Pt$FSt&9L{KzWk{0bnG|aNJ?kz zDsGcH?R%Y#K&~n=zKp`c zn7_!&6Y2E#Bk_8gA3e=RWGva$_hLXs?wtAwIGO?n2A?7IeeXG37JgDKBqi6m1&MsU z&9tSMHX(onandmRLZ>(Z=1%Zjd($H5A+v*o)6)i0v}>Rj(nY2%CbFl23M7pIYM2)1 zXzc0QtMuQn0qF6!BjB_@zn$^`{74X=q>V5YeDqf|Y}(R_DFHo(e5Z_@!LyOmE(GN< zdaC9UIRWla{qW3-MxGTaMv5;D>&-a<7C6u@<#PrZ-kg#GmoROL(C_=5R{H_-9>UpQ zkkeQ<#v6WJWoomyMUjZ}95JD5*KS#=?n+GMgYvvyJbB+^0Me6i;39ZG-@lIKoi@27 zhfYe0CY`e?F~4&5;i*hk|3v}Jl$kxc)X_On?w0Z7@H=uPsyf#YbSED%HMGxfGnG}sp^v#nr5+@_NcM+gQ4Dn%=!C24|pFuu>BM(Y%U2b zR@6kWX>c`!RKk(OXmHfN_Px3Mpa8SiLzwN4O#H`Pk?r`w!%W)isxCpU=^YfHixvp5 zAhn%LM5NM7w@Aqd44p!#!$0ike3*1ygAVL;{QXwHgQ)tmR)TgEDsCs}?mR#e2b$}~ zF4Nc3^u$#o@iC$67_q*_+Ze^QB7?kM5y{qpK*qc7r0u#P9B$D*-%>NFk-KxG(^7s; zo-L>an&v!em&~B6&b8Djj;t!;$6&<-XX3`-CIHs?W|AcYfxx;bVOy+h+0z`oFRQ8- z!M<}m)8)Pzb@KM}X_e09?&s=ad~b7bmZIJ5p!a_OYF%0Q0wa?(^9sDnoe}+}^DV1d za)kB^|L}xI5^)wzUzLn^5C@@IcN~>6+Xv0h=T5w4Wp8HTDZIUpvcdlwjVLvO__6ea zDJQXaM()N*?1~J5jb>YA|D!eGgqr-E5n75Y#m<{1fuu?%aW>DB7n3PkK82M%de!I& zicy?}9^yJ0u3Zzvj*BpYlR7YAOp4gA&0c4$ah6}KxrUMVtj+YaIPW6{7H5s^kX`=N zHk?o-PwVs3>SamrzStzd$QyQ=%}Eq>+vkA8Wls~OK-aOm+kQRwnCDAIJK%U>r_(>q zZTI%pWee3`ddYI4>U#a41L&Ld;j>F}@KR^fIpxq)-F30x z_|4eouClh1(a24_s^7yC{l}v#thgrju!LhigSywUVXDn0Kc?P)kU?nJlWf;2LjV_V zNZL*(d;WI4>tiPRc?)oiEbbzm{M|n_&{MklS^@w!6M5%ryW1c3>-M^fbWlcPXs%z5 z-zSxfVbn6cdW8RDlD_iBcj(=M?B5AnVyp81)pOhfs-FARPnR%zq!i!&aoshVqUQfm zr94{D1Av;F9yUlyCTqJ2%&w{b5z(X>I;mf5JodO_ZF|mcH!V!iF~P>lcyf)gf+r#H z?DNsKXz0{m`$ur&<3Q(s9Iqx!u+Z~F(Ng-`QVvjK=re72GLc2BBd7CUEj(R(<~gn_ z1)ZNBOz#Jgqo*;AvxT#l%~YV5_3e|8WQ9T+)lzP2lFwtIQ?6q}VO#?*cwe9H`)u#q zW2XGP9FMcC1!M4RR!GOx8{+45Hm>&PFbBJJ*N+c8_LKltnO>G_tq<;nL{J5uj4pN# z$CFPig#_a+x`7rt&f}*u-Tng4-|ZVuE$$W&nYw6vWogWn$HC~8h{cpo)5E~a5us%# zU0j%j5zRO`VODnat%dq~*cX4F50*^%!jqmAc_k7WWhmkUnDTY8=V}5@q_e>_-@r@XE{1Ut_(IAK0s(uH?iCZq}6R*N&E^ zAxJj&Bi_fV@3QmouA)1kym7SWE{#_dNeFpYXw#HG6H=&2rczk4|R?G*8!R>CpsW@ zH3oK{sa0yDj}5oOMk7(JAO!l~keG%RJF`ZzvP~?lB)SJFuII{;$<_7NI1*`4&;a-N z@bN=6Zdd*|eqhCZ`aTg<6fgE&Ccz_y)h)N{s2;U#iTU41$h7+c0@H}wJ|A1FU{=k02hJ#tSnTzz@yPqt<|5$IzrC<5l$(k)0n8RyfEt zjzhMvJKkT91iE_bY?Zdv)|rf;@qZp<2m1(7_%YaqZCV(41Wb2gH7b4~i)n_lt;rn? zM9alf2N-M^)u464yic7>2jU!09}H})8U?Z>jim!X`JD#7aS;)zmq4maql7HXB|X+i z*DLP0C$MoX9!w-&TaeiB&MZ01d9wv9IF5c_8njnM=bLJpUf_Scr*Wf?R^dENJA5`W zS*(*Sj?E~YnwuKuBz6pAfU|{a6q8PJO>PG)a)Dji<9&b0%9fi0Y#y=%<3J2HvJq0-@od&dUuXMvtu zWuD?@+W8A@H0E%Evj_51M0#e6q}hQUVfhAHm-9z|$9lotZsHgkKUBk+i3?IpxA${r zk%&*Yz+@lW5az@~G<7f`{h2t1m?a&7RodOJl^p@!mTbk=7+J;2C4RK`n5|NHF$Mk0 zkHgA(sqI5d;9qt9ckCPO9pok16}j8T4xppUg1|KezX_8_2^ytZ-p45ix*I?IX%5Q&ZgenGuWNp?nNvXPT2y9;(aZP5wzoP7J z6AZ`DBNaM$v-D>gkBC!nm}?Z$_z=B*&%N~2cZ|#I(kU9D1n-t_v$DNb+U1PZYT=-2 z^aq28d{z(3?z(cws1R`u=*eaZbW?WB5yRKnlKc16o;yk8NE#;Sf zOdEqo18w}bt=GiEhdPgu5hKl41rbHS>y<}>06pT&mmulku6TkQu2JcEi_goG!gQXH z#1Pk2IXsD^q3M=aUn;uO$VQ)-ZCXshrf2FrJKF9iO5@)bC!#2lChH> z)UoN%1a#<(+-|*i0WLo1I?C7f+(Z?7gV*U!`j{?cSM>lKM6OGZCt(B*3EsW!SMGP) zj>OWKlz0DJ7pJW4+z}~C4GX#jdnx3z*QC79pC#a+0snq$&i@e-3cYAMaO*e&(@cDF zJzuOh><$zc(RV*^hG<`gVPC3&!7$-GWP}&n+8?c z;p47nfvYPBLQQs z%b>z0q^)@WqTJ`rv2H#}k`U^;0=zi`jbG_esP#;@a2coA0Ha0ZBE`kYB>`6;MRgD@K^*{*Bz zg<L+P$z|DReS9~f zun-4ql8e9R!XQ+gH_`?;KH8;7&T>eQ5*b{9gZ7OszR}AsT8CKaZ-f{x40q9m?Ze^x z-0po!qUi{N%(8n{1)Prtdn?uIc}ye-PSQ3+!dTe#ye=ud4J@z%HQwpTd{=iq_-qD< zByvsx+G?dZkAm61b!;xIaaW+jcev}q>*KD(($iQ3#azcoZ{4Dvz!LbMJhxZ>hG@2ue|8)#rL^a?*@6z;ZxZS zI$6S?uioIAn2?wJPWHh8!b#_~!L1aQ6KwevFQ?54%+&G|8nf?<%ggk0*@D~VmUK9w zAJTLtNTtQl7c}kiNJ5fNl6%FrVbcj})89jLaLGan&w2qIku|4XQ2;y=8-hiK*?76i zMVEK*DbW|(S|#<4W7K$j<&L_J{xY*%C9y3+I z=2i2XUDt-V1*oy1NI@fb<)#U^omrvGI=QTpPz6oBgu0fFqXhl(NO47ig9-7JBjOc4 z4jGN(emC|_zbtNa>Bq*f(ePQSQZUPjss0|9v^8S2h;~fy3s3p((s}?0Q5%hp!V~hb z!^D48ryvb8L-$lSN`h(V4MHyC5v(6CvnF+z;0=2$&8!!>V-YTVaF8HJd9nG-hGM0L z8{~rehL0Ff$DD*byD(la8f?JZES0D#_4;B0av!}M2rFb*P@f^%L;CxS6>MPm1*s<4 zc+azGWK>Yb*4K_`W$tX-hST~|(NR)S;Gs>g^WQ|}$$tid;GS0o9rq$+=NU9@Ad0&V zH;MA=f%L;~$ukqfO##QP%4rZm;d=)ay(y5(L@wvT?j)b8ZZu5Gi_!fE?^!8Gq+rJAky zRUu$;`cOdq1pn1Y#r042K|@Km#>@(u)1lhmmZi5N&!-3@KU0C=;_C!`%=8@sad8LLa&N@V9QPjXd%Fb0|; zHnQIM8Qd>gMNtud2+m-~F#aY6;3yq5)X$jmpD-6q&%?9}cqlpL@HX5XY6r$)plF7V zp-CZej4&e)A@dBP1^g6|#sV!B46A+-pBD*LNxH+Ya9vX5)7yhmFBBX*=@MYdf2?VP ze!q&SKHW2|O?ag!lHUN)9naM&yg>Sz#p2K0@+xlb>bY%hVDZt?BWP<$B}#mwwa8W= zmN1XU<{d579&4dA}0yx(&()P^UYE&KCH1OOp|0lj2m+=X!rn(GS$`VQz6T_TaYh@3un12@OfC zo6!;*J(%KZ`PhKqEkgiyozY&FWjS{KZrQZ4^*YM1Jp7Pl>K4y?o+`bIR#hy`a_ES} z;Fdu}TIhiz6gkfGWxaQ4H?9EeL8kn$jn}4Y-2~U>EX?`|!;w5N{7f#TA@W-L>oUEZ zwg?6_!V|eYaambeNnf<;Fz|k)o1V__`6-s=xKc7SpR0sej!r%cXP<(0c>z{+VdbZmjat2^U~ zZ-tO=4@ZRx-=BofU6%%Gyl^M(&b988+(1WZYQU+ygw9C##Y->iKzj(Rb2{|e+rZgS zxJEd-uDSr(aXxajul}|#hbWPFAnHN&$XN;f%%|^3`d?nk|Hg|>sW%-vc?7Qd-kY{t zHV-eRGX%08c5NLuE^oaT&b1qHg563|_9rkF_0KkV02a8#^d%|RvUn0M=NY}lHfg|! zw39g+1oy!i zID&K9H)zv2W!VylX1h#&xp8kJ1vAZ(MIxQ&9enaRtf3!wqn#%dB+tJt+Fvia(>S%< z^*bIWDxDuw_1&~P{vB0X-!E@_O=a^Ww`}?#dV!WTM@w|U!D*TQB$E=Z3&kL3Ckqtl zlOn`jFD9XwXE_fNi@FTLt4a<{!%7NDl0^W z5go);teGLV(vw^v&5kMw|1q;EVXNhm9G!sN9Cui#>l3nPwYbYmAB~eQn2)Gb8JPkx zp&I(8jzlb;s$N>51?fD-%0aKBn6DOC-i%epNbwcAw=-CHqp&kzLu%EQSkuE;y2f0!IM1>(J7VG&RKUSZ%7e+MT~D&nfy|ST~D3GAS~MG z$xJ1%=i?ZIhdM=>4$($5-<^s>H&c*C$p#65^;9n!MBxiH27hXZ3jzKJsdEFY#S?3C zMM{6z$Z0LrdsD-2c#{@MVd)?SwJiVD{t#e}k3ibUj5rxgE?c~5HmE6+T@JtIjedn} zdPv@TUdtZt2*M8kV}PHi4yM-K5*Yhlb@F}vQ5?wZMlItKqu3;Xw&_)4^}@LMMZ7EA z7uVRVOJ#GG*oyDY`?vBvv5P4?T0#w3qqvotVFeyCEdKJcZia#~SJ4C>5uqzV2%8cU znnXrDm@wPvyzoY50cOUC3``BaPp}0=kwwV@-N?^BKV=8jF5(`w+I5bc@TicYtMlL_L0znxH3{tx; z`ndgOkxjqnxqGa{@^$roYmc9*7yfre3Pfpa8PD_?CejNUwK}OWouI0~%_Xscu8AKm zT>HoRmGSzEg?7J1!#m6Gk@EiWA0D4dsj@^bN0?k&4(#F%&=unE zXrI;mH==}kQaKg*e6}!gA##i}-zwyw3$)VG)J4JeIm4jL`Z42iC^_9NMZXBwsiiWW z0neK{Y)-s2eiNt+AuTbO-&=$x`i-0sopBgs&;-DsroiY5Ne_%V7P;KBho`GIh%+z_ zGML=Xaz#L%ufZ7W@A4=e;$TO?EL46(zgP9 zF5a}iH39=2WUsb0LS>`v5(%X#)JIrCg9E?2YqAJe)BJb6Pfr&8MMepKp~c3kGLqPH4c=f!-RIn%1T?DDq#|kHNSwhv_h5j#-l$_t{10c_w4( zCBG<`_#*lxhRKsamQc==gu=6~?>M^X9qo1$R#Zd_@Td3heiosuT%O3sbGEFz6~C-F z)o_n`hdd2vk@!eQp^aAqNs+Q`-DbkutX$y5N{_5*MO7-9>(Y}|na0kA`s9l4LSmPg zNysp;{lwFou{Mi{e-AjM);EJXjdwZ?i1~gF`oy3^Q@5BJZ6^pS|CJ4qF$jZNpbnaqCN3;B>ztA{iYrL0KQai3}6Oq|2Ccgbry zyrz6}jmt5G#5Tk;tt-h690PyKLbK;jf{_axO${ zr^SNU0t(~vnzDSHfkizXGy=E#b{(Gumj5=ax_`bBx(Vjob>~Z{)h0X_8b$sjVYFog195u`tWFBjb#*4UBXABR4%)IKvDu zZvzE_%SQW3wWKx#RQYH4m_4!1e;cJSHXo{WJeLBkJ3%dFa3n9CTX|fLg44DKb@Nv2 zCp)ji`QDe_0h9_SMbC&xl@iT51)C0CAs@Tpt5eKgZLgXkA+HZyy^^)PAK!A^`-hV* zgJ7VV4rV&%KVCtytA_4GIbgTOgYny*5d(hadm|i z98PsUWidURwL$X|n)iJ4eYFmM;C03`3RH$as>O~Nu7Am$HD>;F6*M-3e>0u^ltGE* zD!vjFq;#VqG<=@enQP2=iD#lQ(mjb048S(N(%7X8sTX@UAXH&>|K9=Ve=MCYS+iFn zw|sDP{EL0_{Ytwxz+$%4_i-!wVcV7|ljC-KdY^@xU3B;3W4&p|0&Zd2{VgNc^8&l$ z`Qa5DsLz(wz4XFk=zV!;y4~3=X}`sxcTkrdm+m$wsW9{!(bre=vpN}jZV9RaS0`%VmeNV2R)!*i86`uI*@2(&DRsu>OO)Mn8d?V{q6kS@8+N zzI*wo-5cD{!D(Ojcckh4VRL?(OJ8x4p;eD@+8p;Yl~eyS`ECvhm3x&+F;{L?A0WZ_ zzWul*tEunwugwMgAc-HBctv7--wsr{j^|HAK6GXeRF%$g|qyP(q-GeiflvWAez}F zq5Vm}Em{~k&zSbqxx=SWEGAh;)R^OARKZbErnXDw;<*nDkbnR|N9ED)LutBD0 z!I}djYh?>aahUhZP@H<2%Yv$$H=% zc;zmw9?n-PHw%k65zP@7efdUf#_#eF48OG9T=>U5K9U3P9uFE#3BhUG1FtNq9i|+I z+2i{+Y*qWqdW4}w67hW%x~~+*`Ew_f&I=;+!Qlk{ZPa0mN>CzdjaHOH#bM#DCKF#& zDtJ2j#&!9f+IO#TBB&f&{KRp7P94xX#2uyoY?7*xIHp0rbZo->!14Hk=w+gXX4am* z^91UoW)$g36b1azmMUTo%?aL3w#d|p_ZhK=b|9P6|6HZ3DUA@_q3RDwFhVDMf1>sKTbm zEo&EyTn(m~vI~46e^1HOhVvSbwoBD2a7a8JiW0e){l{~lZn2~jrY$0grBcB7z@=r% zyvll(_F8cU#~ywxqbDZds(avt+uMt|Ruipqd*LzNuHaQi3-O!gS~qavo^C)IFS8^Km}-S?L6N= zgs7GY0RA+i?Y}LL+^Bh@a>Uma^ZH$3tgn)29pyd6#%D+2`Wg45%bF`Rz3(s3_;`3= zRH=*bg_ohf5J&P5>o|E=3Xq`uh7 zHRK3NS?)l?i1!8|CT;pP4hmxNI=7Nrg0VrA6LG*bT$s2y-w8|Utf+?1=j+Maremot zV{Z~8FDUJ9e{uHkF5~_McvsDIF12}H&qw65#i9nhut{#jUehkn2^Xyri|Vc{88It7 zPoIy8#UF7X&J4vUT=mPgCiM($zC_q&L*3*(>3rk{8+BAT)`0II6Q3+;8yJ`(>&6QE97ij_bBLyZ&n- zW))skzX(bEgzFkb9eh}}SrGS3A!O+KtU31ACEm(M{#5PpW_V`*Nx$Gt-cicLdCYS& z8atEt4C;ljmHf(=DO7VSP^e{6%7Thu>+RCZmx$z;aQW|+Pm}w{oFeD4qsAx zdfQ@AhE@@_Nk8c#8V+5GwBPyq%y3S+!NcI=TD*-_JR|`v#kX-9`-l(pECgOBk~(C3 zSNsutX3yf^r*B!u!Nj&%O~rhA?A0xFnaEt#mluNv zBN4ZHRP*OZ))QKD5V5Vd3=N@gxS!%E4LYd29d~M+~6ot!P zEzAuuLY~>=IYFQ=@Vh%&A(K#k|!cz1fx=F`Da66Z5@97Lcv-npN!t z+zD5Jhk76u_eZQ=B4*VhsezqtQ1qnlHr^t#ejH)u1YxAmArEYJH))n}E}dh%Y{@m= zxFxY$-^My2y>IL=+3?xxGKFBVrtkHC zLBQ&H^|V~6yiK5R$McF%69diHAQmzDkNf^!f-n+g(MGR+1$pYP{Jfvpi&mFRexWtp z7qC?82k}A*d2dUSF5^Ga=HC4~!chbde|4NUOy;mN@q@jcy3-?rPX&g zlq_!VmaJ%#z?MWm6WwqSs&R#q9MPY;zIxxZ8$Z~VpiI^G{G^P{Q9sQl0h9GQ1TArd z3qGJ1*=u6U{c_ig{noxCX1b{{#W5bg(ecy&Z4IJ10Oysg!ok^R@6VSn_Vvpe?XQg% z(}mzkAVBb`;2ylI;wSPbboCV(6*n|mhkwCl`0o6+cD^WHNbynViKzor6K?&;lqcs+ z>>?@!j#CFAWtOE3Q#f{g@Dh37%{RSNRL(cA>K$xlaoT_Lbsj{y5)EsJ$Kl`#xbIarB`%>fG`YegGnAo8G zrR|?(8<%Sd+&LPm&yeBjitRbG?$);-i(fzQ%A8I$z~#^C|0#dIe6~j?lc@jIxZ21P zvc=C+Z89i*INxON1)M%C6li1t0En&Wc^y<(6`1<3hu%0kv@T2Y#j@F$U6DBS#kHUh zTS-;0Y(xuuzaQCAN+vTqU*!oa(H^ztYoejb4Aa$Er677MkXgJ56X3w!MaP~_STVGz zJLU39)-1`s!^)JF0HN6%EN>+`4;(8t^JsVAdoeN(H2!)x3sGf=;1#<(lGESAU$>Mv>?D7PiT)FPX-5k9&y2ea3P=~@*g z)ngN>S?n?*p<;-p$(R+Ec!;`0>~&`oNUnn}D|o&!rCH(knS^ z=s1%rG&8SXv7drz`HyBffmgs6nY)pg(?4ll_OtjbT(#7$$uL<`28pH4U8zsoSV$|Ig zg@3jXZM{-p?@{>|GyEEp9sw!<_Q(Lt3N6)0A)?@fO843))1@3KJ8mGGwr#&eI|3Rw zymshZ8qqC}nbV8D-fP}u>22a%_*k$!8C9}DhR{u9P5|rIV6#zOE=}~Mo(0aLD{dT& z;CYJg`_H3koTEiXkH5N~ImE;!zN&6HG6T@X)3foq z2|{j544{kYYBR8N`7G#JG8o%WAUp0F=uWN%dqn_P(WB8!J#tb+?_wIWWy>1ck(|4C zs&~WPLQ<2Ajo(XhGb=Zwr;5f==QZpQ`xwPUNoCr0jNi@4$-m`1vZJHNBFz-|^FNcn zUo$B#kdSx;eZ0-7vU4x3|1DODwEFCBDb#K}UUaN&8*G0ZSM;5za|1JRNVDo4kY_p5 z`i)9foJ69bf9u6~PK|1%1=PSE$f#xQrNA8F2z}Aj_+6XTgDC13&r!jJWX?Dauf&(l zd2TTAX7P&Xd5j)XaGOo9Tek|lE%|WkX3%P7tx^Y{s$sW%CaYZzFM}+#^$dv(X_hi}tjX}1@3Rj{?U&wn;|#6&igxup0xpl; zca^Gs3aIb%0&4HC@BXC(wQWh9+n!RBq7@ILYn_}659VU-b>hbH!S@KWKwr!3Qs;-Cibn`8VZ60@ILcIF|p;N5q0Kx@V;q0i@Bco@LyjUTYBpplLZnd zyQq=e45$H;0qjO>G?~XP=Z`3m-jXxFSY5??5dh)LOnny%^k^_sG7X9xu6=KgkGe)} zdn|aT(DkluOhEe~HujEx@hOlq5A7=HrB z<{@Gt+;Uf?wa!DIeIkLe=rU%j%N1*r$m*z}NRqjsW5&%~`^{K()+wB3z7IaknzT(! ztr>B7xj`Z^^BHZ1#zz>&%*sd>CInuBKf3%V-{{Z&;8UnF*OqhMFY!}Zc`w&4u^yCw zs}s{l#`L0bhBs;^E$Y3!YTO}SA(h%CEMaKUak7e3+|c>H%uuoiTVSwfABTiCA%tBTWPeAwNzLl@m|CyJDzojnH41Q}U*OJ>oqL4r*kTyuWW@V!eLm5U0 z1+F`rho-eR37ATuBN|i9n>uwNQ6na|ZX>!IabEkJO$E&dnv=Wh>#Am&muUIZS!FAn zjzngWOxB!^?KJ#a^Ld|c=w^(1FP>vmh15{cC#f$$*qHufDyob%=V_TI*WE4P(qHR; z{2gq{k}Nb#GK%RswDSnuTov8b`hW73jWn4gJXBUYf{lB-vtb|ymtH_yALm;fKYAjfSh(V^$rLgzlXauexl=f(h={`1Kp6emcY zsjBgfhyp`b6Es`bD$%12i|Y3sEzB~_mEPPV7MtDeGG^GPpeVn zB8Vqw)TlyxkdK++v;nZXSwldyL)PtX8;gqeV^e}g~GycSQ88!VZ&CB-StnOF|yGzEL^_cw2Fp~8B=gGXk;7?1vUlgE^ zlDs*aJnj_1Mj-0R8YWX2-^KH28zCR3*5{zhC&+UCvG*ND2N+Dp_egnnKPO4xw=Y!U zM}rUUc>cHsOTJ}7d>Nc;zbJI9|2;0G^}d;Rd~^Uec-_=0)p;)q5v^lUtM4D^bSFNG zJ#wW;-j}FOn&_B{F5lk1pPgN}U3^|uhA;HJJxBlS{GaR=GWbP&eLv+-%WK=qZ!!R| zr-y(`YLEXKwn|b$6T#iqO{#vEs;zlH;>2NHpyp1%eJ)? z)%M%-t#y`tOTmAzf(3VA)hYD%@XS_$1cj_~L>T)eylO~3jmZ_YXd1cikRa1HCu9DK zjUv7)KBtA2`A9u8ZgC1G$WW~57DWDwdx1BDxpKZ$^dh3Km_rfRII#Z|!jA*;t6%)$ z?jcUXh3|3aGF{!0gGYPf?hTY9h=$~d{=MOtDb<|drofv~eGE{{Yx;f}iro!kD9C{> ztn_6tlauyu^Q4#SvP#bVY?#rv@qx-YQw{qgat17gNJ8*s%?xNC0PRwGe}Tl!t0qY# zisme>Q;ejg`zu*Ropa?aT5_hB_Htu*H_cp>rNcVXXVo`_FGB4bf4xzCSB>~>u|32vWNVQD`^JYQ+KUXXW$>W1M!y#Am<;Q$>~?ozsNeR1)CBd4W2ySHWdqY zVEVofbFz`1db#!X68?4QH6dbBeMa@~DaG{1%sL|O9*PMxSgOcntB<|+I94G3IYQ)p z1m2ww36Li}t!n=Yrh2oqxBq|)&=+#r|r`p-!foeeZQ90d~0+JAc8(+xf~=; zC(V6!t1<><;dRXBf25DH^bX?4!F@`Iix1xBa+xdYrGiUZWqQZ8hBQCTvB%qW6MN_`5&X6czcOv9^6L z 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 Y>6vw=-VyX%|H(H+>vxXHxnCbC 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 RFbJk@bII5ago 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

    DN;YwfDD51T(aaC0_zJE3IqKZ^7fLCRV4aiSoNd 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%0AUObb7R*A8Nut@W(MVz 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%0A7Q>N^SRIH&2d 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%0AqeX$2`AQX4oUT*cm)81A^{_# zLS@ZyP-7ssee*|?{(hUk)lL69SLH0P;TvoFiKY}e5!uU*E^ zTakU3(nZI78kf=!=PTr%Cqgh;QHaX}d(-!P^be*#kGipfDZlz#5S^{JqY^w{Fg@Sb zW>qg8u(A|^TfF%vUyq6eo+>h^qa{fFmk_>OHHSENcQQLqEr={}^TxctH42&vJjI?n z=DgJx4e!G=j_JcNjFZG96X)#=tU3|O!2mw)RW?75l3ZS!7$)TR2zi{Y#ZRmjS6k&- z>UFpx^PP6RQx$*`{v-U=e2$vlxOlQgVR+zh@+Sud30&;ty{Aa-Kr^AL*{=4Zzt6FBVZB9@n%SRCybglT4a3 zoPH~0j50EDqao8~bp8Xr_zYgD&!p=Mn6_x}&M0APm#Gc(<}8$N?!{{@N9E1n%Z+Wu zYvZ`p%O16>ivKLv__(|7P@epf6URqypuVg)z~HI6VieIx+h<6|F~!X}C5tm7D?gg| zbXq%B(Wd=wA)-0oZcS7nAd}90Nzdx`QJmKkBNj|h(|7kiT}Yfu-6Dwm=uV;*TdQRi z76S1QkXUn8_H8z&(QneENErVTsFVHd5c=j_M-p+vh2GwF5(G)LI z(ucEkt@84tbw~8D5SsVcj=hPCcVyZPYgQM zjc-8LF}GVyzy~@1<#VchUr44wHMwIHL$fR&%cmQhz8e%K|6(GF8*?~B3&>nkeHpD! z5c-n`Em%p?NTL|B;g{47X810^a+qOkK*UNXjr^tYtwgobdZWy#>7}7+pP7m6N(uwl z+_YrBRqzVi++}ekK6*O00|#yz_k)Xr4XDkE&oQaBWR#Z5$USF88E;p}c&fo(k#eg> zf^+V&=rDroxbjF-Y~9q*V?i2ZR{rhG=x~f$(WNdLI!JaklZKhbt6)|uI#d|t65q!# zS++@32j26Hyc{&}iw{}nUBaY`i7)5mtfqkb&KcFdG2`A?CBhXqWboB}n% zDlTNt7Jy`{J?C{(g?{H(3IEEbzU4%Wr4ZV#q@G7rKJayJzS@H86p{JWd-$0@4d!*b zZZiV^xnjjn4jSwQ3IasZCW&fdr7~b75tt14v84+~-D8TuV=5yxvhff{Lms_d>H`E5k*nH+)wQ@IzWoqEaP zZUICMa`JRLM17~uz|BYH_K8g?NT!7=ll#n?H#hx@aV@2YFo7?UZw>mId>DBmZfeie~D>yh0RQy7?)ewAte z;I9(NCWQKrgl$8SR*Zx`^egvy?{IbJ`6oK6!>?HgQVw==?R}q+Th5NjG&asx_KpIz z{Xk7;BdPlZ$(WCa?{`<`rTl_!+TNR2>=~2dq&RPblziaJm~NE)_?{93O?>8omA{;t z=)}&*BjQ>&niu*3^hTIUF91H}HY}9`$t9H370Rg(GaTQNJ8iuK3d5!+fU)v&(5J_D z`4}7&3UP_AU3PL+H&mzIB?{*})*J$72Qt~1N57Q)L%QM@jGd#c!kvb3FSUpiZ;2&% zQ#?q~1x$+Os(Hi>_F0sv&e3EwC;;RvZU&7{{jtcX_N`I7VtB^B2QGu^q1P_#<7 zi~p|e@#EL3oBI$jnZ8dDuz?R5Tk|sizBZE+ow3<(U*22XB+%XXIWX{#J=>5gdMk*~bFB_Fv*h35)CKiFn&i=4k8`7D=+J@S3Ix||vaAX(i0geeUFIrjqT%VX+} zS`X>;vih)C6pNn@Pr7cUP@x6Gq_BLFKt`Vw9qnf-dbkt&ZlO}Z<*AOr8ZE%Jv@cp$9p5#?^ z5-HO>6>WI7gn5=)u&lAsOyST`WtqneX{TB(ZXw9?EfNa#0kiK^RJD<*dT&R4>_uyiwHQeZ^K|xK_q61X z$SZyFiMZ=t{$H(8aMR0GE6L@gXWpK_C+zLqk)(M1Q|cHfx$N+N!JS(~C$Yzwgv9+p z6qtY!)AznFu)?0)^ZD{W8Go>B6@e=TQtvs*i~14~GFAVT+3GjZ_x_Wwr$1mv+&aXL z_sq@W|2)-;<3F7^m$ey^|6G^FJw;13_6P(fKJVFDcYO@qN1(9Bx1H?po9z_07|OF} z?yzFmb7$rF9AQ$h3c~sFWATjWs6K?G>QPi8{9tDgwPF^=qrVk+jBg#gyi6EC zuYEmlXCQf&OobeQXyfQM{7oPRsDhR1% zLDp2+qn=EaJCwr*$gQi|9!)xyC7CB`Xe++E&7!0J#PcoXDvi zZ;SmWfZDv%ORC_`eiWux=Bw=c_VT~6Q*+BQVP#T?fskybbze35Uuy59j8w!C z3+rQ_N!*{Xu&K4(@XU`dNsFmw)1%F{MG1>pc9s}^KKS#Su5h`cS5$$_2shMIPxX~N zqmoM7{q@$PE;y?SW;hhC32qH6cN>SjO9{|d`I z`WwI%S1YlB>rucnp>aYrD8MY&%|rv)XML3)8{NN;mvNP&AX+YaPd9MyIG!9j&9Ucr|lvk(XgO>SHP7e`#^lwPo`I(V70V z%>F*@FCTkv5;d#TT22~OhTX*TL9vvPVMCdMc8WgvT8o)7dW1(d>wEj|9Fw?~`W~}w z?XB(cuRvplBYY++b5Q-2_jK$0#oJZp+Qfuced?=y2M4EsARk9tRT}BfYz4z8QW@4s z@OZLQkyZDdCXqvtC5VkF?@P`5#H%Y?f%<^y(7&xQcUQAOCJM#(wY&6Rkx5BW7|?0I zcg;;XWs#loh4ZbFA8Y0!6hRkt2|q~C58J?<5kI!4&UIhr$Oo6dDUqEhI>4a>80~P} zE}J@y^=P>Ew}Nr2p2q^ew&fJ@MpI`)w=n;FgN~`p@3+&~dc>M%S@x5a_=jBve$3Qa zcs*6|1w$R5LaAGeJUdbSONh?*N0rU3h|M!oaS^x?)QF?KWj;zvZj%wHl_h#SW+;Ep zt$><*MEP}ZcG^{_vRtE`X(L+toenSf2mxDoJIG~yttnG%oe-E74Te)Iov!fHDdw_F`DCQz#-jY;h3Q7{ZdDyBhSswj& z>VmX~YbzcbZ|+%|h!{~Qr@Fq`AeT4wJ2bheKk%ZVV|M|63B@2PvadwZMuM!B);CE$ zyIq8IJv)GsJPf#I-^o45W2FjFm-&i+v5>zUE?5%JGv|G{A$j=9nb(5Rsf3(sBm$@L zB_qQ>gLG0ss-v|wY2?=8Gp*HmsuC4aJ)h<_*$GCJ2PFYfnomJ_o*tP?t#YoZsUfi+ z1^7wQp0%XYCR|2El#}<@?#84|=P(S3OQedL=<)Y{(xmw<<;5%V>mm?goeMYs%*LcO@Nm^zl(TcQ9#S@nK)u+2RwE&LO_%Zh#V^%Rq z-WfX};#!3#8@rMB{Kyz!^V^d>J)w1u#{g&AYm^UIyR;dqIb^S7Z%cAnT42xf{?0#6 zR6bYCUs;#or@fiylpxGbXs@WkXf{l{&tn#)V-qJ z)r_tCvaRsEL$VQR{pF?Xej0HjW@h-|=awm?N^ag}n49WyYZ8Ao^+^OIn2+USaSGWt zG28=RPlwz|!hXOM9F*pv=6`oFG9_R#oh(4CJLMy5jxJ%ZGBp5ToLbZE@kFLum zY-Lq#u>`)Rdis~-*IHZ2D`%Y2s^?k$r1hv)WGW|kf+Z$u6UoNh?{uCG09+%0@Dd+x z;%>UGNM^rw@v$D;-pD98SdQVjB0llQv(ED)vzCVu!!e>huobYhNq!!f(oQ?ui--K8 zO7#(DH8(U8i|N0MyfTQgMCFP{rY3tTOsYcAHH)Z+-1b^|LTq&r4>9ff#*Bz#|EAUt zb^}ZH_9F{FXcfjncC$@ytreJSchX2lc;7nyQK$+2i~bIa5hp91&*#CRVrQKcqA;P* z-!q!kP)M_~T(Hm|3;{Jw>pEm~mND&d;p(`9$z4xVaoOTOhp7~Y@*WDy$XO)S{%9}? zunO~;1IEjX0uXk9><%?a?rz8PqWbpo4qrjrn$ClQ*5}UcTOgi6!S7=aI@%7pWH}+C z*7Pp?dKzU%eq05*@~`shRLWYp6V8-w0aR{3X|Xld#@8Zt$Tafg{LTua_Ot9gZpOH| zK4&IgdKb~ZWHj>X_w5Nq@h*tG?$wRWs-V(p1?J|PlSm?kQhD2C@j!-Xkv(=r6hImZ z89?a+AN})(zd%em(TLA?h>2y_@*HYmekW5o_5kb-glXGm-vLm>stbz0jP{Bl;ecL^ zANEwhqpjENtg58tclfUMR}a6p_Pp(23X?cRwkN!A4uNYmE~l<*9*Z91?CZWZ8Hl3+ zE>PwB>&13VC$Hm@3mA+O2}6z$QRJq|^SXFiR(YAir#CFctMIGY19C zE?}^4kSZox>=Uj^*;qAl-JAYv0>G26uVR1ax> zs|`OcS+h+Ri-3)xt-aP6B}9pZ5)xs?n(|7Z#i=KQa4wCgf6BW4Z@PYn(t)3>UsQLy zFo$ykw~JEdOoaocs(SVVG55$EMB@zaPJTU>iv}HsO6-5{JP}Ew9d@xFsyg=oa3E2K z;sI3^eM0pzhg*z(v#$Wm76wkttVqqy@mAU(|Gw_>g|gO78vQBO6Cym<|Fbvjc4jri zq@1>x%B8y8xJz2pQ>{|-?^HxmtWw<)PBq}9&-qrjZbBVaBcGl>eStB~=iC20Ux`#i zZ`Ih*4{lUE_s>r==@${9q3eNn!T(z$-mw}X_GoM9(ej49Q)LW&bDoooUsGiKe{=TN zrlsfSFP&E#CfA+9m%Ax}$H(N}yGdSW*SQf>x&9s-=AF-JRa^T8jsu>P-u#0y5l^u6 z3q2Tp^;|pFeopRjL)DdHpYcDl{S_xOQ^M^F$=iXRC$>J5&XbX)x*aw{*8$e%18e@B z+6u>>+%oSUhUa{q(}P3d7o{l_@g{~)=CuIGLhhnb=MU^v&l@vWfmP2w;x8u$A>j}0##9BcHbT%uk9Tbi6YPw zz_en%C&%-&d(H1XGBu0`Cmqu=RD-=Z7%^_LeksnA^(vEBw|{$UA*|c$;@}bM!jwkaMr+a4v(bb7J4&cnqu{W3Gn6H0q?C&thWG&W%=o%-lQc z^TE5R2W!&iw@gH?2j7VRCqBT^cCx zj0Qtz`2M=)&3|R&hS%jiD>k^bl_Ebd)%|0W7yh^j05=x zyg2>Uv>2v_q5kmtxnI59W?v+i$<{w@$kRnL371Bmdo-L-r;~a9T5Y8}3QO%LSKy5$ z4z@d%Aj6R!g;!P7ti)k_N$gKiPTMur+G+eU=jwns-KvG!jI&WypklSX*d`J1Z?tb+ z@%YdAn_W`X{emdZ(G2?v#D+mxCn+f@+KW}p!$NDz(RJAj`gcgcU++gip}*kFe_5u$% zJXkCpCA7l+$Z$R#C+;Do6RFq)?Qc(Zpee=KSx4IrNQC7uZnf>jPh;NliC0qfcgV41 zISj}#T!X5Z^Tia9oLz#XKkDyS85yk5*T;b!AifynOS=J1ZDFf5dLXF{DyGx3-qW+{ zVw&%Tw)`aru2W7jgGX(`j!M;j-48MvyjD_M0oSq2h>t-LDnighv+b037{8rSX5F0J zP_9n<&U~&X=;Mv4>QRZVb!qax_Xtj$>GKbP8;8<{-c@Axy6!}3LLCy9_ELiGiM?ge z)dh88gFge6%{huQ%Z@e_bMF+?3(m3dE(=R*1vZM4HIBQ!oC~TayLB$?nlNSzI1brg+bN9$x()3e5DC3GAWul6$Ip6J-Meabl*$zBhlRHmUZ?DjaDrp!1k}5gHKeqU#1drEJ#z(tN6kSCV@@ z!^LAiraI&(l!Kjx$f%y9m0&mB`3}NG0%Fj4L)ah(CDfL~2rihZ70mW}GG!7*ChXw_ zp>Tb!X*M`IT|z?k+UBNv*a6XpO!B>Xg3^)^L_HRKFMF*?6Wls)-}^zc2+Sap;A)S( zG~J!FssMu5W6krKT(jSag1-{H%Y0dc1AsA8KZa?&^QwakoI1$bl=afh<9*>lK>*lp z>GOl=&)y}yvz~^3Z4jg16ueBh@F1Su=jNSJwpgQ={PPaWI_8Wbb083Cz!wE}11gau zzs~dO>YV0Cs1tqCZi#)j9I>EGvfYYyY&h^Qtf9XPT(jD7A?&v&Dq!gM9A3WLwZlM= z9U)cA*4rSW_yxG?f_jK6l)iaye1K~E7s7%AeL5??o`u?pfQ*#h$}(H?Xo4`+s8+{&R3cf`YS|3@>`-dc^5*tgdQ#_by zj^$b-!rHlM4k>tdKUMk`GE^%G7kB6ZTJ>c!&*05Nl@*9Ux=f!YVqfHp>#kXfq!}*z z(^kKpfJE(AtXPrKXiIoTzoQg>E|K080Wq>26d)^ zbjR{B5?H0obaF@PbPHT0D&T9|LJ2?$=S=^f`uB&6`Uz#DQws(HlP(XgyE#Ani$||^ z2x-Lb4(ld|JpSFx509TdRZh5#Pq|>&eh@#XBH9N^Cj&sP<`LCGTYIW`O;~6M83Nln z)kDxCL!9al??DCzTjWyPLvq|Zelap88e8|Fof21MnExMHZy6Lrq(TD7XWs(-#U-!aE{MvH@% zo-0V!(*cVDcopY%QW696Z-8Rbdsx_G(Q8QsX|pcpkzp#UckbrBfaFo>_k2UszjqC_ zJ85yvm(;Emjy~r%XXkUnIULOA0jYPn6SKQFnhz06U{9J1IM=Ps&zJji#nwT7H~acd zAH!=827<)lS0B}zjPReMk9qfEg3&SL2Fzrvg=pfK zh$Dfk$|Ju=K!OZ|!`D%z<}8!98tH2hppa;G;0}v^?0lY{&jqAhCqRH~(eKc=Oum9k_=8o=4Nxrm5V>C^W$#Akakrn{Is1&12vu5=;8JE@1l!m<*N-0j0BSxPXuEwT zwcCjF@mfr|=(m4{{}0;CWcFikCQ(p7kznsW;KlJ@3)~Qfrp)Vnrc(7dNUr3Vg5gU zQ-Ncbwud^f8^(WVYn~8FmpQTD4`ML`3j?A#fOGJBkbaynxbacUXsnM3S(RMxjbhXLU%Mo`l$B8B&8L75VRe_XXG`T=bYA)d8;`pkQ~DfdR?tU;Vg*lA=6R8=h_{_@nffE8wS(?N1u4 z?-TnAmC);X&L9>`bHRSidsH3Y-WwWnxz}{xsackEd-#5>I5OObEc0;(Y`jggZblAL zhBSdU3*X1zLlA!Nn^i`RVIQR+l+|HZV{!9}sgmXS^@>o~nzi8FU-1qAddd+#wmlhI zb>`WK8Wi5Qedq(fqC|Th6m>3!ZliA8weD}-3{r#u4DT@O9qaStSiqaE*?SPe|LD1l z$T&#Gemzr~wIY|9;=|6m!4wUX9sIF*gBG_sZ)3>gj_%5`g7OIHM zAUeFCQL18&oFDMrMuqkBV@p(XvxumJz9Qup)EB%1Lf%VaVo81lFCfgob7pncFjt`Z zFL2$a_oef@ex+8%S1KR*uA6|--TOm0FUbA#*OJ)?RqqD2nbeTAaYY1As^FGo@*lL5 zMEKU;^e(T>UgbCHE-Q}rZ48-Q$?0JT*(Drl!XL{v`f$f5MoW?vBAGRsdr!Z@3d=d;9EfQR?==fcmtd(6JN3BN&6CF0!DjmznMi1DMmy8qT+_&u}y~FokSc$tN8L9HYqVH z8dpMFbD3;FXRNV+QJt}9$65nd7Tv5gYS%6LC8XQiKt0>|FC%~m!Sow5EpI7$VR=)S zqa(vrlEwSol4EbGKHMktm)&KZkUSV7`pw1Hbxd@z>ii?FRlaS2_Z; zj5Yvm*>~oz@zHL|;jF21q*%|Vq?x?mFyWcMkit!kFh5l_j0U02*)`rwhSZ+~k?N)HK^K_2*$j#=h!7 zEnbesHe8**TLr4w+dL&aZ3QQ$v&GC-ZO4s-P1fK7_V$vU#7lS2ZXDV7A$%-#!L6%d zHgTIs<>Di1t<+z#a>@eB>g@Vi@kKrEroXE+%|D!LA!L(R^egw1EjLM>Zz|4Hz1g83Zho zTqeY*Uo9zT{0P?SjC`swDg_Oj21~97gY|Uvnru{sC%M)-q<>JiP*FJ9hTSILZ?$fz zQt?l*Mxt@~f_Vxebrv!84_Dsuu)kquXjdj;C$+CM5NG+c?8^!d@T;r+EG)wUmN3x# zEa&?4;`*u6rE^t;hAPg_lrYC=^fetmJM#A!Ut26MBg-j@j1yVvaK}-8rLgtalSFVE z#~ZaNbg$eRto|#3sQ;2-epcg>{p8&Fnej+ZLkAd1E5xHjit!Ta;JC8@Q9u==W>0KEf@$8?n#2<(C_Bz4lX3t4vQi1cpX0=CCEZE; zF>1M`nNBsqzgLg_Yp&cV%cVp>e2CvuCrqukgAUz%ZT=f^tOvif`hqFPo}B3=L>&`N zBLJA$|79_!=2<#eI!QaBg5I$}fIhHq^UwXW%3!{#eM9H!E&F?KWeA<9OnBi2YnaUZ z!H41Y6g%}7;5)fP{g+TlVRf>FbhyLHiTnGkAn^yDpd+jRg8jh?wAXy&DSLu#l8wt- zcz(iZfqg9?7o@Smwdi-SXg1M%%blCs*Gsx)csjU#O^4!NUng<-5k{HP<>mBW;PJ6E z&Of4LASBf}_y#~+Wp=tQ81eM?dKM_K-hA?2%{s)C@}t2YKmkII{rp4nG8Qb!&Z|B6 z&xo8!lb1uSsfAP@2In|FmRUN6D)$SGd-Jp8Z4&PbO3c7vj38punhTH6V_Mawu<)D0 z$-D6O(va0wgm9jGIv7C=C+FoxrKL z{^_TP3}$e0!aSN!rOl%5Ha}1IjhhcOso6}paLm|qy;9H-x69+>C-T7s*SiRgw^Hqj)64>YgzDZ5Qv7Q#JK&J=$2Tvmxrix@9Rk$l^Pi?@`h<;6dUZl%SYIt9 z*EcnMvJ$u5cn>PNYsN|*zD)a0H^B+eh!+)pO|DnZ)-`Ssg@N7I!+TV8S zwwZIi9x7C=|KC#YG318(J#AIjasJ*8By>sn=I0DOh5AB#*1e%yUnyVMUhf`RMT>zD z!PBPg=vDi{<(x-!n1BBQFuT6cQv}dp3M2bKvFv+${NHf@6|#EloinfhFP!P_ zyLSF2m{#aLyUZ@cJ-2^=G;z1ujA;K>4DQN!J^T4^wK(~n$9lon^WLp$H^p45hvivw z^4wgLr4vo(=SXePZ?m|NV)X*NzOrWfgWWE-DgN%fRn;pN2P}48>ObVz=NLYmUWj3J zU1z`GeLG)PT1NZwZXH1Sk2&W2`C{>6d(^J)-~wi$N2^=xLgC?(E@~!rPOM*Nt#z)U2Wq*|8RD_X4@v!vw9VhT&AVi{^Jc{;$wbg~y0uC0wNrn*99c0$NCmL|6o zjv<1kaHDoA8o|?Jl_GTHOXNbDSy`(ZsU*%nSm^hv-aH4f8J+Ii8DtoPmvDaU>YsUO z43#*po=}|Rw`$Vya^SmHdERo!Eg9i*1~;8u01^{K#|$B&3JGmO+oW~g^F7LPGc#i4 z>|YCPsxujbEa>i&AtiPZ51Az_u-H=hcg!gS>uKxuu0*a;YBkdt*;540PkHDAsc*bh z)jNvwt*gfAEBph`Y5H>rboGpZFXTr$WKt9m#=nX(aiL>&L!zLjHi;H%gKgcXQtY!K zDeOHHEiBJPd5pC0t|@nPE=v~hKt0wUlJ@h>xWq=TEx~)dS`_$lQAT{r*V;j|iZS}5 znuj*ofQmns+)g+Oa8;8Y-!*^+8Y=2+t>EA(lbFR;3oj;D-7AW6(}IB=lVHg$dt#}i z5$&j976H8wYAwR!nnb>NT<*@SwTzK?+TZt;T07TU=IbwI;YJI56;+Y_^tMW-qN-Ht z2gA?cJ)LPV4f6cHj-#?!KLxGJ(EakIs-2N)0Y;qUA3X9lPcP-!6j61O5-^%Ug_6Ep zLk`Dnn!vStmS_Ixh28)g0m3`FyAwQ2bWvN^Qf<0;12orivQM#D<(Z{!Yo`Ifa!|gw z1vGboP|GFe8X-;B>f7TkCUr&#td(Efp{`8H|?3x|1aN=ekcI+@{Fr z^2W|51>!H9gr_v(#}ye5la=mqPU_6xS7Nwi*bz3L@mFam(<-NOsP}8-ILPFBH(#^1 z?Cd`j`*;91FR(h_AZ#4`XCorVLOE>=mpXsdi9*9=28%WMEKNr ze+BlVMsinmZlckc8nLDg-RKfb!Q^!7E}IlVOU0#_Y>APOxq8a;@54U^CK+maag!5A z%%2k*6E=2I7=&G?a&xTJ96)R2ByE>EpJdH7#;@O-xzg&*)roy<3akikwU{uRY~<#t ztTfUf)M+xI)_I!m0As0t&5BTEKK--Z``O>Srq7t$LkVtLp=EkRTIodhTfnxswtX0o znp~nbF0h%uSsfbgf0YEv{9Bd!ij(Vw5ph*SbzhD~ECOYsOM3W8!78XZtJuI$g2Kc^ zW--_PY}l~+1=)W2>{V5;Q$dthq0#^jW{Hl9P#XE)!j73=KdD`!P#L?q z7v)7|nW!!ztdTnza4}ChFq^D8-uh1_6+~NI$`nNO#h(XzDSeO)$CLXdgkqn>0I}fO ztAV7Po$x}WC#&U0T#T01O?BtBTH=Rz;j|X=Z35I&sWa@amekb`j;JwigKpa?9)Lmh zdkl;XD6^AtiS1pqFXZ@;{9a|TYrDiUHTUcyA72bmpGEg zr`}urM4!JD+ocfL{h?SR-CxGI$J+0{HM{1RV~(-+Mw)JjD;MXNwwr`dIRA32xf$EM2fKO>0iBWP7I*J5;O zeol~-{_P`Q&WoF%*G8s zNWXCGaA``*tNufDBh9t5_J?a{kTr#SJSCt15^=YEceD&Aixj2}*%Vjzgq^g@!P70{+I-=EL z8i0FTS?cD<;W5b~?Mh{n!;P+gYK9=n?%g+36pekU25_gUm?=*f&AqaXr_0fH#tvHl zV>a;Yl7{!ZGnzf<#l$!7vZ~BYUAC1Si}nYEc`rj58jT1Jz_V_lp`k_G75`k)h42vx z-vjw357zj>64qe|IwNw|1#YLkZ|x>&mEZ($U)nUrwiS z8Rp!^zR0;<4D!b7^V)!s7=eB)bw<2KqAmBn1~8Q@nu+^mChMh08|s& zERH|XU6YmD^+*oy2e@%B(*3<`z9vD_>^mI4SAn7YXIV-vsdKyq4yhGmz#ARZxEj6Mz*HEuh_N2B-4gK9Y{E@N zU3!Q6UL%<@RTkrkvwqPeaIgw_qkDP0I<|TVR;a(8u`OvTmahv&I{X9YQb>rg*<>Vf=lXz@~*3{Vn0?(&I{oI+33YDE8+z3g^i%u zQPXn|I87Vz-|V|2e&3G5Jyon{&5& z06$NXM}S1ua&`6V<+p)sZsUs@$b1r`n2e6m)2@s295L+;0g#SB^Rdx6OL{{<&X`Xj z%XstIcd?b(R3HVRtq(Z8U57P32fKhg*P>yO9KGP=s98wFOEy?9SLf#Z$U>&^C+G^2fB28s4++(^6{gZ(-yQ-(x>^E;P`(2Sn?3M zZ*UnpN<;0jjE0#Tm)lw1`D@rc2I`hvL9wPHO%;Tv`rp^mRS;lu%rBmxe5qRnr+>!d~c34>d zT=|oei%$Y*jy3)44zxULcjx5zChofCK(Ft;>s)Vjy+p$zfb8}3Z1r}1pwapN{=ev} z!WN_Nu= zIhMN0mJ6)K+1j?V;`6K*#TDCqTZ)L!-^v4xrAfK%iahegk{r|aSUsGG#`v>hCteq- z4vryhvD`ZaHB=nv4EtX9oE{_HnMJj_E2*NW?*5}+UDxhIt3V@uZPN~{LYbkVPab7A zy;bcILeP1%g9u3TKT2=A2HdGBBUW>^x#*P#kZ�z^do+>GI@BrPfY$E4mR*qqOJ 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

    <=~VR&NDYzW1LNoRGy*bwz|! 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

    )G<$Oo@i$ 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 z z?U}0=4gvNUF1oS7QQdBX>btN5r=r3&cL2l4PT$mjVRXj{fAG`X#7DQ^!04QdHt-IY zRI?bh!%M<;7l5c3U;M=<*)SRZS9pg5NP0FM(UMCVkMh=_rP_)j4O(p6@?8(fu{Ot5 zp^n!doTr$Cc&c}@jzC(}q8a_a+q{a)qcm_0?@yJFVU?qtx}6Teb_8yD{6%9y7{~zF zSwth59J`&BR$v$mfx4#kI)gjxWjY?@um-O@%_qA7i5j{Kv}DGkb*}w$anqbpqxl*4 zx6I2NQNa<1jd)yLN^>Dx3%}iz3FqV`)XQ6=KG3pk-tpkP1;-5`fEFWyY+`oBPBwGR zy4G6HuD`qRp4cyNyq5^imS^3w@XEt=%nB_27JcK%yZj0aXTb|o&C)UX(-lJ68-Ci1 znsm)K9Vf10@f3Ad)U#2yMe>Q>gWBR`hZsz)9rIJT-y{^4b%14o(r2ViKuXwzvi~6` z^-8e{c&seWuhS=n?V)@f*Ft$C9Xw@MsQHo)3Djt$6^2SaYuGOQv%7VZ6r(M(6BtlX zL8RuAbvpg~|j4GD;CUMM0n8BFxE3doXdMoijW_F85VRpCO@oJ>B zJH`99;b)R%vP{=*ak~Q_!YiLw(&u2y`g{g!7I&6}>R%xPN-2->t8bRnGLOkkLn6`X z%n>cJWs3NlzgH|hYP?shYnW=?kt%Mgf@sPv6vOuP(+u|_?r16~i?gKso2}h)ii>xQ zis#GuSFOJ&Ca`H2rjEu`itc#D~$^#jo>i4WOYx#xr>8r^ZwAI+iTAU@snUaK{!IvAgo8mXWp~S@~W1 zSC>XP+Z?Tz^KM4<+@|FzdeZjuB^j2*hoXN~fa~NWZE1^K2rs<_;^^>nHE*IKD~mg1 z;v|R*3ytKivQPy%+Wl}+met*(-xqsY`6WC6$3BBZ%ou0%3}>Nx#QaHEHtrkpOJTMO z)%)&G;+B&{_^wX2H%hpV@YiYnmSy#*8m1f-<9K|s2O2I+1jRB}k^7(zOvn*oOI66qYtp@&9E zrG_4Qq!0JA&U?RSJ)iel``g~@zpnUQy#CjGf&!bIZ0olPOhBin8pjQVA2@y zCA8R&m`yw&#yTRD5Kb9oBPDi8x7&o;(1NVzDzW*SDZ^j(!h}3hxGd>>TQ4b2#^Qhd10f|Bh zHE`>E#fVq>g6TCPZ2O<%sw-~KG7aXlsQ$F&!kWWjHX4UX&Dyp|MTM#s)lSr=^jkkt=5vG>8gt{5mo z=I2>Svj1+P0FN<(v&YlR>nNL1ob87WE2;OmN^hn@umC?Ua)X2aTJ8qYU0Zf+ojIzZ zB#%0!%aHlkxclD3rtO~@x{>Ct%u$q|++7~>$J3$#_EZ_BHlxL2PD4!NT6ROOER02y zafLIV>Z-A64FA%$uY=GOF>Glg{RDP}X&$~kkl#AmGo{#;LKba!l4fFkgcH)EzZ)-C zN$H~6{Rw{R1$yM8(7d7WdyOHsBnz8$Bf>IH_0|s>^TRVG0dYmYl$Z3oGw-dFjC?I3 zf580Tw;BK*Eqo#wA)Ui*rhG`5z+sXjwwH}Pu(36|HRo5^9h8C^fqUw49~hYzuhcZ{sfGnB>0nOHXFtpqu`Z~Pi_9UY_YzY*RXAHgfnQ&AlLmQUgnj7`I5W-f;) zuaPmH{E58aAyvcrzllw&@Rm*d<*C*lH=GwCoyLGu=$g`^nV-#|NwXyR(T{W90;u`l zg&5+sD^=pY@)oSLiOJ@@B%`hv!}aE&>xb;I>DALTbOSp>Zf{PV9r581IAjxV_p=y_`S$ePdFz2Waa&81ZkX5ne}bf0as zD=4r9?;LoJY%i_^W7q%33@kp{SzZ*+r|Uszo~NZO(JBigx9}4IO38F`Cm(e8I!A+x zLOU!7aSQ&Daz={tjIC^b(q4C5m#F9?f-|k#rN6V}dY{Xscqk(dB83EqPk@U)&MLUm zq{-^}fy)AwsT73QDg8qa!-)J3GgD3-1k2DJ2O+i4TdfAez%=p~{>&L;} zeRy0wtqXcySc-HN!g`8MBLLKIufnF0leh9Xoc07gS7yYcF;xihE9wetIp2}IrsZPE zYo5%G7pkvXg`^7f2tN=ZiW5{*Q#PiW&>1&;_vFTPY+jLO41`~rBkiQ4Zn8utNa2e{ z+uIRWr`7!9}4_H{&PwT3m=yolI!deXFu#v^AX_(z6IjNYEF%`p7d{3%FUtje zRv&%JcC|~qH#Q}p!zC3O!mpT>g@1JjJup?Ra(3ryG67=t!Ia5~CbAK+h|103*zJPH ztQ_@5T}l+~j3?UlVZvhj+}{j#f5qR?{M!vyzXWWUaeM&0tR2+R8f)Uu7 zy7e6=jQ$%viDkW98hj~|ayNIlG^QZRz6my8Y^r=(RAS2ItvMRHhG50j{)zhmPMsCZYO16`~I!%8*Duv-nJztbZJkJ-q13C zdp|1Rti$|fNNMGZqy(~wiN<}G>o?zU+G*^s8l}iL*#U(?R_4MJ=M68yB01kl1ch-x z`GUOhic5;h3cZ@{hEsmFy_HRrv1G#GG@N^WcUNJr}3fT_DH@w;0F4X>17;MjK?=H68%2DL+Bxy(7BQ)49w2y%9*vBnIfks z{*AYNxnE3o`dUq!zWNxwLH@?RMoY3tW}>{@k6U2OBk!+vGH3Wt8+{b^W3z%z=FgJ9 zxijUq&Z_9MQ3JV_)h#poikVA7)Y%vHT3JC6zu@%KCjk;A2Dz3#5WA(f>tP(y-ew7T zcd;9m$>Oi0%u}VAARbNah+Vg3D#l59cY@NasJKH#1qCLv$X$UlUyZ4QY>!Mgs(xc4 znFzLv_vGaog9b{bSa&izCmGGBZpujUTRz2XDr;Cc67s>Dbatmw_ldy5h8~j zmO3nIIQC6yg>9#n<8ay8k5$nY7+;r^EsV2_98xMJqM~+9^-EPXxT|ceI|X4w6Sp;9 zrRJXrCMU|xEvx-nSpTJnTh1G(-#E@!`6jLRBMWhvhA`7OQa4gMe4B%O0|QlArIk)Ne1JOl(pI zY5f~IOC+!(kage8ZsssTFfvngF=X2WcI;T~m%66ZmpjRzqxZ?hNpEvYYv*fRJHmYa zR^_|=T}?X#=A1cx>3ko!*IbS>N1M4*9K+8tqn+NhSPXaGc4Zy96c$J=6f*SVx8 zOhZIsoz7!?nq@=H`P|j$;pn~irV`YvXZcfS?OvQ~KqzJW!^_{nLxwzbrMCU6yEhZa zx;8jc@4xQg)kmQx<;#Ok5{w@-xq16wvHh#zJb`O(i-1ne=1l(6tJQQX;9)cWQR;t8J?sB$k|@e8egR^b23j&(>{`ihPvlkh z3hS*So-;n{T4&i8AS4A?Cba;{g+X^;A=7$-FDs1r70;$xSg^EIKy?2?yIZQQ7Zk8` zRqX<$UMVDHo&Vm5m!HC6T<7(xVA)@itI+0Smee?VjT`~Bi|t+8Y*$?+@cQZe6__aq zUJJ#NBAu<8na~)jGC5}Pl1W8ZokORF2TO9M0y+2Nk{n13ShNvOy)RfqZU#>|3v(jB zln4pcl%ZSj=bs8SkFbq;k8^8A|NBH+;@#gV7}tx+tcFR29-KgTt`#iG`d>ak;!VTg+vG?j|CX+2W+SSdB zF}xN9>Z)=m9_~(1@P6`Lm-|lwh@#%(A%1p`2_mWv7!#rik@WO>Wu~*#rjN$Bot}%r z@iJeuGKehtEC|#H*o>hn95z`sEEA;9zndxCZC_1fmb<^vy|Wp7^_XzV9kyXP&oU?Z z86Gr%(oEe8#i~79GxB<=^2#cxPFrFZL`otUc)s=GKWk33gQTpSY@}STS#fYHtP4^$ z*8hH^R?t{htRN2^yvRnb3YzfRwN&c{KJE6IiZumbzLHSThWU86 zWb3_L-W~zDxWc5fC!e0I%<<1lzKPy0!K)k+?m!|!LUZ>K7SAg&Xld;m*u@;1bcUge zv;Av7dkFHSH0%@n2r46cJtkfp>vt1-q(LjkZd<4z_Xy)`x6VN4`JknTY49P|Z?cil z&D0Oj=P^!6oe!?Z2!*XW##26ihe>A)Ax-744Me8!jLbF4?{Azsjv}cl?In(zh-uEw z`Z-|of7&rf9{bxz5bu_X2x2kQR@H+(-b6MLGc=yM1&v98bpGT_y)!*|DfCL;{L?JW zhkIi}{5fiIqzF&we(ydjLuFwt2dV$Ke)x-(`mlhnr}Urt2xd5|=oLp)cUZ%bbXATygSacKm9EKc|?pii|p50mo_c`q7Vub__1U~qZB13j^ zHn8A&mmw1foWT>fOAz`#Cl&{<$sI72fq5J4c&V??9;lPPbU4l>FX?|l-As8zIZ$c5 zBSIW7w0i9I&u7ljB(GDO zL33~QFivvYosEUMtDi?z>CNSGJ_v85fAiPc{ph`x7|6pg?SB4Zx_Hp){lZ_joX61> z3TyQy79P4d*NSS>TD2fENYp8{igJNNJT7AxUAs-?a&x&#tb^;vN7_L@1_ zRcR&-mvx44R=9eICju&0*Seg0n$6nES$DEE*g9RPj+vmzm z%B-Bh4yzfsK-}|yPW*^ax^p)Z5srB=b#^B0v+4j>ook#vG#eKGNLDv{;?nQ?R#og2 z*^Dg8`Fh&=%PsZx^s&PX)pi$n_GSL~;SBGapNN~idQUw(x$w}CJ7?^&LN-tT>q&bd z1`KPH5S~97?cF7eN*gOUjNc4Cq@H(5xq28jLyJgPcd-H$LsBGZx(NDcoln%DBlV(#;VghQD>Ayn4_SGaahGmBr8Ipf6O=*L(@A&;&I^b0CKS~55!fT4f| zo7Ls$=n6^T0uTNAwcO56q4Dl5F)PiH`*4UOu1dX_Y6l%U$K+LrS5IBL2VaEdcMv%d#(6vUvCCwGbaotTkhPqeCOa2XB1WO4*I?w3qJvS zN19)U2>Cwpt_lzGC-)*#JPL0s{RkJ|Dt7x_) zZ_U~ghcOO8U+JSH4O<~>`*|zWFWU+^Hp#t+58ie|bIVzlh{umdIBz0udIZb<+-H>q zRCf1nkQ)f?Saqdaks6e^uk>BZvB>7-JT=>};ynKM!fjYD$bSSOZljKS1SIb{63fe_&R<;ox~CJ+s=S49!#zx)`#c@@ zU`ODl7(x$K*STk&FBM_SDr^9mrKB#Ig^Pw^Vq(@bzIP^g`5_R6dGl8b?$HA}$gz~~ zJnGQ6}67PSA$|8|=MnV=5I@Ma!uPUdjy7xNgj zH{cbt#zn?s!ao0|?xxOzJv&6O0UKBl>Rd`lGmAso`B-xextqRVG zJmMZOo~1=`3fEn(Eb2D#PZu6e*397*Jby`pvG49&7xi)PcH{Cd@a<(M^pa^jtBE}{ z3Xvi{2MokpOd;Ap?55_df5fd~sU>-jH&v1YXnlO&tmy`nAdwAUxHV4#S58)T!vnyk1BEjgq-CrS#0((#h?dQ zG7u%H>OBlWCH4xEN=#c7lEX_}&LrKZ9o8Y+G&cI0FL$!MQ+#yI{1u_XI&NKac7{7K z9o4)-ihAfP2+Rnb$>=wy5ctYnX7^UV)Vfi@NCaeK!IF1ZT9P9-3Wbgjwwl<4~2 zzbT7njAIq;!dmFdcX4k{1Ss*D#1Tf&GS{aaJI}q+%$cFnQ{AAdw=dCe>Uo0_u2#IL ziku?5#PH|)js#-WvqSOaDWdUKgqMB9VOZKvLw=+;A@({kPwr%rL7W@i=xKEA=cL=B zs&=O17lY9Hp^&u_matD!tn$Op$%em!oFVez09Ts+k6GZWZ>QWY+}>yCHl+Kva%SeP7BMjJ{O_v?g!lTunU%zT8$-JI;Z@1lgOw6i zB+1y-^)`ogLYeFQK+upLs=RlBLQr6A(=1~e9t@)BK8;T-F0%3PaTjx!9cqCT*JB+$ zzeWFL`SXtRSv(<}G!PRKF+hdvfc07*_2YQ(mbn>H*Z~aRI=I;Jtn%$?m>4*uPdZ;_ zj=i%O1=eQ4KTl4q`ZU(@hvhqFtqDm8FiaJxNeH z9^<;XGT&cW>wSxk_P=W=9sV-II^EOB>O2y+2Wv_Zr`{3%c4-z8PC3G=U#K2^2zYhN zWSo!P97i1Erm^74r0M5tet8!&>J{!p#UI>^zDAA3|J5M+M=YP<^{7B=o?c&|;{bdL zmt*dnw~nizSWwafj{)tZyC4arf9Kr9GEV6AxEr!2BlY__Ynk^0BUUDNW~HTKg8~@_ ze|p^dv6TKPfFY>r-yV1dIG^iqsO`~9w1)xb|L*uq?U^2kD0U~q`>D8~dTyv#__kJH z{@=W-MctLwl=q@(pn;0 zm24g%Cbd*mcdW`w@AR|o*b{~WpIVZkHoE7AEHyxi{UxwkDwl4AMLlTi$wbVq#i6k& z<4`i~wN&g*a;yXa4SwV%a;jF+EB~iYFI0&6b$|hgW>6w0Yc5O4ZBZS&&j=?r;86@R z`y3@CLU5BUwrK4*gm#YtS;+c9W5kbBn3MvQu~X`$#@b@5(VjG~*1`i%e$%kZ-@zaI4`Z_ zOvwf2LfS8V&QZh))`(em?l}#`Z&M!jQ}W`oEz+q%T}c}4@z1a$<4+dxBsFFgpouqE% zbZHF{Qleyk) zVD|Qz4A^X6xzfVQRqFY58P}zzUXpfVxBX8QD5O55f~+w9^dUx@)-NblC`EfdHU5VW zOB2^rUR&I0E`~+aXUVFOFP%p{RFao5w==H6uD9v%fW6y`n?-8X3ESfD$kF@f$*XUl z&{jxdoTFFQ@1?SxVtRDC%M;$wf4NtJVmTNmCzPmgrr-MxW@$@Gof@nE+a;=tsX{G$ zGVpZL^+4;!(k+S|HUplcI;CqjYW@D(RQtcu5Gu2KSFvt%Lj-!q;74Spxht9Per!HJ`D3<=d)SO9w5&;gnGewG?S|-c-2ClD ze*rbrmh06%LAo7hTiXzbPs!Fy&R6w)W5w5uX5!|w(ySiMFXNqc-xDcR z%nI-lC<(EW#fgw}E$iH77bH|nRpH1C0Fv@8F-*>HWpfzP{%u<^TBTz9^tIc?%`Br_ z_kGA4l>J#G!oN&yIVB*>xcKw9&5fc)L(oCnFHXa^OJ*5(t+SL+^UMY+SSzRIM-`f#_Uo%336~q`z9{AV zW1rhrqe3fXnrh#No9x$w*Ht{qkKc@YPR66yB-wS?JxDXjI1u6VU%ROdbGU#wv05NdtEJHlsufL!Q z@L%Jab2Q@Mz~LJhAE4G|?yGtC*Nrna{w64)p#k{LK{JZ8QNaeNYVwqTV5R){(XfgP z=*~h!$P;V#6Hc{_@mWbWKK@sQwuExdtW`{0VcC)5%G0k1<79%5XE?W5VW#n%Dt{hN zlo=h9Q})K3Urj^=nDf_Le3CCQGm5tR&o#VkGcmY4>8EX3`Q8TfA>u0FqBuNhx=hCY zO@Vz)v<9WHdu2iepR}Fnvr=`p8IRD=rG)_JxvV05`CDZ+VD#;fMsCnWD{5=UPwPw0 za`vVgh{lJwQNmf@;xrlvk`+`{Xc=FmfB(*hiox$9p%t1pB;w*(*JwRLq?*E`;lvt_ zO*ri(@-*+f`UUrigS%ehTcFYj@V68{o^UN>18k3MO;{)?C>(JZLb%_>3c}@IZDFVG zWaH_+(f%bE^@P>b-C0?=@5)$w$me}lDpl{^X@8Fo$atCM2eHCkUO;^pLgVhkZ zymCFTP{SlO6qmowt!@tkEb2Ry7)vEjm{s;pkXC!E_PXTDDDZE)>d zR)Mve?WSt$SA&pZq}&o60n|@zo-X$I@o2!?tZs5FP@(Q()-H4`r&-@NX-M2vODm$){s@c3T&9vOjF{tEaAyR2U!Wma@rWjEz3!rbmtQ1gby=TQ+v@JqCcb zmDyWlUE)tW37i}24Y)_eo>3R#UB|koc&q4-A#^;t!tM!&-D%dYhldUW&zUN&qeek| zY!|{cs`hq%QvHU66w5M{Yj_7_ooNwzTpYmKZ*Prc#((ftA=duO}?_m+5 zt&6;Q>oXJS{(AfE?%SYZb-vk)@UG=Y9~Y0$+UFZfip_3(kCT)pwK=CoS9tHB1AzDg z{ZKcf2`!=KHzuj?(^RhW}f;3^8Q}sgWzEbMn z+XEu4+7#6i{N_oX}(4fQLd<}j9Haq6Hm>gFro>HIx@`npO(oh89=YS&oqHzh4Bi7h59VWX; zkOGkno*x-@bJJU(})JJ+waX1Eb-5m#^Iry|~Rx|MQ9iPMgHHDSHR>5KfZ zP@wv??`yNblI=8Q0lT2OG%0t$Q!G3F6qVBl&31IZv12=a2RrRBds&%ln}&0?e4r_T zk~f__jx+dYH3_#x=Uy&<+m_ z6^aUs7;~ip-e#K!@r?yVfXJa+A$0bLY#@QrZGz%6C5bb>ujHUrh{1#Hz+Gic@oQs^&gsbeFMGu#lI0Hhca}f`a>W1k;;_Ee8u#R0 z1^lA*oS$DD_6de==`Nj5CxT)6_XkhHQU~bL!4$N(dI4(;R)ao_XyvWv-*zFUE6X9O z$%}<*Jl%1VQV7)li2Gx_(10hI@hRv8Ubj@PzrByRh(kz4^_=`=vOgeI69!EN+E`xC zWOC9-yh(_k-sO>)ie06ZaKAF6UQfJu$s_XFPWd%WZ=xXbK2;l!$tpeF~Tc|FYL6@#w z@}pctv??o~9z5tB?k++H+>YBWTMW*#z)L&JwC^Tqq64`d0!w$vk_r4afMi^*3w0GI zH&k^mKymEdw#{1CE4TfhqXvw{H&Q%%CD9Jo^qSU}shp(YuokrUBLOI5$7 zmk+^s)PIft#~iacu8eJ1>7Q}U>AiXH_PPhjc_E!5@pA9W~|iEuAS`Sq7W zYmx}NL@sb`(NpaEB2)WQL96JG@1W^lE{MlG>QuF7d#D88P!ifl8oQ7xybVb_kWJ4= zv`B4=%&8I3E^hoDr5zd;0gyz*1#ubdK;Y7ApCqp3vI2muvG0rggLYLtPC zdNiCZ05-;o8dbyTmL_nqCuLXV?|w0#ZXW0HKNh{SrW{ggff$q1aGA>?)A=yp6{5sn zX|5t;IL=DVfrib>uj~xGiR3jl26CF1GCC9f8i`B8M?Iglw=C1e__TRObU^ekN5EU3 zHsjT9YbjYuAHBzZArK-khnF#Y6`PlgN7`sAexz2ITv{8UB8}HY!O+MnTt#O0cQL;r z0&?RDO~pP4?nSNg6XBq7J^Ya*xcs!o&)HJvJCBNCs^}j0>qbCeUF*#eY)ksXJ%LQi z>+~eS>DA8y=!U|EFr%}+TVc9jAbY)1BRMwF;d`%;w7B$=JOGOItp=-LoR z22Y9jfY`^(USzAvU6GsH3L6n7dFfFrz60$q;-G>S1V1;m9)f$@}HFCq(#UNh0kG%s#}$d*m(%BZ7rR zwio2Y5RxjkB?ewKL+W#Y7v~xHmLA~wU$gBT2e8A|diUeDxFY(3(~^D!q6u;v7g19N z1KT0c09pg)J8d1l!l$~L!iic%AARz5qO3}n+O}8NZpW-w-%)~pv^81oujr3|9kiFB_AS32(OSq%=M; zmA%V*s(JX+`Hw$fP2H|PN_8M0^B2qO4+)dk6DFR=$OmPrA+{Tfu;^V8>BkpGK7U_a zq|Qpob-G9l4}Kg)v&O1mC=Ss9Q;0lIX>uGDG^{Gc*AwgzU=_rg+X=Ihn7Y9uzSz?W z>T7@e`SV%e)&RkbES6+y%c}Q>n|))56H=j8L#=R&KzuM&`uqe)#=B_OE_W0s zBD@^R^N4|Mhu=253=A8u9Cwp^2uv_o>c^1C&X;&^^1OL-g|%^7s`FIysmIuH;2q=j zcC_f%opZdiP0BM z5$Lry&7iZqv=WU^*XK1Jt8b=u2>vtT?Qp3XmSF`9wN+2YbjgHF<6UYd@plQ_dXhf) zqU7XJbqr7-j3o_kE`mIYPgu3aCoZq1V?3LBi(@}CiDzl@bUHNeT0FiFFq&uQE=9xr{zXQ*4ujAm+#=-t~B_6A2$l;V-0X8+{w4 z+-zliiKC%_G|*2{AWavE!`g-vy!sfYMk;%{26hJ33>m~OGp5VUzN;z91Tq$AVyaG0 zvN|f37G!{Ay$;(!CiiE|Bri46r*1Jg=`7Yetl^6=1dubFCaWxPTMpw3ERx=nbn0fQ zv}-ruO%!&G2y^g#`Yk&FgCNkqvMk}_x(cyO>|Ql}&937fKUp8p;CIF@r+3b<-u@}4 zDb6Uh7w)>H&t~4-aT}-?bkw3dinrK$P>=5Q<}w9}?k2CN>c(B8meL;61AJRKTbQIo z8(=wP!eHgLPQkO18Xf({W1YW!$5j)&l3bb0t`n z^=4v1-E#@GF3{+MPg-xC@W=f4GYOaQ;GMp8VIAQ@F0aq;)Q0$c@RD$4Q(#)F9#A0#l2q>2HvPyme`DCT;4nHrLbI_Wp;=6oI>+Umg_o` zCLbqS_rf<5i@NEqmCtT`*AiWGOAy_6&pXb*%g5C<7tLCC@K)e#nF^va|9(qdd>^mA z{HSHQ&UMM*7S-U~VxiuTTeVrE1s|V@(z+o!>VEvTdqJ1W{i)!AV(dJ34HYB6MI)hk zeS!UWA^G21YRRF~^$-=@(|g@@wD8S-y00*{_; z58h<|RN&UdIf=e=+)!zTrn9)Uk;%L0DSG$*tw4lSAv%I2oV7_5>h|?XZgU^Vu~gfg zWJ6??aizmn7F_en7*$HLT(U|xe2?w6wDyH&G5bD*ue#ru1aVFRmbw%CNS6$#Nu8n8M zYB{AVw6&id4xP_E=h67dz^MmP-4qZ*{Q9>NK)a82#@jG$Q1fpEz~$RUtP}Gqgr6+^ z{?MOoUADJz<<3j8Ns%G8ZHu~k2Ix`5>_(<pRIGoXDBAtQRR zPs`4vv+QQJ@EV5*f-9#}sGmSCA?x^(-|+R7*^9Q!$yiFJ2z2W6as7C-rKAP>nAG=b zzS(Y4de4_+LSFu~+0M+pZpKNkkN2JooOq@WE~5TyL%4%ea}#T=-ZF|(o{-kIrT11I z`-3F3#W?p`t(Sbzk&qC}S`n3Py}xu{x*XAZ>b@;633%RzSGFcvII*KMF}%bqt05ZD zvK>@jaqs}EQ!-9{24lW>HTdEsp*Hwc!qLYbJIb)d_SB-OidOO8&oc8katvx%>Efnr zbDflPzG04EPyf+%XqC7KAzoLq6x|NysLzrhJGw^(({*25!`bSs(ir?;1D@%>j@504 z`w7qFi_B5MJVzam9(!I$4PZb&=1p(5bP;d9v@HEL?DOQIftHq{|KH+DZ!Bo9^76qk=~F zO?N+){jkScg`_`sf%*8_I|Zm}&dT?O=py;Yzr{DIB{aWUFkfxn0ju}yG7RyhdG2~- zh08Lua_jKHw%k6Th~&jatx_#1Zm)j{{%!bSHN(BnI~DSUpfvZ&e??kZda|nQxP-lL zDd$x-mMUK^mjVO(&r0a;te-WZdIJfSo#T0r(B>{W^+=eRb1W2^y|>cY+n)pR^D6s9k!qZ=hM}%1*{TzgW1Q za{oewtwIK%iIQ=d_T){fY(rkWyUIFXF=2HSIF$Ul(jX|ZJ(b-R79CC5UlzX=UprCS z79>w$kMPLC=(2-{?nB&a&^}ex4mRg`rqc3F|#^nXFwV8S625{r1ZY z_jS7H84Y3uu=P$FY#(|}L03NSbcHU7j;49@ypY6w^|R{h9as8>Lakl^_racq<2X&b z<*e%OE-nwU!fdzT_mV#5DIWQhU)iHiI~t-N&xF(jsfFOl*N)5wa@z6Xp+%?`nUB-A z8qx7}BN8n8u~n%S#-eW=rG((&XA};fvn9?xoz=7jwX*#aJx;}36-&?bV!rAqM?BC! z`|-&lo^PSblQdnwUS2g8h{g6N$S9Xh0{BUnU`NJ35C;Q@=ws)9JS{WRBa+wpQ_6fS-R`Y^yz+$;(c zs93+=Uzt@xwCe0)RSAE@uvGUF?J8986>nl|_55A?kLoeWa+(juZ*p=4JrRfg-zsFf zH{D|}SEfSnxnI~+u2XPOGVEhs+1pU-UcA(Zfk*Jv0{4?yG9G6*T>`R{g!a z8phOA$OBjE%i<_euL${+h~=r)IhpHS$FxbShrTVfN&)oo*P=N3;(Og6JzQtv23V3@ zG8Ax_-jfDIt(_Ovi!T25D?hTIF5>&)r8RpIbMa6B!QJpH4TuNUVf8BuAbFcaz}<-> z&A}r1Fh9IGcWmLHq~T6NjiTv{>iaK}2}bdx?mdirHbA4Lq@d#)>MTg>um0SEX0ow5S_66P|Rk^&ta2cTB#0|Ozt9*@6Ejm*>%1NZ-4T=NE$K4G9-1noPI zTFdWI>-kcCXG(i{$NVD`AcTt#Spy>S{t>6L-eBaRX<$*Gw|Ltv0Ks~kkgHBG4 zO{n%qYJ%E>-hr2zNY<0gVVvt-M<2(&Ibj> z5QSO^`8`*C;8*J_#SK@zFOQ*7oQd+?7#7QQRiq#0QpTibuvZObW&Y)UjH%Mo6711ms34 zVmWYTX&rBTDx&_0HuYM_hA=!M(u}Qx&SV6%au42-N&VEO6b`7Mj!tX^HrXUquBRJut)K+k7ku6kA7!jPoWUVx|N8oXg7uPgpOX$ zx4S&BrxWBi$vA z`D*b`Q@FNKU(*lORi@~<(;`YvU-!%|sF2A^Sc6@4H#uEBq`o{|<=C>~Ss-uOv+I!1 zS8KH$bVSNYb!sl4QhUC~T+zjORVHFCV{6cuG-G+`cG|QiaC0dd62j6*4*!RJ@*WnUC`P|O;1l~di7poh`vF9x z!udwg=Qv0&nnvTaMC}A#dB=6p>C_{589*Qk!Jc#WI78NbmE^im^5D|Ko)vhBvt zA0suKt`nwf?1T$7ixURAT9C7)%@nCd*jwfEn|~eCT^G>iq^O{}<@|iFBNYXc87C}U zQF^Ra*1IvCg-{6aWl|@!*UvPpPa8JY<;@m;s>*^DeNMh>WE{of9-=9qdZ6lQ_fbNP z3^ruLu|1)<{M0)i0z7JXZL|LH4Lb`SXNhNzugQzZ^;r~OMHslsRkE@?% z;FSoSe7vXI?y<`c=D6P_3oap(M4isKs;d7T`b=pyE=cwoK+6M-0xZpyb%q`BOJ>m;OM0C?5XdqqBGPRXfCu+?!yDS?d*Giu$!<)+ns*i31O z`o|ZR6%-hfX0C` z`#dg&SD01r{XrkA*)+4l1VvMz^9AO=f0W^>u%MLZ*zc!)0SD9OH;)NDzv6{X7;fGY z0#*5>#&^%m^%N^19R9(@Ev(bAF@eQD8mpDP z*%kFLu@;}v0b>?)1Gz+vE?C>^>6uD_nQYRxAb8x@ucad80witf#WC3JeXyx;%4y9T z-w2zDNm84%7bqp2#S9aox^{0FP6JWXL+!{|pm70k8pxROdL(s@zARw^RthneWsZuM ze5~C!o9dTTO-*^;7&J|KVu&b8rOms*W$_xCYO^_nk9uy@GN{-J^9E{uB{1*c}X?wiGHC}aH3B(=pI>37K->b`=ZOy zeQxII>ylxz<87X6KcCVJGL~e=cmjY+*LY1yLlGMIMPc+XMU|VDgJ4(@s#^LwdlBVIh79({p#AFM1ka<8SgRI{)p zTXqB(an$9k8W$xLbUk3*S2S_)5YccZdeQQ?W~j;nC~ z`pU9L&JUyQO?yvvN%_$@?|juKj6bi$pxuI@8D(tE2@-70gb?0Glb zo*wPv8GB8BIr4}u5<+iEH=}`am^g7^>GO9X<#4UrE;%c?mE>uthFkCY4JZTt$|stR zB^lYu{RwGgGV}AHl7g?KUXVjrZ@$7!N9Fg&N6*r9 z1;NP~bO>6-SCP+LrtMmxl(j6&|Ff`n*9nK0%M zKR0jTPp(h1`&_4}GkvSxx{&FEU~t@Aw&;-_I+Q zrAVd@7WZ!JtzleydJ2J^ci((AHp z^7{1fn}0;Tas5*E{Ca0a;%fFfb^dueuzRZ0RrcSObYskiq407(jf?ZdI=Xyn*3-&C z9pql@(Fv35PplTsSg-TC1_Spr{?==a$Pm%?Z>g>dV<^Q`CWqfrELJK81aL1v0yk(q z5Ic{S(0+~{5^_~5TOs6T?bz!dkS012_P_4hrL zYylrFH1ogojNenV0ae-D-_voZwP-5wYbA}!LS1iYx_cN`#8TahMUFofN+RF7wDk-~ zfbw))z8qDsu{cHcIjfUY3bf3lVNatCV{88D`j@hM@2Md1Wd5g}n#GzB)algk-DHf)Grd8JX}&BD9~7Lr;ipORH;j^IiR3}x zCV8X2mFAxz$+Su0_l^#)_Y?qlQilBFh0}qHWb5dDVlEhs5u3=UrGY6+#kQz9ozT0F zT+MH}gC_^!;S6IgTbA&RpR5C`12tLVIHk2w(wxAHF^O_OZ zH?rxfAF);~n5`by1K|SBCa8!U;VwPuO;H@vD(Q9|-v-PR`qZeXR@@dcQXu@@_q*f^ zj?>RE#r_e1)*=JP?wok<>nxjlVUM!!t2NdX=sArS9pUKgpga9!0H3qz012xoZr3jN z%;-q_be@`uu9xpx9%Nf?AyLHTvnrxhdkwQqCd52X@E+$Ssg1uY)}h@J1Mu8eH>(xc zo72oqjNvoWxgo>Kzb&WF*E&0`h?o0g50GgI?^8em;JB+pCvObIf}HC@ALYOZ!b=~xA%XV0 zyxy-TIFJmAB;__-GlM8{HoD&v3x4|_p_g>~Q>A}VG&avbM`o^A8{emKd3Qr+Y#id5 z3+DhB!gTvnz}Qz@Vwq1X=V+SPe#%)0^Z?3XF|B+MswTUU)_^*Dx>1{Enh9Sn2oCizvAx}_NSA6DT`K_%YQ z`!BScUcq{|A>10v@3<<0Ew0jIRt&olF3mqhKAdJ8i;|*r?ANWyx6$mPVCrl+nVQtE z-s`e@F2F}HsxJ1Y_oDOV?L8jVN4l&V4oj_LnTtvZav5C0-H|Li10%%4i)i45^xI{^ zcz(37^st66m2P(g*9H3>cSJnJ1jH_c@_Y0b=rrcK^Q7E54XqGuR@|sJ`}N(bT@wfcIkT@**D$t z(&<0fk|9jKi?z-(PQ*F|-by|7P}Ek7AM&-mRMe_$fm6_0QBe7;r_E@W6ZrZ;<%xno zK|%su$6{{&5r<&X@conlczOVwYmw+>66%v*h@24H!Rbjj_dH-dQiO;bU8Qj|N z$^xw)+5YG0z33-*oxEr%8av(hK)#9@afgPLR}^u%QGVqZ8e} zerA%)g&aqp1M|y`;=)P$cZYk4In|vU^2K~)nivrT)8Rr&hdj!eHun1;uB|7m{ae57 zI}c<7eC|vRZ$|<`k_Jn;<$aou{-Z@4JbNA!R?u=?Df|yP>3+(hWbDQJlT~04_xu>7S znf-$>*$xINS$h_EZ_I8ueM45KM(hRJ*~^${aKhP)0p9#c|2P* zm_l0{8TfkbPOu}2Y;YXpLS$ea#5Fk4vbfV!85QJ8=OCBZ!@@2&bjx;noeK*Zx zTDxy`+LCy>8KV=}tl4A}Up;fiKJ7SHJdmG(mi1m%^3J&$Uns0tE_ELYlRq%8J1yAX zAFWvBJxBEXCvqovUVkf^b+hs|HF+)M^Awzrj}~|-|FkxCUP==);r;^m{_6d-y6M=p zv?TPDiZnaE=TLpM`F1vQ)7HEBqFVO9tyIs$0nTTsT%2}*X#dS3LQ&sMZzVP8RLUc8 zC6reVBdIt@_i(lBir+;A;^Vhfwn#9<=AU z73x=i*ad-+2z(?W*ck%S7|kMRIUb0!`bwh!E?CkYV%CT#_uG0u^I9vC^E?K29+tvO`>bZfY)DqqZkG1UHzd4G~#$?{bk55d*u@W8_JG$=t-%j*_m zD0jZ7sS+!{DrMe{eCH@tz*lhSE4fM}00b=fQ2**5J8(j%WdwlZTrj{k+Sa2I{HU*z z3|56nK1A)}aKMP)?wvD#93uqxeET@vAv_NN8 z3EM|VBV9dxSxVz@Wt0(3^vqGbCZ4X?#c$3a{c@@o`7PV%0`^q>kVC1&_7nfY` zUcmGgH+l)qRb2zojoZ|Q@*71VSJDdZ!wCCI>ClK3BhbkV$`N7;&8 zbF|~=y&b3WB-qk)qP(FJiBx}%Y}TaG_C(oMr8174bymooVxseh?J?ZQQM|#NGvD9~ zwDRF01+d>q^g~QnOPOCIfB$KQ5oLx8sqQFv0XXvqmag#Xn zmd<0fVL@`(@$TRs^pvAaf|9It0uTb#;K z3Rva6ZV3MZdo^e#^RV*M!hywai<3-Y#&;3+rpd(kpI`4?t~2C1{L=YpEv-i~?|I(y z03~5FFWzg)^d{s7nPQyGcS$NI>qa5c0A)zb~ zcUq>9crYKA)CWUJ2{g0U$y=}O zGbnb{ddFS|FL7v4rFM(U1rTE%`jgGuuI6jz_;}7JYdLL4$zd>nG3Ps1oT50IX; zjwk?Uz+=|6%}m7E8gCrhr?>-}YM31=qW(`o z!dA-eln|kFwP8CKpoE30WG(j?gmFkP<%A=b{q<+y45*iAyFN)5P01O6F!m$TI&;7x zE+{FOzko_jcO3pc^|xPg=Mv>kH(43TQN^{8BI_&=JpKN4N>?hU%15dQ_)t6=VD0E`AL`HmCNY?qD&em|J}$6|B$f!%1g_!dmVkWt;g3>r>5i}+|i?_5Rg z`QnmF_u>+b%15QBXk8$~u`u5)x){#o2hUm&@U-2nzKn$Afj_57 z!IdQ|{J8wWFY52gaGI!O5Ndd;GD^EN21+jxTplEw1v^7!Q8mk|JdCUpShpqo#p-O) zT1n$@p51x9Ar9VN9P3)5CD63|0c^D-{>qv>d4FW_S#F$8i^zf|J^?PHjVg9x$)J1b zq#;A3S+ss>uQ&@B9*e{lOyjY9n`S`vDS|xZ5_p<8$ifM^dO?J`1Tt2T>MMPeoxW<- zF6CKMx`nq2LPETazaDmc_re4njZ{kf9_^-vo<2Hl_oi@!6jHGMo_U~<(LB|vNFsnF z0yiDw(8D@~KR$X;G|uQj_l^ zWvPsV5&w==ptFeRAv;zN=QlD_kZF&e!%j7bV3$lXI(hA-;5xDm7X7SL% zJ&=1W_D^AwX=GP@Vb)Y2r5`4;dHyf%j~7oa%k zZmo~k%x=NBHE^y{#*SZeOC?b!NIzI`OZ#8@F^0Wsn1_XyP^RU=F5HrqKGW(pI40KP zGImAI`ZGoXaOY{a@8Pm}02}aIdSAWX@~5H6r5B^+-$Lqh<6>nKT3)Civ3KD$+=9HN z=_v%BSBZ!%KT}nt8J&I-GnK0Ttcj*k!xg3ME1%ezd&%A#Az)otdoa+=FAjF{)$3Fs zw0?KphTn1Ef!cppW3zZ+`UURRi@fGI55*W8}lOQ3&JZTpb`f2?AvDKSyC5pJ8=^kaqewG_e|cXn(KxBy1y0! z(3;vm!6G!43T%f`37`9MRvcDScYQx01gm~7GQVl26OA|ax}dx%%6sYOw>q4sfto)y zB%VdgSfSY)`?G}a;5$c)0krS~Xk;KSJKVO00*`&CCyXDw1n%6SO<8}*YK(ZrcNRoy^g4qV*P5ahtBq9c7Hf-? zvr4Tm3r$yihRpPPEMRMNd1NL&wjAL2p2@8;L+S<9iq3ECe4SRh1U;#}W!8n5h9ZLJ z`%!DFuTYQTmB@bpr?yYyIWu(gg*XaeFjFhh$X*3#t&$)mMAJZl5Q|w2E}ZPFigRvm z>O?Q+{d-vF{?mbge?NR8lc)2XSyA9h8Yubl>OS3pT0zF-tfnSefk58x5358(C zLoA2Cw&&`{mczFai8f!AwUux2`-*j0^DmShHZtRXwDP2ORBBMDP^e7O?d&8)3l8x{>k@|4b_41b!*yTH13PT%3ie93{m~OA17?puQ1j{`<0Q9)GABF`E*X{ZD$$t(VX}Mo`AqQr#H& zJ91`N+^JoPX|7jYlWkpw%vze%1djdGRcBpCJ=R5(*11zH?c$_^ zgkNCf`J0-Odv`6JUQDFn23nnHJ`Xj2u@Cy|@@K_U4?=f+;_v#D>r$QS!D~2K8W~ z-Y$Mo`6MpA{taQVa5+iYq4yP2;Ce|%Z7$wtAn|5gr#qMIao1|TDeuOj!|GK3@pR<< zUfJhr!*{;O#Eb`tM!P|MjweDH`KJ)irF)67lPIzk?&CTqe5|_$rT<0pW=Y=L(acT4 z@+27F$a{#uZI3`llWfPEto!j`2|AQ=CwjX4w&5|aaOfrua!mPLo`)tj zZtnA2$*h)t7{Imf<7yiqK-6r#*D5$m_zp3@x$_K&q~+23gNvhP=k)WN)l&k(4hiqv z!!WcM9g_Ww7Sj*o%KIgPF56LUx&-nc|o zm2VTveeMiM7~?6X{l{Z+xAzh;AHopr|Hjyuj)EHpjgHqBxeqrOd2Bn0qCM5J8Km>E z<|(S4*bq(8x84d8e_%^+P0iTTa*kjkRBJQi2zmvi5+Qs~NDZajN2V9Wwy&4@=hD}K zup>2Mr#9+Vnf`3jy)SN(?j(#PEj>&`1<9TEQnFb6?%0)$NU0puUU-O-?{70yugEdW zMc^w^cgg-HnQG7#K53~GxRK*38J{ZWrQT{-%1@6UO^xz|S}itM@Ag?kTX)smP{`5OpBZ(Z+mGh5aCfA<%{S3@ z^~F|nYFC#C&o3Mr@IN=ohKz%Ne|;RtLqNhuxNklgRrUDX{3%7>1GpkdE;m1cKn-8qC{i4d>6v5@iemZCN20c1b-l|L@rhv5FJrE7)vBjY$`(iatSkO( zQpOI$t<8d_J+`s@hv7+p9fCzZ^>B4nK9iTp(j{n`)}spvg(8F7#x9<@Qgh!n1P%RK z3&>J6ib3)9-=jG|r(LpuMM+?3Ui-HDrS=MDh%cAH8czI2l-4B$0g zq3MdoB`%cow;ool&%nWW-z?Qz!E%!6mZ%evPJx;051=w_Rl8)n;706@J&UmzFfS_y zrdM#yGXeDibYQ`=PN>+U2^efrNRof_{NhHWOiS!kn-iYSq`GiF(=o~KG1o8db7k&X zAfy_$45dFK5BUZ&s{4q%kVFE~nVxLVC9EA^d{?+Fb7anzgMG*uB|;$xDI-FJ2cX-f zBo%hTf9&e?;ic!F{*L?JdaU;c(7UYtC~DvLfBkBL(v)6 zl0j)6wd=PW(qLakg*27#6CQ@9;xT$@)BBmJNq$ z*kP-NbYJ4mb2B@oUX`=lhJ5jBU6?>v5ArQS-s!ElHr$lM#9-3%MC*%8;s0;XuW5ZA z{Ld9eN~worIrnV7XU}zxlvJ}>Rn382S)PpZfPa+7+Ullq;KKsJy&kbZ4(9905+SrU zP$1*Z|7rEjbR^!_eATg&;PnVnHYZQc-^b@6Bz985>MgSFcA9rUQWlW2aT(t>7Z|t; zcLS||D#@g)G&Fn7Ex+I2-Ulp8o(+H(@AAyhWMvPAScOI(&jNcN6*DtB zJ@G$L6^@*Bl3=h}^KSxtmgn3K_jEK`!_vO|FKM?fx_E4P^Lei;@IpKxpD)wX$o)Ta zI`UOV*7h4?z>>ApO@U`mh=|k6gmkEpf&G2S)HCwJG>4igPAb%CznV&l(`<*1oJ zDtNrndK?q|MB-4~5Z{Zu z?*Ws(9EhhuwhX_bO5iRai+log@SEZoX?&Wq#X|5QYIX&?QEnw5lw0LZ+MnpF~`4x zN~yemht8|XVSWECYcO{tO0+x*OM%MaxWchFHGjK_P z0~>wPHGvOPIHeHxnD{8|D{P4pJ__|W*RV|4iaX}Nu$A0xfU+Zg-hTyXS{|44g^R%* zx=eBS!eXP;UkWPRjPQ|&g@in=bA<`-p@gS*qL^*&$=q}xSiFU3hb>-OHe;-8kSZ3{ z0&beM>$OF;R;$WJUG|-8uVk!1eaV%a%WJ!?UG|c{B}^4LP#1XK0R=?Uy*3z zA1;YjiWE?`UZ4-X0=~VIS(E?7 zq)Ta^%9lCXXJSVz9c8U`fK{TB?&kptjQ)Jx$aTIkaHvxm!AMXJw8{-F;_XkGG&z=5FM$Ya3JwrH~FC-)I@ME@xG^?{}o4^ zQhM4X)?`7bHbKmr;bEd4;qm&{`+*!j%W9c;QBg6VGlB3opf>o!wl^t8!LNZ>OV z3N~fYYtD8|oITup6(KmSA23m#8j7*e$einED=ZWG7WlLc5s|4AJhFFDs#_|8^qv6R z*6gr~i%edbQ3rw$M?BbUf^m}ww&X{hJ^162vul2l&z7VaN{GHpw;e=Sy`}J|C;?MX zJC19iP#ZEC16KR_b-Tp{1l|qFD#y;a#mxY^b?@g>u;Nn{Cm}0wkrm(u-{*`IE5A(m z(HTF2L9KC)F`Ga-|5HN}vTNetPv))-nWOO1>9v8Qra3m3`0h9@g>oB3-I6~SljmQt z(LEfv-~V+SB1nR5W!y&lX7VL50}xyMHS*5n1)8lRPNBUa{D~qDxt|XqRVHMV50ex+iMF^pyt*vfQ;yDv;+ z9%HL*XfR**q-4mHV*o{5LGkgI+{ytG<#vybU=B;n7H3%;q0k$gAsVg9k#Sj|xEtvf zSs>OEOn|Dc-qMFLsYI-JzxAqv)v|Zp`Fe)|T7laUoq2-kG9a0+82BIK;zYzdzHf{0%ij5>ILEG3{)G z=3|FpnO;{me>j6SMHDtH(@`Ourrk+EQ1dY!5LEbxZ9yR0nI*IPi+NV}u(z28fVN^d)<@zx_BsT4G(-+~ z%?`3f2Anc>(o}Z`0bbL9A1-O0$a!KWy6nt8mj%_AkSsu zMrurHVVFa)qi(8W)9T5NY5|ANYvSfCF`T2puH{7ykA z81pD>S1dK816eG~>*55;dg|0o1bt~0Kt|HSbHA7v>8g(&%NR8GTQP0JI(Ct4U@)Xo z2g1^f6px3EKCDn8ooe_X`lv)fvT2M8rXTre-H*#o0?VJ!7!koYYz3Sb&iNniig@z7 z$2PISrrrCl_tq<22`d)Sd;gZ>r7Xt01+XLl|Ei?N=)-Vk_QVcmeHRGMi``akTw4dJ zxC2rTuJ1Nz1{#DsL!p#f|7d#OE&!K*#199y-UpNj0ME16%&?tykAOj z{-?gOP%0hg7vB`D9;80GmF#NO2R~M)NV$F8P5yB3hM!IK&HANI!a(Z@yT_Vu!?3^o z_1T)*|FGb-|MPVBwSGb6U1Z(k82~lU13YvO+tUC~A3FBWm*%YAAc1?GDs8fK{)ePb zdyh+VSthTVK3Bmrzg+DQWD3tlLj=x`1wv%FIK>1$CCPl z-rNlsy|iE>+0r&r^)MC-T?ut7e31;l9kDf#f{5fX>$oP z&}*mOxl>tM9_4xO-eRa8`#tfz9w3!1%+n~IE!-^q->>Mume%w4eyywXrV)h| z_cBWZxBCAJ&UpQvRg*Tmr?bhNM^^^1-a5dBa$KM1n(|un_~44=J2~LJ>|53edCsQ+ zpDa`MGj7o|P<>{(5#h2lu?4xLDezj_kGJ3(!%FRc88^i6P$xl&>T z`Is`mWirB;1wY2-iK7L{GJ)ap!>w^V)F^8hkRC9a6U1_uHL+h*9P|rONug12iCfb@ zI0ys^QAxpYz&oBrn@caXLvt4m^#(?FsSOryZHdS{9dE6pX3N^f{9R$z-2CW^Edos# zMzR|262?Vk1>Z0QT=V<;o~1t!hk{9ht=~UBD5@b{p{hdKos7VVO7_afcNJB@!Ca?~ zFWb#3snLZ_*);P%p_3f@@m#R$d_wDzR&G4T-HM&dTZ|37H{Dh!CfhW;b4l3Oe47tf zv6@|GUaDzFAJL6(m3@`T?LV+eF7K#o@=Lp;rXH#MwuRlL4DH86w8R7}>&YU2UeErc zC?3ciU;9IjQ%Gpb5@lXKKewrC+X7e-RiVYF5+uL^q*Xi-OWj`%7dSgvBx)rf^9vWt({ z5J-p=_Ub{=0u5SyOp2%iqwa{S;Rg%thzm1cy8R(yYY6CD>NuK@m<0uP_3=Y0?aqroZ@M$$g?3mep-wZhQ?m!y;PB2Tl~oTe-qawTmB*U*hHE;XjGp~OoFIo~l3MOz z8n@tcU+dS6i9>O^wUKPJTOlRQyZygv{jY#F270FtU|;R2d=c!u=H%5wo^l!4vdBp5 zSq}V^Fa(YBk@oIyN+&Q=IM*YTOe#qzMbu0yc2kcRp3WlpmeQuHdL1!$n^vDrd*7s} zs5-jY4tq8kR10-_cQy~LdS5XROvNGq0enO95<{b?3Q_di0(=z7ttp2TS%~`&u=dG> zQu4fYG7!u%nmlQP9>ljsexFX;wkDbU679G&Qp&Ykf3pjQv|t^)WZGf z-Aa=5vC6SUKVl}vfYdSlI=SLXGS+RfH-xGv(EqyXAOe!Big4fLiU#-eI|H&HDtzB# zNb68-vW90K(D|pZ>Frth=Dw^eoM&Boy*1GHa_FYoN;IwKwZ^shJz600ld!s=ao?sy zXX?|KW+ZHeN4!Je?<3dhnP${`5q0CehVorA8uY;Z>=8@(m|yIDX1)SWx!mL6&$Fje?5Gl9rEKLL4EEX!yr@XDmSHtD)3XJ?8#SAQffkJV^oRQij#ZOx2Q*@ z)R|Gsr3|)LK6=>TtKgzpng)uv?X)pN*+=z!)?&ezN2;u9XZrb{ejkrQ7|kiOzxHVF z;K`cfQveJ%n|cb=Ep3BMUR>269yvSoJx0UmMdj6@%B`6UgDHcqLqB<1=#rn2( zbjcGUXN`CO=C#yVI!G&S)(T(*)1(Nrh?ozS&QZMC2Mx(;%Qq+YO zVDS=#yZLH*xE1p8rr<6|W!cO5ppK#qUO7UE5h_myI&J-tS&P*|v$yj#$(mE$%8)E_ zqv1>ejW1T~A0@C^OIBOX%S4IhL886${O?f-LW|yHD`B*HzoUil%p!_mMQ{RDGW5&q z2xtVgdUX_E{@st;D~ysmk4(3)=l#yccqKVNk@&{Dw}3d+^wnoQ$shXjba=kq_fk!E z0&U_Ef30esu6Q!PSpwPSb>w1-e|6q0nneJrGTi{OXl1pW?1r4~tCbvS-Pwtt+hRyCc*gAeyWc9EUWNfK$$1s)P^#!rktPeqA_ zyug?tCGbD>pont9v7pZ~Q_zfkWF9OH>dS);uExA-xzc5Xu6%|G)4lFd%Bv-gRZ+Tt)!PKs1|*@fZq zA>49;=BrvfhomUdCUy!lP}WIRm~1pNO>ktqi82hu&y_YS#F*Y6ZQ-Ip<+sTDTfHp`9aHop92hl86ecBR`XyTBE+c)tu zgisuxgD`}A{aj_u-Y}xoITqiS(!h(zE(6kl60j$*9SZ`%LT{)|@|} z`rke4sHru4IzKw*ng6q7|6)>h956zzs_Ne_dB7g{j!kUf!G?n~#pv z(9fS;SA(BxJVda<8H{{)PTiJatL$p6s~bcUPA{hb{O?OkoioTc=+}BS=%hb<+Of6R z%ZW)PF)GcacSptgvDINrW+3<8Rut3Zzrgo4L3U#x-rBf7UjoYEptNN?5htVaX|xZ2 zN1@KRU5t|7IO-AVKdrreYR5+yS-q_+j5o$~an9F4ZKVp}a`IJK6OTs~9fbL(ogSugJj3c;_iCQJ`@29i_GS^-|K4Jq8`!z?%s zz$Nnn({#=PYU1*%*;_59my&=cp+cQ@LHKpwEu!-^P5Te0pKK_#`Q5#i03IaUACxa( z8sLf(=#i;&BsX4p2LwMQr+d<)IU~i@tK5db0X6Aq^oD#t5~DI{UuuWI)=HD8U!H6F zs`A+%Ak`dAojBPK=_i7Vf>No0Xm}o~i#UJ>zmtnW&Xr~9abLn@fkI_H@hL_xMezz7 z^9}&`7Oo4eWd!mAW^V30bX0?rAXt6$aD5vX6oVR>&_q==vNtRL>*|qoM}K?uZ-9zs zXqO$oIy=69NsoHbpWE)ntS+Z%Eb3t+5P3C&T>|F6MYv`>#k;KqsqV2|%t3Nlr9+DP z!w?2bwPDx+PT}xE^b+Xs*eZ4i1U!cKk0tL$ngj?a+{x*BC!h5#AFldI#a;rd|Ng5 zFCz}l3$$=*;KK%KrdewD;%B;Y*A>c_^8Uvxu|K*O!UdrSMj@VY5Xtywddr0Ne4a0L z+KbEWDYe_w3Wg=I+P|YY#-7Q>C6qZR)hujLst{jwa^Z8$z}-$>~5Uan@n<$jbi7 zJZgeeB20OkWV~EaLPqq7+W4n(3)v}8_!bedn|fIjr0z_%%6#4p>>e5Ez+G?*#@JU* zCNo;RyDlBKzYGr4RK56K_j!BWqjxV80)=us<|QNPi@uMr zMn)cl=jA~MU+X3NNPqnT!i}#_eCGzBuDZ9q9m0hc-i}M7ciz_Xfaf{r%*w)NKUuZZ z|K3QoZ+2I7kB2YB_UBp3y&GFHinTR(q&tU~|NeVjNADxjQq5L~Jmkd#uT9%*-^JjE ztzhKTGK0CRe7E4(se|Je#RsnoNwJdy3I4Rqo?de048PwIW(fLyM}7|qI0=cLPmsh2 zR~Q+h#XN&;>1fk18-Hd#b=ZA98mKYi^f(!4I3!e0Mg+?7Op=J61jT=f$v1?JD-;C9 zOW4%W3hU)D$bR>T-FiabBf_us%Pqfr=>FJ&wG9!wTCFXEvpF%e5!aaR@|G zj~Je{RY;=wy6n|mNR-mP`tof#f`G;`un+W+3tJaZfO14W_+cgj4ppF5U8NsI`eTJb zPlS*n6Wzq+c2qHI$O@Xb&-z5JoXP9rOob?$jZ#x~&&BJ+VQN|4*D)!Uw)mj$L6`Qn zhxpyMQ+35{Z`XF#1H-F_T_(oLGyWTj**9I!-Q@YKIepFfSAyH@qNDj=z}r#g5GX{CNsNANveW9LFM6Rjmm8!$E)mgMCD z`f~yr+%rni1wY+7j=IH%cs>@qn@XVcimDP;_)$=%=GRCATU=MD40yNj#=6v700sFM z%aKpOvdRSgQPJ^i|N2G+6+a(kOU<{KNmEe3Z!57^1AN=k>NhrQH7XPc6*deT?T!nvGIY@X|ekwA%u^j-#){O4cO62Q0O~(y7Zu;G*j6 zuTZM_?fW9+G<{fM`1Pa(vsbbuE9*E_fxLlottt^j7I8V6$_?8E%hNf6VwEh>^ygnQ z6`VCMBYeemc%n*kB9^F{;!D`%D*li^?60w1W`>38{=*LC1 zG?%)iB^xu@L-RjK6Yv@wnL>&x2_g zNYd35rWf1SbDCbzE`7&N#eWn3J|b5wo=D{tv?4v67Ka-fcEaTRcfu#Dw7cQ3k@aeA z-XX>Nw%KX3Di$R}ksuNu1e4bkJof%_Gqvw!m$H3lVJUDN_+DB1Z!pR`p{DjShw$5E zw)VJejXe&IrQvYo;?IjGHE<>sl_Kn1v3a#K?dNX*%CinvQy}+8FXQ#RR@Z}RBA(7p zsH+r0XgAh)HgG(uH)?m^z<<7y6{te~e#3jnY4VVpRtI{_tx1;b{ATlvD*cdrH(^dA zM;InpYAq>Bxyu-9WxE0J9A=qQQJd!eBSK6p+RSS&@i1-AiNCVp;p*r1N1&&SAwhbi zkRnMWf7;C;@iJ!3CksPMaL$c>y)Mi&s$822UM=HpXb`ksyqT?|jFI$)=~l1wxZzp2 z&W_ zze~@8Ih?=(3r`+)esNWw;e|uA%~#CV?WE0FCgW$~70=DvK(O$CWRPfFjX_hkiuW4I zbVF0`rNPnt@vrocpiSedgNibt#ErMsKo4lI@aiV?DBf`!m@)pp#M8$nu3KKuLAC2- z1M|37lls1|VkU1_ItMrje2MYCvxhgEc>)ppglpv2SSx`T0QY4PU;41PSC&;WI#2ZL zeznCP%X8c70!ACKX^|aeQW*b~-GJ6hrz~g$uW*bnz(j$3Xtx{U)e`1>yKJ!>3BOhu ztl1u;MBiDwEMuISnD$0c&SHkTE_je0&8=F#wVxi|RwF8B%{X{T>r7&SIzH3x*h9bvKS=<=%~-$+$1I|h zd9*3f(I4=3rSjVa@R^E{!zK{LBqk?znI_6WOfRehKZY^Ovk{@Tk_^#ySPVzPg%K(Q z%=Pt3?2*T@wqGfS_4ixtgfzf8h4r-2B{dW(mRrSwm8%nUw`ra?V~-ux>mJw_XFDO) zPZpVQV3naR-;W9mHPv(DB#+rHB1Wi~2gIM2TFR^IrTpx7%d{;qiIbjL${9bG+etPJ zXl*MVdrj=M4>3$jzgHf7YL`iQ?Wl8J&<>5{wmVx&@__xIrso}YUwTNHe^=g91hkh= z*{aTFVb6$^+g3*ENMKMW{z$Y_zo0#GLVHi_k5%F7bK*3?I_vMS@i`G@2}j^-WqcTL zWtRw2IfCD8Ucga*lo*;I?IGJk<Ej!&?B=w)rZ#`TdI*nw=oCfj|ss7_u%w*o%P~B z4nDF?77bm##vOjsynf8Vzs30f4_j{)6-OI3izWerySux4aCaNrAy{w-9^BpC-3E8p z;K5~(pb4%s!FAy9?X}O||2pg3^+or3yKj2cQ&mq@>7IiW=Qt0p%J`uF*{Mo-apKX| zqA1!#SEX*?qfc+HDDy(tqG=?s%oq&CivlDfYjn}a)ZPoP)f)Lk_brf{~-4f-)tv#lG!LcI--itu9sZl@Y@~Ba0F-iKD zfxG+{r^pkbl|(g6%mrgQh!Z%w+A<#R&3OCL z^h_oO=7QtETe6$#dJ7UYKsbe_$XfKZ;HD(-2NP-}qp^p*gxK4G^YJNh$I|yNW%*Fi z_vV3No^sl$jK$<_0r~`fEjdUaxj*!oZ&UX$I`(GCTb3|m^bBh zJO%O5b)PQ{Wm!12#3GV!_+0xHC`^ShRXT9!#<=yTV_Wtw#W1WLFG3&m75x!4Un}n@ zFVWzyHyAD;uHfW<+?abEB16@c6aG8h&Lmg<@}tFf=1Q4QEP_{$KDlS zaX*3!qD$MOuL^3F_P7<_p$DL{8@HT7UUL~qua}x|;5I9{*e}xkNt0aCIu9!KX!ktp z-TenW_vF1?!104s)JwF_l;)~M0AG={$VK$cN}w_mu=Y3(FhQAlZqL5wB_(wcpG0th;PdeVR)Me2D)G->XNXCT(b4Uiv)3$wQ z`Da$5`Qjgu;2O&{urP5Yat(yp<$sVgUsn9)i1>;E2W6Ezx4u&gm;LFHe&p;44PK7P zx|YF~+@If*K}dT6n$l*Iho+9B}G2UF+>xheG7 zK`}$KWV^KDK(UZv1XG4eqdy2R&C>)EpcK}5Nd3A=yjtB4b3r_UtnP;<-Wdcb1X@?! zqwa7)l3Cu0MPonZRADG9i4UNZ*Q)hu_fs=O0peUqL84#OfAP=bC}us>2#YFYcZ0|= z@C^Yiv7d15;VdM9C=Uj;>_6Ny=kp7_?Y+V)oWPX8zL8RjWW2 z9ULT8p|nBo^=q5>MoQ*P8RK>H9pf1_^*~OrJzO0_Xt*7M&(Ha8ObL$w81?D)JbPYN zapplt)L)~mj7iwWW`%V?M)kIN?SJtYOQbbglMEIrO7quhbL=%ei3QKaNZ(>J6E{mE z@Je<*MlX8b+oG0cNC5$+;5+^GBW9Jsdy?BrppQ1vw-(r@vqy6}pc{lkM^COHqcxdHo53Rq)Y=VF7MxQ!^BIonGs+{xW<>gf13$fO%9=#>Yg)_GY)>OpscB{9JX=1q4y*@!q7!Ad=y^@)h zhdrlYNUN_*qy#kTM?nmPQ*_#SAXxUttIgSmO3eUf^|OPlvld$eNwvC~1m#Ov_`9D- zcj>V`InnX#=f+32_G2yBsn?OHDf=i-4upvk<*$h>zszTvlSVRuIwwEjP zf61Kxb@iHa-i*vv<{WU7+IldS%NRp#ASfzPN=^M<3b(4`bR2Xs03muh59|w0=-=pj z8z?BG;Ux81l{)V1AybgK|L~qaFjy?nsP6A=9zY{b@IkRgz3rXjTFZBiB@On1gY1z1#|9;$lT~+lfr-k_ca7#qbZ*9d?UefCF>Qdl${%An( zJ#9ANX0LE@T=dcVY5P7<{?Umq08T=jUgQRRQ|KIkr_y@AnfM$Jb=)A^*=$NewPhn- zWPs$I{M+3_7#AY7I`|BtfSI)7#|4D}#BQ2O)flD1=YRI3h~oyGl2o&^`0a}72R#!i zDcyV&k+!#z-><5!X-LH|>FmE^qM{eAGpdb36$Cpx8c-#&B{A+IGKHSkq&%cTAg7fQqFN!GpqZa!9E^FY;(En??ap?66f=2_(|?XNQBit(WKEBpcgHh zo|GeK#YbT!=d$O#t*EUVMp1%?Zn+)sbQ)@#5D{~^(fZA>CEU^mz++Iil4osGxkEppK;(c~ z{klQyz3E;`K5xOij*RoCBqVe6^X2~9&3 z@Pcn!1#Vo7uV&3$>!(w}^vsfbDV3iBz;&k{{suhWd?}#z1dr9JJIir-QccbJ05$A( zKiIY0pk%VC1Ha74B>&cJ@Pe`vMbFWa!v3TZ`=q-t#Ir25n{fqp^hf8tlW4PhmiLnD zK9|-N+PQW(?%*#sxdMstLR+At_a&0`k@j z&|my!nDz3%F9-{pE4PC}KWdhT~Vkwhvmv++~u{A*R~Td^;>j|D|vNm48$E z?0&0I)o-8rB!AAEl6BfJePyP#Q)K@^>UcVFAhm*S-1bXPa#Yk)tt+9S1p;B|)IB{F z@sud>U70h1smnV_+(!SU4^Vvdqrcut6f&*cKiv3pmx))`M2B}tAm1xHnBG}@+XMpOD;AdCFRn#~S zUv3r4jOV|q4yV&GWjwDK>iq8M{4h@B6rWkhxeo+-4qOd!qCPT9GXiT=c2>#s4>vV! zkgG)|Zn4JKFWhQ&6Jwe5o{Z_}FMZi?qNOysq`g0T#8Pz>i4J}jRunAftS>uO2IoHIqJG5z)p~$x8A)PV=^xw8<_9-Q zG9?SbqprTyB&vK5T$z%=P3=jPE#Xw72;`+hjwflL*Iy;}5}IX;s#Laz)qniYxtnfQ z^{0!v#Sg9Hm%OX+3Ak*QsZG&~z1c@MK$sL8F> zV-?6IE);&!6{td7Mn$XgUaVbpk}G+j#dO}?h5_aOzH#3;Fm-neC=T%)hNuAX8I&HD z7wYef4|LeGJ*B9i}~3q$6+E)}svT-?}NDj7)a) z#M|B(vjjQ;cR(TstEVuzeSmaGxAjju#1F^5-%XZ1VmgbLk}OovpgO2F(#J?KYrEvD)+2HbZg+%qHy#vo`E} zCMRNeXRQawZ>|j)t{h2|;SSZqIz=ah;VeJ&tJ#Ms@<{F@<6E$14;;EOQURD}tN}ww zo-0Q2K&v2AVe+S{T+#+geqq0yt&tDSvL60C=wZAdJ^NmWxq7M zAHC-P1{stJ2dZYcdQP_~nQMMC>~JVn3q0h$`K9I;;?#Lbx1P^x*q>K$8&~;m_qjz` z_bOMI8bXs=8zxfi8US7J%VW`#cW;-(@dnGSXm=Sk1^^MI1rqnv7cW;pvRNA6R^h#k zb5F>DLPY~Mik~N>s;H)a5onzHQXfpkBo4w-Qq}=4l}>&46AZgal4V8uGoRtMB4|{) zgwPaT8^=ue*x0|1Jctg`dg&vO>yGl9QyOWwjS@^(T$4^0pXdjFHYJF5N@wsITyAORnsawpgE#O)ddM>r8r^d&;yb{_7~ z^XHg#>&p(NGM4kU{->VT5Ux0}MqcLr3zzsLYxr(miqY;}JHu-!VK^Q#g~o_JEhH|i z_TTZI-g~L-bdL8yROY@wc?2)j6HC&Kx8*y(Z{;T~juF*#i1c?^u&CN_IYn2KNe8KZ zpyj|8`Z0blSrF}mTn+ycE=Zs#VMHwc3sqnKj|LZy%7;@+qs)m%0h!>M_39oS_VE$L zy0ziivrX;zHRF~;H@een00TlZv!cyMuQ;P*s>&mT&1!3oYJ8MF1=DofwBu}Zda zWf(k{;lbY8mnZ4d>#@Zr$s*rNbegKIK_;deZBpeDK&9n0SbMKlg=Lnt^fScW3(2H8 zD|{|iGj$*Nd?Ipf#S3>&ke?p}gGr_EMC?|?a<8tr@-!q zyi<>?pZPrO=SA=WThx;LtheO0gh8baAorYg*esKW=hz zXtwfVHjMUh;ObpfYc!zuz;tlE0r&jPM-?zb)g6)w>Vl3GB0{*6<*S|~JOi@RPKc|d zSn^?b+?>DaDhcRo|e(Tqp!^cR5_>=X;4zRs8Ru*XqwZ}Th!*!j0b}ZN(@rAsgNh^eD zc;fDqdHseD-ZA}^nX_HKjxsMH4pKug-HoLyfa92OO|_|tj1uPXtUw6tE% zSA5#&rVUF9AEKNFsVE5gz@gBhC9Ra#d@OF8a<%qzwRLIZ-Rgknvh=9G^{dU#InyTi z3=b(2=QkU)2<_91q}iz`kc>Ky^P$GV&o4r1cax99N{}qOo0l+WPlOJ^Guxq2gcss& zyiwLP!Oj~P8yC`;6Z28ZH?nzh#fwc7OkF~7N}3u0Xd~T(=Nt*8V5+*C?&C<12oHm} zkU%^pnoe&CsHLeG#yQEZ9^g{s77PAu@cF^iuVQ8Drbp`VpJe?7V9Yx6S2z5e=BW&y zVzl+Go(3@faj&%&L0(e)YX79Ho7`%*?7GM|khJ)arM(~Cl*=9{%opf+z9tFrejCs8 zB@Ntp&UgWk2~8fbTn<-GGpcv^Ddh(AS123OG+&r&i~(A$rwxtTe6p#MgSJOrJM#Cw z`&#D6C;FSksnuDA=aMr-bg7_bcV<`$J$~tUwj^xR{}^$}1m7jKc!mWdK@Mt~PaWCf z&}2xr|6qW2m8Pm0w+31d%NPpy=JcxA1zbDA2?muOA;8=R{P6Nl1Oi%RoeU+FSvFnp z9^>@#PccU=Rm)Za%3dCNZKN0TUBpO!J?FR-ARg2vk%&9|X_7fa`KQELAF4Gnf(FE8 z%&-5awDl7DIV|hK#22nAY)WE`+3`tWbnsA3U35Ni)N%Wmu*iA;;WCRjimUS=k3Pms z!sKNb9Ej-oY)xRN78SFXovemeGzbq(!~=bDGfwi zWhhNCe@a(BpyV(42O2~-H2idJsAVr=`?)$}6z)rU(ac4D`g>(lbdf%}^9Bw&y_L-A zM+LJTZfF{A5P6$Iv>3|pGW_(ik{$33L)!gc0JDvG@4JL^o-;q-|1j=;*Ha)qtk13b z69Vf2Qs)tmym;UukMw9=!^&Xx>NPf$j2ChX?bk7sQ%UBXzqPz{NJ|VE7;c)8tBtWfEKp!| z?&mw-rV&bznQs;yE4@ypiL2*KKaBuzTo)zH?|n68^t`2mbY0z2HLki*N;DO zJU-)8;#FGtEt|dcmDslYg@%r0yQ_4?v1Gel(O58IOU3>Oe-Tan>!l*IK176e?0%uZ z=6099FdK+xt3oeRvF_2r^UGj{ISF z0jn(RrrWG~y z;R}B-Ic~LET&60uyEgE;PcoY-BDc7uwPOoDIE^n}gFQaI6DY|1et+lE*59?M^M);1 zx%KU)?5={AuDyhH-|aVrob3}fP~Vky!-HvM-Tydcv@U(*X3<(C>h%`7lWZ~2XO8Xv z2%ou73TbiT*X4WnHF;G)!RWg4*CsDMPg@YLfvUYI>cleZf&e0sztL)CmzOTgu1@dF z4#$5GO;AF5iv#O6lEJ^JCH74`zYsTk>#qzTA^^Q5TZO7*K*+v}BFv2I@h8;6g9= zjvvPx{?kt?pq~1G*7#T}(p83Ue+P4aUNg}_gY`qdtOQGRqn(dijv}(YovbQ_ewMqO zL2<)!S;?C2B$HmgVo@2|U`ufU>H#T5zs2e6GE;IP zEYU4Zt6S^a7lyNuP7^$vai<*s4%k~>vwBici>5HeC@e}D3U3DcZg|t%lGR{vl&&iB zrJ7r_UTIdsYVlyG1|}vfsnKk z*_zDe0^dHlzX12-q$ms_G+aH;(9-L|hF6rO>T=0+wL0zFP+tFVjWBbTXd0DncDi)B z8*&o4B%~AO`Am6z=BM;HA;87S!(Tx_vlQ=V!HD+&FgAuQ}M7BnizAtjKd@$XhNOfUV8T z)oNSZQMm*3>YLfH=shHvw?=*y8TEOk_wDF(Vxb|9veHC>2+7Hl-5sM{|F_-M{s^*b zQ02Qwl@VNJ0mBp(6Ee0UwwOm#?LbEs^m+J}XmV<0!d11D@+-0`xMow1v5{!U+HC?yVyo!~B=9(u z&Q;9fZxS^@rJ0@N<-EXIl+;f^uULV?XUAfyZz4H+-L!6eJcLfjKa(tk=dyjp1(30a zLgUKIJ(Z+c$uzyLgtGUaQq|?90@h|he(>ZjEMJ*0I(*9<^~c*0sm_$p*2sVNeqHPy z^fo9cO5`n+%e?Ag;X1i~-|T;Y2~V3>vs5h_Lz;incyAJMkzm%PqNAblK3oU_L|~;B z43JMdxoukCR0Q$8mBk)b7wF?869cC6QH2#F!NE^vD=^7|G3mz z;7APIlvrVrAQ*N|F#V|Fm0@F9dmN5sTE%@!!nfNFI`rH=ApE?}3Jf2CN^ngw2@t+v zQ5EwtDiSXa`Af(hS{#zL2JuVp6tA~nNX$q6>71T|=Sm#&L-wC$=#VQGLtOX-NnUn9 zv_ZB5mo48hnCcS~BzK+TB>Fe&l$s|`g(N-nW?@HwAMF?$=F8yQi-bE?&&Y#whObQL z&C6a(S?$Q>w=owh5W;1cq#0eVEME@YEbcyjC#%2M8S3yp?`uE0HgU>hwu$MI zW35v@6*&M9g`9G5$saw`Y`U|aLm#&C>8OOz&pcQDd2f=#26w3P z$kwx~+4(bkEny!UBbb9-M&09za&wf@Bs{6z#X_iBC0`sEI2*nJ-}~n+wmx-C>eReP zBOY?PKb=%Aao+dB!%0wFBTbXCWakUPzN`$|D$;R$%eu`JCf2(uEs;;6YMb!Y@ZbVm zFEl#!pU&*KQVth`4HYvLVEo`~Gj~PksmJh1cIjhKc(T0v3(h_+MKos6IyOuwS}GO1 z4LPf~F1PYe*?zyKy$D@x1z5Lv^EA1NCHMGO_`}B_g;`%&`LO(3wR15o{Nr!^pOmxk ze<_v!uG;A@VojgPpTOv|UI}%^9SJ7T?$reg)c_t9RaK3YnfVsed-j`f^tX;YZcbhq zbb*s(FHhJ1l%+Qq&r68&chQcNtd&}WC%K&?v28axR+hEVa)y}W!tH-C3lZ3g=)9YBEY)|1G0 zC?ecNg$W|`^h{q;v)t>ZD#U6*EOg79D{OBPwZDeSJZ^-N#rqdDPZ zrlaC^*VN-Kz4|_iHkxzkI>s7#=weD|`YRX&v5wn~oEjZ?>SR2C(c2r{5=xEhi<~<#8;)HAE9wWWW zC(yO8W_uAD$mU*`G&D~m4*Zg>BhP3>fQFs=tr43Tg5*!T?pt>C!C?H4C(-QAH?k@m zxGR)$gvFk{wiDyUs3|23M!f8Ek`DtSWCnsXqV2N+sLr8Y5jmR7Zx4SzznZn^!ZSEAC~~@}r6~qB0l7`k+IK!71|Nk9qoKgp zmVHhOe(tbIL*y`OFrQ@9nKV&42kHd#fbB(+drylZahedm|d{h!1kj33p*I|#^UZ`WJEt0Kn} zKazU-i`^{9n%Rc>%6#p?tK(A^l_k7OxfpV(B#(NSlaK?04ik6%LPq2|#a{T00wG9!@3GKaJ1`#<`@s|^3SWYM*h ze&#(ze8z|tGRgE$HfT#Zs-csBzI9!+xO`J9OpN3b)GI)G*e`5iKN?uPc-~if z%CpyB=cKfrMu)q`n>%T^ZoS)E9Pl~u%udw6Y6ZWxIRD4l#BzUF06gaDq0{Wr4Kn4o z-44Q9$M3H(WjX(vvT(g+f18K@q6BP`!%C0Ce94uMpjUQB0=7Ey=xcIM$c##)NDrj3Ke`kaQ^5^-eLjJ;Re*PsM90vCz*Vfa zhoNCJ?@$II6|U}g&DiEyc}b0wMk2Z%*TIrPer5|l1hrWKJw^H77NK_NJSIj1(#v$ZOqPEC( z&J_r&wRL;^y2T1+u)}L`Vc93V?Lx8g*IVfQM;uq)!Q6Tm*A9`H(_T=Mp97-;3HdVWff>R}mcbd(}PK+w?I93$@?-Dp8s@I(w zGK)i>xEjeJx7C&H{CDLZu;An_mf#coWO0_Jq!yEs0O0!8VPb#P@zu|4zN$O5qF}eL zg7q5Bp!D$hXB^|k&vTy2Uz#C05{Sx9%*=M%4}_NP9u5=!hqd@W5=g`f-{b|p+y9#H zXzh9Xn!MHhHy31+1LfpKw?B_8cAh?89g_FIo*89P;eJ~8Vs}m0x+jZ#Rlg@d)Aaev z{1&pxdZ9{C>!cv>92o!a?~4g^Fy?KH=Z$qBW3-qO^W_M|!WIp!=n zxZJ*t)w$h_x;2zSUa#A%c1IeI! z%ms3FPExbB=M5XLyZl}Vh($C)WK3{Hyh%dS^hs>ej^p==#R#;S|1cSc;M43%q|1%8 z7`rq!?B9EIGy_EX<@)|%1^VPNXuRDjF zi(x9U(Wz3=M%PGLhPNh9W$aX&qXY3-5`mKUImRVU&;mDT3~{7>H)!vU5=`z{YR>N{aG0lkY(C!fn-+!FlY^(J z=(=BB;KaDN?3wDkVik#Jyn3g@8jC&e7hU)>i#I=DVi~khTNXnS4Hzi?p=A+nG;&K` z?Mulh5ng4^1Yga{xNH%46b~x29u=D8s8(&z`D~jWq(cs`c))BIt+6S+NqtBj=mG|e zKDA!HZIVs~#C_FGmf<-P=AMgaHWbj|M0YGn3cSIjrDfNrE!K8ijxP=jTONsw{JRmh zcR__GI0M4R*1p87}-`dVjNq9wK70ko0R+0w{2EzV+0XVR(- z*0|Xt#oZL`ENK8RrGB^-F_@DOg=dek8zcW2IzW z<##rS3klo{>)r-DB+D)OlY4VHTFFeUw|Bi&{QTsY$L|3gwp%Q(<*{*@zF#ixy7#jI z$GfioItaQl$R=o0@&(=KN}hkEh4Rs9-a3x$cO5q+TUJD_a;KeBaz*rX5HtFm*}j;4Z*Pb-mSC_ z$T~mRv?m@Ih|UlwVBx>`>USe}WCC{#130f)`=xHS*pELTMMSi$nFKCcxC?%dTAA8! z5zwi!G_t0;qcsnhLrio7qW>IZaIK&&jnMzi>#v@9rcw`*8a*~UWQT*@l5>8qsVZ8F zT-VX0Wyv1DC6&>SdPS?o*Ga<7*~e*RFvCSpGpmY$Q&z-09@Um}_*xE>GEDulBBMwot((Z1f?tFb~YEvZwGIEaFj=l>iekGlm%DU@tQmwRJCySsd}L>AW%m zzX2o;1)%3cH8$5ey21CIsbIOlPFHMW|E)Ur#2zx)XGo9iyOVlHSnUMY`2i#Uz9lx_ z_hs0vK;m75q!baueG^5vcnSs%VeaMl8>%n&h{E_C@;&dgY|bw57tawcU3)%2f9Lr( z6j9qWSGI57l3LUc6j9IMQ&{CZ$H@T5kHELT*-ntxjqbzA5aEiiT&P-Ubr3#Fl71!E zM?hChCwD6}5Dc8)DW!Y@bY>GIYL=Ewt=+RT3q?mo^>Bk`fPEl^T-pK~gFT){Slm9m zy}Nj_zv2x&C%3pFV^@9=A$o_#k`nXRQi&M9_V6@m_66xY_p1mqrOG{T7thN~=gjPn zTgWbhtLKJIAIM@bQf~Q>{n=m$TQ8op&9`&X4KsyHLfP#1QT3;ux#ob>+{Xea1(eqg zqJiy$>xqv6pG4{9X@yNX_R2xlh~I$o-;t7|TP?qX;!PzH zxl~Q32ier&QXh!he$ERj0QJ|et3C4&w~LqD(tNAaRn&ctRi|pXNfi7PIT*lmTlqxj z>MXOxAhtw;4mCsE;xf`<%uVWlOPxA~bADh!{o%yNy;{oL&~hslEj4)^(!6>5T;uR9 zK)k_cHSzdm$EhgL|8{3-L{Dzrdg}>;MN^tA$?h|9Z$XGxN~cGI^5&9<#Cq7(ZOtjd zWatFF=LGa2KcdWUM3_05r?LL%>D9yDtw{RehCMT*{Mu%9>*ZE6FBNr$0KQ!Nk;F>Oc31tN)P?VF76vG^3nz-h%7Uno(mlz~d7>Sr_GoZ>-@UcxhSNPaPXlNz zqQs#N(-Op@-Kfw1jlt63{)F@Mv>oy5#N=m{}=JZ zMMVWXEKQwwHwf8xxF?v|4dmt=&#$Lr^DLs-I+@86cT6wdDDU(f5{vbxu=V?4_Z=0I zN!<&8C&ZN2{{uYT`M%q8wo`cgI2ThUcUVuE{7~vqmviS5hj;l)<2m;CW^(LqEl{{# zfk2zC8xS3?Fe?(CLy3dxpI^ex5MC@0K`M6GvONbA9#Fm884-h20y#R6$;a3DYVrGY z#$r+$kU{yuHCuG48pm3v z_~ti%<@cBo`>m}()4PYxq}_W2dx{49aMRK>occ+jh}JVGp~x#+yOP{P9du3UN!hG$ z4k%ASHY_n|K5-Qb$w{rgW9-neie#$##j72b3usgGuV<*Sq%V97Nbr)BT1J%N@H9Nc zP{fZ?kGf)TQ2u7zu7TpD8-33ROGez8(ZCH+y!)`4>@bv(7QbIPUS0RIwToX`eodwN zgdl1o_Eo}@fti*pN)9X4K(Sf!(*(otgPbBb@YpGm6(4J`Q*IJBZY!m!Tq?w=4gbS6J*%MY&7Zyq-8^-Q#+ zSV~{8WWFkBswIi^S*%{5i_pg76ARru6W6V1-s+}0IP{-QpuP`~_8+wJE;z2t)0BQI zK(!$1_sKsRCJIKHb@x8hJT({mRx#TCMFy%Om6p)QT*^EL+tkS;7Q62HB;2Kii~R0m zt|lIl-X~ozX(#Je{8Xokb-Oeek7_{SRJDCV-nOq_Vgs^5)r7#_f?`Rdp3@k`|Ea0MM4qP!YT za<5Zw2bOK*>5yNUjpSzr6zoV;c(z{)#WmZCirr1(Kv~8nbv6JnQ5MCMBIR#2I+Y1w z5B7_&fwBzxGHkD$FV;$myIlMDg0mi2t8{rTdFV1@0LCxk8KH?mdxYsWmdYZZZcEVv zl@31~vX7lm?Ybhwm5?h&Ox1snMjzjV^z-DS+Ipk7raIbFiuvMeia1%dP4THhnn&U| zjmf+;USnHX8wXN2{#x*Au}8@j65qbJt_3=0?I^Ia)l_9MB22Qw9Khaom%sN*M}enj zcB0FT(3Axvw_<(yv*si{^^?4@Ulbmg8S%l>^TuhLG?v)c+twCK?M{afDTC8}D;Ac` zRvdqYmZX+114@rXaqPr;5}&Ha7WY3;T`7>jN}@7HDbzqT6M|7qh0f<0u=&;*SBK{v zVH_)pH&?tisvdTP(b~rE=%`Ap6gC8$;jvZk;=4pSKbnb7*{B>>tIvyh(4mY)dR0l?B-{xNsYxya3n3126>7w(}|9w?2UjEnV!nLKAC3aokx zIzH`MZ!1k@#N`UI*^O2|RygjxuS1a<-KEv;$Nlr2GY(`$X*&FAl^~#Ac8HcfBF{jK z+a#6tkV6&hou-5wRmw=!4E=a$1%Ofg*O@ukFmq_Ro%DTRL(aE+s0)8A_t$)RUU2ot zSdPk|6K@nCuD9v2WiuH~K)BF*`A3{D%F;jE;={O`oTw7U{ZLX@@;biMt|Tg&$({uo zPFr}zx(N%pvK9BwwFIgmgNv#-R$7NUdT;LpmzfO^7BQ1nKKtKXnBNEN6uPlzb?v~Q8@e^BhVc?U#9&`)s8$dOUQ~W^KDz(gc^Dc9u`=cYe&kGdbZ>Z~H~N8S(aNC%fkSAjJqkV4(iD z8ZJ5|>dLHTy9o6LCG%lO7SC9i3Ub`~ne|*sNwdz`+9s-HJ48AS0MzQVK+z6>nu^Sq z@ntPo$Bc>cZEcOfKApReSw?Q24^k4m*y};Fa-`{mJLq}VaxQE%d!5|s-YQHW5&*(x zbNVe6C2i>J@!ZLbH=9STQuP&u&Nt$Z)39$zxD+-9<`6gPvz31oQ%!xWNeeV6h z?)8K`8Or+9_{|Agy?pZeXpVfgmu> z?_kj8=WkGf%=#d2#Huc;S?74Rzpc(m_qlr_*S>1rPPun+J5<*d2}-_94Uk;+tXFt* zx>RBDr4RAc(QXD~uhNwM)GpnB&TbxwxI0*}jN3P5y;)Qwey}c(4+MhYRI^C~9?af% z8ogoQ(g?|^zK>f^lUF~INXKeie0^XYEA7`;!u5u`!P!#v5UW_tX4M`h^_{fe{&9c7 z$$fvKoaF;NnV2x0@K?!QHFwFt$Oir3&Ej?|T{-4D>RjE)TxwXsoX;#GxiNrWnGrFM zOcLB|;zOW%h9E$(x{g4Ad^=n9FKVf=RGZFoj!h5msNnyje0f^0OE#!oBZJ3^+n&!` zA~lx&6PxPRa91~dBIlr^GMNkPY~Al@<<6il&U0p3y`Jwi;0F38=_Pg-B_>-i|9^zE zJA5;!gyKNoi%$1x_Y6o?QtFx7f}?>ohKeUND?G4MD;>)z2r))uJ&(z6I8`_Br5#MM*M zx+tbmh8*>Iu>>RBx=d>Vkm9jipYsvYGla$l+~D~u>8Vn)rE4`>#Blf6gSUfyA|2Cs z?ZW`VP__6|kmz8)_St+4&+Dtl=f@Al6d7C3idGE}?G8=js?SUfn2(YwMvL8F%1hAn z_x+4?aAtq+yYw<(D*gSf!W50jodc=mXK)sE?_GN354G zo%OwQtV>?5w@a<-*QqlFk6v+oqS)ZoPbr*PBg@qLJ1Vl}4Trcv9yxs2jIapn&7i*& zH|tm^Jq*R47917F*_@e2*-@k;xx(p#xwJgtdeU$hBId_!hyZ2g6SlW#jLW71UK)Mn z+<+!p-fZzy>ol_?X3AHtf?*-kVr7=6?5(YaV?+}a&n^M20O7wMc%`eRWU3l-^Q%|V zxD*S|79`UY;=Vg+zOaQ=4>5^Rnuf9V)Ce_zVTxy&%Z}fdKdbzcJ~OCToL3xVtzo7` z!Q>VKE}mqJgI&3JdrbTEe$`TSaL!})G(@oNzAT?A@Ug$PSKF`K$twZ*0Xuu7G!lRA zp(b(mfgSx8lK%j(#3YeVImsJ1sQ)g+IY2^tnqt9o4H55hFY4%M?>5k4dr7-JK~EQ|HmH{(@Qp8CTHZ|6UPP|-;J zia6PYeI(zkUbJn|DcamV0+YY z-QxLOe`mJIqT%b)XtI*BDB78Fwl62Hr6F42Y}(1E9p?!vG{N!7GAULanTGxT#_=P` z-+^a?W!SGLXaNJrFa99j$B$j#a?XBE50(i8@48O|t-Fm>_0<8%1fxFI=51X^sx62g znrdzzQr%~svD!SHp1h(rP;T*S{AAJ#B}j)YQ{5$AQ05co;i+`E3zENYdOPMMXqS&s zH~ZIJj2rgP;a_n-^qKdndb5CB%G;Q*6}juagbB(Y1JupOJ);Ye;mWG)0VrmhGX2s5 z2utoQgSYVKdbo?<`R(RtqHVv_-ud%yx6cUOb!UpjlLcOdcAev0?uBURD-{9{r?^(b z)&4$3bs6maD>P#aKkQ!oaro$`NIh;#C>ai)f;z_Dzltz^Gsa@c$BelLl#boKwwdqn z5an>RjT<;?G6<-=!lYa1xFQI7q7Y4?`XDZWfc$Bm=qaUv?GU+n_Cw(4i;8T9;Qzzc zJAcRdh5z17lQg!I#%&tgwlT4jMvaY$ZQHhOHg0TV+SrRv?dd_e6AF$Wn z*L}V5nvv$HK@t2O&e_3thg%&j?)687?=YwePquT_l;lIg=}q=5^fu657%+c`=7$?F zqV0CGMTjHw_{oZF9Kx3;-Q8JyLN9;BmYr%UVVUbs`j`E?*o@pC9>yWGldhRrc9gm; zc>sRKQzZ={%YBZh5w@MOQp2cjpf=k{%@me#&`T^_nZ4 zVuO^}zh(-)Paz$r#%%Q5tTk8%D0;nW9)--fqxT#<#^pb6p5l8RX9-E+y^MU9eS3>U zae1l|RHRFdSx6#rMe`gVyD#)vmgj!FSr#p}E6Dz}b@`7HStPt=KLc%+%6)TMK4wMG zFnxnpg|nGsD^z%4?o|>!w8iStX#-e$>kCr0&KbWir_>5jHeMii?H5>4!Kjo8j38+Z zX07qFj(W^!eLZdYgYQG~E*W5sogX0cw&fXfE0NJweBmDAA!j`_OEj`_&J-NmKiT{k zTcMcW^Ot)h`NByo9)?GK`Im@fNZof6n-5QN|6jM~?FStNOf1i`2X3qLN{82Y@nyjp zzh0{)tNFWdsa$#UyjvE}KS38tnO9Mc?RCrA^#>uK{E+NXUwB`%lR(qNDqd+GZ6O%L zj#q4p#QM%R#8H+|)bz>P*QD}~NYeOMU(?74p0hBk_eF3-$9c=>Was$m4fLaRuDNaB z)T7=Xt@VW6j6#e;rQePMxu-Lo{K1Xc?XPWAnCck-$qIE3z0I@E236_X0D!80*5iTeC*-**5@1MdlhvGskv|e7>aPF58*)f z%!o^0Ks_1?b8<>v@UJ_k_W4ah5B#2sVsRznA@md!PqP_9rJ8T2c$QP+G*3HH2Euh- zbCfi-!Y(#zoMNOeiybbYr1zNIo5Hv`Eu_SR zl5r`W0p|D`ehrqeoTY54i0ku8WJjZ_-?2*0*v&EQfMOU?LYaBShSA5|8{{E#7zUBn zz>+1u{R7(8((T9aVr(AuvpJW#nkHV}U}t;UVdte(Bn6S_Sw!fFjuPxf9sSxkuLced z401eUF(a(AO)+W&`cYJLcEGUF5+phuhbBNJkCL;yf(zg#38d}=beMMNa(%x8M#LJ; zN77pwN0j9yzM$c71SPAmYn*eSZt`X%ZjkJGlyJgzjv8An@@SZUuf|7B+2K+<7r|mJ z)+>+q3Seg`R3iou7sWxLSWv&>F3JH)+Z&Q<=B2SPRU!+ncDP-*rPNYgn~CBwi`;0# zYCk|%^8G_+ys@&^X|Gy+Pj#iC?T>@8N;Q+{i66Y1a=rV=f}S*f)mtggwmK~sbfA}4=)ZdLN4Ku(73d#vh-jI zj6ge--;|}cjW+CXE_{_u zoV=v6RkjWy$r_m?Z)u?J14MKE2^ErK$MNP7wFE|lGtd<<5=!vVF%L!(yPLU z9_S9838wLCoG#Aj=;N&)U=OlH5~60Ju89OuD^0Z8O?qM~KH3?a&<_tatYV&AjV00n ze3q(opLEb(wMFE#wHu-;FhncErwq$S7s6?LWf8dNSZ$qfOa-A05oApfOoQmlv};eV z9RJ2yH2(Q4z;$p{y+5u4`d@!N2N-TrBJ3aNCv zFgvxvi70A{Zw-+Cb~a*~86J*G!u6_;{f*r;1n)pKJ;~OrB%vF6FAMy<8^iVC>b75} zni^$f8_$Vp(pBgvF6B>i$jr|ker2RA6dQ@5t-#8Bf)btjyaM*9@H%)k@a1~Ijq>oVKt+bRgC3B^@}=Hj2iut4uH*%Uo=6a z*0h>TY47VHP@bd8{_vmbm!n*A5|{J^ea| zqe#*GB&;Lz%LBHS<|l+7_!s#UEJSmGv)Por z+rKBL@@sFYMgC~k9yF2=;2bRfvtgDR3-@Ho?J$weUzS4z6z0>W#!lWgG&`}=jXSMV?iB2CH0+1#s2*e(ta4$D4VE z5>ac+Q^)(459B=paYv45LXDiS+m4ISEt2^(bWXg>ma?XV3eNpw8*Y=cq_yqCk0&4A zB)f(Damq+c&)+?0WpE^Z?6zyYQ3l}Urei*#-XWetInWyi%6y>URh?2?Y_o8|%798X_PsOv~ahm%7#?zyYzSmp<@-1B9V{5I*jM|QjQ&*gWGVl=C< z79%8|u#GVPFRa<4>$)JGT7_k0<2L)bp`s57$`s|9I2_il@7qB_+bXsXt}|6eox{dt z6S+jB6|87}DYDl3%c4WK|HL#`2KtsM9yADZ zkULc|wU$FuANr+WyC5h&eCa((>Sy>g-PS8#=eMhSlql!RGzyfzEarLzZyZyS`u1%) z4~C++ESn1{3BAm9xIkD&=dBH&bqIBTjl=aZn7n?wS-!dA6Lo!4(05s3n>*a~DHdRi zgMUrxkoua=f_2`!WH4|@nbQYLUA}z;z4l zd`Ry)T2n19Ea{_A(o?~qUCXOIQ_?T>(b=$^4z{s$<`A!B&G=Iks0PR!U-{FM3{v4D zIpRQxAH56yuN^vwqR2NlMKq!vDOAfw%mgvWKRsQJ1=t+%Qh&13WAp@PF5lX~X*sCb zo+oxo=`1f|srn_?XY>O@dfS7lRKVqWL^?dtm-Cmz4Qk`{-&dpy25r5=P}oB>yh3BE z&C0M88nvukAU1`iSA5N>B{*sEw^OlBwZb zhtX?onBvA*<2_kIOA*qhM*931(44kA%80z}`p-1pGiFTvmIz)Nt;3Wn{U~07cTXnf zd2b&&6v6zPP%D1Yl|;$nFRLOvWTa;##F^lkBTD~1#YKwU`d`q>im)ixMeOz@)?l&{ zF1eyO5$@7lCu2$UQhzIld%Yj92wzKZj33`8Yn&*+U4-iG6yepk~_qzp{e zK=N)hgkk&1ZHdoT4OXAPh*qEZ#f{}k{dLER_$K7?}pqT@CMQQH46>R(=Mqz_deeHg^iWJnr z1#JzV`a5YK33nw=@pp&YU53bnb)*Gj#-p=jzpqM|Abue&(0EuYGLk2EKjY<3wILJn z8{H2emHOF@VHdnH7~i)0eKW$?dlb!8l|_^ar!hd(K6iNaQ{U&e36-~hUxobbjKt5~ zt?jMq0QBkAG9P5$*p;ZTepRtfv(K%L(s+gLB*tdmoC@E*pmN-L(dvs<7&G}kyTOrBj5z>asF zlNKLE;l5t0fZ~RyWamJ=OvgkP<&|PR1}um&dZJ|E7xai8Z}F`Oqew{^C-|?9O?tmT zco%Z8@Z+l+csKFEQ7d~$6h93V+iMB8MO`W+pEZ>lrkMHW27BnNVoSR{LL_=;_=~x% z>mdVAAtXP5*++L%t`LifZO4?LHOGcM;T7B#aC{Pp8;vlR)`hLO8*)@ zW$`|-l`{1kJF!}wL`d5FI7c{Sdvc^lHlmM7R6LgA10v%)W$domPIFZ}hQD3QmGz)5 z`5uX!e#_aWvXm_+0v;{?$dN7+JQhEhvMaK#1xfe4zN)+Ia}Zgq&C~FnSD+OoFg}3; zz?v|a+o6Bw+e2RMF%xI?O1QTr*Pia?9w`fana}%~P}Oo!W)f z4#`o&@B{B(-B46J_n{E9h{I;vrtru&etqT{lF6)I*5l8|HYZfRrli58`ptpifi5F5 zeZk8^p?94qcvdVrBOZ)#^0PzP_Ou~O6cpodDs3+)=x0(U53w!+`<~fL4<+qrVGR^E zB>reBnTN81uo+Xf685Ysr}B*~_(GpMg#Zthjn0-2XGRL#3^TwXG=ljs_rAhTKO?Qd z{PVao|w-nJ!BbYerAFq=&@s-NAJ%>zBqSEfx0vkG5Hg{`LBF2zSwztOG5fa{uz!NzJkX z#Z}dHhY*yyb~xYoWI44Qq=(mY#%Ag{yAig{A6Ab(-7pPndzmtkE(I$nEQ_Pa16=XzvM%iBKWDChV4SQ$FBau1B~rinx7vV$~{d` zyqSA=+Wt6?BLQ=M3x&OF*i1VJPJHET$51;!=XZ*likW-;e6^|L)1!u@ylb;Bwz%!o9c+sWe`%7b( zDyGzSXw|UjX|QtC03#>o^~mddXxdFn!8$X=qpGXSGSP!4@t1*6e3BA;L9g>t;+Nm+ zz_%~5i$}NbXC^Hp^&L~2=5|$U+}5H39lNyi84Q?jj_rTn06D1x0+=bHEOON3KrCH! zaf63)HBS4hCMV$6`K)QgYVDJHPAjlfYKPG5k1ZNP-JU)#lsJqUy$$JXg z-|ifn<9H`|sV(n?A*SVXr)s8o6Gg#@3)i@=xhR7>&!=(CnS@Km_P?5tU1vXW(8Dy)Vk(p4%0{k` zJVhwntHCX&3*124-)%#Y%wf69M+OtSJsA`ew6ijt^pbIi3O`Xaa4lXlmv|YdAlHpL z;H)cfAF{?%b&6E9MM1AEkJbMBQ8yk1DFao$7vlcy_^dq;nW!^-g{(naB1FBasql}L zzU%Rf8qvbPLBs&I_AF7zC1T@8)YRkuhv>+yAB#k%=L238s?()i1}w7w$L~d*ia-0RPOQMdj3_HVl&(l%2{Js;r{gvh2`P*}J6{NB7F+LaCm1VwNm9=tI zrMi#Wt+mIXu9}~q>XX7i<@k;xG=qA*<}8`s>#=GNo(~NrXMC3kAaiYbqFahlB=YbqkzvjT-yBfn(w}k5OBKAa=%g8^SFuRs&8ELLsT?u{s;w<`hxoA z8ty&0r>3VTu7B`9t|gZ)W%1Q@9O!s1M*Ns>>wVVOX|-C)Nm=~pM>1Udh_YdPfeH~3 z4hRM?hbqKBF(pVKLdziAu&AMOz64WvP@lD-^DKH8SYRK50BP$Gtb{P!QI7`dG=0pl z9yGN$MwL4Cf@U#Z;}%^~7)%9>D_}Jkb#LI6zHmmTQ!)foFAP3Gl`dICk#O;vcSq_G zm%>L`_Gb)L@>Zayd9O-#IXqn^{?Dn@pjk%9gjAC~_VHJ^7P4pZlTD(?MQ;xKw(3pBa#C3|3!op3-R#)n-F5FN@uyC>7R6ZA- z!+}}QU_=XMgmP1mM*iJolxB@WTolR)1&g%({lp;;Xu`NhFkBsM?U77Do#J6$`ZF)1 z7V5=5Ku{_q@mh43npOgR-52+9FJ}~>*iX@N+SN)*2ijR;2XKKa&C{wIex^Te=q4a6xX; zdj_J#*oX9@kaiMTnyh z-}`X80KHRTyoHNR^ue!lt#-@ZFT4-_J*Ls?3G0pykz**cwwHg+8OnzPEK!LY@sjxN z2sq66gt?{BZSs*1-2}d7UM8tz<^1X#qz2>&{{#2@>e!}qfuC=WT8L@yy zc>62HfIx@KUEIqukW?Wsk-xs96W&O?YtCd|I3`RJu^gM>g#JW)NG`H1o7AfqY~K00 z^0iG2of{qTS3un5Oo6`e(UV_kP%8KwA3`Xi~8iWAL|@`n{7Je&D{VVn@2S;6VC0$%I*Jc z(0HZ*3Yc4$gGni43>j4#ojY$>5NT`1Ptwve>!O4VP-7@| zlM$KvEk7yC40u|d3Eekj{)S~$hBJ}<<7>xz{+!1#?!$y^XZ+-2&p96afxK-X*VBv| z4Iy~zW+PUa_8Dj0`yrtv4{;uOC)s_(KgjbTF%`LkFR`nvr$26%p}mRe5L=~Ss{WGo zH)qLl(#)Zbf^$VGf<|t!g8Cm1&S4(+IewDP91M=Ege8pvqIr$zni)T%?hjIBj# zUI4HK83sPyq$wnkw4mD{_;Rrrk(`js7O`Jn4tGfEt|oM2uj{KV6~OGgrmB4i6+!<1 z!%pmwp9b$Cw8SfnmOsRsq4@WfDkVR7x%uxUilfi;dcnNOKJCYhs|RQ&w0moU zu&sekOFe&s%o|5AJwlNdvN-YAa6_K`y&_?Fyx=zs#4;6KYn)ap&!0X4GHNo7(JtB^ zi*cU|yz1Z9-1@@)5zL$4@TV-*kn!~2*ic_`R3RLv2>%-|&@@v(E6z}%CjDU>r7V3z zBC$CNOQTxjE|`txPE!2`8IUt>$noPNHpgpbBY37gPymdxDyc)o32Z@)u~-4y5NDEw z>l6}x!Keh-%4Hq`LhzTs7@P`d0R#BUCp&KpOx3~VXM-o`QTqVS=_d9FrLYj3fG!1H z;tH|dGcX}0+(}3)v&(77ZNm$&FIqOlcDRe3bSxmYR&2gi*^jHx3s#LU< zE_l5p*A4c1O=j#crqp*DmZazm24l?no~F+KgR3nZMAefCBdw1_6|%rlh^2xo7=olh zk7Ft9Uk@OGWu!`BXMOXj4HUlE$ymn@Ek~*y(Wy$V7Io^X&DVF9G7Cbzx6Up@{nkfE zgUj=v90%?e-G!##1WqSj_X4Yq2$P{)SL*4dU9_M~x-a4QRh(oOaYM65SX)7JaF?5OZBEv0*+GPz_70 zzy1CFa=eXzB3fPU;3V-=`+nQIn2Nq98YI7-bPr+yF9ueMO&a@Hof}jgf2d$8o>N31 z#ux-RQY`KfLy2#Z68RCEwtR8f8D<6r0aT)iH69phJ^uLAB|Aj}>GVvN;zU*Ss z_pMM5)AxE+fcQUenV^-H|Bp}me~Wl$brV(;#+vKOa5cr|LU&pSj;Kh1{syy&DT%+W9wkKzb z81L~D$!6iU{l@Ly|D9`&&ES^j zX>TJ>c-KTje9>+gas)>#`9laI-Fb0f3n{rYlr;B#6dh;9CHckEa{}s>J@kHoO&$W0 zL4BCjxr~{-w-=88s}Jb)oW8%f;?Oj7M#;H5Ghg5Vy-LDJY$0pZf(+W{S7x7khjWH5Jav3`bws>Ooa- zQQYq$mNba|m5qc$yEC@IJ#oOjNZaHsc*Fx(bQ5Pm8OB)udKr%_>VcL}ey%;$e@y2> z6!cHv#%|4jiEM2^(m5SSfCxn)dDe;f_**rr%O|wx%6WQlS{DZnO(rQV_~piKJV06K zLtn33!v0hTQuc|BossJR<0az^B}rwC%f5DgpD*Y9n*hIPc-C#U#EMh7xhHs`=n9}Q zMieIxTU}YzK0V)4So}aN-n*Z-OTyA9)~%YQQuIZ8)e#yaC0W)AX#3dIj;zB{1RPtTR%x!JiQIAv*t(K2 z*K?CPwf^=-8%fxe=UT0&7nUAT?lo_REl`G1D8>jH6G(t6uM)us7(l*mer%3UDF6hA z@~C-qPIowB(1h2#=OZu_YH-ZRifUfRafAE)myT%YvUTZl*kyaMf{K;?Mxh+=e-Cc! z(xu6{wR0Hzl57DWB&!La`l^l7B(z?s%<7QxQPQ8*F+L-_EmYMp=XZLSFqNiOc#)3h zs1N06jIe@3?LGXEAyGfs!zKR=@Xo`<5hts5AQZ2NvF31;6i`JLOf2+q{T%<)9_*1# zM>p!IpxI;putFWDUri=VD3Y+Y%H?yl4R>~FbJ2cKO04FxJaGt>wkw`J3$v0CQymV& zQ8*FlFiRyICXG|@`OABV&eyjPb!O-69+FYaTq4wp;hC?yZ&s;bXbIkzbdIl-9<~$7 zkEnKdWbXcipRN84#HSEuk2ISlU(*@aS`VHZAP05$z|GUxd z_rWNqrOv?x`EArQEyi7cJ79%k7%aAwSxLTTEe-zbKD5t4l5vUGo-Jttp1!gh;bHK) z?#ebv9JQe#u?g*>^KR!J0vE5m1Z$Er$kU8YOLBj!K9~e=VGHf$=g`o_*yZ@NK`gT2&sG@mf_i<G%C^W zau`ctDNj2#)eQVm=PHP%DDY{wGL}ISj4F&&KLxB-YW?xx%>S0*aHIURJQfy{)w{Y= zc-q(bhJW4t4LDOnUS%e#X_|8n#i*G-+l!3efB@bjX4(KsaT> zx(Z28B=<$5JM0wA_$##8cN>*$+`D39v;L$7gZWkQg2xYUbtNw>F=MnVA|Ytf1A5ml zkUO*wHqJ{dP=~J&CYg$}Y7=;jK_($dX4S7##ZyCeW3x--*wJ9mp7l>(y=I@9A`^h9 z9YP~3WdKdH2@J&WCGlZd39=+HVa7|}CR9(Y>Ah^Dai zDJBs7vCEco5iG?mxU^QPG+(Nwv|MohrEWN+#N>3kLA_J}krPdGSQ-w#b9cH-1W)FOl-6VjF0d>b~T8yLO--SdXz>Id=%2582@H0Jz*V zd7P8~Jc-G{%D3xaS1Dlr-KTDe#cW!o`ee{z*uzNe%l(p^=?eGC!)k^&e0HI>qA@n@ z8Z$cGVY|uX2x1DZ0X*J8S&$zEiKr3)!Ji8)611&IPxvkL`KLbz5H-Z@CJ-=BCp`Cj zAdKj$e4?5&A@FiuR0{b90aQ@3;<^4R+BMrXlxAN<#G&H$?*!VxHm|wt9mOZP;%-~W z`{B=8d1H|Mee33V?dpp*#JF65?b9iFeWoSvpL*GeICzNWw{Nn-=FYm?24&yJ(Zi~h=jHt6H$1G1B40dCD?t!ua$L8U0P zT>wK;?|t*OpOxa=Vl`<7Lv)5UY7c>36TOhvyqAKTjJ`f6N`1#ID&Ef!WIGd@a&0UA z_6-cWkC&4tCHH!~$wHH6Z`_`-D@D;~>F`+f{rX!b;o-fS)eIbQeW<~EAjf}iXu-^j zUTqhoGUfw|^-Ri`EmrJGCd130znwD)ZFMA6E1NR7Lqo zKeH!KyPNd}+XMg$4-7@nm90%PoGdvvP1XQSj%Rdqt6qftA_0NO`&|ocN zLw~(;?RSvT%U->S^bP3rl;R#>hmz-9&jIJ(p6kl)eg9&SbnJS}_)zuS>@yYj@g2*! zoV%_}T<5*){eT#fzA^V44Kp3E)8#=Au~x?%fjHzYbypHJEE#1@eGn$`SjrT5v9eKA z1q0M%A0)NRe-$$J9e&&umVO~RNw|I?6E43Yc*P3ReE4D<0gAp=M7*FhORY|jh(H`> zOKP=z97rXU!&eLHy{vn=OS)^xR;f45E4>qj6x~JX8evHX9)8|y;Qv`8@O;*n2Yy_) zuG#~gCnoWr&xgqddhn8?`kNE%zV8>`oJ)DcG7p16H?6NZ_vmhHHI zt9ulA+BT1~Vp~dAyCKT8$;&|AbQ$gg_N&d0(fxJw_ce7vD`xclICSfpDv949TlRi2BoFaemV=?X`<+}LUUkTCIDzn3 z?wxRf^rw$r?$-aBH}ejg0LkKWbS1s)LQ#Agqe*B!ME58Tm;T_RMND zmZRK_-G>s8C&#yC9gefi4qK@1k; zZ%A=!JCzR3w~QZuCk-v*GqL&7`GiT{Sl@waJUaw$YkdRYVHqmE2AILlDqQ{AoWE!EV+;Fx=V1`LTWJa z#qj$&n_dA6aoiK(Tl}HCDYl2~8Tb5zSzD=?8zc${d{70xfbnWFxe9=*tTAbnC&~%i zB26u2&;}2~FU@O*e(oEyrPz)d3jJi0&!@egWGYlagULIx$d~NeeoR85w&IJ4~L1R`1XBe=Gf@XjJBuhtmNF^BhFZJ z3O?;2Y;Pf%^~UtT;l!QvU6G`SR{zoe9(Y#TQ&e2on6hh6^cV7l$4VXGYU9&|&c=|(O2L176-+N@V%CB}mB#-$-KZeg5N zwQ~pJC5N=|$|IJbGYdS~$YyZN*Db9il)EXq!jR0xW|up=>&w9bQdvh#JGq%@l$etVAnR8HJL{GxFF@%OK0Eu-Js z8ge!{xxj1rasHf*DIoHfjtV!;ak&Fc@5^tje)fMMwWpc9r3g+R{HsqQ>KV}AgHH$b zCCkFYsgH=Fo%pmw#Kq;-^-mlH`LYcje)Bz$d_>-M6K&ftFqEUfEyYtl7;z5y?hKR* z_6*iUgmB(}J$$!CQ|A*?y=u_+W`CvcVGM^ZQ#q#o!>Ym`({MD~R>=KOL!EPMW8C(< zltM=Q*h)SP;wRFnq);bfZZ5=FSMBQP-i!t+_5bwymeUmh9&-u)GZnYVw`lUKaDLt? zma=9rgqS^@?Um^zTK>@-|Fnnwi=9s5baDsRy$B%R(oc9~Co_Hhfa*evV*Hnr51h4* zPwmBSGZkAAa8Tq0?W9Yx>eRgdeaNRfhB^mp-e~ClXhb$q7ap@C<@2B8H z98pq-*PL+mO>@EGN* z2cH;jHUoiOJmuH?Cf|#?&$g~J!Gl+O5q0~h;wrm0b8~x! z6)2AV-K6jg+pn|epwcQf4ff&%;r7#E%#|x8|Ch_j#dUzWz_3=|IgufWl~w#+u~IUm zX`Hi?3@UY@a$Cq-w(H9#rc>D{$*kZRg>_~|CLOHN?P}n4!5?Q?EsmN|&iGOX?^QOF zJ}PqwD_@-N<#MVAXiwiB_KiI(r!Oi8!sL^o8z@alDO8$aZPhECz&1c%oxO|_6H{6J ziM*zQXP-YV@BpgtJ~StQoW3W#JY7i8$2}Ib#epZVLf4$>jZsB`m&`B|icJ$q@CT;w zuew>7mnRJT8{KaEMIvI~q$@!b6HG}TRa1SP{P+~SyJGzH{R2^DV;pblAH2#2fgjbU z9iwoA(SneSyt%NMCm!{+b#*LA2T(Ac>`)>wh zG0i8m)uwH6wyG8+TGoAoT|5?tPxtw~4NX)3_7Ab_(!Lh_1Dg^#g1}B&0e{~4i~d2PA-dI#{3gN{jysqZ0F#~>{H8b) z68WvFThNk72o+NPLExom&=Jy&zA;|u<`yDriXcTJgnPd$9J3LbsO=b>+37%8zW;nz zCSozNPB%YGsDD6(DoBpFsEegAhFN98d*$zQd|T&%(DTN@L{qcRGUr^!0cm)DBg=80 zH!;gyxV%4xEaWN(@x6~?!-4I3v15pw4_PghEo>j6LBeZc=$(xe_3&n5b5y~&6p!pH zC|(OxJ^XbF&t26@MF;&V<~lFP)jdc(){8u78eG%;jZjCLgF1n!Qjl`;W%^$sRx_gG zv70wPZdN34DUGbg%P03$+xqGkXHHz#_y+&qlN?EVANcWr$iA3efhIPqLgoK*(l2Y} zY_*!<<-Dyk&F`;l8+c6*X=UB%@Q75Uv0e00AWyuHbe^YQI9{_w(B;7L3(~*PR|=TL9sj^5S!&C zhg|n*te_1;&fAdJyp)h%z}M$3oVNMVX~DeQcXlD6|5k-P#K8Nm`IDt8%Y{+}r!zFa z_GH25wWec&GC|M(D#<@EbY9-Wn6xJ!wOq+Nk?*2Y$uI>aX7MFD=-MsbwcjS%OEx>d zWPMVHpC`+cyN{Bns9-#p$y_C8M=x(hPl7j%?R;paje#EDC#mO6A;f0IeAe4pyRoBx zPztHiL366n`B-(214@t+lL|ZmT|2Jwj4E9}tS954noZQ4W6FA0_F@UEUIp(jEA~d+ ze-u#Eh7)#3DkBY}O5?UO(g=l3P>s|oqia88q*8ryMRH4_wTfP}wn11LLItK7>+B^W zmfJTwH8OR<*~W(dp?yfejbjYV>Db%+!LS5f%%*nq}e7QZ^c?JrT4w3<5pO|3ZBF@7%sy6Fp(Fr zB?|vu?DZ(rbVARG{U3$lx4dN;114J0y`x*T%bWq98A z=RByyQnerDG&E3y412EdB6npM$PP08VzU!*4~C_iwo5X7-c>l6I(*R!X7f%+B> z{A-jmxjBenNQb>EU^kWqY`S=cyV7+&agr-MT9w@1T|F@Gg3^+tNlj#R-#{H~LV~x$ zI81!c*Ig!EQE|G6*78B8L-}zIcCJ6orG^IzNh0EiyJcKtNKLVbDWGmFZ6 zkZf$Q0`PSqgrGW?Glsq7CbWuH5INUfH1alKvvHV~h@*GNaua{7%eZtE1tZgO+{5t< z%U{GCPlluogXyUv74-)9P^r(=Y|M)pqV;iTlUo{}1+{{5+B07A7R-qcD zh-78ZE4BKSFS;-_>Ze5oa+<~kIgafdy^ytX`1{$Bqb~tzK@<)3m!7n`ykF;Q+BQY2 z9=W|6*7);TG(NlX2rzKm4H`nB(Epr=Wpw4((}8{&cyFq=r zRQ+kuxn~1c-WQFCi#<}kz(a})2R7#FSsO3I*IL);hHo@d37tXMw#(}0$cOpe&_9=D z%mroghbF7*_wlnrngsO+a|sc-bh1WLRaHQ1yVS_ckOx+FDxE!B@AlIx>Nko)+_>*# zOFy$IH8vF`OJ}yGuP1el#HAE7?^@A7eCdxd9ibZ))Fq4fpZDRPJg9>h8V1#&MHpbB z_;xu*_^W{Eet7qh9dRM+?dSuz_l7L+Hno01yZ9^Bo*jF2<9Z=Dft$JJoLLf({^-X| z{nqu4G<0q)f|%VzzY_5zr%bl}#k?)~9_37OU#wXZu=BP(K?kC2SU;QyzBLlN$sk!vIk^5LP#-; zi4_0-V+LR?V!iT{x=Dz0T>7D%^q_6UV>dhJWhv|slwlrojZ-uGWw`U?`y>yTK=m$t z03R8}2u+a10-$T3%TRNqq?l--WbBa$!NX5{qwY!_|MS|Ey6h4ud42eo+ zYPXu+rG$y!yvzR~gYqgU&WNOH<}jDw1fh0@tIQ{Ojc$1(a@nTxXR9n7nNr=o(#twvMV_CK*gBEhb^9@U%6?) zeQ@k^y!@%?+4<~!09n3xJVTv~T#0t>Z~MclYiLIhiN% zki7e4CS{fdl@IsFT}Q#|d4-(ch|GX`q?eYVo027S47 zOvh|MBmFz~m_nY)-l@l-@wq`A-O*_jebaqtV@BUZ%V(V_IpKr&X6a1fAAhp@oV6rp-smH$#nJ9zTTq zyw5EWb#((016OM(nWpE9@AL2ZZ6vxe=k^xqxI?Lf>m#selstROWm|CXd!xsDZ=R+e zniVnNZ{Tv$bCeOslPbxJpuAp|9l=hvaMXMP485Ulj1FWje7!S}G5%ME}09{^4^4>=4R>&PVLvIMnR7tP}xUnAAa3Fr;j|iT)aD6 z$Blu-VI%wksj&FeJC&V!J_4N?arTq`=kOkT#W^j!5RViTY3cye>C(!eho4I z(2^jYz8;x8<%Q`K(%M5KPX>bn`2E9Df-U!n^X7Yl32K?MY(sdQlkHBS1;{7u4P&fK zz!aetdTWUxA?;2umGBM$D^M(Bz7(3!X4&G3oX&dx4(`?dGIFwW;ie!bNv3>bCaeeVOAR3-U$fTWowEhjE6 zVKrJP{djf7cbz3V^k$ii55n*bt5$bjE8;J`L6W|D9D)+emnHo3h8uWd4?{vSbg{q*~tRHUIw{ z#**aH=7$!s&ogsVO;+@LJ8b@g-2Q)sQ~&#GvgN(wug=qySsU-@$39k-#Fd@c_cx+W zhf8uSx5amcEW)ae!8}kPSw5H0rMu^L(xr&zc6~~1pDUie7nrm2p!D43c&Tm7gNRdM z;StY3>c{3O+0#|od)OP85>$fC_lNdOz}_8O;6?EHb!?%_ZVDTVf4cD0ew%ibYr8i| z(d$+lvh*&PGa|W((wXBXdg=Z;26@2qo)1mJFNTc!y;Arb56%=Ey$4axujY;5dO|-f zt_kJe`@Qfrdz>oj@N&p9xgQLEoLVpZ7TqYDH=DDWUr0y-PbWN}qPE`iEf+8Yk=0Ds zT_=$RGd`e7z31@*wD*6a05bRhI^^%Ql%ewF1z&h}xZ!T_MjPXS8gLiWr&{N;pK(6H z#L8DJileBA&H2MZMcDLX@@$$fZ&7ISO=6^RYKF%G=5Y5sX=RhcPn-*~g?$t1;+K{g zhGau3^+w6m_y`z|zBTe)+PM6lf3Ik!tCiFypD8Ez{Aoh!HoJ83mno|->?mD_m!zUt zW)xRLLorFO$%~`W*Raq(75Zt7rsu9HaPEm<+8;IILr_H z{T;z=lpMGG9#yYWvX+{#J{eK@SDPBKu%KvxdHQ8R{E{WME4JT7{SC5seDaIaxltHN z$I8Gjw(=we-AIZy6spcwq?q4l4`=7BSR`lI@||+ktX+(EOp(ma8>umC=NF>M6Ac>= zs0sFJ&nXYKd!;p{3=x=Ru`m5rrT*&nYI`NbRYp@BvN=O1d{uunT<~%HGNgx14th&) zhQ!I#r2Sx@P;LgTzInWN4`R_*Ovb3ntGsW$z=;Xw?ZPt3gG`&sQiKvz${J({gBksW zI$=+SEZp!wNDy_RO4B`k;z9JZr z;~vX{2;p)%@&OX#$FwLkL0Lpz<8PQHK5?8zJK4o$^FtF)3{fL{H-6X|@0&I?ZPlv+JlKMeafHJkE#1Y_;_=;FkJ&z^Yc>OXT`WT4!suP=YeQ`f06UQG>NS%1I z!F#4UsRo~H7Iu2dN~Id@=zBfcd*j2W-15#d5$P1qgnrtYR+8+vcK#D*qc-3X$!RpY z+Ymg7fHNqFb0+^Mvx_D3L4`R8jBTUcehPuc>d=vYriMrI$@6&IP0F+#5-|9EMVl? z=a@Y2;!VG70B#f$-Qs>o4o2h(Jfu`L&$Z0W0bTSgeo|s>OqjCxst5;P4tmo;B+Q&L z&%yG`e|weTl>^2O9daXhWoeK>fU36HQSnI8fM-$Sl;$56relKBEVIm!n>C}N_&Y4n zCKVc5Ywb%{1>8)S?|6u70qRdUrPT+mAYpNFqy?hKO{?*g|HaljM%NX#ZM$vIplxj1 zW@DQxww=bd*`Tp)+iGL4*lNrbJ1hCt^Nl^;vEOI!KlA?_bBsB!d7bBd9F&RmFDSht zJC(UC2HJBG70e+W7loqs>>{O$oR>n93}zCaW(JYYfL-rZL?P9@ZN(fa=Jd^<-j4xe zWXn=li?-u+*dMMtsZu;S`E%C0Wn_85S_v-Twn5e9nGaKN$;d*iqAe_}7$@AHH5d%Q z)5LzCBFm{+6%b5#=*^zNmuh6ca?r|!6-c&#C5mzAn*JKwa3etXWxB`A*VJSdCtga+ z<1hHjzaK(buVej;wtV5Fp-ZFF8SXtZRHZd1j-11Y zb_aR2pG1Uz{J3G4kC0SD>oq=!s+Jk$DTk*jzd-%&4;`m^Cl{jcfBSXTH$$0xqYBSa z+pj+l-1Ss>W5@zr#9Id=1Q6{;a7>PL=?W!V9PILdMW}_j29+W9)UYI_^Lq|g> z>bGOdk{ZtGvPt54na~u!AA1>{C}DnTJ#~3*9D5mZ@7QCYT=wljt@Eb@s!qV!20)gL>mP($GIRjDYU2D3~!}+8Yk{g`PE@+;jatX*r_+=mxQ6Hm=YqFulq{b zd$t-evk_Z&G`zciQl?``{{RU@!h9n0C-fi<&X#2S$>hQYr*yEaf@!7*r3AtxvOOz*}`M8Z;ki0TUNU(3CD-17zMU_ZjUJ^$$?*49;V)sqNFa4sho zS=L@nyQBF-s_*^0=sIlKD{C3>k05iaW1`Psbu8W+zNMcC;B^iMP^HlXbr&n2F%Dzt zG9P#pS><@fdtTxkUFBZm9#9DWNQlvt_)aL5q{HuAjE6o%t#Wo)F4T#Ho}m1TZE>W? z|ELo541@o+_1CT`eE6TQMqiJWXq(jepH8Up)K_VR+Z&*!OB~+8z!f9}#f{&PQZ zQVh2pCVDTno@4j5r=Y1JB=VT=NMB}K=%aG{_Hoq~fzV??rxOSX)Wt3vkYPoXo9xb& zOzBhOW>+r*s02f5I%IvWr3q!zN8B3R(Zbww-4l4$ar5-b{ zf_D|0xh~s2#8OfO{wxq>&Wt|Nb{>)2Zb(IQNCyNXLH0A=0R%s$>5YPVU+)qgAw$EW zBSM0RTFyC#_Y&s!g@YA$!wtd?-e^7Nk(R(0?j&gy&t|0 zCXuK5sdUq{r-kY`2m5VuY3*duQavX3}fb2N4dpOj~scJfO8*vLlE)n%&Tp|#k+vnBddQR9}388BDSKCoQRhT$eNLQd)M zIqLSa7kP3JFIV{r8BA^CA}z)9){3#iUgwQZ+WYS@s%~AqQ;8@uQgG~V553({>51}3 zXPVL{hL{X!4cI^%PBt@9sCZ2kgCG?ZEFLd%#4 z2J2sHCLQ`84)&9eB%8G6s~X~%G&*1X9cat&o5N%lTl1el;iv+8&GX0jd4j)cyXTk= zd(LV;B+K|8oSOYpNJBT~^2#ob}7$bU(MM^h*QDosSQ!?SS(bBvoKOmhO%18d7s@@4KUFUXh^>Y9V*ia9$cd2+Ark6k8A zsTU5s3Fd&p0hslMxVKJEowt1UTtMkfOJNZT>(kYj=KA?qbpF11Bc}+L>hd!h6`9i! zLF!7J%V+=Io8gB`gf`OxVJF;z^Ds5EuE9hWcRvY2kIvD}2S&37=SeF8?7$C-2EWZ- zjgfDy;-40kj_lXPb;jT0F5}hW?a~7%NuZ37Mz%+6pfVS$=gt1%NZ3h;DBntx{<(f+ z)QCRp=+Y=se>yhBYL5rGXfy9|;Q$l~2PKdHu9-Pd6V65YJ1DHd1o7|TBwOa*ax zGiTKntis658(HDQBIOs&C+(m@Ym9b{V@X14u)6`QGh7uWWiJ0~3xsrWkK9r$UmOHR z1Hf3Gry)%m`q(N<=Cdp&6HI#u{x;oR_X28|p{3i8c>7J^+ivnCJT-}zXgySd#t7oJ zU7}WP6;;(FyP*|c6cj#k#7!}Rr?t|MGlZ_#@7JVqyfFm}W}-JAl2XRi(e*gpa1ZOy zJ6xHo55j^fxGxac?WOpsQWnb#%J2wz^J*G%jwtuzbRhu9M8eqUi=>5c zUGx?7s_B=}QkUbaV?=K1Q{R0vkou$#JyJ4xTm2w_SF{)(8cqM3mgIglGui=3GXN1BaJO? z)jjY$k>9tR_jX9WgSm$^RXWNRKUpxp-$JT3CUF`Dk#$gwn7pj?355}Xv*YLPnARh@ zZ~Fp-13qRThF92}9Xr!g%_HV5E^+W1FbFYYemg@GN6He1Ytj0Sm zE&FE}Sldt$-A>xRkjc^geX+?~Mo$8%MM;(nJ8*rpAwGJ9_*$*m(x_e7;I)8r{?<-e zhFDVvIQ=?9di5`-_kslnm3M<;$6>Zbq)^MLJoG%TOS(2!ocdmgq%Pm%x#v(L2+y|$ zC^CW0XTMPBIXa3)fBMB>w$xc{8U`$JP|Gcz@IW4G#Auw-jdl|KLXW_y1ugL*uKh>; zdYGT0IB5q^va?Y9xz^^q?N&8Lj;TTSRqMrNv7=Ma$CXLKpn-$^7W-YADzBr*r7yjT zvj<|KIma6NUmGllh303C#xDr1>hZ@&X<6BcHqe*qVx_?px#a&lQy~c$!N$685}*2e zjWOO_PSkG7mx1?I@7j;@{{07l{PH}+y+Lcupd0Xe?nBR&Tmal+SfFZfPYJ2jZ+`43 zir;lp6H7VxZb4h!M_==ihAp=GU#NJ02nlWRw7OUXz3-84*YJM`dI*K)-tVJloA$lk ztGgaz17F!o>Kk~oGIGl50v@4`RzzMe4{|)FzPD{^%e-W@A-^qK_Lz#G%!mDBSdPoc57?eoP_mpUd|);PD=25i>)g9TKI$1{U%D0Ox&m>2}aj+E3! zNH`f(s5w!E7d~~Z=2T*`{8Wh#`}42z@T^u0iH#On*?6N9H+7D9c>x%DrmUOHHUB0r zAu%h9!3FS$>L;*fkX0j)7zwh8b@*QSeT{4`)E$a4m1<*WwH(i#FZYz~ zwbk)0%rP3LU_&HmXLf{1<^*}NE6ABQx{iI>rTVY2HJo|z?sb&5JWRkLZ&_s=Wcp^3 z69j_vm_~Y3bMQ~ms`{bh02s^s84mnUA=G(*aTwj%rOMm-lWeJ!5{e|vN+-W$=i`O^;L-};^IxSc)d~_9xXC{_5yl9aG8&df~zqvx4Vzzw( zwn)^wk|bd%5$DOMP28;4Oe;}$8$<=B?d)ULZ>@se?Y{|L!lz*`3`=Jzt0n6!1K`D; z$Nb*;FLkg@L^_j{IF{qC1#vuWw)<)v%88ocpCm=^Z!FF5FERj7VvE$HztlPWV6@q+c z$J(d9@hIA^KT$Sqj0mqP%G)P!Wy`N{g}vkZA#Q*56U$HDQofUH@CsOuI0VNE8xOcS zYiH4lyn*t0iN1HKM3h_J^aW4g_3p-2(``;h_mQXJ!Q9cLZDtqRWdKm`tyhZt{i}4djbvgFOfZx?uB@ zaag-kz)ez=)U0udhKp|e$zqAgmajf_?mE2~z13K`urZ%pVaD2Wk9!o?spEp-}2Rk<0S!MfC+k2!dJ~KrSIBi21p+gL(EJ<6G*X4tn^X& z=a!h*kEX|U7rwDakyj1v19D!q9Q#L;2Njc>`F9Q>77@?T$8e+AbHTSA=d%_w#QB$# zQ2tcJc}Gm{6f`w8(l47{ETi)7)+2s!qU!2?vUwjZ1g6#dQ7H{x+ta^h%Gj*G4I=GR zC%Rz}R_UqYJlWnYdw-9RF_S<^vBCxUHtz4=7?v~tn_qrf&TgHB=Ri_$ufGsGCT81q z9wQ{NAM2)rLQp|v-XXu%qVTV|v%UJ7KPgwCfm;!!%96}FuNX|K47NckAH`<-*yaEB z+EuKCu+tPp8-Pfs8ZZ0n87mLzX3X(dN2SBdNPE+yg4plmC9RzSOM8Ejih-i1SMjNX z9`eHa0H+aSM5{--uZxV-?1vh8kvIAyT{x}ZwhHkUaj2srQ zU)=Ij)F<-IDST7~BN#<|Vd0s<#lIH`7@m^Sk0Zng`1+0rAF)AAvshcr917CY8a9#; zKsV>;>#Tfk6{?HLZuT0td}6ivNy|}C6o`)g!HuY_&H0;hka=D2}9YSTim*c4y|{qrd{e4B3#_B3L?Z#j92Y{IGFu zY2j@q&d-KGV;@P2>Z~Rj1x=i7$!p&$B^>@t#AZX%ukGx^KCg1bsSK&;Vp%yTR0FTG zj}{)aK2FOG&dR`}m2>~UTz;%%-t)R2DOA+@q4#df+w#=>Z)0~2@d1hU9Vb)>?92ZH zoA?il0^+-|8QHcYKDveU>mCEV@9nwWQBZT7cM}4p&3GQ>x3^@8pT{ph%3}!wJhs8d zHO$?_u)iX`F6Di|Ux_8jDOF?v71b}U3j*<3K6BGlhQ`6-o1_u$!H}gY4N62A{fYi9e*Bp( zr{pbVo{ALkoUkHc<*^Dg?No(=)U(pk5bajQsLD5I$Dr0rO@D~EO6Z_oekjUaTk+f| z`H!h|f!LY71nGJKU6xk2#Z(ys?X+F$%5kwxxlbB?B{S#cMm!?00D=5eJ9|rde9jLi z_h1_z&a*9}Hd%z>F}P3~od&7{TJ3?#E@XqU#VVHb)N$uRbkHm4@ltt~Nw_&ue5^*@ z7h)m4PtTi*kNnz9jZdsjPemahON&(*{ps|}ID++}9r`n|Y>)+IX4Es1jb8cW?^09( z%Y)7@ll!vYq423BH+eey%Q=r)-~R49t=yW1i9~&Z4W@PhnB#Z4e&&hGnUaOwY2_?Y z!*=6eUKu>M34f`N>;wtSV9OC0^w?r_RqCEHdr`4gz*#{3Fe@QXMpq8js6d(u%KRsf zVf8jmgXYG16Ynd7=PEDKg4_t-MThSV$80jt*(EnnE-?l_4=e6hCVwP$KcUj3#iKEZ z4)Ut*q%N(EyrH;QU0Kju7XBV3nSxf&WM@*T==70R!9=<5y$xhPCHX?5=GBpfbgUO<`$d_U3woVy?2w8N~W?B+zT^SdAwIAJ`(<8V`z&51@t4hp0ejE(u2I1 z5rc*2z8XSd^lJQRW`6IkJX@lCIvVvbXz!I$G!N^1Mp}q}FoHf>UU(G@K)!$8Vx)3n zU4=cE`#O28T^bW(F8P@O@oI^VDer>A>}HrjE|5ycVk0VS|6ir6Fx=m3alno;wTQ0v z_+mN9f_l;e1py^qyQfaOCQTKgJV%CA%QG&h+a`0un7&F;(?hC5_e4{|7aV<>T zT|$$^%D$89Md#jPnta66lh`bTiJ_sT4bX!{F`$^E^@o2E5^%4ltHH_ioxXG&b7^K9m#e!Iez!J;1cYFSLW`_n|fWX7+A%qy^ZWvZihkX2%X*_c6^o$9Ro{xgbHV?W_` zgeYMH%xGhfa&xkuQMB;&SA>!j*xEe1D8gfMOjt2r>TXNrL@lRM+ON^w*vbGr^nXHE z$oBchRNu$EL@DQEO?6(82S?RETfS(0aM(N0xXr_WASDBH?3doVPMR_r_0LI9R zCP26rv(dK9o71G5b`PAW&Pt}5HT2)2J^yJ1qA$0ln-NY@7(s$jqIFv*9i4JNMYPG8 zwTbuCIZwz&Uzdai^sw^)rP0yk0SPe8d%Px0r{B}$eow7D;I;dip=36uJkC4$JqNC| zeOC_%zZsuJVoCc{5P$Ba(=c%|gqM@dVz`Hz11-V7WS6+EP(8fn%5s!MbSC}#r!|mp zo26D{YhFk(p}i>>p|Lps@Z4|8c>qWI-zDfTnQ+t48Ge=l3S z%xdSGkobIH_AOth@j%=uhx*Dyo|_OJaa8%}JQ`~M(hzS;3su5Xo|oXT3v}|`;YBTT zPkPVJwbZPOl3;MKuQ2XT0`Ag1!$o0s_>dK?n^)yaZzB@QEQAX_cdOJx+;e7{%I`RP z>5%wBCk|IT^4RtI+4a;kzO1R&hC49(V`AdO#jq<-(Qo70nF0TSf3Z&kG~kiu2Bk0s zP$Q7rhc@rO4-h`0scTwDNzm`=&uqlo7Wom`5r5KkRv`0rU)q3u$8EJ(!1$7JUySCl zDhkV>uhvSap5|;uec7%sPv5h{MSGUx*V{bxsCJeebc4FH+#rMp z!$jKL9q#ovENzZF?}|)E%W>W%zKd9bUX$Sw%mMDA1ksk!ZexqH#*mhD>o}`v3AVUi zfC6&UIxA=<=iG&B)Lcd&s4QqIY2A6hzG{)Kdt7kuodaR^jn=QQx;_gPWV_?Xp6Rmu@eeMVXw@w~?=qeT$e@AMHpqCVwyban2=L345dhd|qd2X1aR{*UM+uJ#L z5XBLMCM&4x!Zn*^@UFFHyNGtQBGmUv?|Npph~J0zIM> zdZWqBz0D$`Wyfn(zz$mjg4EDIzmSB425t5*rFGhH5Z28OKkpTb92AZcXQ-q{Y8duD zK0cXBdC9qtRB;$V8am5MUDKnR6$}gC_;u9kJIaQKKyHAisA|1S#}MlwDETDTN(^aP zkzydK1d9oj7;7j8?NFU!P&dIwT{YrtwE2eW#c_xVl#?eWCi!438?z+S7YBu2c!9=n z4<$+|*aql}Pup!BNZ!{zBJDf&B(&1dVq6+uCg?UF@^n3lnN4clXp+Br^II zbXXNpgBWppk4S8x^9;fv-ZD84^GJM<0&D|1nvzy}2I>SAd*J#wp$(|`Tn+PTax$G7p9jZvO?zpC)kW&MWwTTaRH(m4tzldz z7kpD4SAr;l@i@&s0m_=TVNI0M(>sgCbqctt65z6KkdcQ7;z#d;a(C>Ub664!!zt=i3vl{%n2 zXqvAI+9t*DFQvf18_v0FOAomlCrox6a*-YQFYLY!>>A?ArY-`w-xYL3K!Nf6z_0jM z6C&2>qh$wUK`QDHxJxqy>$v2#b%)i+Gk5?Si;{spH=?POKsj#c{JyzT?k=RR`VF0>IzHv?TZMwkmSiy8#`^2!HY!j$i8``)b2sI zdC6BsSwFDu6I*4rE3x{pOZ5OI3sPRAa-RNX&y5bpSUmHeTT+~-}pFf`nnrZx<*%u+7 z!P}O-{b~U+rJgyuvuAxa@1rut8<`v2FBrNsg6m7th5PCXKi&%TgBo_8Va@-oB4{$l zc_Ouq(6o=N*%H}1G+gDd%E~B4;FyLLbUD9;)Db=!my;hkNZ4dVFm?M#XW_l%0b~|} zwjoMKnMo$odWA_%Pvy%4aNZVX?qEtXUNg5OwlTf8I(SBU4Ns<4MMYZKpT&xcW5lO> zFYJ0?q>k#YrNK26$@{y^75a%^$eH*CW9Urv4a~7~%1q$SM^yuXkAK!+CG%#;<;Kg+ zZWza9Z!NVjM7>0b1D}n?-RS>G#GuZc2#4)(3{CFJe(qGZ4#a%(LK=T0OT4Bs-X;Y9 zr0KO~(o9qE5fytfwDFH>fcH9e4%kK%?ch=$cbml5y&*?i;yN8RlwcXv?_12uJA&?z zq#;hojG%#106d0Zy&m)}wh-UM-$JV_vT{ z3{%*;X7>Jiq{3!Nh>( zQXuw3oC{iZO<7L}hATAqnLaY|cqf5;k`*P9e=eENp;a8oLq0kEQGTo6jrLXLz^g3R zZkVJXT=`EY475TNiiyDDXR(btR;@lz7A z@r0#+fxC*!J*jEOC^+qU*(AkNI zo07Bk?8Pb-mi0dM?LVFC#T(p(vmZ}-2qksqga)(Rsoh?c6|LM)fLjA2*M z+?LG?+s|tLrkr>mGuOZ-=}`nMc_#j;F2)cwPD)MBJ*g2Io3Obn_b*VkRxMYD~Ihg$$?HmVeP^`X{L% zo4)*yIWx?bLoH3DckgmJ(V#I0qLRBapW~L3wHCgZJ(9YA{^gnHF3Em12HR!0Ik(Tq zGXn8^(UHypiWI$Z21(1!b{>q#Wnbk5@V)miLB}nW$YZruRybu>R>~ci=Iw8Mh^GxR zHT+OiN$wPU@(*fv3R>S@EOrxpiuE;>D>z#a?IaK1quay08ucRrU=RY02?U2JfCC2;`5tkIY_f0tBOTZ&!+rdE$AZr~TK-Pw+1VGIBqjimdM&XNMe` zBdTc-y-$A$DdzDV^$WeM!EGHmdhZs2P6%EPhL-1=47|QA9jtLOD>1v$->MBbs9%#%yXk(FuHtP*?3?KV)`#^- zZGl*oIx1OcOgJlDkXLppCG^4oEro+!)LD#t=3!M#$YEDOgmyZF%4GOf^K{rX_K6N5 zn`JVnd~D|^^aAb5_d08lg^^k{p>pL3yvg_b-)1N}MS4EQO)}@(_W^)ZC=# zN4hs`Uf|=>;zdeSyuOkq?RR8R(U?hi3><{HC(?ywln+JscfqZ(uTieu3b&}*rB3eo z4>z@4A#Igxipj6-Qeu3Yv|s(34JfmjgK{qWOi+3Kbq zu%8t>f8NR%9ot0I60=4LPgeI4EJ|1Y^E=n3hD^x|5zct_VyyZgQkReJMYOuxa93M- z-WSlacLZAj#E=11shlv+K%Y~l_TJJ7%ja2eJcP8-&Uu*4>~*8Yh-rg|f4;PVAFb7L z+sc(nMLewN(iLJj)Wc9RHH_@z>s7 zVQu!h5UAjtxww&MCuY^Du8Bg12JSaqX5D##b$hwY_tWb;o+dnayFj5oYy&qNt5(_1P zi=oPs0Dkjn5r0%@{oQ#g(4*dxvc_5tM6A(qH7Z!r8-2Tex$<+2KxC8O&9+ds z;`?iaDiL!DeQifd?_&?~jL-I~2;{^{*Zx`NL|s0%(1F^^>2jaq-rgQjkQqXo3nW8H z_QvlWwy+mUU%qQh*3px+m74U)9DVK){Wo!M#9~&$`-g zolZ0CaixA?g{=TEPC63)DmdL`V?^^9_&OQ@+h*InZ2mcUy7q%-zz}21)l)DD9j&2u&9d98sM526*wUPnPly&RA|~c^&+xq%*hwQxjz!u>PbmK` zY@CR-0h6#oUhXeWU4`B*z8I+*s??fZ`LIilWdAY`tecszLgMZ4LVDh$U;GQ!0kj%fX#r=LvnF4fOQ-$!!j;llWkiLoST^-GalCH}^ibwz*Z zp)v@Upxsq{L)$|UV|0U#`e?nwrNYCMSa#o1@H8|&!fWxA4jG6#zN93ioORo4 zi0AhQarNKcd+qTBrdoiV@zMD;UEv(IFxAQw z)X>)Qqhf{GoX;Ru(xy>%r3|DYu7yW~7;pX%8FM3t18qEtkiYe~quYUlDc&u5`IXeVpi z+FenaaeGQJ>DD5t+*gm1mt5PcgEuI7j~`FbxPS*Le1H4nrJ*iJ*(VAp_i~VN%Tuvz2a>)r zm54p)+pJsJq??(WL@<(bT3=> z`m@&Pc16ei@U%0>>!&Gb>wZCSeTUVYv(sg0*+1&*jRSo=dCVBY)fV_{-II7P-YSw= z;c_GlZ&e-m#(Raue3x!2WL>DZxc#|P6A{H6(=x%!_Uqw$eK>kSpYsT3^=3cso~_vw zf*qj?sm!lJ%X~uJxu|vKpPQ>?*dOa{R_UE?58K<0bS+7Gpg!lQvtNcTV5&|~>H5hZ z@RDaJ*$JPTAsq~IZy6Oypm?Mg+@b!ek>WHi%QfoWw?jJb?^0Q{rgOxnZtCg=UR=Lj zqw)%u49o}t&yk|CN%w}6VQ!~)neK|zLZ>2kAY^|@r0-wEyn%2+3LiJ&*b_Xp6+8*t zAN@L8-MmP_a356IRL%~rPOa4o4wP=QC{MYbedXG_uO{|Y{>;qLldY-}&P#(6LQAdz zHQ)`B$ZoG@doecm$_L_<@v^K)Jk3PR-Y@HGxqH0xU~?&6`Zx$nZ>ko?R&j?izg zcR}`Nmh^ng*3|>hD=QcOb$(?tULczYJPN5O<^8y&Z?4~o7=VDc`l#0T$Za+1opU{Y znYPJP3yQ(cBKFUD96AtRzo3uCiz*pF6NP5_?3B{`PPhuM3!aQ?v z!X|&1*l#V%-GdJ}LqN*8Cjpjwjk~4uhjm+a6b-F+ijpk@GRat{&VbJ_ToyjD!TRoN z%R|5B8Y=nwL)K8KqonEGRcR%HXwaU(w1r`DeE8+JHP&IGJj`z62`ao%@G!r$F4XR} znx@<0LQeTRbYw_|t)s5L)fPn!W@ylnm={V1>d+7?liGNz8%ksVZ7660)%E(wTs>Bs3_hUO(aj&4v6pI!IjkXl z2TNM@a~n>Ck**5BnHOJL6{b6DRm5{9X7nGaq002LI-HKOAc`&9zVh5W+n-x zpp>Y7;8vSd2J_J5Yp7A+*@@olJfIeCCB<;l=#4^Og#b$%!?sP7X!WSfGUlq5t?|^LwiOQJ7e}sJQuhiS;3$IG!@^o7Znv zd$vPoVd5w!2KSW4gu>;i20Fyz9(6na)|vcvyVghdc}O2Kp#yIC=wxAG2m&^c3cdaX z+?FZrrs{e{O`vSa=P%-_dQ?U-Km8W}xk^KRhP&_G%X?;kNTJPcjl$gh**tC5X@o#d ziok_K;K~@e$VcGruj}!w2Px%RYR7lqNWn6{+u({LIHk-7$Mt>&#(F$cx%k(D1Ww2b zg>BXNbtVS(HXEo9ed`rIf#E_8$hl>|uAR$~gvD}Oa^Y#bM2S^pVOfYAOC_WJ-o|6D z;ew#K2>^-hq)i<@@fSbDWlizW$2mgijz9L_+AmN-(f1uL z(g7N-&AdVuoG8l>v}O}PF^NM-#$+zaYgy4B4xLf8Y>DmSu=1ovT-;S|JJ}Cgwz9uu z7b6M~XtALoM*1mG#yO6FCId zdZ-tDu>BAzv6mtYEcIhbRH3s33B&bUrM)1JvR{0)(zT&-;u__LejW6XxAKhB-a-3K z-|C>@fZtsghVxIFz1X(Y0n@z6%^_-K3S7H-TlU-);)mvNOX zOR6ym72eP%qgLt&**F>W^OX;S?7^6Q9k4eTLhR<8>}9l{z%lDMYiKX~#tvP#JT!pc zh!P6*;)S%%Q;h4Pk388H$q{85cjfeqa=r^L>v|(fnr4Vz4&GB|aWf2<_pnk4gm>8 zwtMINy=7zp!X(6uTr{^$RK~G)8zz%3lm3>2lA~ZTydtIvO7Z5IkR2o?D@aP+j+%x* zne%_n5xr{Xi;^f@IXqp~AJv+hpT4ubSl$}+w89`S_mPlg=HkwjEVtXL#QXj69>;P= zIO#7MqoI>Rk$Cr3-d$QAI>uSbs}fWaYRYcqm6gs3i{1wuvFeazy)yOB@y)$Ed3|S& ziR^tJ)11xPzBhk)dGyzRjzJE12{;}VTb?f>c5ikOI6ZCf;EC+LyVjhg1qLA7trv{I z6z1tU!rHBP;@4>N9F!RXI5`r;j&zfL4$@h%2i-&fkScx?w1ZE@t)b|KksesPIqf9k zO6$eOj%D8;HI6c9fnG+L<5ku@A}F(Pf^J0M7v#1d%Y*?NL3fo${95B*ABoRUAm$Y4 z;{nTD`lELskhvtS??m4FC`+;AM_hfVs%!|K(uCJEwa{3g*5H|yOvKSk(6vJ}F1sLx zmxd14U}j;5NBLn?=7V@N5W_cd-9kB(Pqd3dzevNtaL+`p`eiL#{E0*F3wvT zcOMAWb-E3hG_dY*UR;-b>&JUP7EcD`&m?_unH<<~`*Fz~$3_a(n|=D)V|fCk6G~4j z>?pR;*Gp;$$@cA!Ge9yQz{v zP7}R~k)EOK4DWGGqR(UA13wV0YX*8hKccTHOH8z**N}SrUv&+Hn)LsyhQJA}{$Rno z1BYy}iB!hh-mde+?OC{wTSC`UF=COA_?}y&wBpL8cU^V;O~0EA{%=mBGQk;E!V(xU z6Wc+@57>pf#;)~J_KhiUAvY=jsLkk{8^_J>eCRjg%V~*YFLf~ zTcfvZ2I#h$earLtkaL>NxEz=;xbpngJfUbo?R(|Z{@UB(H|^fKY`?zqMw}PA-2F6G zvfa<}XGd7#xOa`qihIMESn#7}+h^OZ!IvP9_Zps~I^@^NnV~mJDkIT-@9P6Ha3EnM zH^ztS>YMFr-s&`8?cg4=Z}H>iiDh<6l(VX?-3QM4{yvh9sDt&B!Q5SDX(2h1=zV$V zOSPpMUI9Qcbhs^kdPi;ZmcLS^Va8M1sMM#kLZf9< z|1r$pai*plgIfhKTf8;c^-WbvyR+hk`6f3?Ku z0%k9L$%U@WXG8C}+b6b+ryKCCc}-tt)M+sr;b87jfQI^aHUVz7`}FucS?xKvOj68x z!GC02#c*dW7AgJH_)g^xOzNZ(jWNUI_h&(KoH6}$Y=|XlDA?9ENa9;WE>z(FVcTBt zo*Y>CFiAe0`vIRc-%Zw(d%|Ioxent0`_pPD2YZ_CJgUupKA;jB8wS!*YrpZLG-tK} zR<{|O^uU1Ks|PbVG=Fl?l(241ieSB1mt!-|vmmln7LozV8ZVC(+V5gAsYcPvB+b*U ztGk_l%6yVdqV=*W^~w~MSy*n{j^xN~1t8AG?9wFg)0vZH-^Z&aMxYQ%ljmFd3~F`8 z>Wvu`VyyN8A95sz24AsCtQ^*8qj?L97oYrL@g&CZdlv$9c|R?B^e#JUl)STVT}`eV zEkPd}PjM{j8V>u9WzmIYVPTG~B&vNpRbp3(YRI80G-1%Y?q!v@D6iNtwQbYPUiGv?okI6 z-6FlW9E*vK80VHaM5#M@ro9>Tx_dFrUxAwBWmxaEKwV?}k#s573-|AG4P5wsC}Ld4_-Ua~HY7W}JN2x>$x04t@DQs^zEHXdQqbEG*LS``zqThVP|OWpNGO{5mQ)5o=5Es z(wj5Z8n!)njhpl_xsX#4>S?b4W^dC*MY#ndS{ih9^dIP+y3sq|66A8prKItHSXXXH zhtSV#hGSjsGc7s;BV4$r>%t`n`@mQ26Va1U8e_NmOr~R210@2om>qX#wrp*ZnF-E% z5KT?i3C9<_+KVQ!?&;q(Q{5-BtX+K+x>(!AkIHqh-YKn*9P zyvtxzr`9i_7h=@SvY0Xp0b#4UFQ2*T2Im(2*Y5~Xm#KSjFMS9hyn&U%WqWCZatpGK zD#vZ_E~;=B83Ze9b2hHY+Ag_w-nsAUdC#Hk z>UQ)1G&+@NMTO1Uks{N!TIxwTXU_p08dlO>3KuV`QJ-(*e>i;4dNzpBrgqt;D1G~F z`ioS>@gg&`k0Y+6b2||;u89H~m!8L6l1&LWg*DM{zdD(8-5Te`>!{_&R?^BpRV^@^ zMkd7wE!wE8k0yaBPXt*!Nrc~fC$-6Ycnp|PoN1Ua z?OyRy{z!}cPFu#DBJ>e+c#IW7St?3q(gcpf{~_xw0@`Z8wLw0ppoJnWZY8+8Q`{vG z+=>@>x6cY5h& z&he5WeS`Hv>ElGZ$0j6o$0;4OpY}~p)^=Ao%ffNtnM~17GDTu|qQ&rrZ+%>O5{8Kx ztDQ5(^XFP*)i?J~AS%$tONf8OeJAS|zFn1D1GMMM-g%XwQNN1+5<0JXkm(F&Sl;`4 zUAsmMum=4lP_w>(_IJT?t>fiM@+zZmq=MfSLU_9+d#y#D{JM&-<2AvH5-Y4?GqzVO zkez@`B0jO1Y8Ah+D$B$cO_iR`k;@L%x~qkE23(={ zZVBE#Y_+&m>`xP=CXUcjTjQpw|6yIuwJ4QOwm&_P3(d?yL6K?LSFkRfAvhvU0JHMKZE!$KD4u&I0pv|OfqKOS;? zG%p;$|@Bni}4^PNjU-()4u=slVz&RA2bA!{>{AayiusIrJAp2L5+g zxTsltr|KZM@%GQNZH|atWp&Hy|ND4ea9DHH-PVOYk}~uhZ`_`F_r{ER9xDwO*+zTk zx}dhK1nP?3e|r*;WVip{Z&t94a~lPYzpCl5#nGwNPl)oI(-T=3>?nV?sCHfFR1xFJ zV_cPcWL9H%-7qVp(0eA-DHbV&M6?`c3Oydg#<*Tzdw;oJWva@|=o?tQ4m_l2bT=kP z+|>S+hCGn;AO?5p-71TVEyR)6FGzy|<==!f<@vWgBy^?3Mp*Fpk);c6a`S!oOM-C= ztB|X{$XBHC`CdW~d9BZ7c}DJdm<{5k6Z2#Djb+!ZZ$iI_2@)Io-NdD4M@wA8)6L4- zgJ``MzOG5&C$BZD2-TFrRl5i13cdH)*RcOq&C0};K+@QtL=mZ1?Ml+3wR|S!kIhHC zq^XHIV$hJ<5$gAJiBJ+WA*o2lf2sEsC#{jFTtSW)DE!WJ#JpHV@2GWSZS>X7ewu6S zB+fD~%=-}~3;DH>0i>f+k}7$4Dlh7-iQc9*#XZ|4;s>IcjV4#7l*I8=@h8C6MAK+A zRwd5>HVZd$-RAS3L$pupvQp6LZ?>5@6Z#AfN$7FN$OM5>VV6DjcC41?4qCJdYJNXy zqobLb>1>Ks$|EuN3_!VQN6kP@*}dq$Q#>hgJ+_(r))*8NTJX$PorBgHeA!RvAPhUU z(E!LoVomh`33eNH)k%X`7;TQC1&x@{0C>4N}|uD}(UHnBmf@}CMf7~O@KR`T;g!B4zGOuINbd7_wI0GRmDAl}wwQHh(Ai#qjSYblM| z?4d}$LEYz&zsaRI z9eEgKXav5JSrSb~|J`}44n@bz8$SX#rf`5hGdUKn^gbEXx@D{N5~q@0a-Jt(#Ok^i zR8Zkd5bLh!n%b!-TC}4wRnjXpfuc{Lo*jHv#p8o9hV|E$1(Hb{6$p2Wzo z_lrva-nsl7GuHlG0a~-E5D;0YsF>yBI?-?0*MF7kzh`P}3A!CWWi1eP?Ot2XAV|%x z@X8Qq=U-@MO&2PDMZ*0G60&KVNY(GEJWGzP(klbjoMWPee<(nMVbhtiI<$4+l0{E6 zv8J~1x0Q-SaE(r9WR|c|k`0bhAsNLlLcF)WOBh8$o-T}X&I@^5)~bWnHyWoz3H;%` z97Az#|Jb9#f-dhdP<}aKrM0c$N}qK6avX7F$Hc@JT2vOvE6CD3t7IdL3J}|St~qOe z$UTZy0#ceN&qK;%5yyTtP8;ue>kFFOS$Kd1INw0b0zauz4wEIuGCh6=HRR#pN@NFxl+{_|`gJl0BwnlaxO<1nTszQVdAfBh`Dq)|`; z;eRkFj(3^ywbBEA@(xbOFEYmOVZbt-`;alZS#DAIV9Om#gRtFk|iA%7>>GGey$|Dmu7<8#u3Hp!~H>UO4DfuRqCvnbN zPJS*?5> zsUqSg)V}$BYcoFuhX~_r6UhgstI|Q$;SEk-G5-U5MlMa2SWyLBh@*U#-_LLIanYNd z#z4o2!lx5T^rODy(4-?hecqG2$MC^x~Xo)`n`Uo#bgO3SyDEH;Y*@e zCPA$$CcuKMdvwn3PZ*SnOC0ix?FF_E8zZ8xiH6^XV{7=ynQ5*r&IcCp4#q%lWpjdO zn#sCanhl*gKbAfnh<5EQM08kMJ>EYM32Y7E1`msPt$PpPYdb#)pBe8(SlnHB z2mQF@=s>OYAN}pKpZ`#}5S#RpjIN7Eb-U0LJ&2H+?E?ShYC>;3UT@=OaHp!_NbS*k zDR*i{3(unAlgx|SSi_#YuQJUtkZ&si7}uMPu;lcP-&V_1GrP$r`OvtBu<5W4rWvvh zSGR5yM?7s6^qRNk>T6LkjVVtc&YHFnqW_`XtqRfz3DtS> zFjv=B9(+tYNeZ6ihIOn~wN7TWw58P)bR2AU(D+U(v(Nf-5=!*o{sjp~k5AXjjPSe? zoAd~FOV+H$1}Z;{n)sgh)SU$1EO!1+@bm(s_@}o8hb-Q$Q!zXtf_LEQrs49U_rOhf zaK~TVY(|^^NhZC}N>fhV_7oUUT{=P-ZdQar_vQ}khRi??0>qF7)O zZ7&#wD#v&9$?N22wcdWjjj;q@jP7&qv>%NO%HKJ7Fkg7Yy@5<}24ffPRI!Dyk zV7y}5m0U=pHv|Rfzm^~(5$%FL5wa!V9GligV5`PS91qt_b|+QAG{f|*g6&cS7s+r& zh~7Vr4Q(`Woh;mIeD0dw&YvdZH%ZqgP8EY+5fiNS6s-%?OHDR1u@FCszLnHtGf*eLLeN|btrAEmg50aT!*$Qol`pQLmiZnsvsZdov$`W=79#Z#&({D!r z<`N&Ks88$&GEzY5q{R$vVdR){Y+h$jXX~KF8gVdONhu9wJ08Krz#pvAXtm9vMN@H} zu6U4iKOqiaxcSwoCZKvPRA+juNLn4P#Wt{QOMXSa7aofrW_`#FfXhJb`BjCFev|0G zZo(&Hy@|VC_N9B{&@#)wb<>{4wP03ogo&yDPC?U5!hWsr8k6r_W!zfK@aba4Zzu4O zK(s>Txb{uHMLYU$Y1>!g|4CYWQ5Jfm4>oO_PuLUkOutxYy1I#9!vf&;fa57^k0_i) zO=6T*!3ds*t&uW*9v(RmvwWa7!?B$(O!QasREl3n#3YwcxxaK75+Y|u27W91gP8Z2H;P~^4k>9gwzHokG{e4+$HdFY*-FE|l0gM^gF%%D zC0Cro(Ft0@RO`>ONshy5f6XAW!iG9NW9~aVf1g$ZG`op3-wQ!nZ^H?H1oYrQ+7b~o zB^3XBDlSe$0hbVCJW_z%>Wq5|rc}jiNJ53F%pM-;pe55yXYIwoq>z<#;pF7|&0wzH zo`P>X?m|NXJpB^SZA0P_7P9@!Lt%dxa}CqA{HjR5c`6@qXp@5s251#AR5mmh+@ikG z=zpzFT$}Qsq~uns#@p*q!Y`1LncelFA9hvyeeRqQh-dNF4LeG%Yp0)j1fI^UB97P$ zHKmLTGa(hG;3DMUYqyryq36*wImQgdp5=Z+rzU*(QR&@@V5$}ClIWH(ypQLdZ?wtw zkupbrmgn)e86aE~pQfW=yCY~8n6L9L!Q$aR$g2B8j%j#fd8aA9kX-he_)r-B?`sU( z_o|wCdei~@1%y@A$>Cf<|jnj~V6y^yvnS732D&B9}6x zib*QoD^()a3MtOxSv6G=Qux}MOc8neA5r9rHhHEUZ+8Mef*Lz^)-sPd1cR)qCp}KE zc75OR85z7C*vo`ZGz=J*V)pL^FI#z&jINEzBjp^Iy{!l+7~%(V!ZEoIOiX8cF83 zzWD0BW9=k@=F=p?ueEHUG$Jfp9kEb`zj4|Iuzs;N z30F1+6WM>O`{|c*GuBL84pyqsST!6p2`ipo@vVN`3z$0YN5!|b?3wVCwmaq`NV>=* z&>T!5gH>ncSgDQ?JdiClZTFE7|sf=VYz#$J`H{lFrx|8YgG*>AnH$?EG4 z5sKeCZp*}vqOrU8z$%s?u_x|hSbF8Ih44jWR$h$Lg@)rPDTw{$>Tb8?!09fDjZ?Z$ z3}UCWY;SLu^|r3|7M+WFyY3nfkR85lq&`J0ai6olpdTmI2Tbc1y23OE~ zk%|d?(fd7FMbRQnxs%|+9#|UVtMKLDJC!2AD-g5geuy(;^qY=Q9_#s z2I_cAOH(6zFKy>fGt6(&Jv9}_KB5!{FfNQ$-x)|dspsv+Z=U$UgR-UD=O&C1_f2sT zXFXPGsei@WY4ojZ4KgCyda;J(&QsO9K0pPZ0=h1LK!>hkC4x5mhM7f?+Ejzi!8bw< zy{8}UHjA%JPYeb38y-_Q8HbxA($mT;9^>v7o3Y0H$3FXZV>B{MKFLi{M+epxckgB*J;R-zordLjbImqIaAE`RI+*L=)Tf!|8HKX>(0M!}Jf>dStYvdd1# z#miO^ll90?UIYCIqbRTOVB7nCh-v6tkVgn&!=$rzz%V}W9u15@vhdeC4Lx+ZJU#A;LSc?>uy{GVuP2(+9|cN6)VmItMrjA>wA0--Q7kY>qEWO_b;_sV zo#806d=(KR1GZ{OqXa18evNu5&x;v*dqjy1Q9e zxX0e*Nv(YpwaHV9Hkui2@|B2Hm0;wuv;b~t4ib9gHjK2`U*BTKp$?bjC@|G|pD_%H ze>+RL0-fIXR`xl`oyl(ZD9*?g6pR-1odY~mp|yJ^;6RX`3FSkwx0^PZc1M)}*MO&c){Ak*C`c&&;!QR}*+B91tBFPlfi^sCQKKeD{a>1J`$n>M)ei#b?rucaK=SBSifgL zxc{xBJ~+1*dyLr}$OM{Ml~5SqT2A7N9q+k+-FC3|10?LJm2aj+Qp5t7EXn~jjK&4L z;l#y8hbv(t_%z4F7i#O2Z&9vZ>MB1^m%VEa(l}Qps_`1#r(7OBXp=Ckfr3OEYk*9{ z4X17V1VatClLs4)WnBDIZQ8Z8lSFy4L^NY2f3w1A=3arC4YDGKtcUw9MH6H#(x1%K1S%qN7XnZ7!}W0g6k)4Kx=Ju-z$!Qw)r-!dmC_*gydHI7bO z9Mj(%56BGX@=zmOJLh8zckhZdhr`3=_Qd`Df>Ai>0~I+v!^U@ZbBoU-OX`mlPdJCm z!Muf1oHy~6(b6WMgL1o=fvTF$gt9}Hz-mA3Lt>CY(z4Yy+gw!zmdiOL##T!f0)<48 zOgtQs@Fg;RjQ8$d@!Yi0u749X3-g$$8 zx}RJ(RVx8Qi*xBI5W1<}N{Z*>G!Vr3W|nVuD=2a7j_DCv9JuDGUGz$_vu7&GNPoW; zVjTx|$k_4O-e>u5D=4Xo}0K-D!2jXr%?b{YTA^VCf z*tWA0E<4_tr?ux-SWErJILV7$|9F`~ApYoSpo^Jke^>Ijnd>&+ThIr&YB1s(7s^c( z#a3iQfAoX$5n=+8qiFeOZ$M|Ao~pm@(g2%BHny-L!&UB9Yo?yC7Ypde26iU1nCbS) zVN_pefxBd(&tlK;B9)^A#j0bL|v99 zaG<6(<~rJ{hVJ_x^)-`mFy2YlNLC5UwbXdEh+)CgtCAkr*ugWir80v?1s(>Y@bu-< zxnir9;@O`7vhVVT-Y;s$N8>o;<86DJ{Q~GPBg8r~!bu=n{!B1D!dW1e?L#x(tJF@L z*9Q8psM*v>ZmVLo{O$G&>AVPgZN_WtEQ*D#XRBy~Cv5b=AQN}-N?I?_Uis4DEvy(= z&Z<+JJDA?%zxAfpfAKE;lcMIj%XoL&R+|6&VXnN3Td_ZO{4R)YdzYt4#NTj3-L3oV zv)=;22O?hBI4$RQxZQ86tIf*wXG)vONBfnjG!@ zus|I~529*S5#tY}W8=1`G8sQRjR@mXF8`jSF4TbN-#3>1vi%fRHMz_?IUHT@CT)$V zP^O~{G1ZeE^D|Z*v?Ll>UDjX2F~(5pkAtI z20Q;a4fV}7ge!>Wuv}Z#R`IlN-8_C?eUXb48nJED_86zBk(47Jd$G&^jA{m^1#FF3 zWXMgk-nQ#c;e?Km%kS(n3t+d=jJR+B@lyJgErVA1kzL_(?lb9w7gAI6GEUs?jeNo9 zdDMv?F+W(;5^-;B$8iKsj8loUY11xDqIMGJ6@d*cd|u}`wv@!+^z@N8;81G5qhJ?+>i|;?Rajxjc)WOC9G8mZ2z+z59c^ZyLe><6 zn5EMdkOOX{gH&~t;x${U@5mrYj*T8!=vjBonrrfHkME7MJxU*7vXd=4&%QR@m<>*O zrkC8h{VJ|nK$qu$=cFQv-&BUQPL^Z)D%4wKyhh)`Cm032mq(4u9X5n=G(3_q;Eh@H z5prv|?tQ8%gClQ})Oxgfuv@Sgc)To&!vbU_^!#3VP@5Bn@N(F1@a{GjK@UkHh?%r{ zRH6Zo|6zO0vCm5XfZ%*r@Q<#Dsu`S^AuKF|jeq~)yExd9kOcQ<)A4RpmkTZlZATrUmD8=wsP{+^UA; zW#$dQSCI6Eh~E-NfS-uqEB{P&vH??9*55NCoXoKTZGlX(FdSH)Hwr3-8PVtygG$5q z>ydh^&$;QA??^6QM_Q_`0!2CV3YDr4m1&`7?7^-~r#T#7l+4l&BKz-upHCV)b03<; zmMv`BwL99DPFjJ(Q_87J9BtlTEsi|hBYqVMdb!2!A^L%6e`CUj+B#F3#*1cp9Twh$ zOrLdy?C)VKb^n>+r40FnOC1h8KY5m%4Z=A($YFW@HHfFncp*}0ptQZn+!j=y1hG(0 zqkMf=XJ}24NdW7sNm)Skt?zm$>-rXHWSq%Gwt@X$f4dg-^*4@C;wdY}_#`E$_wZFw z`n2_2bAQNY_ZjJTe+r`m6TNk2wRbVE%~rY}98i80SgE5Vid=scB)i>PQB7W-G08e@ zntvVcW~Pulxoh?La4@cm+&ZvC8fW-A4BPONXGY+7M^U8}Pv5iOBF*gDMS-jB$v%2! zQL`HPcb!pX)9@Z!WhB21dDsCV_u@YRnFij4bTUp3^9u5}3o}w@rsw&7)B=3Vwc~HV zt{?uxL^rnW^@#ivM_G_+C3-wMBxmvGx?5X8N0vJ~&cuX+(}v#qa0i z@H{5S#n4acm?-V|i;rIb9vwBllWlbF1DVb2*viagm({MiR0cm{x*vABikTDi(+>8_ zdzyAG8LO{1o~Ei?^S?w!?T<@^uh@>{FnOdg=+~l<<(92^_3f#~+ZcN)9EG2cMXZhewXW zgnH(zim7GJS_NBVh}8aeEcFYe2uir9iGIqi^@!9eMGp*dCilD|2`%$z;kt{t-Ty6) zz~oYKSj23cm0Jrt&oiU<=%JZn=3#chqd|MHH(a7!f(^aL0^Ts}wqfFZd%~mn%&8JVVrFyzt+4CEeN2%X-(6 zrptIyj&g*zg`@PIMXpelg_O-~T=Cp7{l8536GiFs?uF)8dlbPF;P`omjYG_T*S_mM zV_&qtrifzW-Uq3#Wz7C*ibD(A_}CEnc$XtuAywfJyJztY!jOgYUq5%VRb{-8B3jhl zX|3*hh?95!sl|0DQu7=JdnA&OfYp+D;!^oekJml57%GWBiA%qXpXi#d&e{`a?Ns!nr-p@xM%Tm5{yw4p z%4$CDwiq4`$BM@cY8y58NxSPy7@nlh^E?Pz_D?uHk7Vcu_Cky#;8 zr{(*edl@z!_nKoJCv?xg!6lqMDFvo%o4!qN2$CDTU1e2P;<)28ecTLnoT*kMYxt9q z#?{|`(DrA9;JyE%S;BRWNYL$^`*{eW>y4|!zA&KjsGfP#)sjic1Rd}mGGV2vu+VTR z6%3odcx(}cvqh0iW_%rPWoGGhKZ5iivJVZ3J5+)N@^h;=i^oQPOmP+qm3W)F#k=A) z-07UhPI$JoyP+eC9ZkOXOyWjt2JPaMl?5S=C zY&T)WXm{WEvwJIj>*X)RjGuR+lTW!WAA=e5gw3m+~5;Y|3k*3 z^C!cs^wkbf#_}0vSy`B*-5h^V9Tl62cJa!@CgLj4V#xXeHQ!uN%7zemoar>HISq9h zr_yV_duig=7_d;hvj<56SZ(XGh@F*UR;w@ z&byBJPl&Qgt@1E%=Qyc)%$L;u?k| zOzjIf@MP++L%2O4O_l-&t!<>I>P!u&?_nPMTCb|ef%h^&C$&`PeC6-+ zxC~h(Bdz#S8DpQ9B!(p+yg*G_sn!X)#?9hO8^c5-Pd_7PyI52}bDGlKM1jk_g64>@CyiD%Br`wV)lWzQyTy^><7suOlx&O3SP2dB8F)%(R zJ>h8uM^kf9biqG{n;y#w)XXXAj)R1!g{H-2Z;d-qx+hS)k-#o3kA!`b+{Lppfw6N9 zo4bC+g8_&o;oi4##23uPOYyr=slBBSPt|IVCndYm-&-*<)=Cw4$GQ8OXts+(C}H=_ zl((M0fLa7TecIjtk8oy#}5^TWe@ z305xmil?m;&CYGg74&$IgbiPNqHcgDhj4pPn8x5luti}UV&##(8)EAqKy8)RA!wasWH)% zT-^AbQmE)WRw9cRtGLPe?~Rqd2Rhrx^7r}2*dr%z`whm;{J3H5dh<~~Bo#B+Qxi5N=u!i}XC(sQ#JAM+-6FllDF3nQ&I zvXg?mzhNrIAJftD8)ci9X@aw8(;%~~8#XNvnR2YrH`<|REtXX_oqYbfAFv!@V-;_j zqA#e&9;BwmeN|gx z#b@+Od)b;v7xBCZlOprnP&Unv4hCKC&qD654IhY$qeB1qVZNLjRwN2k8KY0OU#at* z`QNs09wacrptJ?q)B2-P7nx82MRz-nWc#r$*#pmw^3HWrjBH!!Fc)n4=jVwZMj`7tU!OQx|Wf>;^^secGkH?B zN(x@`-#JJC@X^~U13#@fMcLaAUq5Y2M533F>hT`JDWc2%5CLyPf&2WP(i7eslP(Ko zJ$@AQSA8H?o+q%lOwyMEeA%@)?V?cwd7eqAlcAQ(s`zzzp{Mi>f%J*W2lh9@F`0{{ z`{b$aC%)H92K-Z19|(R#WGZL|;{^>RBl;NB0+{F*Rd-lQkr)u=yJztQ_SjV-flx?x^2{KjxB8k>T(?Q&Keg8k zNzn?4w(18n#zhys<5^s(uRF?8vgY0~EBx$Jj+tqI*_*uja1hLvg>!Au%MotA)Dvgr z5M@v@K}*n+^<+xz9`{>TeDtfZ*Y2;aJw(FDbwX3tI`IF=ApPHIq#N9(OZMAJgs+DK zHizhanQC(8{{SR*m8ZziYJGv2rNF0DjUWBqrQnqWhJz(BYaMCx2*b_tst8)g#CDhO;l%2M*HyVRb*iwjTLx7S2h- zPMXi^?}Wbd+Q4^eVs}1|7gx9zmOgdRJ-beb*UEyo59IBtszdAbY;+;gro~QG--U1( z>EZVjEc9^jV3JVxS7GM##^tI~Z2*GwTX-||qO%0EukDIS62C(K@j3OB_r6o(H?YWU z0+u8Npjfz75#grPtgx>9iN%OPGp_iy=#`$5)RNDgmgH<%8wWFo)VW$gU#L<)zd8W` z)sNulcWM2hvMW69=*JO`w!yi7MT292r$qZRA?9(EF_QY-Oc0MHY@;exz|Jv~< zv{`ux4CnEGl)LBD220*&PAU(!Ux*zmTfWSDsUq99xd36I$N>MMUyNdXiIf8s3KZSH z0@a(fMC=IX$=vF4NW;ZdrSGsoU1UTT89of$AJ})A_*L5LGG!#caatHgcG=I+c6;ft zruNO|xqvW-j)92F>=P zFU;mJC_yBccgv%+**D8>>|;MctsZTVLKm4XJ&pW|-CYiBmdWRGRZvx>wMeUpF0J{( z$F9h0!$s9Ox8CFst!IwH?@sr2D8F6~O(S2x?9`|7w^#+}3IHT1N$ZgLG9~1Xaj3h| zB0S6uiNJ`mj1tuquUspAI&Zff8su%~DYc0#w*!hJgl7`~Pl_MX1PAM9f=r_^Ov_;W zi`8-Cm3EfEXans6d=@(O6X{dE^=wZ^c6)liD&}uRy>aHc{1sADaz*4uLW5xb%VmN- zWx6L+qI{sC0!4+n7bNDg&~3sbcq5fjY#^aAZ}>3vI!vQcGyXW7Rc?G%<}!D0n^gF_jmP&<)yqAd-c+&1m!ofC8LSoAq8xxT<=F~kT>O^L!D zAxC9;1(SSL5mgF&>%!BPJTeOq27 zaw1iatd_}-{o_!iUeR&ITzW1RD~%}(B4piu=I0(fW?u9CBdV`BNfBQ7dPm2WCN#dv zTwd4~cQayrw%#H!#8e*&!bo#mV)`jGZM|Vjy#2NpVzzNA?WjTfby_!ejkmBw!-@4& zeS{*7vs?x{Kh#53pK>&yBwdANh5DzPz?H2@{hyPs?}^}S!A|)~J`^lZ_05nKu0#Y$ z#(CN)gpgMR!33NbaCl%hNgyP6o-|jk3orC+!1Qz8O$6j7tqd`$kA!c_A9118~ku4s^Oq686af zc-4uA0*hqg{}}zy$%1%UzTh^};;w0n!OLqX;67$FxgE-RU8^rkGBP&~51Zyox%q(G z(cwC>gXps%W9!;KEzMsWuSHzX1xJW^Z(wo+uV8WUi-+%+deg-R4qoC#80VZ{Bl|7} zrI*bI&6^Ys7@42uco{MX_j`SVCO3av(M*~lx)k>7UXdB}oFzWz!T+1sRKp9ud)XWJ zC$Ki{v`q8Uk=$pTT*Gw66Wqse^zgfmL-{r}5xU!nyxi&7?uv1`>VXEr*zjb{%GHlB zPa7FR3s@DK*mgnGD<7+9bv~ZNIU}(*kyE<)3(?nisTfP#H^keoVSJyr?pA`9i&E*0 ztMc>`Dr-6kyf=74R-B?E;O%H$YrgjFDOc_55_BYeSA&vxdzYL%9E~1(0Z60$o(2vl zgjPF!(+rwIS+scyLS&}@6V)?b*WvQVztRlB($noX_lB;2X=?jo;(ajHM7Ol{dgdoN z+|45%7sPHcI~wGo6q>Lwl>MDA);-QPuO)P4sQ=W{Pu2pP%-i=Y<8#V@q72X<`{d~s zWfD3DjUXOnZp`nDwdq8G)V)itEzI{n^dYcU9KD-s=NlX8`R?a>0IhLx2AEO^6uRO` zH0p{L^*YT_<8TH=ZYgk6ea3_Redyv-O27!A}0OA)SA*s01-x`-6dgeQ$eWDr>uCC0f_c z_I863Rw)df+X;c7-h2O_4%Y*`Y7e83ZurY2dJFR3u|uz$>|v)??==hgRn^XUF4glJ zt+~d)sr%to@2Oic%8NFE?M#j!36rO^=`9>*BM2b?Ef;}jR3vpkTARt*S;$rQM&pu`;_5sMhJ+?4_PLz=Lf|3#uw23D%-Af%dhY) z>?zS?SfcdnMQCS%jDP4vCs?OG-iRU})(Py;eCsIF{TwUl{^DLz*bpfO&T^EOu&AWI zIOuR`#3+?P?+dLhizhWMm_cFwe~?C%K6_6r^-|~*IMPES?sQ`;p^?lf_;f@~6Ffgr zBa~{#$bnaaWmoM^8PzS3KK!MY>W!&?6%5Vf3|buC3(rU$nQX7kto3TDS?>Puj0n;)-T zep5tczOvR*%uKQ~Q(eL77isYQ?Y+`t}XjkMTU zw(l|?!qVl$pGTHfN3h?255<>8(MW)si@N#6VkLb_w)f`tK1rE}=a2Zx3e=B{6I4=K zta=Z|n2gd7xp$UL6wWSHIL{O0+PyC9(SqB>y*(J-Zq&JQXvMVAzR=WWK^Ue6`%{s^ zXYg|!M3ijI#0K53bD?>cvE%I^eY-B-(g6ioq7sCzVujrS1?yf%FMj?>hqiC8sqnhK{F745Va-j=;Qh-9U?@pU#xP3f`XrU;mBhCZ zSgo$8aRFf&c8)J6Pe@guv2CofDEw({Naoz;zw`Ko%?%Z8n!>T8p~pgQOl=kMiR#Yf zmE^Zr_ZYh0ozNTWM+8%8hiK$jj7;qH#bYa#`)Y|Xn&H_o+XPy{ zo~`+I?uZ9F;PUI2N%7J1!E1`)wJ^8for0^gF`hQ3>PiBUmymz#Su>m2>ow4KCe_hcp_5@Wf=>df!oESE!zI4q+uipSp5+(jcY1>^YD>ibE{3{7 zfsIA;akMu{L6D#B=*iY<)BoTx)24?kptpP%dds4vWnV9ye=+#&x{Dxr;4x_|k>$@U zvAG>484(j-Y*GKyR%b^VmEt6>BxTjhF5z?A$W#P=X5o1dx6T|4a!BE&1`K1^${#Ss zNTHefA<-+e=isf$~Krai#H4x)Gje6#!NFNQxxJYph;n3{5RbkEX2X%59)9x zOpM>E$z0i#yRTwL1%Q7JPX_PLmDX7W#E0ZBkM28lB1!_83r4vzWQYBfrDISWG|As?YRXH0Ms~_gLbotRN41Ml&@ab#? z;@-obwTm(32bw$>DeTvzX5MGX5DU1=a*R?usvgQ>FV*@oL{|HaQ2irg>O{ zu4_WkD1sj#g_pz`HzK$%CsSf0@63bBeIg$U<7mI;uZkOHT4|jHvCtYQjF9M=4yac# zU=`f^!TIUxb=!U#hMpnkY@feLJ1*rz9orRYTASR3tlX;pmOkjcHgh?#S8e-dy^49vx8 z*md;A+bhU+u0bXX@43yG9I51yaCZQvug!}0s(3;aJj6i(+!!$Zob+&OHV26{#0AmA zU&;T&)?0-|{k~DZs36ivHzFVn(hbrL(%mf$(hMV@bV>I}m&DNBU88g(3?Vtfzzj98 z`R$Ya{;&NW%;{Wn@O|ccKkNRiwWcJ$16^6m?rfXyEdF!W-uVoqnxr zgS7rNmG|9qr`x+c4-`>7|2Ep-XA6(@$5KWu-p8cZ&^oRD;H?K$Tmrnb(B891)AJK@ zlyuki0?Z%^{N&$u1Z!xXDJNIP;(w@Z`lol*J{rpZO6^FTHAXHCSoQEq-9+zJm*KDO z%byq0HED*sOPcCGje^}VrLqT8(46$5WD_bsyw1% zejcSy@k@L@pvT}B>AggX*|uto>hG*`=PUKzf@=2MQyI8T-Lf=3$}GdCUf#wzATXw1 zY|he6vve=0GSzm+cP&kw1%pbHm&c2+B8SEH;VHpK&sYbuk^+w?fPK}C^mBRC&lC~? zrU?U61JUt8Bo zL-PbMHBmb}b5%ubZFDdPMam=iw_HU(GblGl$iBSjmTLh=?a2So4<^(Ee4M8++NXL3h;cFh&NGzw}7E$zs5k0M;vA^#Lt{iNGwh& z9E6(HRed|!dYi(-< z9n2RDG!^V6&A-$d*#e;fy#}i|F|Ugi!*T>)rZ0mNeJtgE*<^@}C_0$;^X;3l&Fptw zjJ+*ijVCUbFk2oY#qFK~@&Bao>@^oZJVMPdLcOhGk$L~b?*ESV4&}tCNIstMhsH;p zf}?bpm08=B9=Hj^G1zRzf^v8^59I5lsnUFy**CfN;^$jr*nz>wag_JHAH=C?`^YQ{ zJqJ>j*V%A-=6_^BZrS$x^C;h0$Qxffy^-WY!CG$c7BtAs;G*M01UpY)2cN4ra7wB1g(RYMCd0XDyBgpvhmpb69U66Be^(twkK6 zChzjvXVTM8bytM36&wR4-UWIg>pz}ZzxSfH&=r1sqJ~>H*&Ouq*PM!3Hhk3nQsgn# zOV8*lOJS%UP+}8bjQXoR|IHgIr%Jd78L#k@E#}@cQt?*szZJ@iB;sPCjSmQ9q_q2R zgrgrkybP(z0C{7`*c&_7Oy2P|;ZjFQ(WN2O2fh)BUwp25x>KV%r~SlOUJ`tNhkJ;u=#0NnFPXs`RU`at z80h?u2M)PHceo|}A~XSEWM{@;v0eW}`2L+`=k7_DLdRq7nAS~V-+;>(2zx-GK9oP$lP)qTdi z*)lG|T@s6I(ALL91(~(kY3~{NYD}m0ZW>^l>ld6#Da?X8ptE~DWtWB}_sfE{%$BR& zcpHZAeQf8*ea>iKT5r>+4ehS#?GG?xH2#E$2z{bX6AWYBv}O1$pkx+!iN-Rz>UkQ!gIOn2Rs6H_6D&h^AE(n&-^>{*Gu^q4&p1x~L-ICC6a$Bj(hhG}+fs3QlKD18tFP(d*3zv{( z51RHyXykLRe@@#&~++Mb~g}^;jBZ2 zbJA!y`|6NaeX_(276x()HtuwPX}HluwX`nvjlM0^Hs|yA ziPgv}qXXzHj3wrfoYeC*+5QpNqmps3rztv z9M!SH!`_`+6~baaQZoT)zTXmaTMy?XF@ z*EpF~F$HT~=~C>NSC}D@aF)f-ak*8M5=S8d0>6O-aU#|Mrd`)DrNgM&J+zK13>@Vz}dRdQ|T%@3jx3RNSu=;^~}^$PJvP zwp6l(6F%PoiQ6|>!#mWODC1uW=?hYe6Dy6+&IDM zD-Y|naZmSigr7I{d!+}*?uT-LX`Qw;O~zyL zrMLjdDs{f%aqLfyI#0@duk^pll=Nf+uJqkB=`{?J_f7oFBC5uKvkxf+z$Hu4kHHm6 zKq0hR|9sVYAgf+7F2t`#krf9#dSP0P){wpVMQGnFne{Y%otX$Gu;SLcp1*(YHIf2k_?&sZcxv|$PO$rjd!VP(-mcANjR)>7{oM@d{gvb#7`SF4- zytN1M{-kA2T3W{GW>(>PfMqi=lOClmv7elo9tz51&s1=0c2m78_d0upXJ4$K^`UqN zI|8kHh{eqa`(;hF>TBfaNG|@u+E)3iUlm6<5l7>M3&HBuhTA48;ZdcW0tpvPzvmRP z8LA36ot{_1FL30_Jtvsn0rYA1`bI(_Av{dSyy-4(@m0iZgqQTJ7jY)`Jy$(dADbx_ z)J3}!+8u@kg%*j=9s&&Y1NSQ_pW3OA_BN@AJ1*V`fn%EV&Y*Yc=Rwjtf-In)4U6nr zrr$no;;j!Te5+AC`4K;gY-?I5;PtL7Dg5Mx-V;^EY#~5+B>23z!pYf{_a^5?m>iNm zeHOrOsjKpo3$ifjmwW!O{8$g5Vozqis*ewJQ+rl)?!z;d-V{iWDY~SUs`xDrVOv?) zhu(5FKKjd0!o?!|jp5J-nfCb(3|HUH#52C1>}#j2$Q8f;+ak#zo@hDe8bA?bdo;)nufW zvy;EUsv>L16eql5eaX1gXnST|Wq)=5PN!GlQ`kE2t*s`BP}mTsk-{{IT34sV5eEjf zo<*f&?NAX3vu|Sj9Co8vqV!LPQ`$WjHr3{~=p&s8&lWW5O?F8b~`lt0yx=g`#53%fNf zy^q?g+R8g!TA~VKP(|IC-%i>x4&B87ee_8w?xCia$w=bi_OB(S$njg1E4pb`OhGac z{S|&>N1A^78|08*einF97gx12QJua>)v;J7`F7JmGI?4!g{C3|&+QR%eW6VtgQVk8 z8Nq7hopGb_Rr|wWZaeJ;^xpe$6`vv=GHgfE^!Z^b#9IQo98(ai*)1w;Ic@t8`>FBZ zNMWS?bJK@2>8n{v(gnY_e3?v>^|c4L(N*nY(%!XmZ)4PsIFcaFMrYX?lg*v7BNVA( zD@W}?;F4~g^bLVB-{G&vnXvS%g~^wBd=9PoEtiRw!eFu&)?d8}HE~tesJ^#!C@NFl z#~D;zCCKA1Vx))|8B~{=U8C$Epd{&#fOS-a%QNkncY6Mn&(~KzP_V_=CzPy^T?~D_ zyag)`*__Bm&8CQ#o9C9qa$%JhRR9y3YA}HlZ(UbReUrN`) z5$=S4Gi!R-tbT@u9BV#U?10*6mkUL$V??}><+T4q`W_E1x!`Z$<}yk{bvIgnn1tE% zCi>#M(&l7r$^D&{&f+*{j0U$y>GG9MCR9f0A0>rPZpcQqpjfTpNIAY9)tQ}P-{H-a%5=hZ z4&0KP$T)WGbT}@NGH-N7Oa=wv9m15~2)InEyFh$r2*?#!dp@Pq4$~BgUrH*{;liammt(WM!*t~CeL!P zWH=->Hve;pIg(sHbKgyrE4m7rm>D{I%Ia+IZNnvX-aYwm!^iz37Xc?QiLnJW^GA*< zeJ@qf)aBK~a{102&3;%?t{1P9ZBU}G(4~AzRRvoSvZa2&f zTyW|L<>w8CE+Oot%Kj?0yRu@*djfxaZssk_7*pt^zeb0aUrQhCWT#j5FGp=Eal_g>bFp^ z_%Wis!-wh9c#mu@ijU_Lg7qL6Sp;jq)#wReJ!sKb+7iOFgXlW}x{Wi-k3EbdY&ZWg zuYWdO354R+1z;xv_fo$j9I##992Qeak@EiX>WbMP%gV%GGjg1$0kq+}Bynf1r2tYC zqa7vf>1jfj!~PRTuFoiYhn=PGxoSHQi#nTVR_?VCFpw914{$_uIdHVbYt_**iP_t^ z)-8?9Mmfqj=TFNT3<0M^e;Ux9X^L$R2lO}6$B}m*y=Xhn=)AQMpXAE}Y;JiW0)eB0 z$aS(@F4M2=?y=d~qEj=zypsG;;P209?q=n@{z2*9*$>_r9_?w&rQQD2TKbu7-9r%0 z3Yz>*==@hmjOTUd;l8&~Zn|k*A5H;^iNB8TZqI>>5M4WF*UbKg=2tI_2sJrdi8rk~ zr#3fh)>8G;NtN#Hxaq~O=#aMr5S}Y$9mnF451NzpNN45aAm9n?=AmN;5XGF~1o(5n zsjZ{30NB@iV=*bFy6UeM`NjQiG!*7b&-CB$QSm>x_NP1lhVQQ*F)nv^A2sv!{|Br> zK~D%W*S{Keod##+u-WcBda3}GVlmgayU(P$ppCtwc>ZZiL~)9y0ZY8WHd`B+2+_tC z%(>c*Kv`OGG%FhbW~LbchSDs5{cYKL&*r2D>JTudnQM#8H?TsQw~Txc_kY7FYB!Jg zvaG+f5$4!)l4{hr=s)A!Y7=rVskB3>6)HdOeB9qKoxwG9Z1EsKgKQY|JWOxhF_pf? z>v@F2!9mj-;tTcdgJ1vfQ$~{#eYijEqihrAtvH5+QH$S3P}fa9=wHv8>y1s`4Jygn z$q6ct-n@XY`Y!PQKWw9+5vmc5i86$+7Sn)vJ@(EG z+3q9-{T)c2-I}rMT_EbT1Jma_GIqDq)7S*39Z8{R53uyF1i_(fIhHpj2yo-kDj)Z7 zQ{7muHVt@FUio`pc}IOgN?(8Pl%Wt*gYWW0p&Gs`nh zFQ}m6As-8=zdaP{9jgMOK#H#H4C%#3q^hO)tRBzxm*Oy;^gezj?ADI0Gc7#>(Wc=FKfNK&#qE}rY@UB*c|Hl%Td=b^cV0D6an$odW3d_zZUhu-x8y!d=-5;x zOav?`MQNXDR2Q@fmDO-hxIO?n6#ZAPkb2-x67mU-Y*p($aguUoKUNMP`=$zH4d){Y z8&B5WAJ4I{i@!l@$oa8!YSmX*oKS)V*Ci}=dS>U1><{K$<1=ZxDb<-FX~A1nqr8&) z9ArZb3%i#O%}2ID?{rCJw5&EwGA@(46E1wP4q|#uTd-G2L8LnRL3J&M#*?^p3O#yn3>$dA@N9v0(cKr5wca5%Z^m6jT(oK2uFf@g9w|O+Q%a)zM}| z^Y2rcWGZ&SDjlHB>0e|BLR+Zi>Ob&qwxFgjOT!L(8S`JnDupVL+d6;TD3UdKrE*Rf zBcn6AJZeXzWD;TBfH!W*0G!ekX<|)(7oSiKww{(08hGXg^c%7YWz;$T+1mBXzKQ2M zpS$X_WV?QonL7)f`Pgg$nJC{0)-2!kj7Cg(=f#YcimwtX5(eIx@vyw8Mt64buv?L$ zF(%u6d;EYjt~K-Djt8fJJ`dYt7M9=Be5J?@md|(@Q2dCq>`egmrmd+8c*V2#7#rPdR9t zXJ*d2T^^s$Qe0l$$V96#rA;|;8u_=?0(N2eFBpU9Gc`JRVFubhuH-7Y*&Q@!zuY}e zXQ$+OF}FI3x=e}1#744yz12f8U4LK@(0PmJO-0_8U$)+fU0m3 zw!JX$^fNSmsOE-1<7&FK|3t!5F(_$1E1t5Q%`7H3_L=cUJqaaEeK z!Lz#91NpHC9qxGA^(9$L0Hke&CMga79jI7--5#(GxAae@kEY&tta3lvhcx%y*BbRd3?u zxqNuK)7$WYDS+%4aUvezW0Fm8#9a1 z)alCg(%%9GS-`s`S~|H*T+TjWQ#UG%i%n{Gc$JUc=8W%BZ~Pk`WoC&$xf#aJ_#P@6 z`V$^fIF046Tbe@;P))rSO};7J~@cL z`xX`x0<8X9PnVeT(yAW$<>#s2+wJKu!e(VnWM$QZH$@RV2U1QqfTV7RV!=PO9tS=$ zBy56T(FvYjx8>2SfkU-&ulz}#{&#{TLxX=Oh@6#xtZU~U_f8gs*CXy0)vdSd)i}5a zRcbrVE$$Hm)VyNr1KV1gFKQg5U_4dPVcyi7R~n=x(Z}u``jRpW?PXz`mDUF=Odn7KAE+P~5GNf?y>O!*9(?6tV0} zebTkT*JLxZf%0Mj-QC3kYG9;wDxX2|!E|}$WzN~`KhQC`u ztD>KDh|~n9XcFwX4V3scJFur3HE*Re%++-hTPcJ^%Zow#t0l{R7~NiBmp=l zBOt6A9i@V~r~HAM)RWyVYXI;Y+}G&Oo_;vT3HbQ8$l)o^sPVxA)|M1X_EPT&0(WPKkRA)t(}Hy_x|~&EklnnDdYmb*EaLgpLwmWXHSe%le}r|Tgxm1!Pvv2Xvp;m6#})icW0SnM(?^wy8F6xf4<>lvmU$rmz`awhgfjtm2Q|J zI5pASXYMEv2-FYv!6;0Co6LOUD!q|u(m4D75$-T2#=!?gKaKyL-ZVUVMIKFP9{VkS ztgR*A<6Z9>&J1=YN-9fl1udC$o!UA=i+T^fM!L9LL?CJWKlaW&D*4?>re0sR@{CaS zwwKlYsvwODK>7IfH{vhr=wCLw1+DqPd0iV*ZU26mhixM6e~h&@2%;pn8PM{ezvnMGH{riw(CV$5X~hE+Po_oEs03>I zdj8HbqMWwjQ4xfc2D^B);kB;!ywug@H4pc*l(6Tbaj|$ko z7EoH9%2#u3%5o6{^OhXM^sGK+^KuwYL(^sF#-TMGGCgbsPm&+4ZKg}!ED>$-1ZVre zEKATwZ>2*T+p1S}IuCc?FAG0gB}g9uqhr_vtwe&wJB|DObq}f&zm|(owQleaa8muJ za5fn=jJ-(skBlolHcIC?Pv1`&*x)I{F1U#vo4(9TPV)#~aPza$A0yfZq^YbRyp*q3 zUFF{nGKr6Gegtd_mgG4wxx1v4t!5}vgEK8%Tp1;gv7Blb>H%>GHgAqsgjnB;Gq0Cy zR`KLEqnWpSo@M~jM%Q8;{B)8Z1I>8NEZ>0BU1WF8nX73GZ}qCK{_0cUR}rx%U*|KY zBnqx&QZQTpIFFl#EyiA1tcAn4REISPGi=MBDZo}^v)aon=dyN;c_4KZd-DI6S=WC{ZHwEl!!@l+qi z0(}&G6sC--sCTLC_wdr2Q{3k{104pU?^!=W#FvkHzd7G7 zs+E$*AcNYhZDK}3nFAxSQr@NAU`wFmUG2OaSI)TT4|2Kd?1O=*IDK>qM3BtJnj;2@ z1g~W6YNmHhbo}(XOx`9DrBfK1@2KMdK9K&IrK|YqoaD}SfOmUPN-K|NDKqD={!ee- zwTJ@u$z2>S2nc^OZVA3A zfZkCt1mcp-yXIMHjCr)Ga9%uXhit!oI+sLy%iXm8^pgq<&R8tf3a4%F>C)lK-mgnF z+l=5pChq4!gDpxWXNP98iwy4;CCBNUgQ=0i@Z)n5He3f>?ANS;-+W31Q(*3s3y`vT z%f-9GfN%Z@HFgnA94Ln4$$@9u?NodCZtf^a)!C0kO8(uu!{u*1UF!i{Yb5avOmu|o zglpZUDx@O>`u1KYgu{4;uW3LO&<>VBbvp|sdoC3T`0(mHuA7YCAo>mCI*Bhn9c-26zzP`nKm?W0{ zvksTdd*HzCTzgu&lBe%zA~`KpH1C3wSa3E3+~q{C9@6t0hT79m3Ci~ z&D{4s7%59_Qn{(OlUMIx)ok`t5-biLeF*Ju9PD9GscsLDj_6yLOx8^5=V+0oJT0HK z5S2F;1&^HJKRzt#P*rYpf`bj1B$AZ}Z~IXEg~lm8Hfi1}O9}nPVOdrOO{*aL9-EK0 zc}ivU2T!Wz2PsRgI);npjl9QfKoP@>)wUz+zZ`E)yY*_9A{ulWN>sT{lg_DTdBBkE zg1p)rhkHm+Bim<5l7w4nFu}Iq+ukHBTz%O)x||Km6Mr`qM!_e$Xp}Rje_?F$J6q)6 zDLM=0e&yQ=PMR;JQ4V2g)+hV+Tpyv0vAzklovj?Gf;pt-4c6+qH$r$~oQk2llA+-mMaFkIbxb z_rH!hP4MCApnQbMIofOlS$==$RCD+H{CZrjr$rbLnKj$n!EYhD;R8dLLgq_vH}UgS ze->MDuIA0VtWvVs<1X)!y~J>w+hja^H>{Br*?XsiL3+|pZ6^X?&A1Ql<&y?M775arsu zhfQ-)^5S0+V?pARYp_v7V&yy$+Dj-APO=L0agEmbnQNBh--C3y9>@fKoS0xltdf-j z_M0haoC3EF;65vN?|t5_k&`dsRvJqy$nx53C1(N5XBeE!S|iKoA+zXlBT?t{byClr zb9TNWLk%D|H+4Myc&$JC7bj6yA7}FwGVDcQ;k|$2EFYOe{W7#p15VXIVPG&gPaXqk zUL>C}LlPNgl_es~WgT@lb#A2U_Xd71*!C-MvP4 zZz`p3#9+V^3@{q36(Aa3FYU%izcno-GP){D>GGKNTpG1U|8Jb<0gW6LIB0E$_G80+ z#}*d+LYByN-#K`%dyOOuMkAT-8->o7!d`;%0dNB+@3tNubmnQQEOKLwXG>|tRrY_m zECbM)BM|Mavw2%ujIZtkTW8XzH-nu|UEpu8dPGKN@AbO*%U1jeGRWLtx!t(-!%Q0j zqn8Ov{tIUOAIIa9i%^N0l9C(H>b}ABUg+VEW7+??!+l6vo^r*MA#@7BSv`tz?mCr| ze(EHWfc`M|oTV=KB5pYSr;_Ipdta=*@6e6@K~(ehawRM05Y|;cxES|n7?_`zGnm=) z$P%)LnQ0Z&vWCq1T}Yus-^FDSFvjgPqHWKbxk_i$3GZ>f-`QCM`vEYMg2u3E>X)!# zNOCEA;rq+3W3N3tWcmzhBY04*LER>-PrvFfHp-82CCXzhoxcuEO z16hE70)DOs4c}uf8mzr*08$;`Gh0&FQ%c%ii%!N4Jrgz5L!-*nPbqrNDfBhm3`^x( zR|Or#u`$#rPKPl5m^!0^Zc45e;R%eAwRCoXmXfP54MSp7dY$y-|B4n##K<0yE;*_E zbP4*A`sX_p!F?bUjj~kd_uxcT#R7aW=!5Zs>XRW=kQ&reLG=E}+a+V&3fgvrB%OaI zck+k7%%CDby+G^o(L%DjBAm#hNry%&BLe127Fa}XX)Gh7j#2SEzo|F8%K9(W&j@g3 zqrxx~7T?dL!gWmORRb4>H_c?Cb4g(ZpMv#V;ZfsTxP%9{ntq|0Js~`~y75ima{YN% z9^jW*A8mKdbXVnXLHus~x zF={fSVCDNr2ieufW$ubA3hBjCi?@MJAnUGz$`ZE9Srzv$Hx|KDW_trn3O6*-VjS`E z=`5Rl#c`<@^C!&&2JX#anuq}hO`Km( zie~RSsB3x8Z7B&y>UpcelPMOlejB`NK(WPV_yH4Mfu^@XdNbj0WhPWG&b$C(4kyuD zqQ00xLU?1hRF4wQ#S8>#)|NliD!$QH?(Wo3?@u}4BX#Ubt9zrz&pX&Hp`Sb;Kvz7{ zC3Mso|B19%C$pjXRH3oRD;8&|b^^rgkS&UK_e0T?1WHA`o@AJh<@=8HW3qR8VN+Nd zdW0xXP2S5Pai`k@uaFE!B^_BgEFV4u$<89nvvFCSNCsP(fleM~kNVKdyjkYMC(Ned z9oW{>Oek+>I8>~}jY)TPiLErzcZ*d;1Rty+_;Ni&(8ayXx!oo?ROXcw*etk+WddCX zHt_LL(sBPfBeFE`m$x@c$8bb2AAZ=p0h7b?KUHy-W@Ngzll;-Sy4UMdC+=uv3d_+8 zuOn+dYfxD0TfH5}Lf=`Jp~5(L)3wE16x3CN*~Dalh%b@kIFeZW&QQsp`j*FX{xh=E zXf`VqZZO_lX2+NXaaB%hIeBV+EU)OW^;3koRJX9|^H(V|BVZB&9j>03NS{%+WWlc| zL_+r(MC^mc_^)+v%WAnLLZ&2>)|jPkgW4Z4YPh-uuJ6ltLsuMH=;<(%k$%}92`Z25 zEoHG4v`?*LWa8B%#1DnODW6!O&Su(Lh944_^i~y*ARbOM2aP4~YgxTwK9`ND(F@e@53gr^nP483%R=~MxS(;W{DMs%Dmi7_WhA{ohg zIy$t}pHxZ1CPvt1X7wtW10MjS^ao$gmlM&xyv_z{f6E&6@^k)VW?L00qO>Ez0+^Y~ zq2_HAQtEhdW0vHzHFQ~-_hMD_*&Tdn9cTruC*P+~CDQ(c-US&`_TU#_1^?2a;h%wu z`1?VjKYyN@T9^2Im<<(qX;;SMJh2XTS=7q7o{;c#n=FjBQ_x|flBqa;>WDwZ)lZjK zuf6JUuw)JIMf^sQPOD27h27Vzl8>KssdI0amju31V7iQBu!Jf;1Q6`nVk~8vr9#FY zo~=9?7XdEEH6sgWR7vlPd!f;_&VD$sMT2M%Y9W_1F87HuF4Oj zvq6NuGv1c zEwX?kkb{ExTNb{wpV$C-a&yXSD(`THbU-ti3T8^}FgeO&P5?g)!j-U)R(?;1*Ve9^ zRvL}Do@ei#Pc64!wsHRzPPoW6BW<+Juf}D|tFr0js)nbX>~DWYA>S@1L%RK?_kbk& z;rP5h{Qk!Q2Uh=H;cuFm(G5{hfU52%dBPpW@`5M`@Ggmh)_t4tF_4uGK9(-V4g805 zU~^g`-P7~um*hFZ@Fa5b)nSaF4vs`IJIr`op#x@pJzW)!+tyge_Cf)!y<(f{4*8B{ib*7& zouK5+V$+v0N5hZnqQ4n6K|BZCA?wFJy*C-q$` zQ_C!Ws9xxE)CwW9xNU(!Z%qw?4>!7lu&vkU$-Pz4obgB&sq@N}g9U^6Y06Im=AAlw zeiP}9_qE<>E3oG=Z}0se^*xxUKfU=_yEh)%R=2Pj#&ccCMeTjojbVpP+SEeA>r1kfk$U z?>dP4wmp_&`ZN=^G8y!%nYRT->9#+t%HwqlW}S!Ki6U8+V1ApC=*17|u-0 z&B2%P9%h`g>+=4lbJTrpy9)#$sspZ-|LdmDhnD(`Z`Jlg3(i5;^O%-V<&uGx-SlXx zqEJC^?{||71Nr_n0|&j{FEvs75MHn!N^#4jb7Ls#@fa`QH++J@3GH&l@aMJUaT?Yq zKWX7hP%*QCou8KB)S3N1v4oaf(;V zp}>ArF=vB^?R8x`+5}QLkltaE{ndV#t+zMXq|&W5pf*J4VSQ{i3QlgBJM(|5$;)@O zvO-Hxe9>)jcK>-WLD;&Jt7YYL_EVU}|2LHabvys0&$nke{1OwDnO9s~8~{HbWgyX4Sv~&4 zA}x*s6Fp>XyIsxgvX%aKNWbk*ZXT1=b*Fq%-wi|m1#$27A(i@9N&iRO@t)4vWKRg) zwR4)R4K)+|UU;e3oJl1d&U4K_)B6YVU)AwbMp4t#MPu`7ap{jeWDQfBq>^!?x2Su6 z4>H2i`_Fu8dYZ97K!{J`i(${TaR911Dd3Cu%XnOZoq4VNFE5_p(9vFKss-JWrsnpT z;iIrc;ALR|%+xa6!AGi{$8#lJct^MVo?`3qy#24=UED7;tN!a(8QjLvg@rQlxOg_7 zKT|w;9(W{R##((1Pe~~F;HSkl#NmnYWK4a0Ta5ps;|+i8*EeW3=TvqlUWnmB2Je3! zc9NHP{ShM&b2;oW`<0t^z4S_1dHnGOy7(>9Am`8hT{lYx3vv2ZcI`Psw}0n;K4(sI z;VWpX(wGixXWtl#Y@HD2aW5~lv2%NDEHEv_|8XFC4^@r52%q?o=2d3=-Le@wT#1cV zTjfKSuHczVd9w85@~5jo2{SDHljv2PP!wAkWQ$^LiM`k<(EP~qqon?Q!_4gjRjxY; zy6Ci`#7f7J;7N5ZYu+b{g_-2QcgH2J#vrc#qO4X?eWDnaGMd4Z(%EQ%qcgmFexj z*&;UUhf1~9<7FHqL z_-2aKvV_5Dg~}wJy_O=$r0dp}h`z*3o?$K5Z&`}H+*qoFpzS2ODBUaT23Noxv^zdU zsz*A|OG<~tEAC@|Fo>;o>CS!ve`{>w$Kz9!*x0}EDz6IKmYNKESRmKo+l{}BU?qR$ zO9quWHQ4IL61&CF_o^hzerE(!sr~@dtz6tw(r>VD`tShHhcX~~%m;g9vd7JrUhT*r zQvJRuX)2tJKu%p|YL#B0&ehJ$fDb#=nA*Ok%G39;fJir=GB@uLa<={t4@YpGYJtwT zkZ67L!}1zPdK#{_Y8AWIL>E7lLxTb?<-(cVwFfsv_!iTge0^DD8?`Pyj`_kCe>7*f z7T~AgOWBA&v*CQf8Hi)HSx%Iq^|pS;_-HjcW^bx}$b`}~LnTM`9W*TDr7VthcY)AR zi^DH@n$Dn&KSnGNYf}c(UHEIqjUNe7Ct%D6?{Vgw8Axa-nP$KVF&f%&DP3wAFDtSS zp312p_j2B}%p4@bObs!`y~(v@GKm-Z8bIio@Q2!&Lqf`B0)DE|22H0Twrc%zGh8$yPM2Ik$rv7w z{VyszTZdVB((kZWeO0-=_moIUj?0KT#B@t}<($c@SmAd!$|QVm3L|&%MQh9JIV>ep zb(trcO7h-Y@8C`Xn!gI9SlT|FI~J>JrudXkb?;fm@0@{CYD@_ghY&YCjdiBq_WZD} z{U0tcJs0g7;pSUK|MYAL3qLkc-rUf`dXybb2F}5UE?{DC%oJCJPP2S%k9{9_v-Yau zo@_#c>8gOY)(696^iEyhl2CmDDRooI-^`xmH50HxRlR1YQ(0v)j(fpbB7M+ogkSZl zmH6VhsLnw^9`P!#Zu;p1)shais0D^O1C`26eb>9vx@;9PRTPQ7m|F#X3TdH~8;HnW zvk!bczW4((_nM|~Kpi%j`TOx1;;YmdE|cSQ-OYO^_wsjWo@ohCE?%YCY<4|~pfw

    Me&4Y}4#J9Vu4z({@7jHNEWtx8C3R=QfIaO5LILFym8J< z`icQH^QZUYRPp8RAjcso!HgRv!;$AH^JzhjaMwlf@ApCKsXiNtGKX+8B9RlSEEMUe zQJ{md@)7Pq&rhT%bJ7rhR#5dj=i>myX0NdK&<(?wU0VdXaZdtNF*hLIi|Ne<{vh9kfxa^}jJ^Tu}~Jh#C3LR0`W~{1M)ut&+$Ok&Z43nGqr=w6oQy z)0T-_A#5Mo--Z}w%GEseDhT%<9+<_=LE6cZ)&Wl@&imKPj<6cFQOP~8&NtC-1$L~* zb)+2Vbe{&w^G+V z#DwV22KL_vIQ~uB|N~hrw_{%Ra>6>^mxXgbn zwVm&GdiE&xzaNF^t#bTV}Z<0?r$e*EtxJJe#zrSRn46ck=hq<_2*34wMx z?(8Svwu5;SX_G`f=|lEyVf_A~3qkW-+ zFf><){;?c9VVrx*EJj`BNfJ29V=NNh+V!9ya{j`y0J-_hYR^FJyed) z5AZ@`*`azb(0q@o=l7s-Z~H6I^;E0p8SJs8KX@%TiPmeK0~FyXeTo$gRdl|enH9Bj zZmlP`T09ENXS{*aLZ&)q&*D-Lpi-&Ah|@*Hi(9kygYhfMrCEh=;X3Q+jHZxg``Zyt zdbl~AkoxcX%Vfe26W{_E3#aIej$kRimsCMEfRsq4UE1})H|zvM$@kqps0=7ZuSFKTr9e=eEx@Oe6&DxN^gCw z_Fxyc?Ybyw^QQSgZ0QaUvcSr-XMJ*~GHI^C0^PC6tvgYGBK zJEiY2wnR=3uKTrSMlL@;zE^^;Trab3A}u|JwRt3HwnM_VfxO}m%Lh*n@>x9m@gKPv zaL&B#>~`&`2EiZNvyd7zWsBS(qP}3Lh~W)5!kOwfAO`&d1}#(`@%#}f@=5>?n+bm1puU!bRbBP+ zSkM8pjGY~mYm5|Z00ueVt5joudFfnUSqxG*JC?AaT-q7eBvxngIG#p`3BEzohT z;XKwD_?%?5@FrnPm#s7~`BrtW#Eo2a`@L^7MH0EE+w6#tdt_+#_hp#{9a(lot2L5t zj2EzwV2|OJv4%a-`$<@~N5L#d8s*22hA#AbTXo%X4?Q(V642qF$-oUi{_U zi}-dnK(h9g^OoJ+iww0w3H~`HbXAXayu?e0G1Dv1 z{}s=PQg}?MVmQ)QF*OYv$z)LoqkEjG*u^R`s-z40WKg(T5e>)G|1dh7R0_WaXkVrA z;TN9&fHX*KGJSM<{QP`tEa2n|Oo7JEjn;7oA?ri0*AX*d-c?9VL%pw96wm&@r zKcT0ewIAB+6>U;K8?7|;hWNB*4)NbP}T6l>N3p4s#SZ>PQGStDeFbI zxI3RXic2$Gso%v!A^{S8p>q|F*vOi5sD6UWKH(9a$9$B4D0f|98J&}ya(YO4sx`ti ztWI@4RApSYr%Ly@h@tyqjT9Rs8XC+cSG`_W9*>GEbG5@_X5Ob@uNpm@ zH$S^$(Ej#bvweLnnX_vN(Xk5i4wbPPHK)ydDz3)Cf+WC6z57zQ&L&|rfrTB7HC6O0iLU2+612zRl zaM))o)Fv(!4WlZ6ohx2dAjw9;^$pMe!`4{_wH0>lnwBD^#obGR;_hDD9g0J7cXx^w zcemnNT#AO`?yiO4!7YRkIC;M_XU_ZMo5`NZO#ba;ve&cLy01$TM_4Z4c)V55DI>gy zk+o#A@$?~RWS_AYy>jToO4A}%xva$6-QbjQr#R6;>HBh8w4A;}lkub&_U&L<_Oa3* zYJrMn?CFCI1A6o7`Uv%wM?dqVy*gf;P<`bNDf6t|Ilosit>sld(>Rw7|AD$d%(ljW zliocuKQa<&2VI44pzhG_rBHU~+dSY&mecZtpv+UXZvsQu{LWAu-=@J4`AFLeno(hk z=!t1JL(I#W%Ij)1PxoC!#`JhqPl`|}rHwoJmjkIiGd|@H9v|c-kItT{L8@GQ|47=q zy?1N6Zd`XNOsMP+flWn3NVG$uBa}LHLRL!WC+mqir-1TypvCoSMtqm!a;hQaOtyV< zoabeKHJ@IuArM8}7gJWL9d?FkMP6LWB6%89DlA~U*6OxsW9~FQCDs*Zy5j~H(YoBq zF`8E?oK4k%ML}dKdNrSJu0g%_(J`U`M);jDF8fv=bADl>mB6$3LDHRRj@_fz_9WkG z%H%T)K`zja+&tF+$zFx-z)xviasW$0pk1xAB3n}qdTf=gu{`WSDP^pdE(fS8M2?3d zt$)*{2xuU~d$P;WbHr5Z1}+Dpt}&&Ektpwv`WnN zc8?X{o}%+W6yVO_8FC}Uc-wZlZLo(fdl@U@rGHV8dgz-_O35_C#&TdWFc@16M3iiZ znY7NO?3)wmZjl%H+&Jj;E9r}w#H59Mfm0PTlN<#kpeC52wvqMQed*90G~G1AGOOVJ zGI%J3YO~o7xkq$0!>L`E_4y!X;~JiOVbUb%Z>SAwA7mib%qeBP2_!i2N*@`1|?j`5j_CRs!K49JSf=jW{paY5oY=d4KPGRTm8^}Tq037&*PVt%* z$RWrGHMe>#HLVO}_Am1#o|jZH`nM^xBc{dpwn}FH6;EF3>-K9CVA+!d`fXumqkA7# znp<(vI$^cy-_Alqd7+Ps3v%7auW7KDW6xkFnHnQ?Q;7oMz-8Bm^@h~943UP^&Ne+& zKn2s_E^@?*fKx|)mmQJo^7g_r0msQnFP8(J_O$t$Ca;=kLgAxmpYw^r`15wXFFWO& zgz@*cu%`L01@uJo3o^;NS>B^VB^W(j$c{?Sts<-dfHBil+EUJjUBmQ~!gpu=94ge@ zZ(UK^S*hyRX}xwkZ?RCr^;eYbRlnhre692 zO;anB|JNwT8`0qB7kD{6KVa`?0#h9MVJ!7OJNn z2=f`%9NG)Lf(@H?c`+EzHRL z;IKt1SEAN;OP{o!`MAW4O7l}HG=s3I5=OxsJy9CKSp8!2$J6=%gj8#B9_I{#pqgJ6 zx`36G&X&+7(<&0$A(h=#jqWM*Oj-H4`j~Z%7Wq``VXP_qS$!EAl37WULO@x`4uy~YwvnK#iq3*b_Z z1+*Bsbe$G$k~h+R$lWc1^5Sp84I^?gtDyOgRW%1xXhNwU6lYE{O|;3!iX7TAEc$!t zxne%&))myplsv!umWW%i5~UWY3pQjEr8d$h$x*WEjsdbSueK3i{-GLIovQZ{w~OOw zqpb;gf z*m7>wT~N6;tl;k1rws1J?%7e~Ao}pyrf}#+rs8L&DZc_xUL(qVt_@B2l{`|JK-eXA zBb?f?dQ%0xpC9;r|6! z#G6f#1f3=Jmuz?iZQ7qE8tKUM0&bdNsY1O#mJkV{ij}TBF&WyHSQb;2eXr+V7rf}4&JMO$j3&{UmvqXRE7SIJM%*jpUGe6z4}Dnl+J}br9V9#G}gc4xu6#w80NLvWB~21H{^b=gqfzJ|=eEg?$~P$*gxJ`bXTcBez2N zA{=)c>j@7=;I;6$*D({e7*enqUlt5qG*$5o2ATXcm;e%iEc=&Ip`?em*f<}K{&?Sq z6Ro-&FIyH>`tJ)^BW}q1{#l^b=!rW>$LV~_5i^e%7!qlJsD+olUQBh~#a`F4EPZZjwEt}e-?h|A?&m1qd(3;Gsb>9Z zt9k|!DAya3m%LF-Ct$$&=9`)Nm0#xR+QB_WLw1DrVMHN0$z3D<0K^1nW-L=5sG##K z&rd1vz}6S$&q&NBx&1cHgT~(@=H<`n#f!3))!*1AG}RF*REymb}j-0i#mlfV&2+3UKXc%ibYxb1I&^YcCl_%p81xQ5aXdmY`N`qB9 zCC=EV6g8>wkM!ulafxhT-}dG0i!=QGra)`%MsvVL50k;+X%(~D zYLt>I=b=7Gxtzn|qJmLE|6bU2pb%F~3jYLUGTWSXQ(=ErEq#ZXaL<{U)X9qlxl^$B zBLCf@Kx^?7yv5Npk^!+_Se+K~#GGKG)cDPh!P{Y~E`9~K;h{?|{a&ZkOzj-Lue{(Q zVsy56ApJw&B;RxDSw5SM$C-796s?9>8Ny;DcnAGKPf(q3_gY4DQqIOu_lCJ=6esPb z^K%;KiDq+)*_H;}$O$^@ZQK?Bw}y|rgzwb0ur}Koq$uk@PS^Fo0-8nb^UV$Ru8kt+ zuhQrBK7rgcok>vHCzW|5?`NS5$qN4{O=1e2hJP$N=L(!RG*p;^@o!R%GSYbGOq73b zY+3!*k*55*%!I{KW`xnvk!J)-{x`?Nt@wMTN=){b9uf;akSYfi`CzA*)!Klm=4Zt0 zCr38zl;vOGj`2z}wuC82H-kN8p};1r{_@MMqh z$jHcdv(*|P9j(}H0?i9$?}yuihGhXBtU*EBJn6QM?FVn+U9QA_T+CH$criFnja?>J zMz78ur;ROi_WUKr0G3$uEAi2m_;aF22m})yHdU%8Prn{AC42S#Wni#kbQRiQnSVJU zC4gVPr{7g;cr4&;=mFW{-;)>G<(XN-W%D1t;0G0NT*Vg}MNZUp)nAI#7Gw>7cma|= zE{~0ThyKp|glPMD%J+S6#=l&z&8)wzZ)bpHCg8$9s&Z)Ghlt>Z_dv0Q0d&6YxWbn^ z!m8}y<_2KP|zgArK)cN&*qPh4D|TPEWO>+Ty%pWWP@hG;i9TloL8 zYFZy*>b_`eSmLS+?g2a6Hi+X zrI?a|pQCmgMlRqKMr~Pexb~zH)DO7!*DKO;%JD=7`um2_nBkjOmRFjBgv*AY# zLREqN_qXMZE&=amM9;pGJv?qDl`~v)`=^IDxvGqWA+j(Vo#hEs=S|x|0rmruh>!ha zbpP^SA#R3#YX`c`RyZ{}^+Tu8@=8ix4SrLC-Mg#Qjjz4_K>5TS6jP$lCwJcbaN}1n z-GA!3sZSC@af@NO)d-B!`qfGdgKV@NeLmNciKyM&chHzO(*>&9sQDgl@ru6~k>Z({ zGSquyWhsb5a=sigDT_q^j?&STHPRHhVZ!6}ogAHf-Y&eu_Mvda=T1VK zd44g54cY7SAQbZ5>^M4XFy42>`_?GX_??kzYgtWX`6fT+qb7}phk8aU+#3={zxXo3 z?nPd;4$=rtJ*n8<^lyR|D&0OyM$I;qX;_Kec54rr@8M?71F!Xxznhvr-Z&q^RO~Q< ztv5BRe;me1NY-nMchbLDnIkqQe)=0~N244jZNI3%0m!`5qky|0E9qMd8TR8cM=vGo z#PQR}2(vqhzxg0Uq0UxceWPP3of!UJz$Q-0U2Cd}|&vdoepNH@a{_Cab*VJUDj z%ar6}htcHQx4-N0Yt|=QlwrNAiP;Yoj6S3TI^Ci2XR|ww6qQMYL*3O@;~d&h#vC;w zF5TtY_Q~D{s$N;AL8^go<}4`czLDxwyv6my+HC8qiIg*dI3NMfrmGPUslZ=d=gxgw z#K0_yjO1R&In7pHt8?b55b}{4aEU&jop=1JF-iJ2eJP?yn`}h>MqcCiNo2IFv-vt5 zzVXo4I+X^;zkWmpJRup5XCoO(a_U8dtj81idJ3cF`KLKn29cHTk$Z%bzx`-HXXovR79tw1X>kZCzUkKy zS_Q;EyuIVoj0$U5Y|D~a{72+`h{L_jb}6|u$?8la;Nox+PVENnFw0VvhZfT8{BcD| zeY?Q=C7p?y=_k1YHP$@R1W$p0kUngvR?5 zBQmCyir%Jdo@f}%Pn|ZAw-kNiF{?6ExX^nlIg1u7!US8wf(C5G-5kj=f!7gt1fiLa zFPVVQ#QZza+Csxi{FOw}S6Zb_A1=|Sh#7MW2RwJ%P@zM^{7&m(a-nUKtLY*$$l?1H z@3sR0hDPe^XmT2#7SfA9+T>tJ6NAp-M!cZ*Y`+ZdfYu*tE%mM$<)XDD{n4OItu&`F z?&$MwV5gO#9TABd!9h$sJH<`VLZgWL0oH?4`eoUay7Gpbt#|k+5B-N0@hVBp_WXl` zY&@z5`K=Dmb%4g&_;o~~q@)9CoLgL=nkdE??Ea}FN3r^%zeDh%w0XNRN0yj07ZGTj zqcvnxyQybFWj!+eNeww^Shmfe`oO0p4x4$zo9QtBOpzpL%ku1zJw}n6Ra;FviN#~u z+~&RH2c5SFR$mMap$U~c3XC&v=V%)2NR22tkz3TAyz@z#nJgz8=NXYp4*x(Wd{wY~ zN3Mx|!IqUKey?qbOMeF`S0t)R1R4j_{)RTVB#4!xC}f$N%sl=p)w{`gF`Ru^=@3*} z*X!28neRq-0{C|a~Ld7t2#><)F(o$$i9>EH#d?t@od;g=u)DQ29nt=7^X=ISsCBxN~t>jIA zqHIdc%iyjeyLV#KnRSwrhrjZ=+GX3*xC78u^OBlt=XF-9u2xna${0bu^Z85nh}uW- zZE**U0H=MJWwEIB@AeV8<|ev+o;T6t_;^YPe;^OqKH!m{Q24B>$>hDrr#2UdzLI#F zo69v%_s5~j`Vm`e;{$L)BQ?jn%ujKLJ@o;UqxJTXKv&1j!qw!_XP(fS zTy;u?kDv+>Fay*nOf~O8M`_CC3`Io*=++Hxh5Cb=Z+Sm7!Y}M0WinWc$n_p>&`-ts zUd@Fr=PA4d{tGQK_ebhRk2$R2kx18_9OBI`zaD6p-Mst$u?L^wmmSE7$<^%TGxp_k zV#PmGe+MRCYAPaQ$g#+YAwTma6yeWV4CwktVlcVb{jGW+Dm$QpCLm9P!tkDKm z34J1P;_SL-eBDITJF1emA3I;VgnEo8-XY)=sUSC(lGcGQ6_>Tui-Nt5NIApTfXok< zUQ;>m%jDl@T3sf9tO9u*!n(_g26RF21I6r%Jz{~|Xkohs*#`A$K(n-N?_+X`wIq#Q z%k}<kYvHzwZ+a4yzrm5Z@zzbf-8vmL&~ix!2hvJ!$rN&yKQKC}^e> z4rugHwAy8G5Q2zGBE3d|kkQi9UdiRQsJZ>iN7NpyFKCYZL@n6*+yZ~#=Uc(TDEtvqhOhi!J^W)vWKFO|8 zgIDLF0So82kJD_d1DMmjIQDc$j5DOf4tqnY!KlV}i$>%yAmu+b^^Nl8xm|(;pS=YB zgON;-w-{^--RIsN+#f>=8wiSzZ@Iot>_!;rhHN~7@V$@0H7nEqN-CZS%&8q~^Uo-U zfiJ7HnOiZ|-Osl^hpFJ<;NHVAxGm*bD=BChMC${4PdQj+B35#f_2UO`@AHtV*+%-t zGp4)2|CFK|ugm`#XXEj=as!j+f6#NC*3pmlc&0Qpe+mih|BCssH)#rUS1~aaMpZPYo%Cv<7TLPRb77*)La1OW)T`b=Rq^+X%neS%p{&k z3!oi#FPPzK@U8hoDD?n2z2;rE$Qn6rz4p+yrA~{OtT0#mZ9c~Hy#0iQF-vMJN~ry{ zI|GM5PN$64S50rA<#QWLwf9>SU-J#KYr~h}jqjrOqBJaiNOT$hO7=0Wjey$n5~^7H zwi@XQ3aF`CLs(VB<-Pg`&I8rId|t~{84nlq2Nv_|u2b49D^GdbmKtgikkHT)n8m+Z z#xpJ}f8DoBTfOt=c z=ibzpM8mxp@$*9EI8_=;78{0#uM~&Z@2X~73jU}M=(yR%$vs3+^_ZjV2blPlP}J$q z@Xhk&68$K9oM^$>?W+W6)T|#t?&?_S+dmK2H;?N=m_6yvYu_X>Jyo@+sd@l;oV3R> zBoOd~-4GdWS9M;v8M`-cH>^x{{r79!)+@zB;Y>FtWo9Ss^&~@e{uS6XTbx~p-C|$j zQ02*;6_(wk*hb_tlwMDL4uo@cLDhVucdEV4&}yPF1t&AS=xQCn%u)$2;n~2kg?AyN zm83k=Y}$3E6rjaHe&qv56bU@03eS#b5T%&wdZN(nwcR+WLXF?-Vh;D&5QKApHU-;0|=c^cI6dBYAR(hIK ziqTf3vO?!iFQA<9)jd+N&uy3d0yku>^Ixf@->OT=<0fjxm;2}ywBG1oTNCt3Y$niFyG=^4PXyL#-(^HAkySi*;Evp;6!-GzYhcW^D0w>B>u|k1 zO1mKeH8jQ7a&wb~|MZs5(VW*AefvoQr@+Y0*L#qeHf!(o5<_t&+Dvo(Z8+rLN5skU9SmuJc=a6hj?h;GHPY< zT%03mh;=GRqM5K#gmKEo{n|?cfg$v7NJ|A74rgkARR8=2!B(I-9^15HzdIU!r#&cP z^e=~*e{==9w+hkgp07RjR*!k%-E?ils3DLsbpn zhNpsi;zY04WTK~2?x^3YaK=#c{g7X9#@+6SHl|V`Ua8S}yI(WsG$T5Eg_wWjD1UT4 z)BT;nih_j&)Eja9`-wsBfW%3vK#=wDOi7*Z`=Di!HxP&8ftF7n72S!mXhi|4mY84O zo_uS1pxPC8${fVNVUj2rKD_@=z_I)8vHsuThR*%eMw7H;7CcgUcZLYOYn+kHFt>vF z<+<;o!=GC9gOo2`P~=2s9%?e*)dHWf1@D1Jxh|=xUwV5Hu%1VO2JVlLAoDS@vZj1G zHdN|BQPNyh#koASs}e|=_s3Ft8K<~8Ac z!gRg(1-l{){+kWx_*gG``lpVZh~p(u%y6exiLovkav zPf;fu3+R#h3$o7$I@nRg19#isigGU08~CBIDB|W94u(;?0fL|j>rT(6JFD3q?(5~X z5CL#v3e{Mw7~7nPisVI7*cJz;_mMHz!mM6Y**Qr}3&l)pVVUiy`}9hrZ;*$Pd`^BT zKIR`^;`f-F3sZ@pR}774wr7bdJyToNZ2v@5xLUTTNNHMiukx{nAG~rOLiIiyVx=(u zyU2b&ZSMu9v(#$`u%EQf_*r|D+e>ZI{$K*2R`zs`07-VK&|qs{nw)$}d3X8u4kbgX z#9d}gb3S+My+Ehx8Nh#Rqs%cYPiP$#&x&n*(i@r_Pr;lUg|GYH{V8~-Y6H=dP2dp1 ze;Z{om@*VZU+ihWvWGt-^7aT$xzDAy{{PLa)}=Pv^QYX)zu5J#0JySZ=mYXKJPhFT ze3(Xk9p&8pjwvJId7z$>Pr}f5Advsqa;Fa@=JS9|E|kGcG+0vzWq&@6mEC(Jyo|k0 z_Bv>6h1stcH@+eB`scOn4R{<-zAfP6TkGwvZY@w8X>UQy8E%36275PGz?g`*ti zC%E{_?5qLXboZ;NGwB?iG_SP9Kca#5QyP4s4gd6rAR{6B=urN69rfWyiT})|UTtj) zgLQmMiIKp=FHYui>u@7qbK>3*7vs-LX3pzF#YV~2v)}rmcM(|G9pzrR*_G$Jt~0SjkwEXtu`eoC_Pb`q`6NMt08PG zu!tkvB8=w`gw>wrbKj>A7-_;~&P+?}ZAS#=bMisj4AtA=L8MIEsvN1OZ*dT$?j)W6ji)Z3zYOK3qZt1DcMz}lflAV5-!N6 zd}Y-cYzp|e6%DY9^OhuJU})?Y9jEMZz2n>9>z`4SSEmtEgfM893q`QwEtRI|#1$vI zGX1XbO}vH~Lewlaxf*$E_H>cOwKS9$YP}o$;uk*f$8`g3GAvBxDNXS*r(5j|bLPLq zg{n;yNvRgSohuum8|bF%$$mB@SA7;#zp330scy-tm@F+B_w|)A#REO&M#D8`<2w&O zHIEIH(eROceY1s6eMD_;n0M$FauT~gRt0>S>tE_&<}P?F>-dUfs;JcU=G?#!702%v zw3KRj;AbNM@m82?8j0q2L7Kq`L%)Bs({0mS##BL2jO0oE8m1*PsRmD_MY`WQo~@R1 zPl>gM^_NZ8Z_=Myur-K=N!-r2bA;J6tkYVkj*z)l6xYF{fqz_op0`nR0IK?k^xC?F zlHXyGjC@{Jjvv@&xK|vIFwP-#Y|B#~2{^ z)`<6IVDzmut4#TtY@+-UgQHeSB>BNet|U9U3jrZ|af@{YuXq{096|=wEyQH7cp(y? zYsm`0_}iESEP#QS`UEKL24k@>9|MEx4AVJg7~3?L#dX?fR&*z32$`zy1zy5~^cdCa zTfD%wRHdda&*M1LMWvf&b@q(SWQQ{+0H8|NPqkQa)BYQRabBP zX%P<|Vs2N#afien2<(FnzRss?uq5NcJiP`RY48=uY$IWZ%Z6RIu2xE#s0Ptz;!<_N zPB|xjwJi$P0a{*Lbt6n=Gz)<&4=ZQR0-3Ghs>bVeinhWD=J7bL=u^0(6L^PYxfx{| z7z*$m;HG&34NY5-fqNS}Ijhm7d96X2|5)jjiXL`)?X*|)&ccWjdl@r&5hr zrZhFa>Q~x1SG$R^o;UMiJc~9a8tT579`!}Kjlc$~Vf4&~&*?CN#&|iMQ-=N66V3EVF{^wPAVa}+xfOPZ_CVBC*O051 z7pxm2k&y%Wvl`F0^W&)JP!SIE^1Fo6qq*)cz|YKNt(5B$Z4Rxu{P#VoBNeJpu4xF+ zGb_h+hYyG0C2a}{k7Y!Yi+;h*IH4TsPTd!$ z9-0!8wvFH{RC@MZAI{0Jl*pNs$ty7r8>E@xk~Tb~ zWgXD>BN30M;Mb5;Kmf+_Z?Lz)MW;6$o^e)jJ(ijPXR&SVY?aVzMhAt;);VCWLbh$~vo_lemat@hcj8-CSjqOmkXeGn5UGzgf7vvpg7s|N6H$n)> z<~+U+#!AE{odZ2=Ah^zpA`>@%0y;?Bi3G*jt`j8?B0Y3(cYnW3rFf{?)BVmAmB{Q{ zFTtf>;Vm@f;n%(s;I$dnv8DPj&v<_Hz$aQ!4Rd!iR~WtkP)_@*t>-WKSu+$DO^ZEy zIliZT&ZBwRmRk}*)+?wGemx|?zZ0pe37hw~vo+r=;RU3I5}5%H2{6O|7bwarsBI~O z$>p#-HULEmU4ACrw>AbI&+(yO3E~aoc@q+r08|+sDm% z*?h}~(zn1ImR?Izfe%}tuexy*ZXW;BA_5c9=^NFdJ@}!{WD`K+r!%LhUl5_p#)*a$ zuauiU`hgpthoiUR<@2fg^|2YbLL3gQ#*9CsB-E!eS1_5-DELx$n66CFO64>#iUz0J zo48^XOT@-KqPqc>ob%UQe)E_(9X>=)wC=F1o;{{C&!kDBM@3Vh@-=o-VjfSXx>mB@ z*jxBG6_|!cX4V~5u(+j?VV8aFyimGSS&0I{b=d*O9a&4hV3Q8vY@jz4h_-i30toO; z75tNFvQW-<9`)=@X*f!vXv@7N*Cp3o(S+hhwrmb!k{Azq%aDmY6?H;6Ijvl1s!u;v z9jD%9Pp75FRZD@*#WI)VatNAr|7sf*y>Do&UaZ3I=HROCN$yeDP!N(!duk*#BBQkiE+xo8$HQ;U!i-^@3t zu-qhbQ%IuEFfX0-VC{2XJ0d!bO!=9{J!oys%O$raSDrLuS{Yt0C|;L8Z(rEH^zho` ztY!B1U5H`PFR9qR#jqB&sIq#@%7P5@a!&XlUb5)({7THbhLEW8t1X9-@>I`s~wG?8UA3nyTZ}g3kJue<7COzv0JdzoHAfj z3Jfb0ayInsHjVIl-Zl4|bia=^H%0L>o(d@Ybvk}v z`&C}*2RLvbU|Ar7exIvMH2G1(aArf`docAHO@bU^@)13FDyOvh>ZiRyFo246QlNt% z{#H6hQF0|&Z)axt>?vpDz~3yzFNt!-Ju7zt7$q&f-xdcM43h1(xR5A(pYm`qC@?~OKdhCoc z9@Z3jxWCsyCLa~oaYr=Bxh4%2j_|rIcCj;;2lBj+DI!(5ns3v%(_DSl<7sV2D!3ZX zx4n;geExy}(Hj>zR~JF`M(uqoVqP>dk?Y4(-{NFV!@Y$1>xAlNXMX>^aScP{czOJt zc@le7v@5QpGPTR&EJPms3}OjfKo05g)=Qu+=tL>#e#Q8?c9|8p1qJQTC9b|14Tvw^ z6;1o_>+6>om@m=!3l3Y%wA8r54g$Q&fiqc@Su^k4< zZ_O^VfH2PGwFTH_YT4;9?t7mfD&7W<9kF;nVaOu5jP!sSA#UyE&G*^4D;Y8KHXnEx zofv7UxcuQN$Z#+nFsH?&?XTrIcEHs+Tfbrfn7{j$TC5L3XndaUJ;c+#7h~_la?mxd z@;qaHRXQSyC+|%VkIbMQaP;|hy#K{(uBCmscXg8E-blHo!JBchP8^O%7K(BALZBrP zw|!N9Kf;Nb&Lh)1BeUmO;`YoKKM=4&lcs;McK1;}d1d^Fs4+7~qNw;w(`pi;GPJIk zEPZynn>U(p(syW`<76u1P&k7(dx1yLSig+t7fy25w)IqM=>Ysd<=s^mz7L@M@pg2h zV=K0$9N->Y^|{B4M4z|wq1of?C7EPgq*TnBQG{xz*KL0;Islh#~r$e3E0HSeY~0q-bch%Z8i`WoD$1@5Fck)=|f_SqA-B? z#h@q8L@rulw7qOQMWc;$WXxy7x<#`dRChP$COBE_jcUV26{q)ZGs}zg;!eYRU%L#A zJ~4?dFi-pb9;Nv;H5aahFwXYP@#i}V`{g=0?mU#+uK_)JF2{q0Djz@OB5^}MTNMyC zc0kx-ClbDF?nmFdC*>d|iKBu(ti_`PH2mQGW z1*GRb7h&Mv=AM-36AaZBIf@kY?YPYKTA15s*%@SZK20k}{4yASUhh}V+Vr}2>wp&e zXDuyLk@L!>DYMMzU6XAVz=j(GrE1M$X_Axs?X&O|tsMZJ5&=u=%e=8^b2Qs6^9}36 zLM3NcG$SePlY|bR%!eW~rC05?7<&as1HhWx;`Mi~4t}6}#+QhQ`pdLmI^W*u?^lRx zer)NQ2>?>!j}Sg7=jT1LiQNpvypJ}Ao`){vdy4`x(VfUgKC^P?LU{Hg3>awe`GyXB zd7_MZXdi?*QiEbuzbUyD%6H}S(^;9?BsR0X-s#PbaNH8I(4wR=R~ZVgPML4(IF|=! z5v#;?oD91#>(3wpdS%6N;aYl#M%v0Ex?P~ta`s4@0VPj;g8cPQZ&l_Q8GG)X{li$) z(FuJ`wvLdGhtGtb`c78O8ib>vvuv$UZWhPIqXc$0bk~N_oUrW<;sK`y$h>K2k>#Ks zL+W)Wlc|x}Ls(C%pTkms(!gYcNeVk&PP>%x>=^PJ=#NTUG6kiqTxR39;d`ZX_y<@U zMENX0%M`laq*SOGES(pR>mp)PCv&&TgFnr;da+O?$Hqi%B!p@@`XGY3{_iXb#Hv3I zthWJ3V=;NyhzCyg&Ptnhx0(K=DGd8qF}#A}rEV3stH+;EkG^@h8A^4uKPRPAFUD~0 zE>&<8vcLPw(%=mOL5%lukI@Q6BoUg#G-avSclT^>OvkZXSGxS4x52lhlPa+#hBn`#)`K)NYF)$XNm;cMe>=q$wDsb>eoINRSTcmrv<-AM<8YzbS+38^D zw~dL4eD6~-`l};_7FUMMzXk5A-$zL|RJn5`<;pG^#1_zJQmD!JP%Ye}*;*lQjmhGU z)vTaqJiW8$zfNU^RuP)>XVHz!iL+GbR=J#|DZ_J0)aNjcRtX}gWEABdUaz2d15}kY7_oqZAs+Rapk=14# z&el3oSSZl}(j{R4exi7%2X+Ksk$;{sxk+c4Bx6b5ua+&n{0VW6yW;~6$JORVz54)-cW5eZ*{h8m8EJ)t)oE=li8pUb{ZTs$n?tNDkXG|s84SDmg595Q`A5oa z)+akV8;S>$PRK;i#IIpRmEpEc1$R^if82typRaYNBR7HU(9nXF$kz@#@AXKSw65I7 z4tUScAVQCXaWz<7XFS!2%chmx)TsN}Vb$ZcPjGznC}79qC6Jg@gr4vS0<}8Da^M=U zzb)Ca1`2EUE(Jz>3Xkbp#BKq?;>}+5EO6E_n^de&L%CgHz0)GqgRa|Ljh>@t40g?F zB8UdtMv&deh$GXIwCWX5c&Y)t{?*0-h-$=5U;|7 z1`x)?bvmXPWteMZDOcEzkq--eJ(W5?_wRtWQKaPr!DygDZtBL*sNt&>CMTw?8M>F- zF`Hr5v#~`bLNO6g{o|r-=D(+nFA5_VZRWz~u0ng#Gk!E)YfjlgJGtgIO)-Rxq_!k} z(xS4%zHckNw&?}*-@$@c?5A++dVK;ICdUI-_EWYOaS!v^8fwV2n`qRn@jYem6&a_C zBEG2lCeo}Nx{}IKvlltt*h5vCaZ?Xq^Vi8Y`ptA89uPMUujLEtG`YI@CrYL>mr^zk zXcQN!@?)ftmvQuEzze$12nrxdv-ci!n!6Qp-VG!08A?{21|(hUffXGXa<1??4@mW8 zJ4MT!2ZMS9E)pN@-x^I^LEO%*CH2PzCm!Uz(23h`dBP(xj|yV3DO`R}^PorAcw{w) za>dl~gO87X_U#C2s?aRtmPvMn=cIL-LesRsyu<|^W|zjbE#Pdt2WF;aAMHS?bjbiK zGD3+i9Rt)iT4?T>Pk|=stDgv`#$1hDrNkp}Q5G&I~p1tJNig&EqBiaqSvN=>Qt~ z?VZLov%>8O=D#;@{J)>4?sTKBY;})AouV8w{@GLd zQVq)>vo~6i%S3HF&SnNDPSm^K4bXpdIlHBd%oMY)*YMNJ*ae8WXMne?6lEJdcCl0h zU^scNN4eKWOczq^Iza-w+*m+#wgCD9*Vzjq5{F^|&WFUVx%C%ImKHl>gs}7Af)xi- zqP^1B0MWKJxA&sYYn0~aohJ}g98z-PL2I#RL2eRoq9~jT`JVw=n0;=*qC95>md#H> z)!0)HV9=HtZ9qBe!rk6<<1EaUn1!*b>1F&w&FEF#(veT5vk4rIrbJ8d^QOO2=uya- zjgI1HJHZlr%mPi55OdA(Z`mGglNvq7@J5YegqjqDU&ikEXt9iJuZ%N+eiGFN1!b-d zeM&Z2IHDT!-|8nAo8${ZAO^n}v=&DSQiwLK8N+pDaU|1C`6+(ee>2tZw?Ev+7*e<) zt`v;ASJyJq_kQji7R&;wLv$)}wU8{OJfr+Rrt>P$P~CsNpP*(F0Fddb{1D&dWCJZu z%M&6Q^# z6su`!f~eXGZN7nM6_+%njg$SGk&z6E5G)$CH%MBFq&qnA;s_v-+8TO-nPSe74atBv zXI$U&bLlJ+7}$ae*(Y&EhJ2);55L-%yC|!f6)jl`PpVYwz3)HsZSOCqiZFlQWl|4B z=9AUr54pyvyD@A>yZYq7lssStzOnz)wv2V)WWDZasIE*#V(Fd4Zh@?gSlaMNB^&yy z3RO{C)PTEiXhM%`JiLJZkhIiF4K{LRKFIrE^}(?(&Q&nebiMb6&k3^qYE%u9f1Hi_ z>`VA0s&9v#aumZ!>eGJL5B9xb3!a9s7T<=eKSRC!* zW;AY$X|fnh6?w%MB0scqwz$wDe1}zo5cU_5{6?Cmc%ld=&PQR=)Z49Q| zu3(jFW7A?*+;u`(9I3?0C<(QOXdN3GPP@QHaj3#FTB=07*n_d)0tGHh0(2lo#CsO)0TVS!Lx$f+>Z5%u3!LS9cEB~zEry+e|Y4lM7<~aH< z&V8s|v5VZr_~50jqb+$ZI9d&FuP%J|J5h0+rhx8e{OgCWJhVl$rdc*0-;^F}l1hZf zq@f`xB+K-vN!t;C9fnDzoUUfPx_@0iCVh(QQdOICUMKYnew$$-SJN8Kq_QF6r%NFZGF+NwZ5EktuI7?D z@}vt%vaytVFKz+3uAWKeF4rXSk$;Cs^uH#sK1|dIfzrHd{qq3yk!?d9bcKX!*CPSC z-OWS)PGLA>j;O5M^YG)0`kNj~887tlqf=Zo1=G*O7s+79{$9HLcR`=dn2@x{l=l^hrZS9$BchPck<}6KwK^!i@3jGDB49_F;Z%gd{fUH+#H20 zC!9Nc>=uNN_SP99lEp})Hyp+ikbPjw9f!=D%!QrGET$o;l;B6G6}M=~+!RH*`|5OD z{qxMx8tng&^_D?#gk86G5*z{qch^CKI|K>t?hpuW!5tFZEx5Zo49*aOySuwH_=JIh zkLRuL)Op@If4cflcXd~F-F@AAt-TjOdG!^*QDeq7aR&{FY^Lgv*VgaTmk(1zPCc#} zS7@5L_AnW%#bJKPXVJ3QoaP9Hg8Oc>ImAPk-G)My8E%>U5__gn%kVv(-f<+I$}V46(K=wLq)VqVWCEfd z|K@DkRHAdROldfOK~JpS@)2T?A!>wwVo>jL%VGSE*11II7Um_3od0pC(Gqp*>EK|q zVa>kboypVo4k5+I2FnFv;hkglouAC3UZu?>UxR$GoR$+YK^A5aBn!U@HOi?=V;Q1sbs)g2 zCdq>it=F_YqtZCkpXunpgH_i;FBA6x&4NN_ux>achiwkWt^UW-At+dmT`bxdr22Q4HnU&FLUJzV!o|QD$k*^ORVwei}f(!{$ z{!bFs#zMhdlkFYg$H1<0$1a#F!M4UL*crN!2J)SOO%5p=#P0RIHi)(88&9>{F|Rd| zw~B!7ipK74-ypgNy2t7T-Sb!uX+L#xUjI`>(ZWRzdtLDbdl}VMz62`yjtU&*t~t5P zF~3(o?he6FFYyMCEh z`HTMzH)DE5t3(w3S=#f9bG1c%MV?YFITUbmTu0xG6Q>@5>-M0~exJV8(1W1adxxx{ zKA((mt~Y7?`lL3gGi%8a>8frae+)B*V3+7-l=|IAElF3+W3H1Jzdi&+MD>PXYU8Iv zk!&Nh*`MUwbps4vTu~x%%DKwb4Z*Cj)gR1)e~Q1>;x_3E8+~FvZ2nMPDYKcB#OIIA!2zj3w>k1^x^iuzBUbvuk4BZ>RKn976^;de-a8;r$Q{Uq(K+a|60xS zuKjsR8zS~CmMQYtGt&Pev$Jy8PT@K~Zi_4W!wr-EBN_UZ_tygLE77Pq+nWAJfh_l$ zffLR5#g_Cq^HYp_@>JCYO=%eAMFs${0H@Ei*-y91;oEpQ1eX%K$C)Mk^C>vYZj|Nu zBc`IdMP$YGv~@nWMyN6?Micy2nuW0jGa*9nXmXd4-H8>QX_h=@*prsL~GlfAmh{5hKl>*u73&u!A% z{&ZrDWb?T|@Q>M73A|tSSvtVyx*F;H>7bt-C^bL3V=fXM+*lYb-%?~tqY_fnf4QSA zv?W#^msKgR#OsFYHFJ33uZqozW>w2NVdUO(vp2Mq^v6a~n-pJGOkB(3s7mJJ=CGZZ zCu7fLr7^A%ibk7LQE$u}>!h4>q&AE5=B!23K>JF z+O_3~9&j|MYkr!6#q#C7I z?0E<}8r$mLojQNC3OMS#X&e&MGQJv38YG>3 zgQMbelMVT-@f9G*_nbSvONKjZT%4%!s5c0RYBZYf;f-yN&1RVztdCcq|k=SZzwa}oYSgd zBtf@Pc;mMc_hz4U%zWNWFb{H(asAx<(&ZeGCx|E>3W@^$!pLy-B5LY31Ita3owUO? z#d}3!Nr!LGk@qpq?~nEQFd{R6kB(R*2*DLmluF+P4hdEn2rVQk7@8-!nUEKe6@>;? zRn1gDf0r2xzD)seay|FrE} zwu|WUs{A_Kz0q8>Rl!w4e7GI`vXEJwG10#P@0poxoZ!PAMU4uRs_7P|+n71S zFbUgQOl)@2-8gHwQ!@$T2tMYOHl`X46UkuYTvo|wnIDlqwe@XO)T#v!I2+NLA?2F+ zQbYN2xjx7ga~InqTeLn<)o9KpDB^s>_^6LKcuhF9@1ND?9axk};fo*P#a2VeMK8$S zI%hBT;VxDcV8Anu@Vt!w0_4cW-NB)A)_Y9e@naqFX|%GT1;5^{^xg(M7BGzNoSHh{ zyZat&i9unhUA|A3?Mrfc`#!;r~#+n%L}w(HBdA%`TS&1*{UXBbA>j)ck0 ziPY{%e*}8h(H~!&(!r%E0W78~zF$egn|wq?BkA=sKE`8*U(cKiL$!_oPyX69WJ9q- zv8Y^Sb}%iz2QzlsJZ0LMMrHTdJ-jojlXvj(=^z!e0D3h`eHP03ZCJCaIL2BFB}CMs zGuva4$KzbA$FD$m=Nm%@_s8|jP3u5uE7e_0WWE$`j)K3FRI|xtUqkkVwbzzpG0<__ zs@YwRO^q>DK&)*tDd$Hor#?(YqQ6g0yAsW4w-ahj&DDSmL#sK9cn_ckoh6AIQ}YhQ z(UFIYr0SM5Qh+ncP)(lpu3dH;FhJR^?JWoy7oq08e_;83{E1X4vtv9WcbaBo3lS6< zwk|EgiM>y2cviS68(O$Mi=1fzoSa!rk&9%dl~d^}a9-I=++>*Zb_zAS001&E3O}Yw zL(y#j5}NWw?}&)_3|qA!1T-js)si?#>#bpSh$l~ywu2Mskxx5IAvN^EDkWy>4_$L= z{H~7}X>m!h06obCLd5*izxyevhA|0wI!N$~NOVPo2aFGkDMn#Ac(;6>jb`xqcp z^yACnbrevjH1IPk{87m=K?%%J<@`?v?wR~^7*_ZY=Fbh=Y$3t3>ZZy6I@~@Kmx&p@ zz?5DYmrMeK$vSrnqxUVQ{92x$A{j=TOzwPdl83}Lc_W1W>%#Nb!fptT)$mQ_Ya(qN zjrXR;<+r@ov$3O*@27#^<$ueHK0t>?T;Bu{mkni#!jdh8{of`%Qg6ZCXTf_)E#5Fy zaT`lz*yTN$d$XB>(ogZ~ zc!SuObU}hOg{~-~u3LqcOuTnr<(eu1mXV1SGrli+rSzC7MDA;V$==DEqL*kK{@l@J@bQBkk`%rw6)u&3%6W|#EXm4CQ%dV5_K z&mj$yegs>wanz0T6;VH2KY40@H(0$ZP1G7GfD}#--X;i@QIme({HOwbK|#C$(Ee=d zoU{=42wn$r-D+ZLwH%XdLQ`OgxRO}^jL zh5Ag~cRWVAGQg#U1#G1l6G8A&Q>2Tg3y{JWU`i})jA9H+halqo-?2te`iYit>t#Jff_0JX-GktVTCGw5OG??>DW zrteW3FZ5lfB$l4!CeZJu04f;^_q;4iVWxt*)fMM1F;uFp>8SLYXL=w@HWSt426?n8 z+o++*s4s5|mWV6|!ad!i5whDL;8tFq%G_PV7AM(HqIh{xat+e^71x<i~zH$%_-D2YcX#) z*CDdBXTkl4EDc>mNlvKWrPJ0o4A{YVtQxN;HkOKCzo6VSuH6 z#RYT!EobZF!zF1Uw_aYPH%X#!mu>tZ1#g=E!jp}Njp`>7J!A)|r=q`5L~4wcP;Q-i zuo}6<5Wqpr&^xbdXub=gO|x?;6RZCOYEgEOr+|Wf#D_yHmu-E!{;f&bIZ*ZNuEz|z z7vThBNV=&9QXj5%#ocO>8c>uF;z$fIY*jt1K z)_K+6<)(z+22tnDN2q6uHnYay{Tp+Q8PS(*9xjr4ftgdK>|sF!l;Z>yk4p4ZX1V|DlJPB96*bp|&fXF=jGEY`Q7kOYYoKtVs#$B5!>se{r_pI=438-%W=7lKe zvz9Qw50R^IKOO6^rRsET%dSb|cB81tV5RNT_p7x!Bg%y&d-w#q|?#FavTIm%OC< zuFj-9)g;a|#5_W5vT83<=;1_|B7e(^;bL1qj|`_>sufZc0z12hM*lU}2_qcgelyq= zpc?$Prb3^f@1Q*IWr@BP@eWmwe+`*vYk1W=y zYPbFuq5vRaG!E%;_DX;9E#(2rL`MvbAN^_yK;@zGXUh%f$2?kWW5o?g3Aorq&+ zBUYd((co-3KuMxF9?taX^=te8f{-_~fHU*#Jj*#aGkI*z_8JAmZkas)M%#}!zB0He zf68M@Gjy9_&F=Vba(OyApJnzKtrgk9%)fQri5Kw{IhkH{-{RkkT5SMcgUB#>tItMA zUbbIublBU{1U%dpS0;cgu?N?f!lL{S4eT$n(&iXK8~=kAdRAh-LfiXsj4rReSw4zx zAGGs^)FFIFgasbi$uH*{v3si2m($H{pl>+jY7?B@W0@(omXj4B0T_?@<+9PdEM0U3Sq!|cdq~zb8?ub&bU|3l z9B5*>^P}^UmA#6nh$+Aa6fT!3WIjbu`SK^7+I(;)&WU%C5Xrg3B^esYEXlpmzG<=K^PmP5n>y}f3hHQeUe)p;h+LH zt86>argXr2Wnsm!EA3Uj87xcg6?Ql_WWMML_aSJ29mxWqxhdDdNN0^i+LYc@R$10| zG+(A#^l+#ZOCk9CvA5o||p|4&taXOjV(v=Sl8s^s;Yf+xob?qsnILL%njndD8ZK* zOleld^n5o6^N_2cmVa}u;^h2;h5c|EZ#!`*#^irown)PGpEvy1CwKwK(k#i^-D=II`2t$Uk3r;!p}F#iZN zhC~xEH-@0*Zt5s8;uW|O3ga5CEimmP8$nl{Uk`vQYQBl6FX<$~te z3ea4RkCB-2cTK2(0#6{NYyNrdjIZfr1oX$s@ItqP{J;t)!Nco-E`VA8C`vxtr@3OL zU(F<@Erf7L^;T8bqW^bzCe1>JRYRgezchVin7-a&%D)j(6JB8g*rq0cTD5B1Cr)T8 z3YWZScC$+-{ zDG~&B>4oMsgI8ZnbL>3Z7g3lMGHJfqH&~gy$aAA_4b65ankB9SV;77Fq9cZ9(T_TP znJ4JgR?*9Pk;zEsvOg|)9~3kEB=m+mgJZSpl2H(yK(UQtCSjHk990D4j0fAgLVx&0 zRfr)S)Gv90X{A?`oj!6^LX5tMIeyp7{)DE{ZK@kzu(g`q(NLJpBo`B@8FY;<2Jd3b zEhn9Q=g+oiFibGmB+#Wp)n~Y5U9)OgTB*E$)4%?Aak7C{)d!nrZZW0HlFEWhJs#9L zeQ%X`3c{(wvIjHb=b8{=VgLAA9ew}Qg$zOSnnFD?_Z!PkV3DXaxnfIi96iI@&pHgy z4K%H>lQNuUDN3j&DqdM+?08vdIpivIU@GOs4b|il!<=d1dH>51dwr-J?RQGl=qJ}_ z%TV*~+L=vUhyjqt36982ELrz_<~rBtNj*K1OvyKR!ge+@kuwvez5SkuwW3#)6e|g0 zu}lz&TEcckJej*N?I9(s6u$UC-Cz=JkX3e#d*3CEHb~9}d-u^D6qhK#_$}XtHU;>7 z{0!Ct!L6m>Fiv51R5nCDR^DmLF|DinH9J!Zx_5=A)-JAPy+7nTyDu2^sni@QFCfne zJi$=;BJ1N4{yRS9@tcbFVnD~ght4#5mrltr>C?|a!PqD`bkekVryoGkW!>9CYe>Uq zw}3>Sr=E804TEj}Fl`B>39L11W}p>^xCXH-d<#XRQ<4nHz&v=x^zv+xPLj?m!VA0C z{(O~eSV;R&bCI8--Ja%Z#jopVlYLHWl)BZb6xP1oiR{ZKwd-}$=fv*x^l#bXjH(=W3jFf7*EQ0mguBH7%snjYk9Nrf&m?Wx$;-gIS57~%fUi0VOVnid5@`lTFr zkAl;T4ZRrP*K$wt>~Dh*ly-IFNArx#jN! z<8XHx0~`35ddJ-iX}f+wjJy(1Y456EAehLGhF)M9(m_8S@zHQ2tr{q^b~Hg8|MaLY z+Y+f!IRtFSCg-#BVK!SP zFp@Gj$(;?8y>0#f|DW{#oVB`)H7e%bG?$IMHHw8jzeNDjw+dW809`#UmZ|cfQ?J7B zD*mfjQQh>K!&(z#H>Aw(qi`jlfa86L1K#w4)d$|*RrNpy=PU#}Bje=WZfzD$(ZvX9 zHzT^?-fjy3ejPqK2)D{(KT!HXc>bSCN+-|Cdj`*a z3xK!YXr?zq=$7RBJjC3!0;X&Lf#TE88&hf2c1;Nc;UVl$VpIL=tG^;>TFAxHw#aqg zWLz1!ez4cAW1`#0k~|+O@i>sj7WfSKX16k~eRFUwAlAiju$-b+k=9u2!!fGTB?2m@ z>v5Rydi%8$FWqbvDwca2v>b%bKmEF)ziaDzs=?M;-%42i#bCh$e)O*(tDK6nGp+%2 zjXw}9eQLGM_eV_<6R^&c}$P!CXD$-c0ox zB$a{3BhtmC0l-2>e&l0H$i>v+u?EORN72XoBw4R(ae0lcDoLuW(azlp@Kzg%p~aDm z^y2E|S4$aF^-GayIi)Bv+D0h_On7vsJ2$*RnJ8aR zCqE>2>glG|QNx6*BucC8YCY_zG02GF}y%@kh$|Z z#(2+L`!J#obT~{;`6c$RDWnU4N(yJ9 z?aGO_@d-;Z{l(DylYh-efS2UNoT5C*tw%+gPy|Nlx$l;3l#d2|2+Sr_Rlq$rq|hzge8r8 zAa0c;Dli-p@l`^ta#9!450O=if-qcWGBw^;PgwRg#ha9&?w!Q)PMXGSty7HF37?GI zBn*j4gP@P>!)SRAca%y8ENR?Pids2#t8puB0$z(zm4O)K9X_0dmDEakT&!r3X!~;| zXsDj^9G~W{FtqRdA*y17MU=05-+dDnB@dg88I9;>Z>k=U6!Yr?(EZ=5ajcpl_B}7p zR2Q=uBx`50VTozRX}uRRa)w7{tV>_+bv_cG`q{`*H(kUMz%r|3F5$SJqn!X@ z@8Lx(Q!Z7xIri}uZ$C>@n316TX*tu3T&QZ?iTdJf4xO5sv*!TbQ$=e;vU_hKDwvXj zQWLiv>6%e^3+f3Y2fMg*$D|r={qzn4d^1SSlMa2~Ex&~@rJ_#lQye_7?ZuPJF&o){ zeT%KF2%N;S7v;00*9-rdQx}q1A-K73&K4FP8ti2(|L(;% ziT%6Qm`6Cir;l3=bWmHGJyJ(D>q8 zX+u{Bgek!6?6zR%!PxDCztjJo**5p_rN`(Wr!FxWc`u`HqS{e-#`7fC<=YHU2&`|*D?i(%lV-na3-l$xZ->XF zZ_=WDqhW17dh1!vPCx7XnfYN@fMAo+Q#hbWO&~v?-JEW|f=P_*V@*s{B$Hq4_iUuR zl^;`xrHK)mkPt`Fog(GqWr)b?rj=cfm&Rr2;id$eO=oMF^C_v`NklMuyJD(GbOfPZ z$uuOG^TYMKycyzES^Ct&@2>~_kK2$WQ?}w%jIf(|nJqbx_o0@@xv!N8fWWv#!O?YG zJcXMl9EQ8Gif$S%Fe$&+IQ!zguVF>zMp=t_k)#8K_ligP{p<1f4mg; z@W4~eYI{Ye0#szdBX5IY7(1nlzhulg-#HyU@5_77a+y#q8Me~2O^q;+v$Mncj>gO> z?kOX2{7ra0Q6be|X6gm`5$XE<#Yi|VM)U$V5Yvhua~HhRv0kh}W(Pl6ZSNZL!+zS& z!ST?nFYLQ3+-Lul4Ww$IiSK*Kie0MzENtFIoQhOVyuiIp-YvlQD(#Qg6{tV<_w*@@ z>MQaYhsxXV!rnB8Hn2@|vR+$Ue>bcndhnIE!^Pqv*hc4rZOWxHVa5kA+2a8~1gykm zL|Yrb26VdxSvi18)8`RM}2FQ=_+PJw1-T zQ`}_@f~DFNLKPH!PT|>N`ktx6Dm@4nZ<>MI|00MScaK|^C~EINeH8TJWZof$-YW+l zerMuq56)Wu5gPsvZESR$-*K^_i@u(kgK5J_n{|cQ-_(xzVC7U9k>l@JSVSt8H-E}N zpzB=BZU{$GNhGc}RUYi{Yp*AaB{7%}lcesAL$b^b;z> z=TdW=We5u8KMd5pdp#XA+Wu2Pv8V9(lOuG~n4B>yB<1%}{g#S*z_fGeF+$i&oiT916=~F~ z?w`)r59syPhO&`__3U-djC(A`fC}<|O+jKYgWKk{f=&G8c+c<5x_q2IYp?z(O6;Px zGrnT2bhvdZ+tUIU@nR!nAM9L9ZC=)pXFg<@Q}U&IOR6cVKIqE$2;{Qkaw|Qe<_14~ zJSUHUWoY;r`ynd9fvcuanI;(qrfa&J%+z%#D#Eh>7GDyn5R^d_Uok$)BhK>_|Io(= zTII8Tap;iAGAxx_Pw065qQ-^j|6VKA3^iWvUO? z&i8V+D_g8;fxDYPJ;~iFPyz{*)h_Z%k8D=@>=(5cQd{cy$nP*0UgKu+ZsW zGJbcM)9B%z_JoC#J1O8D-G|c_flWb^v}ap2Aazr}oN#rlq@V2OrH$n?HzcPWJvU7L z4-})}f5^?xUXY%?`8%V#ug??zfSD(yu#QA3F!2ZJ&E{qSgGawjocJN?nT)6&2i;d~ zHd|56$b2xnnkHLSQa4n5?aVY-6m1sX-x7nTW3l`D%oc@%(QBb&Fo*w5Rzq-amO%10tf5#O|QTxbS7?Zj(I;kL4ED~yyy{l(5TU1y57?#eU$F3 zxqB+k4U!+-xu)Jlp&?PIL0j>8?1g|R_&p*bNCWR?8WW0LdT+?PTbauoBz2V2&fVol zn03~h!0iKoJrVN!GGLUv<;0=zr7@b66Wqpk%k(zLrq2E0 z?w=HQm6Vq;zL(`y4o2DXx>5-)ceDz(=~waV8c2qkpF#A1Eo#pCr1=1jrZg5HSXT)x zl))^xi2dNKy@%bUPLb1su_Co`_TGvzV_#Y<6aKM}tKEsyHhrLVBB&YfQPYwbvS~)K zG}y$_wwP=lQ5PN16}~(p9G9pyKL!NmJYdP;PxgG)#x36LYx-nG3?61AT2+z4DFfxz zZZc9+)0DVt-Y(673Xg3vR(#P=42HquyJ+F=AB$68y3Pzl@Tw$giyrae6J#N7ioRs! z3c_MDo?**7sSMQFZJy${4!JUCTTALV6}$^f%Z4Ot>rsA2@fPF-HH(%Rs9Ow! z?$|cIt|z|ihnw=|63eoeCIpIHk__V(z@REj|`pDxh z(3)9_y}_u1zVGu@AO3WXdBR3plN)V%rDQMNc_%WEU{>(a=u*JPa)W;moDm2h~KJZ$HT%%xs?<67Krj$n4~1f z^?-Q@BFz@)rcdx(e1|vBCGc25$cpvUdC+e2BG467U(D-*f`&uj?Va;~Ss9bGyB$Zh2GSlM# zk0}(ddHlcz5x=z7kOPC?My_t)fj%;IM;pg%7C-_);kRIbUY^fJKg zBd*e;Zw^xo+_4-x9IChp6f~e&5JjDa?be*&d5kOjZ@jC&J?nRpUY5p@gUQKLPpjFX{Sxz~a zYY`d;!=p#%l#79Iwg)=Ni(Ojy7o_IQ;C~UHc^I`5Cqzyr+)5}rT}R_#HWKC^21_lu zq&gNA`$}XgnDbj+<+44zYurIB`siyWBddKY6$aWw(@gnc54?AiH+PDy9q(C*x`-}? zyI>Yq5nn0n-ja&?eP8ng1ps-&Zk=Ln>3~2$z>=i5&8s3zFdl7K!JTD5q$%K*$H5g@ z}uU+?03kAhpA z(mJ=!jFA|5{tIc|$me32M)GDoYYQgkGJ?lR~!E@iV&UBzRe^0GdPE z_rBc_uy^%5k1>-|K{{`D#EgR8JRoL0&@s=n9m;18n0cOvhA8a76+<;juu5Mq^(ILN zW>;iB)WS*v@%2~3y}4HLmkT%nu&0F!U(?-Gyb73?+_+BH1O(i*kV$yN^4oDMMrN+@BVZSSc&fXn)s)IZ!aoy1ofS5YWo#2 zp>N*8JUVwm6Q5D&GY1c+jBKDaR~K7d|F3Y-idz9Cgziok>gvicz3%y&cfaWAv5{6o z0YC11=slLbOLY43K{xH~Gx(Y%6`O9ZmE&Kc#M#7S*1z6cuh+Q(OX>>(|Ez_(Q2jRK)DrrrHF5BS>N#tyH5$Z}R1wu@Mw`V__*G4E z&@IIl1Xb0xAquwacMj7yr8xwi$M|us5fuYQH91UJ0p-=IZ?nIU=sdUaTCn8v$ zLbfN8N(~yCP%dr08J;UtZ65FJ4wqlVP58cvvAH-Y?O=%-3U!NwpJCY80;9;e<0*I1 z%R-UmuMg9Wxk#;tm@x5mAP2xP*A z(&e8AG^L#d?T+|-yJghWBNYfMOLqh?z^07Y!oNx?D{Fk1aPA{D6G~$y(kyt5QE!Xf z%Z#`B0!HWGXO>2YEZez$M7b2os`e_8C9mYHskNB&^-Uifj}`Q4&m0EoUfC(~r4Zb|77;%+V}56`skgv|tBBv*bZ+gwAn)zqJ| z)p+RXKv7wm7=YBVlrS$`ZUB75hk9SO{w}(&BRB3`KCr15@9%m7ie=iaSj4@2UT}jn zvlNgLl`|FB^B&cXQ7@%^bQ5h%Z})J`ND@BegH9Y;l<2cq+t1U!N%}Gl_yC8XxBc|G zJB|Z;>)N*2_@?FYstjkhwv^OOH$lg(w&(DVOOH!^jnK|#!#5@KrXoI&+UpX{~*it&stR0w}`X)kX{X=}p;v0CBL_4hpMAX5g>wUo&HeVbcDtftaW5+~+y%9l?(i zj<7D{F)!C@usxI;lW7h)M2ARoncaxJ_my@vrXbjS_01 zO*y^tmwZ_cjPUDBk&*Aj&j;6^!1SCx5t@f3wsLKYS=Hyrv#EfQ(3FXXNm@<(BBzYn z4uP9D&Bab%``nn)p%`~BZ4Ym+JE)Mec3R9Z;P3LH^!d+IhT0g<7DuT9++LpQpEa>O zpY`hua-$x%pZQ0K7n!)(S?Oa<7@+pmef6Sy?xXL%ClPPdu_sR6fBDf*JW*FAfj}C8 zS0(6JckJgIjNO8NxDJci?rm1qOcDAQj}Db0 z04Di*Ehq-!X&SSTsyclpMX^KhVs~&eX6#~Zs&v(xKT7G%PTqgL{7{_G_ye{Pd^wMd zq$;HDclr~Xrq`3-(N%z2jQ9(VFPR1Z;IfZVUM$}`;<3*ca)qvc_zNM$U`n!|`K1>* z$5iPiu3H)!pc@ZOi;F+W@abw;`U`hm3`?_?cFjv|?sYZ$unGNY7ZF9ftXa+6Cu4;W zf|-u=niiRG-epC>k;rj%OW-=%%X%d^-)&lU`cdUw$9(`;191~v6f?yIPZ$3?+o zaKt+Ja%dz+MRc{!dFf10DlP0$^T}PkYL813(v;Zg$&oXE6is<|yzUg(AIA&Fvs?N} z*px4&zt>_*dZrD~A7AQ^iN!(JlW4D6%6fb5L);g@sqFK>GL1D#O*;43&C%Ppi7vv- zu;48YAAc(H?H@+*TNyMzhdki8@Oc(aIVnEiA!Zxe6yTR4d`ML`e=~79GxPs;1||UZhu#vA*j2~Rg1ui?SZZNHRKV_;cW$xY@8C<} zIa0(~&)7#9#2~DX50F4y(!kk0a?00-_&_$B%;6z1n3ng%R*%M{0Qm2rpY}PDl@X&D z|Cz*NXDj<~-OFs`&3^cn@#0jcU*Uhuo>^hZLuP`BFNhmee+*MsKWs8A;z$>Khhi?; z*j^4b>_q!aw2#H8LRxMtFQGj zVVM(SR|c`z;tgzM%+7Dm*)khfuR{83(9(CI87W#Mag{XsMfz@OV}0ZAYCnBit$-0) z>_%77`qn>%d224=#&6^IZgnuifZ+5{&Cw9X?5y; z{hAK?kx+|Mu|L{q8OyD_-uGUSS;BptPraz}(Jvs(4%cOF-%HHiZ9}H~$}8 znX6$$^`41-`4Tm1=KbxPce5g3O8^2z(JIRy)WkxT6J@pJTBFhaXwfZxm4n>{qPM2ft?#2D$lRsXt9 zXT6sfk*(BLzvHKyHY9TPDX%*@mw$&o5sV5$)d?vxio7h%5qJONsdX&r%Re0p?aID9eVAn3%u>PTHB3F9BK$iX#Sr(MeB+*vN-5J% z(FBeZc<=IIW>lu=81s^hvD6-FVP$zWji#{c5Ed`TWSwPAzO^W}#4Dm%cE>lEj41aAQd8|c;a-E97SrO1mD}-o$ zKQr^Ob+syUdu>72_*-o(^erZef9zY*e!XzZ74_-uA`=(V?*Qt0+8Q-3pPdoy+GyQpkda%f;40y2K3@}G8`a7N>eN1TaQ z+F_)5bZMMFx&=0)%?@TU2uikqyb*jY^rG=EXPE@!2r2no%lT8;h&HS^5R81|X3sg# zBXh%_9ll1POzMptAETZ}vyG&R(}Fd3p%uf&?3svK_r`8TRTQ_C{iikt!fw3_-9i&K z6e-{oO}vem>$EPT$m(OWQsHyUK_#zA`#elZ%R%hNXi9U>TxgfEPNlRn!GY<$6ZGQf2iboCaYb$oE&P4E5JEROFpczHD+zmW}eHGNAagsGf@;@ zDm3o%Kc!{6VEA3I-0|x$vEk+71jrR-5!)VGmKSp0#uxa>h4?TqZ2}7ia5{3T|=e_fH@ir{+f~fBC~*=KL0yq&hB9TM!dm z#^b-{VaP&wS; zW|g%}yTpuhq1%Wr@Fdh=PUD~BH&f`YQLICpltwsS9r_e_>@ z+5xb_UiWxp?e*WL%eu2Zyndbd)fc1NdD8@eJIKdgP}U2gc zeZUlze8$AmiDxqJUnN!-ard}duoi?%gLUG>+S44gdhDdV6`Sg&K{{e>q+9guJgd3? z7U#}tnWh(VV!ABf*%GOMefjlIjH#K$uvFuZ*Xi0;4u;zCK5tm4Pvu!@giT7(m0u_M ztu{*Bp65-(V7kGqAf)%deX4H~&?`-g>k->ylg^9QkMnf%_>`^JjU%#r-2VkRubVGN zSyl*nPcTJeNLtUDm!mQ6P28(s>vboA=-vyqGhyMR;FG}Gv*8BSo}`m^?)phF4aOH+ z!Qfa14etq(EdtWcF*4spGOnt^d#4WSJsm zudB%eLX(p!@=&^}cM{QggoIo77?W_}G5U39eK}@6L^Wm$rnYZsE35p+twtLL8z6RU)dhI-GG+N|>A4sfFd{HSZ>xV$1tXO{QN z*|-z7`Cq-FPq~&{>e@o(Q7Wt5fW2W;h z;+yA3NRIQiXNW2j?Pzhdj3h6DZgNt-(n+&GOX`jOKepZ~tgSZC)~!(rMT)z-ySux) z7bxy7L0a71-95PLk2}GGr?|VMNZ_!}b1v52&$*ws$sF_h-tmr+y8s#Ab4ii9=z*6^ zGA1~HWwnlNW3BO9f#1k;Gk5Wcew2;@1=r%5Pezz=R4Y#!lKXcA2)yPt2W_a9UNWvw zF_5JnYFe|0PJd3g)$dVvMS9T@u_#NqfvvYRb`4Pk9^KkOieKoof5|n7xZPMDWF(&@ zqN;e%PELI$`4wN3;Jcbg&yiJpsh2A?A)qxxVANkz7@+%d>ueY)Pt1^W&-rT3*{kfd z-@Q9cpes~gm~w$eHrO3uWv4D~1dkO>I+9mLhLBn-+}hA;M}RJY>J;GRGCa9~IYW`2 zhnFaT?h1?_Yw9h_vC&vP5#Vf85KgsM)(V#dencKQ38`G;EZecBldYyxmxT*?mUO|T z;TZqw>7eX_--NmAEW1HFCv7QkUx!X->;Iim^HVT8hMcLtvC^Zg|Idwe+yjz{pRskD z3s!s8=}{45)^*hYzDh$yr5~flAHy1B#}#r$u=5zCuo=`(Qz|W=vOucm`em=Zq;B;S zuC3tVD5K9VU!o9z5Y;&eEG!FJoHDWwuQnxf1+%H1fJRFpM9?VP$bJt~tzp3$p$SGB zYNcl}F1=l@hVAGFkB9fpr&r$lxfp$Rp8Dhy2DE0*Q6}_=#eU)Tezo$!OCN_tLwwTo zw$?Ci>u(&%7QB!=)_YoJpEajY8ca3Ukt3S4W3d9wn-x8;ErCo7!Ay`OUvZ;8-sV zdBRh|n2C%SGYz5Vs@wS{rx;-s%Qm>yRLRA&*3zS-%*4MW?AqvC{M@xOkDRKpEbb>N zf_s|5VY<&VQdfD&bBl2BZLKab3iUi~*VgKD`qvb?8@rRbtSA;uvrelHE8NK7c~y;& z6Coxg{$AjD=bDj;uDO|`vN`cL#eco-`@gT(Fx|7#=;#W7LK*WlRkUVOflXE2 zwHdbDHRtFxE`<{Z-lyc!Qn+hRwB0yQKN6zC)^1{JO8vi$CE-%kI~vUPa(&jTEy@^t z1yScR{M_UcRT>ihv0qnyXqszws-7Sh@o8B&9?jHjv_#V28~AsD7!ZEO=y1J1tIU<8 zTBfzNW@0%26 zaVgj|XY9wN#5fz5E4-|Ez{cP*+DYQ2>p*Z2j@bfppQXR5qNHMycMyWtVv$% z`_p9d!7`&aQh06|ZA}Di^T#e52C}WBRHVb}c{-L_MrY1$9gg9S$I_+J!hizDSDQ>N+rq*=cOz2{KF9cWz>{TJ;Vc$E ze+xL}wjsudbO*8JlauIcV`K^34KzkyuMY-b&hd<{Rp2;`JS%xL!uKrDQ+*w^Cixvv} zfvJ1*+QXE536wQT`)a#n?WE*H8p%o@3V&ZX#cl>EoqfbY>yiK*w zF?_BrbPqT`uXyUdu7YeM|Jr@R!P?eO3{;mq#0l~X@;-vD3Wm<^gOlO=u3DP*o(MeQ zR08fN^EL3=BDxpZX_%uMC>zF#iM^8qPyW!1w=PEKArpIb9lzoIlK&8ey|bs1-Di ziFNNEpQZN}@<@B{#f@WG)Dd#X_39ZgsV{SJ^nX@|pGAH;0iL_|i7x%ToX=e1{hI~P z3Q>hc=}!LlbNMr0KYK=5mpi>cd6*pqU%SgUfW@Bfo}y=4UP8*tb>E5FU;ov;Y1DRG zT1{f*ZlXVIs{{!A^Q-I1r-b$#(uv&rt8|=uK8EWEYWH3(o1`8JZ!0$j{28p~Tkr5< zbb<}}xQ@UH;P<`t$JeEgzP$!c0E9i_QqJa9{s-e}CHbFP4{sD@B>APqrzIi5M!2dw z)mRqwLZ2fmTDF*Wu{~sssFqo-f4xnJ4Z+j2Bqy1l;RaND5(_?!pA%zpB=Or*{3iXH z>ZADRmf*EPQHW3t4GN`UMn*)w&cg>vCC*tN6vgRg$x9?)he|PV6F^ufiDmh!JNtR^ z*Q;cI`{s-hl@ZT@%J`kMgE{duKOQUHr`4v?ZNVjf)W6Xe`dTMH%hArPx2`79Lt~2+ zf&DK>>qe-!6)xD8qm*5IV>{EA6&o`jcR!TBtH3ziI{ON;nR3r0Vz;*x=NSROT8wiM zenPy1m++O@aJ018Mj#|qh_$y2L7h&QQ^~OZk)|SPTVWexv*(3$IhGb`ZDw1h%E!$t z-uVr4JHqNn2xsY(RR%^PFSNxv9Kl7!8wipN$0q**cIq;b5~C(C7#JQUz z)o|9oQ>*&T6YlxY$!s-%$bS1ONw^i!Q=->1^f(Gh(EWT9IS04H*Y%aSH3AF49E&-n zXiG!H9!FYFRu1l03S4Y*#;<#*87f)U*K{%{SljU~04<-t@W#PcdzxX{$j){sGP<;@ z^ZDklt9Q;WOJ1%xTW;qKAqBO0rcR2a7iZl;llXRPKs| z*PlEh%!7(9v`&))p;w&c(2zcE)NSFBeK+Y}_&-pcH)YGu<#sB*uiB^It1K)zFIam( zEiTmft&XPoqNMqC%R!3|d7Yrrt%YxBkLn2zp_puRo7+k@po8|KIPy7!-*6PN; zjr;E>?yeiYUavtjs!>$o{&C5Ql98&r*1-|~9!In0Nm<`k5^Uduc-2V|EHLR%Q)GkK zzTL0c%I#>OlDzJv??X*q)GOCEHgdWTxa!a%eH#tdpQ$Em?VSiaUbc-k1LhJDTWO6b z8Q$fXmN8cHcXSK)4}`_(#}KmVrYm8J(y|qSxu-cD#E*F`&ueam_$fsnTxF6o$!SB? z8Dm83IpN4bnGLRsN4&ly8+}EbpWp^lSgn^;!xG6+V7}ZN6`q{4Ajp*ApBALB$QrcwwfVP8}KYk1mu({mC7 zqqQ4d@v`4dTAjdc%1wh$%*N79F{>|4%74?egt&AqhKR|s89W{9&1~&*Y%tLeX%o+~ zD-LE~Z3Cwzj^o7i|@oNge^%HLCxO(gKS0gnhtvdroTu$IK!!Kehu5MNcVY1 z5rTJtp+M>1Lp5Zm$JFT!~_ZA`AkB$H?_Cf2+5CCj&d-L8a<&5X3o?4%^oK1J#aA;}NKD4g2 zc298j|HH+0Ku7_Eul8X+y(f-Xa%|XW0j9{uU#Gz3rqh1(bO5E2s}~B|xso`jC8~p5Ds!@hgDxUOJ+d59R^*T+0LSDn3%hEi7 z(WLA?sg4<6e~f;*`5pns2Fcbe8K(S4Y=;|@+mVPP@vcgZvd&f*j+EWnh+9W;L!kEj zxkawVD8m27?sFLEN8Me^NSPmd)=5dgtG~yX?v(HEan2`0)JTikbvUPf&@nB*D8e*^ zh#UV_gL^ss&P*LCrk+1yKCXKCysH=-QRa$c7w6LQVUW7e4-FzMZo@CHk;V+B`Z2F& zoEgqrwvzGG_dbNnmy)9ct`}5V% ztsyI30wveS?6yFK+V%h`;|Bt0lki9#!`J>MSSOp|>7FFhou*l-(@zyvg2e}zJ)}h3 z@W-`<-IsV;!$~;F$|x!7WKTV@OJwu2zHs>_mh zZ1ml*-=Lwie4@~|3 zm4)6!{>x+Ecwf(9pB-fLVCCTPT<_3`4xn=)f+>8Mu98OCaXf>N>+#UkS`NGE*^|hB zY4y9TQ3&1@;T3p!86+`czJ!6CCcfuA{_t5M5djuYzvP@1zxj7gS-#4Ew+mSMUMikp zhIw2(V|zC7f^vB;e|PsztqkcdU-wpT&fRKz^q}6CG@vBT$o+j5?^zI4>^qgLpr@DC z$elGdu_mjnGuqhAo|ZbNsjWTQ*vOx>ztm8}ST}ajQ+*R!_a^JO(IE4lnaTxU{&=N< z{P*F>$r}S)jPq_|M#m=KuJqV(Ig?%ZyRdk@?uvE4BIlLig3Z)l%GKmik z$GU_tfc5~(-HLP$|9UerbnNY;NQ6>PXhti!p2WxZgW^>k8liz)uxUhS#Drj&PHq_) z%|6=6cUlfo&nepJj+W}Noid4!!Dr5}FnzCS%uV@^VlNItBy7&B1tn`aqFVJjH@_sD z<5GpXVN{wi%!#g!J7`p5SwA>>WvoZl$syPUok8l5Ehp=|3k-Fdj9&|7L1I1QJMCp5 zTua#ZSj+IDL(4&SgIyVuIj-zMuGmrf2`o`ubYf|$!`x8dvghmwqHVM`kJzRtzJaEg zMd=o}yHzTmXkNng)Nb<{X`+vC0{y&P&IhZXA!FT(?H05M$h?V^qlR<9%@8MS&aW3N zkY$)k=8Ew#q8`*2;i7P|d04Yb1=3+L_MZ0&xm7pN2-$pyvLNUE7xT5U%`rx^y5&`f zDoDnI(R2d-)fVZjVwRuNt*Ug%C~oCr(#Mq9vMOqHjWrXJ9C$NnVIsMl3jFl$UIVNx z+~>j%TrEjy->Xo4`gI^nnoA>ByedxqDq74QQG}CjD&)w)_LfS4`8r=g~y{>kfCTT`W zk@c5d0^R;05vA8`{k$lo-=EG@_@D`SBxgmf^TnPy#k*jC&crLCYyZmH3v|uZ z-{drVs=RTu;{~dI+HWrez|T#f8llG$vL&eBBFjUl9r{Ezc2iDyD4CH5F?X>~ViXoe z7KO}vs+PdbCi(G3n?`ln^SIgN0)eDBIe@sAJ{l2&kzJRBOPsgy{ITS`N5u})gNdgs zF7Qm9q07Cu6c%^1{Kj1Y`Z_UvF-6xnV78PcA8@5Q^PDQ$8R0O@7ymtOS?;$T* z!oMFre)u3KDW)0S&!us}5s_yK{X9n-HgCU}kxrh!w(|FKvq+e#6zhmgmOy2Y*O;x^ zxQLf+O-Ah2Zc+v>0Cb7$j)ptpM$BM`nMmID5|>U`Y35LVnqG(*Y{s1z2F4#oxFLV%eCnr5dcfWJI8v2XvolFGR3mY2m)kr^30r+{IV!1PvezU%N03W<;pW!6gY&^)XHL4jh z#4I)HL6lrnbaiVJb?s4Ng^?8Qw`A}m0_^wjvwv^vb)JeWqquRIb+zP+5rd^VJsTosf$-Br zM*_PpzSu1AdikN z+K_?VBP(GUzBu$puDDqxngFeEXSrqbG|2Q~Yb9P3h5Zb)a^q{*HFBwzkd>4d>d&!0 zSnN?nQr>t4Sof+CMm=Tz)%3^E2V*LwL9+ShyjVd7)zhzEoN9iMB5G-XjIhH6E5hq? z#0TjJWl2%?2D)U7rgvvNAI4F&Xmv6Coc*I!lg>&{Ate7^@Dc8m3eB<2UU_TMc zKR*$3UPjwhJ>nioN${`1_>h1b$I!zrXLaoUk*o7dBI(Ln9FJc1Qw|ssfI?XI2cmZV zQf77IXDec)pah&w!P}4qBRX8x__9oW6jQKKVE`^&Gy2t0n%}K+k8;xgBu|X<$f@O) zm*+B(Bb;0}dGmV=1O*o71kQ0iD^^F08EnC%dlji0Ig+??2j zM6WNP!J`LTpEr&B-sIxcxsExGg72O=e9~WKjr`E0RpIqW36d~bYm}C4d-*h!c@qxm z=(__{-ZS|eJf%?**b3tWHUD+8Jk^Ba0RW2BS+Qv^EuPA$ZXa z803q$xOvi@A1JX8nDraJNpCx2*!Z%RtRiXP8ycr@#mH<|4VB!{U+Yo`aDEuRiFL{) zgNU4>uQt2n`n^_LO-`KXM(rGg<2@aC%`^pGu6B_Jz8#>B#RvE`bYDV@$@Ew#({A&= zF)D#uKpZ|S+9EiL40ey_M7JwrhV;&T*`i91e5?O|xyVt+*gshC;+@6a1f&J+_T5Zb zz0dfbl``r4A9ql|_1>3Q5^mzay}=7ARKEM#SO^mnSmIIWl&vkD@w|d4m&jYd`qcOF zpMWuA-+iOkm<}zaY_d`rJj&r)wEa6k49xN0+V5{D7kVsgv&L8EbuI3`SGi#6d(5ia z`~@GcbQ(@HUfN^;WU8wfKZXdX^gf=GOxQq#R06IBzyD&tM`4p8?Cfxf>E(l3J)fob zQq=kEKJQ}%-krVO`v)fePnT;*jZzLXGh9dzitP7O6Ge!*)W?n^bKqiCT;s!^!GSjP z?a%U$-NS=Da&)M}qT-03y#KNGQWw1ADxW$o@3F+j1pq+Zb=@r7b?O;5j0;SWC_0ue)iYIB5yS0U zjzCdGtp^K^CIHhEB#nPcHGLuz-_X-|3t{_XN^Ffr&jgGeHvWk0f&MBk_c`j*ce|=z zxhFzm-#=1)aZxHPxPgMZ%LvX0Y0Z>|;pl&t3BY$q_MC{qizy9S4Ta25lo;)f$x+2= zN1J^?M57%TUF6wYbVOUnbMf^z8t(eVawBiI%zh6`pr72iA{y$|K&q+wiSrJ}f^C*X z7B`7Cld6&sZC#Y0%^|km5$P**M@)rnukG&bngP*D%gg0My;IS%cI4LQOO-oaf z(K|DI?xPOQpx z_e|0{OnO(*v4R`Avt{}p;m=Vzf;e!IjQWeT|F-1ss^!;tEUlN9n`rKoLYu0;@7 zTeX|Pn~1(--F-rfp|}$jq5AP20}}q6!h7sv7EV_p-QN+=jc_V(QhEQ3q}GVriQAT{ ztgiN)T4LX_GEJH+sR|LN!=6uKmHutb**C7{R`9!y&Q4Sqy^i>6$LEQLEJO_vj`7k0 z{$CU1s;9I0$^1e@HOZ2xuo`vrcda_E*!A}HYl73FSFv@$<)SBMYN|`{)3)o$)AcFK zOHxFHV%moHmPw!7rCimi{-(VeE!VlrYz_!?ts35pY(X%1+pHMfK!N=y04__GJEY3s z@La2<_kj#U%PsaUGvgU_?JNH#O^R}oQ6J$lwK%?=5!H=MUj;aeXQgwgxpb4Z_<~;w zbeME@_)czF1t3cEj4^p&w#*9>2VnRskyMcCe!g4^@LN5gs$~X(A5lh7gO#CGpMK@{ALj0w+tT=uC z4#hiI+pHeU!my7oc8Ouot-<7^O@!@jS$K|ne}HWfqC85dJ|WI#ocq2mkWmHPmVK8F zX~uNuS{*j1C%NLop9lB~PWmO9M}|EjEKn*|3Ly&XoX61+FyqgWJ zn+RmYm~e{(L$^ZF&ZY>~@1Iy|3zlTMY)hRbjDw+#h{OOpTU&%>!RrBxk)hA(>rk-2 zc!Em<_~Yk>;^pO=O21tJ5rjCRZiiyQzr9|rD)#i@_=#kMf!-#_9xfB0yrMv_(KbiI ziFIS&s_q>~Zy$t}NsaQR3gTaZ1ZMhj9AoAPeFDgdPum8Mh12K;ON>vn_YqGE4OK~0=TYd9q;Du-uqHbalAq?DVj_u-_zoM?0o`M5B& z*vDZ_s0?tcc!pDBY#nTZ#4>{)VnC*u@R#(z~m~rY+`wXif`*nsEPJxhrIj$s!KK_|PR?o~oog7r|c@JRQUWxFK zH)huXmvH%{hkXu{r#wMeo3Ry`g{f{FOUIanhA?mlM6b}t>iM6Qhzq71p%lAXe8$H` zmII(<@Xz3hEo%f!0W|vGAAMNGHmz*oVc&-Zx*J6&GPJ(#G<#n3!fyxd@>N;ngu^!4 zo)ROeu)#@!{Y6i=N3eXX9qt;9`ln*pXJ4!6GxmoehDMLYvkaHuWEC4 zDj6R`RsH*Wqc~EbkeL?z(qrc#m5(J>S?B*rjl1UT&Yz$G&PFF1T`R$kO?h%a!@ovR zd|y|c8;UICE2C&DmZ_3@#tvSuMfI5Iv~K;i7^03fotaijSD-bmNq)gynMJRX?vI~O zQl1(>_k!~HbDwa+TdyYw=f@<$H9?~G1`fSQ_gm!c-Hespfe&FRu9c?EpnHC6Z(*f> zl&mJdlUjncqTDFkrsU8l079$vnQ36>ZCKS!DR}oR)Aq#ZKB$f^uH@=1j9n8;TlJIA zWq`aR(h}+kM|Ij2qc>VI)J0Io&Y(N*FjA)vTKk!SJ00;_Nrl)YCYTN;e_Z|G{JF z!)OS0Wuoj8DR044`6LrGi2sAF*8M9@3%@uK)jiu{D?@r@L_8Rgi!YfkBVf=ry`-x1eSg0CPJAcA$1B;EC!c=% z<1ZX_P5f<%^)qQI6GeG8^kM76M=|t*yfk+SZxs#fhu~W~w)xoF&p~sB-u{>hD(glj zw>~h$>1XP`GdX^4|j=!n+1c#*77Qk#ly-6`PC^Rl_sEcp64D9g2jH%#zu?6_FIiXkpW*pg6T=rUI?KFa@De=@m zSInyUPqTWOx{iWotoioSzwPOXBzzbgonmu|C0S?@HU}PO6z_vS*;)X8+@X-2ZYXH|LtkJ2^>7A?g2R6MxHA{XH z^BwF_EM_{0Gzd2g6M4nGc^$UY@D;Jk-Z42>L)rhu4lOY?H{H5-g9C873=b}ohM#+s zQRu0QRa)`^s-X>V(Tz;Go}9!jY1Gh&De~3%ss) z&Oniile+YPL9dT7&9dyd@QpG`F@Lk0dcb#HWC3Pg(acyA369!`NE?$;S?3t}u#Uqp z_+DI@Jhb!9+mWXg z6RTs;n;zxl;40p>7D=1sRXt^4fE_5Q$whO<_ygUIw(@NXHNWuCB+17H{o(4ZMPTD; zh7zP{)aJ+1=CB;!E98B!IW80$TlUv5_6m0WqEBX>yKwdVmp@>n_W{~%5f)ADK^>tW zO<>~_zQjJ-abytND$hwWCDB}$8~gCJjnl#nbH=T`F1Dyd`Pa|+Z?f@CYR>W8!+~;? zKVAuR4u9Qs+TQmJvlT}s@ahwcyJIge&3O~nlmmgO8glQ$9Cwn!*R42XUIE^j_KInH z^$w?sLc#<#xoiV}W_-+MU9$o=94J(|lNc(H8lXQ%&3VFRMmk&bLv~$I_z}OMHH*y2 zt0day&%JNk`1T`Ob)lk>>jPZ{n4`IEC4na9Cm~BXLV+ZEsMH&#$`RKD{#zSb- z)AatFbV+qDLowu{>-5gDS?k1Y?(&paI+lA9d-$C+(fGI~)2{u;bQ->jXeybp%ypzg z=PP9MF{QW$bPwI zoG;o<52L!Dm`sxUUXy9$lZKE_C5;%+#t8u;q}jh2kHKNVYyMZpH8Bm+lLK7%$(b>% zk^EKXB?bICzf1$2c2}&Gb{?HUa}Uh-u#)JEz;giOw%4|tLo5y)iB%e^k<*XF&RE~Q z!*=&}>7k0L&`iatzQozfh5)CIOmW*~@ySL=FclUF-S0fA?e=v^9x?phS76f~*yNwr zdw6gm9G7bo62I_B$JiSiY#XO*6OlhS{MzWa8N@gx5FWQ(KqI&IecpyL57&VX0}YHS z{8yM}Igf)_MKWq2dFs}4UOQG*(e39R_{oI4q|~3-oFKrwl90t9K&FT4|F0m0z1BHA z{P$L_Jcm4++rgHv>wlYbUoTfgy6-U>0V=fI-Xj{sy^zy+&O`uYA6KvQX(wN2p8xc$ zycHu(NHR{~&hNltCa?0!|rXAFM*Ll*14jCU5(#FAk04iS%eqw@!ET$h;dEdp|B zp`Ggv?~rk4EaMc}qBlSnm~9TR`@!d6*f;BJX$8O0VNR7lt}e{gvL-3s;yb@5Wh?JT z?alky<3nI!&2RQUJ=GaM|I_?aROHNRQQ#{)Ecy0(C+HH5z=*7yx%9SV-`hA1U1LCK zvzW0nKiwa_WW;XR~lKeStH4-Ygp@ZD{eukwW!YDcLbHGFmwV@YR5(NK!Sg|~Vq z=ak)QT+0mESGq-|F$n$epg|0v7lo}qSnx8zagR74)(7o@Ei;K#5vv$FO@283JxjWU z?gsF8*SqRo=I&KpeULSAYzIdNOS=R`MYT*nts~7L=_p>or~T-`0hO&jQkv8>@R+H# zV(Q&)vmb%wqPkpKP);cv+jdPyVZL`YJgx%Jzhny-~ z5mr^T#z@STq5=ax*&k*a$BQ8NiXLretDbpXY9XnM*1fKN<2V8rC?{PRD%k6ymfBN_hi>aV0rRm|7A0>5;^%kGnN&l_q;rl@)N@~oqTl9B6uF&f899k)f(~paa z()IQKx(gA(De6yW8JPQ8Du)j%Jy*VcHqo4QuHRCJ+WT~5Z1R()^g1M{3>Q<-n?nwVomWx>plja&zgO>!!4Cnlo!##|3g_y z8NGUOyTNCN1K0MU;qzmdFQG>n(<}QTifS+}8EMq%gz%#O4*@eR#`3jvWshR9(Xgye zLU0q5=b%BOeCSAyac+-ah^yv#b)#PlKj!O4Eow{zRq>t7Eb0WKm=JMj4?K@~ArwL!Qa@I6(#ORJ^F#<>|#_CU8Vv_w+;SooH6p4jul)jXhx{ENoGR=L}B6GIsVk$vvbj45J?qX*63-Y&ouDr zSg>F3wKD_yQLZy-2m8Txu}Nt5{(I?EBHOg_7+zX8ZlI9`vwDJKv{HE&z2_s-Hor3E z-$y=oXLNeG^gN&nXeEZ+E+F;7V9!7yHC%^!*}r)UKbS zgXKP?OqI*b-<}UhXEpnlVVmri!|5} zzS}gY4x~XZMMurqo!{6{`ya91{kt^lz2A)tY7vS<85~MX*g z`0#j^08{8Iy2O*<(>30MHW<8SIC!E^kyP#Ii)SMRIef7M#}7;LiQ#_^N{LvcL)^xn z*IbpOidVg;mha6tL%3PjIMJ!xLq5yCD{_uo)VPrw+RZ#i@Sc}Ywj-Ll7XxG+anvsK zdKkR@@FE^zBZ7+ZH?XU)9Xi9Z;T?iR_ZR+nurD`rMBCK4QLc;FmerdXZ59hJVEoeC z)UsLVE-ySqovpWQu1i2u3O**wo3Gr97A$e$RI@fk()h0@QDlRn>1E-0Pl()DLtz`N z<4v7L-Ha9KN2)YN^6*`CS+5d0*ggDUXNZsOL1ZjXX#oN@sT&9Wz9@Q(srS@jr+m_E z4(9Z5_zL@S5Xai=)@+)1@FO}ZS91YyYMm<@i2IjLXib- ztKclXgRBy3wOmC1dxTEy!|$R$u@n=dolpF#?qBr)HW+ReQ8;_nAxv6{^mFY##d0vs((75mnH!sPErN0#wDId_cuW#Qh zml5Q&>l%p|4ajBjvkC`8r* z9*^o6C$AD3Ly@b7M4)_z@y+Py>#|yEpi3$Op~ZV3H_PZtjYXcLq^Q#1Dvsd~KM|g@ z$#f6RQ&G&xI@m zY?{nH#nK^Pd>G1QAi;5~#p_3opn^Kk41GxeXJy2pJ(EAfL*K(f+cJV-JIjewXx7Xx zm(6N3aowc=*SXZ25UZC;My7Dhta9NC;~UhvfH+At%pWzICL)(ikw=*z>q}FT$F7qs z-*w*OUQi-aHu$}B#IJ397eq4&yV$1X4SoF@{P;&Q74k@HCaxE<8#C>Y68Fm=W%;YL z=>cc!OIW~%v;Wv6{x^{GKe(`6Gm}Bv*L##JA|5p+7|2=WGZ2nH`JPY5zbwDXh zD8ut!s&BGcUTHcm!ZsgZnTLkt0$$;!=f1` z8#G}1L&*^iq&M(^s{c?R*Dt2zxHs3$kxl?s!>}(E_`Iq;in785zI>l_IFPt8U;)Vd zB?RkPwS}Zw(Dhvx^`+-_yKS4+HDC`RYO8@%U#<3vqG2{vl7wQS;I>%Vz;W%0Cv;4zQ*e4sS*;#;Cqi?o`C zqI$e7|8lFo{(eG>3LS#rK*abya7oB-ZI=oDeIdUq3aBj=`r<7scGzWgB7$$G!IR}n zH+&I+S622ZwRYinrlEY zrQ609tAA9j*OcCdA%3Gb((nmdDQwunF@yT3hshJZFip&#STRQ+xAj|0nho9n;Fk9Rr8Z$Us`Z&<(nhKsTgYteA%Q-%!sqe#!V}tI&6{l zg}b}qEJC15_TE_SwdXMGG-{^uDD=_MCFE4|u|Tx@d;A2x#p)hxjl|uN#BfR|lbRu& z!9$Qo*ee}PC=U{SGs2w5&q;lpLPfnZl6P~Q9$z(GMfYJ9ad^1pbj78qg)*m|Fo{b} zbYw~*J!MP&pK#wH&3x@)t$#6|AHG0O8!m6{si?fY#zIm2lIL&s>6Fc_XYIcgEXoI#bSRcI&O_{Q5>c`Wg$OS;=ugqC)9D}tHwO6(3VFD z-#FdGJHkI?km3gIU4B!lDJR_nRhFWfrtPm#Jo(~w_4i$nmIUb`CPh~u*MfU=g!hdq zr#9D*t@u$gVkz)U2$E|u9$kvDb)s5Z1XOB4PCRMVbpwU16;h^#&si2F=>L8C zm@ATgk@c=Tyh|a_^gzbq9`TLQDFFO&Zm0om<#aUH<J0_2;g>+=Ve|G`9h^E3uUVP z5nl`2GIzM$Q5ppf;V^x%3$gi(?=;ElPVPnlY?D>lp7;;6zVnubIoD=I1_exV5EqlCDi>ze2;C{tNiW#or6Q)j9s$`ir)$j(AEtS@oa*Xw2%?4ZN$S)~Ni;DT3 z3{Z`u+|yCtU^DJ&|5Jgg7#st^BD-S5%@->5oV$V7?-sUf357%RAk{3Nq@^tX);sz? za3Falt(cD3-r!F#7h=M?;Scc*TdJj46?zmgyKcR)=8ChPxx=`DhFNJ|hPZ+lqihd~ zT0)l)qklX!MKUQ!RC59Ffu^d(#ql)@wGqxiEj0&lnvuE-Lle!bze=%B@!i6k56wi) zU$z0agrD5Tl4JqB%-Wjg8O4>Iw)|^A@QEPedA6oP9(UCxhB@uD8~$P8Q|5S?^x1ze zUo=_#7d_-d*EYQT73+Q@ZpLP+{%7R|BL#Xy`|~f>Z{dbaTCGcxNKBWR)G3I@EVj60 zXW>-=D9CidY%YsP6~)6Lo)3F0Q6Ie1Tb;a`LjbVab*3;Jf(qCxn%&spQG5S*p9Hft z$V^`*#zyC`a~X@wu`S~y#HAg~2;N(;5OGqJJ3etj52!)+sCxU4a3#XE_#L~c#>juj zc*1-85vY9sj2=QvwNL>1>myh}Shlq^NSZgsXc#PKAwmb{>sp2@6JgUsv<#u57M;x@ zk4i!W#ze?rst{j(-E|a%Q;S{}enHZ2)6S25#5-VKX_9j-)ultYx!k%VxjE^(9SVvM zcrv(2b;@Ik58M;7I4zU%nHKTqlpSo4aY#3h->^Nsz^5xtit8U}{mU}OM>?+L5ulco zMT`ic-tV2Pf8M40oSAFFj`dLsDZgbZ=C0y4Sr2tcHCLnJMhO1i0kf8F#q4&g93A;+Oy9s#^EjK#8o4WDln?=<*nmL2)CL%)0!Nk@f3W_S;8{&oxG`?<@8 z*6b1Ny4kp`Is?-e0t$gmqSyvF%R;(r{rBbqp8FmOH_w63dfBHNn9oCpy(}^$J`vq7 z(0s=C6?CfR8j1$bv*R^WrOK8gw|X5_AgV^`-y!Qj$%ag{&;*3u;@n!$IdHwe=7W?Z zI0^5(Z#eSapS9WP&R{HjE<)^p)z-9qYXo1U+#|d1Z5G4Ideh<@wGU)^uCd-V)E&Ca z|MHBs&?{d#HEHcQbV@nfd&e`sER1R3E9chr01MpTYBnCq`ENvYZI}0124@@<)WO?+ z3A=C!>VB*XR7x63C$J_w>ax%0lYb#*e3)1BiWwqGGN>6VGJHQJS|2kf_T+ioJM3G} zkU>lI>b-1o-JR*V$qNjKlvLBqlz8@IZwNkopc{*4eV#@FEKWC*=EckK^vJzU{SWN& zUp;Sb>UMmO?wR=lTV#&6Fy|YC7yHy@1G z4aaXQX?j3<&6~HMA?mP-({5}*%1Nt+~D+rA2LPR%8beT5_Z-PiHRP&joM3Wl7+-7NpD_b zy<8Q3|3Z31MMFcj`9nATZhCbn$AJ~C>~3JC5XmAMpECF2J=J%>kulo+`$B9JD{Xcq)n>s7S`c)U@eAs0^uAi>wBeLF(47l z9=y1UkK*=GV%Wuc!h1;wd(^;w{Vz%i|BV$cg+jn*ku_M~X`WA~?yv+u?IXrd*R6Rd zh%BJnyu3@N^81Okt5O1uoY7-O@|P!`Nc)_%>fQOLKkH-hO_PT5%~Q{!Q|1ew;@IiT zIXox=SI*et-d7IIBj@5gcASbU`LQKCn3-D;@R#-1Wl_OPgy+urxy*glPdYfaE=hVT zWP|Xo0BM84k?znXw{t9l&s(h&DRQ;zL{YVQ#%HocBk5=--X2C%=EYIMFSna~ ztDPR%^|@~2Qm*;yTOIcm2K zz=(I-vlx*`4yf7bzcPL#n0JXoc%bIbZuTj0isNe=I44MJ|9c^qJG*&H`CiZbyw0WW zm_8uF)kgl=w5!gswkU8o{Xs)hGryZ35n7+$PV z1Ol&NNzY@J8(Dbl^<%i(h(thgyJnpLto}msKr#BhIPLqBWH&bXAoj)z?i0?n#pagB*_@79P*LfpFQ55nvZV#3;sN5kvA%lM(_`q-X+abWHU!Q6Q)>|dJ7ZaFqYV)53`Z2i* zDa9O2067+#wHH(&|K@!UGM>cE6A+;-v34bJ08d`$6454zk1`k_9=Tpuw_`qm@v@vC z<)!k%R?Wk!E-4vCJh~FdaHPifJi~Vm$e@gHH4AV52$6apxor1NxUtul)EsEa#mlbMSH0W& z*E_PSiFy*3kcFB%?5{lzEAD$<)TpR{l%p!-t;bvo$Q%z!A#Nu*+XH)F|Ch}=^AP*SipUO#7SB@zxQ{MJ9duT=*O zFzbMK_bFAr?j~OAQt--9sodfjrYoPM8{R~yvG66L;&u4qqQ6AEv%Ja(2(eQ9Y>Nrx z0HurPC;|DFjCx_4-=|$naAK!Om8%gI* zo0eR0&&W3)<@oN(&#xI&c)57vx^p5gWT-fJwtr(=@@8<-l#YHxkaOL9;Xe zN`=Mz0o5jZt0|4a>ZzxRp7c)hodMblvrQrAEsMau-bhBvYwqKl3n))`T)S;J2I%|B zP^dB3|17%s0PUp<_IG{YJSP?XYXU(P!~tlzBvk2VeY>vZd5i3V4j3o#^zP1ICr8&N zH12p#s`%e=dLKRsQ}^Dbgx~XNIQm&kMW2~DNVxtZNCemvzUq?{+Y5#JE)7I&sDrpK z{=2vPpP{n{OwLdi+o(oIQqSUd@caLw{G|Gsbx!^wm;|#kb3I-2Od$)4TqSxYKeYRguZWOG7s=*by^($0dbu{(?|_JW zvGRTCzHY7aDV#u;KuS)3TkPBmimvOCn)d)SzE{2&DNWJSlcy_O3J}}mRjr$*j;!Ek zPnHic;#PHun*?C(e9NLk&7|Yn-mkL_XV(6%#pX5{^^cqX}tHP}#5PJi?v#WY2|%Y1jg2SwYCs??X!`qpSD8 z5)!M|?3xl{=j>Iqxl_$tf_bs_rzv6Bqd4HK{x1>kUj5`!M_1L9egPPWjVt%<-D zgUg_?j%OjjhAHjoE0&*F|0&lS?z9t5y3GlCoL!`-!l2N}Gxz&PWvuF=89l~VzHP!{ zc1?jVTgdY|XlrBs`aVmYp+_r13S z-Fx?=Salkwyv13kZ5W-VH1@1B_O>GL_gLMeYMo30!_gBtl|&+uK0aCB)44xhZ_P@2ch;my@gX5nb8J+v_@xks~oI1aA#PwAW- zOgRx#!Z+K0=bjKZ&*WN08~4&%D5y&PD^zsWmu7BV1Ukb~K%)MA-E1QAyKEw(Yf8q% z!qWIHABLjy%;g1?ie3x8uI;)W>wU>Z#h0ju?}M)FTv>#h;ZG<#(g*U?ZldT#TVPG~ z!KsEMyPHRE`$OKi_K{dsJb)2TsZbqxBN@>I$g5QCWP+apsT(fFT zyVxj30ias(m-N|k75#NLF4VK(7arOq?MHH520M+$VXg6tjWdc59JK--M~_KL^QY7} ziT}ejuZ6z;%7tFV`)j_QN%fvySM69nXAR~4mYeqr7Jk1&rN-lluWL+!hp}*QfrwO? zzYHwgF{~kHcMlh^f?c4#ex1_^3cq!fgqNLDEj5={IHCEK%4*1VfXihXS_X$=W#3vp zovbA@$m%j2rJ+2Hw}bX5t%RVbhH=gN0UyWAeAZIEeAGZQ=Fe`Hy7tr2in-o=%NYb+ zl5T;AE;TU@K%ur6q4CJafukWiTJNF!@zkwe6}44E0~@t*rRdCFjLjf_6%#_9o_d7? z!I;Ta%&ay0nY8PqGC^qDMEl*CS_0}>Zu5Q3Rm;qX>f=%$y>qvY3NqP8RGGbKA~n=H z5tKuMKSRXsmW}EfM@wT}1pa}Vd(wldIJ6pwc4&7e_TNfOs&8C*X2jge0%(RZ_4(-O_P6Z+VCIH}Kdx7Hgb`z2!|A5*3hZcyI)fpDE)Uc@N+ zgtPX8R~0SYD;trh{rx~KTS2h?Lq~DVnvC>fbs~wVHESc~9RCcko%6P^-n@Y_=iR8F=PGG-qWR;#yw z`P>jI+>@y^DUnV&Vkl`Dh zov%YeE-$g27V}~4^0cXULYR3BsoZsQ&g~z^h0|FjbN8c<;0w4@D3HF!tM#Z{>v83h zrNM|rcCna6NQs;>ourYKGhEVY$cKMyy|ruCyq_pNno3rt*Tl7ec*D2dWduZ$YdN#O zYP{nNQJI?Jf!AFe-+cbq;Y`^l6MR)r^kxl`0ab;L*;9HfKiMO11+hQp&rj6~y~P{d z^F9oG5>zgty>@TAKFQGiGtFKM5Swk_YziBZBbo+GAcb1kPy(HRv9T$YuBgaQeN=_s2j7R^w^9^-M?S6{ z9e5>l$~dr1lFZ0que+R6FDi4l(d%ofO1j8B1d2G!W1j)LCkQ#)@K5e91stMZS3tWY zn2CKZ~Xq#Aan3srSD-3YOsLNk#Or0 zK;7XF;AUm$S-{vcsO#HQTrSI3aUqktSqkuP1&LexDVVI-`t32v- zBcByGd8PlBF`ch5w^6y3D<(-QKRx%3xaM6QfdKwsm8}PevbI(3#=n*stb-l&w?{g@ zx1zT<`pJMiZM;y%x{$d1(}Oi}`}M}V)?Vm4?@`C~UJqY?2a2^8M#TRJBVd=V&Ax^W zpw9kr?@N56na+$TJgi}PU0l^bZyUO zsI_d;|1Ac}#?x&xwiliZ+{~7+{_L+;^2^&c!ybB5{2EYQTkCLz^KV(H*-LcJC116f z)OWbjfP+?bJ8_y!S&^{Mxde_(YyR@N=+X_#OYVytjEJs-U)mgMrv^DKigBs0@;LGsRA;c|;i)2rTpcUwV+ExQ=p^y?%SX&=%g-a?o}!%WMo zu-a09c4FY?5$3_>coNPoJ5P2y)oGMka>_|2^l$Eyg^j#U3f)J1Rq`D@>n&GWiDLRc z3hXz}rwnv`V+hKbiPQ1FgxbJ}5x>St5W8@@goT7>T(UnViyri!Dw$}?7F8B28r#`A z#NF@WaD1`AxkB>L98-V(tY)PX?VH3;KuS(F-z8-XnDm4iwe;magR zDZzdb*AcP0}{=2+Vd_WpIPjz{onJUqxZKl^9G7Ab2cezmMT2U^3f1D{ z)pMrDk9#5GPzp8$4FnS|i46iO3s**lcd4{&gyQJlLL1j^Nmp1kQ3i-fz(uT51M*TE zP8_=zsU=}QOc1^6DYu-r;=`+_e&91LV4N|1-z$71sHB-UC|Xw(lX=nS+9+$I_=;W? ze=c~ltB+*WxpACPm+<)6?KjEQUhzmuF1IiZv7}@PPb=RDX4ivWP0%Bt!Ck34jQ2z} z)P}T1UCOKWJy)SR{tsNz8CI5a1I7v4GY4a`VmAXPza(BM3GGUp(sk>XIvrd<&aA~R z^&f)>s{`hXxtJ(%Is~>P+FH&}uW&CJI*UL5z*)GKjU5!G);vY?!5zRCcomXx-xv~t z^V*tfj{H28270WdQe}UO(Ls{i>DNk_@!ZVcewF8CPnfp!X%XbjSquuK}l7tu=l4+l-x%P zH67VT_6g`=;oK*;kkRYB?ML;)hzDAd^ndQ>C0_+`8@e_8wiHJr?j&k|1}6MuL;QeH z(mq0?P0$8K6L+Lejk(G4t)@z1Bw;875!H$?j=YDyaS*xwel4nySL)_2Wy{@-Ozuy| zPp!|APc}6}a;*?tuWxEkoRScWgyTZIDvMsx1vB+MB;E~Tg({_bnS#GvyTULl#c>G7 zPE`uQpl0kz9mVfTt}^~eT2Xmfz-!=uZT&=AUerEO;R5YM0U6Ne$T4g=ULp~ zmbK!4b`;moPF$94w%69^q~ofj70j(;59_=+<*+0DchO#Mkq>x_X*fKoP}u!a0c^mY zLo};2>*www$uxIGal+;yO;cvHK3BPlC z&g#<>|B%2Pgk-0k=Ktad=Aq(EDbbEDC;jQR9ciAHMSJZ4+;)YYJO2o{HYZ4DiH*GLyprf;-#(q*RcMvIMoj4w}IQN1rE z$zC1F;GK(S575dT|K@PUp&xbU+Tb5Ii%HZCo2eBu0|Ekf)tZGbQQAZK|F{a>(8*@| zbF+Uq-;t8D9J?c8i-2}p#}8c>{>7;NZHHDU{MTB1eYhDIz5=FCgD#!5|9_HXo zv5mK}=ZRP3o7I-wwBYBeVE4h%-)2sEo6)Y6)DKNOooKsuj&Zti?O9GY+izUW(R)#yQCa46}6qL-f8Q?~>YldHf~UGkjibI%#n93O^zh`3jmd z2A321mD=M;Irve{(G+L~397d>%4CZfi1miFAc`Z$j5}%_6TUV zXdhIn1Dj&hKs_ScLIz)jPFLyX_@LH@w=Ian{EZb`*cG)j6#;-;GuFb|9pL7teecM` z&H*8e7y*l#c(~Hc;{`z#7W!f9xA#xb1w_kYYQ5Fp0=yoVpj-Cd?SmS{tR`+*;3BNX z_;f9~er16n&+Xq@a3wqWDElm(hq3Qye7kmiJ3oci-_Z3sNe(ksJv*>xSIe$0g^En) z#~N;43vhvmJA^Be2_AfH+P9djNZX75yY>rf^=ez|rH#tb&J!gr;MU-9 z>}0iVzjEre6?C-T;B-pi`KPw+8y>|3`qQ<+w13X}Df1W7og7GAia@ScNRIE?K+x`% z&?$=d3gFFPOn;&Sh9*8{QWzDps-ecafop2t5jDDdQbCW)h3t;+MH>t4zAHWY^B+|< z6^m@G@GL{E-OZ}zXCjE!6l_}1>Km1Fe}3*k-ga4oz{6+3ojiiLo}VY?zHA)^Sjh2 zdigUU?SC~<5mc4tU=1J-&c_r+=?dC2Axr&FWsmTMzS0v`ShUiWMnCX%YM8~FivvW3 zN4e1DNTT;r$cb^VdX>Pj{un$G!dS%V-70IV&3WmvhNlcGZ7Q6m8o@-9ep`0d$sB&I zFTBYy_!82XYVj@_4PV$y$WOX0mm`)@}||`6w-lMc9k1JGK2nP52619YtqV znzs9}Xr$N@o0oEGB2*uh54}OI0T-2mU1-o~&go}0CFZ756ROiQS z%A4_1t|q*@TL;rD20L|@k1)Hh3Q|Sk_Z_l*kZk`U52|zFH&&7)3DaFC-1QG@HgOk3 zfoPYmvK~>YG$OKVOW;cu8Ric9{}!>#KHU^AJW85ruRJX@iDIMg!<}euHFJRrw_2Y= zY_zrB;q`0vV?ScWh-8GD8*p;*msR?_H?!c@z>$a^b1vY_HH=h{_T-qp%S-*v=YrzV z)Y&Yg(_!`VPvJwjk0ZCUO>UfcDAh@81xMzknVFU;xHJB_;06L?ME+Qxef0O5?pB#_ zWL;Y3cN*rYKngJ4T1>4p8d$ITG|Zjho+slc1 zh||rR7)bwvStk@tmc*5xM}LuT%3{3poCrG+Mu@5nylg15F(PYk;W>i_UF&{$uxOzl zSNJmeLj`B_p-Q1qYQE9mB{U);#g(QFn0k?EU}irJ5OMibBy@v`kFBEIX;eW+J8Ykk zudGdVPG_R1C8rvcw(!P|{BzOk$gB&8n(F7zu#ny0!ENT zC+RC${(?5;SDj7CTDEVrfj_oJr8-8v{l6F1i2H{sSG}@UClL%3IFy! zC&n#rHS}j^k^sI{IiL~VG<8+0gwA21bh4_L@ULp>9HAol#G^CU=sT{Rd>b2I@GHS% z1bHm2hTtQ`FMVc^b^eeg*>La(m-2qziLut%aWiGDOgYpPXP0fP?;}FfYZDiGitc@M zH#$2h2T)C4r)TzYyiTEip1{3l6ks67)7D#Hi}gTVvJ!eDtZE`E1fOZ6JWij@FI9pq zAXqm?%H!_BI~0idIaSHINXPpzo5<^Y|Y&Xe246+q*IpzM8e+sbK02uq$dSdOQu(;)AOWX*1Eg*)4n+c|D?1PidTz%uVJ$ zxEC7omxZPCCk2*X>7M6i@aUMlYmcO4ZP!rpNer{;hvapTS04Z8so^M;^RKc2n_Y-Q z-qQE)YJSK+MXLR?`C@_E2d{|^aJdmSIO6MCGQs04g-eq%`cm3F*?U*B!FAH_KV^pB z3N+$)NSRlcE)RYr1biq;YQYvb%TzOs?Z0D>fd7~J@P*BR%3+<^ZAwH#-^|nvo+n*4 z>3~zSg4ds=D08{bRVctefs`&Hj^-*Gs)C?&U3+rnWP^8&Xd`d|Zl zR`QGx6nVMM<{+H^7a&Nc{PkYi>DZeYw4Jy^@mjAN0DX z1N>hFR~iILChb=}QqXwFn8>}sTcg2bPn|BW2++`$<~FUcl#f1_GTVS=Wj#pKzOrOab6PAvv-Kv| ziy(sR9&O;!hg)1|*4>joWfRNH(xyiX$!z+KXy_-VSzkZfnIsyX0?R#}6WZ>#5}? zh3bD#`G2vq@ox686Ku~k@fBFBV{12aSWc&EcYl6v@8C{Mf17*?HiNiI@ZI`*;$kNb zxW z#+{INCFWZM!wp?W2r4LXeuqeh<2GA-t@}~NiUOB=akOciQPkS04Rh1 ziZ$=Ro`!vuh~*Eu0UDIvqGGm2uRBm4tB;pUw)zoy?-*ob^An+r*Rg`B7HVqLP2LC% zJWu}Wn?)dpyNPgd_|3eCSc4N|^^T{UkZIbLVkTb?IW}{tvQ*!CsK{VTYg1zD;;S{h zy*7ad(+H6lxwKzo)-fy0<$xdIea8j7$46f(o>ElGtVX%R8H_v$JS@op}d(sig-4wA2KvG@A#!$mCc@X*hb;=fd?nG(;z`{H7=bg`muaDC+2x4!9;kdrixMupQb_y zMU=dko4Az5adumPE#-N+NvtuBcNlDj864DDJtBq(hOZ&|D>0O zCD4I`RL2P)DT1>u0gk<@sY78KT`==3v!&3(s55idj74Mn4Yi{RZu!!W7Zv?RKC<%4@yE2n)Y;>K;Jv@7xfC6uMo6lKu6~)1d@TpNHVF z5`d>iC?^NEBk@gY#PuoFe<4X?b(e_r^0jfBQga?*Rl10wsR0QRMNGAtzx0+>v(;S& zN;==8eO}-{D6EC?K_u=y3ni(A>VBcT*8paSV`by2_F_;+alTx#B5fcY2K^}O7c9i!uc-EP*Z(Zga#UFS6xP#abMDy^ea7@|^M z(@=BZ9qVT07h1_$6EE9j$XU~ER}~Rq2X1HYR_=ZhGbOSXfV%}$Rm!wL*SF;KMnGr~ z44rSCx9iT>>NWKQAH6kS^)g(m=iA{xU#j>WM{i-Di3+>O#4RIi!3cib5tNX=-?iF{ zV@X3@gF*3O!nU0n=i zuHGv7yxB(MIL*=`cUwqTnEU9!RflSaGV*m!jj^FO+7kJR4a+LC8V~O#J(?fN#Bhx^ zR|maYH4DhayhHR>FW7zU){ypYz^VwGpJ*DfYxAoTG(Idp^bY^qDZ=4+&NTUg{=jXV z2mIK(M@1|d+YiYEC=#5`)1u_jy|n36q@Xd{C{3G^%9fOW7{6CC3T>bZcUM~Wjdw)E`2GhD)HX^Bee`h?=y~RsDEIhk!5q=$NYyl zab(erZt_UObJXj$(Reb#O|kAYVYX0Q*-vDVh2dt`)Gjhf&PUJ`0?1)^U84Dyhkl(V zA~vpd9F85p^3|fq!W06~?mvB&ns1_Ww;AkcOy!SaTLH0Y2!zY!>t;_L>rV}_6?4z| zW`F$1dMe?atFCPKj}+S`X6gG#?<|r%Q;Zew01^3crgc!sZSnFDzk+g$-aJmrc!ebz zG%khl^K4gX6a2?I9v6pF!=a{@f$j$5da@LbiAWoC^1$svn~bYC4F$9tqZ9>9pfbnz zHA45KlU9G}?|SJsTs6$GT{1r$t6M~Ljzv_%a4yKvv1Q2ol*nE`hI+6fTuE55Qk38Q z{Ghf<8bPCY|8Z^*h|4d+#1`KAXipPzxV9dE<1bBCToSn4H*}tSXD7-hAMx#`ca{?L z=xi$lB0tXg$f;p;3VB-e=4;K~Ue7o(ly>`gN+WB=8uO(K1CR|)kzx)_`!eKJEoG8H zSpeoS#htu5je1SE+?Gph12;+2Z?}{6OwTF$-;QD?ruRJBd%QvY_7oaf6y_#CE8Lzz z{Q)iC+NXNEiUg=k2_uU|SJ#_WrW4pUn?P;wwj{4J?H-1ofYK59uh9M5fwM=Y*oBGxR z{Cb%lJOFWh&vz0bK@FOm^6RmB9Jdk8JbdJ_&Fo#;3{`XXEkZ4BJSIX1s{D59YHT)) zeJ#HIDNEMeLHO|a$5qEL`c``sNa)6*k{oG~IU;&WfC!S|YQfYu1|f6paKsV?KUArG zO4;kay1M{(1WQSpH$mxvo5#i?8rs?e?U$!-ccqPOZDNw^Id7ZLj#j}pz4+wTuH9Xa zsij`m!k9dP)TOtpfxXaxhb?tHf99KgurZUBQN6_L&5CF0Q~nu9HneAHHt+l6QjN}h zg960OUGJd9Zpn?G{amu5*qWhX)_(n-dweMf96Q1`sEc>NaM5}Fho9v1#@AsG7rl97 z%Ih57^ookgWqDb4kI*1|-uVnpIqh!wF#xnAgjCjsRfs@kd@=2XN!8fQsP?xy`8I{6 zm!?LtP!Y;y&n$V$_Kc`C=TEWLbA-)(h>lt8Z?K|u$4+p-p=bDcWYc0Rt+wkCk?%2r zsre;bpWMtZTBYioFJ?fvWTdRGx#35zY<_gbe_lHsMiH-5XoL-61RQWOL+v$CweIZ@ zbNoG-ld%%TSRm-Jx&7<3apsZO@PlU&=m9*v~s_-_|DEGx<#-T!1nHSK#uNkl5( ze~M|$*xETjheV6 zL)HAsJ0l&OB*ASk2eV726a`L#dJsNN39TY^iM8lT9QfP=%k^~F=q&Y>g0)_MbB?0W z)JYlzr3~xmGliS5hHMh;HF6VgXSEl75Lhf*);5arn`^yGpqWh+RI@b=)Y(D#C0t5%osduhAq5K*Vae=A$0;o+jT?e?OWzA4zBm{iYS>nspEhZ z0ap{ZKKY5Wo|>~?zY?cb*6T!)M7+guqRAD;y7Y5=V!F6?NavnGj8Wc!y)QdL#81wf zCW&zhpV+msX{1Xi@S}acTSi!+g;JdKCMg%O*Ldl2S50p>2+u&t3`7oIgZ`*zB+>fj zXVT9n$Y`|7soCsW$CtEy;g1=FKj7{5AcZn1IomY*>MsaMYfxxuk4jQ2Nmxpz*9%%M zgcog1kBy9dsw z`T28pemPQlR29>4ed_{sQHPqTnvuQEPhB=6t4~vYq!-9@N$;qksg)0C_|mK_mU}vi zruwz~ldn|!`2{Y0m6IS(YV#16cHK2?o-7+N(v~x$c?sR-5x(lzuYe_^s>9A{p>iK* z;W<;I{mSwxdga{~=Ttd^^1d&HMIn!>=(ZncTnoyZUIpQVzEw_+&v-n_Ka3o-aQ><| z@J463H1f*B!5qt@zvBGn!`U;Yfz`yQw87>=%sYSpmFS|ga-F0(35?URn698Hs?e~I zh}BzlL;9N}ZyePCPlcf*5s-Vzp*nrYLwZhzz#lY+_%+rO#TKI~aDr9&L|}z0d&Gyd zj;*%^%Ihp&@p0iRZ3!P)yHU7{-fT--0oZ+MqT6ah=)nEiI5W=fBIsdWOn-VQ$;l|N zDHdQBtgcyI=PWb4TDJn@)6QLltW7eFiEe?#B+`48Dw)5}KCFiID2x|EYR?Ue=~GXa z3|GKfMZ?I%hPi?+Ca~5)PVbX6)2GCM`+@Y4yxhV{R|kJ(qeslT^l-t825wMcT&&jB zR=$Yzd~ACn17?B}2DqJnVcs^reG$Z1B8@NMng43n8l&_5d*+%~NQBp*$fje3{81^f z|G(NvAq4TiRXUlv?-uEY5GYACO;3sfLd9)GQP`vP>Y!P*9o+9j)g1fi5>@Mfm5M=) zPF-vJ_D#c+fI`u%HTon*@lYcAEu7MTf?f`;HT`m!t;<##c@AER^wAYd)H2yZBg9f7 zeKUoY2);2X#Bxk~V&HajF#}ZVt9yRXlV5#){jdPO++0m5j^=^v9-!L( zRot>Tp%T98w(zroXe4{os;X)3E;=I8?C<{D!Bvd#j9hh+6QbL3%~ko@IX=9(l-o~! zkC$Yke=CKEpHQ->ktXGz zGE3pcww#QnK6IRrtPHzp7X>E`oaw8^c}>FuJ{S%Ez(6}Go`f>Eo)Vb^9mrQx{eD5T z>S_txakyWNXn6GgOp~;dhdx4G%XBunVKu|eMgZHpTVk9#6pJ6htWs8G5!D)Jd_xmP zcxyrpmcv#ho_bGF2&I1tFajOI`HV@>gP(q=)LZR`_NIP5tf7}jjs;y(=uHqxNi13D zju(?#1?7P@DkQlv2Yu#cNo}#0u#0{V@*fW59VQKu$|xJ=|0dj{%LTGEbGGqJoYb`l zRbQzd=$_8!RJ3SUx1bh1>k^v8ngZjR9OfVzUzu7iW>@?wZ34%7Vm}b2)zF~2h{&V} zPc)Bl`FP~c^xO0iecKNtvI?x0LRn72?`NXwUNM)tMwX4cAeyN1nmk=f-RTOOWOy{V z`%rm!#r;l=yz=CJ0Aq#TvvVH#dV^zX>(I}{Di0W$_#u&C0y}#yj6Gyd)5;#`ycC*0$wJ@kDyuG$oBSxEFRsOh= z_%N1Rm2T~XBm2%{6*R0kzkLIF>OCcOq%;z$(nAhc^^s`GCIcQkzh`g#AcueIKccCEq&Q*3xOzJhKJdFeeS?YC0gUH5#;u+1 zuQTSqM;mGEe~=7q`M0rXZmEdA;U($Dd{DUEUh>~(yUyb2F?ML11?LMrg$8^ve!kB( zKWzyP1_lZ4AA7jsd`{%8{w^aZ4Ct)i*Mr>qE9m||@~h5JJASfpzOC0<-^={XuW#3t zI^&or%ffSaq~(eU0&Z|{*^G9X3L+JB$v-WY(JbwZU__s{F1 zrY>l0XHlmXhqa|c5P{RPx5k%M4RRu1BVwh!{^&Yk(Taybu5X@<>ro@5zjE9J_^v#L z5r&1KrH)*APiIzw`UJ4sptVqlj~TOTc((bpw%u7O587&++6x2| zvS)D$Y`cM0UaO@q+I(cjGm$pK26|uz*sz;8ROSxxyz6S6kyZWRc?e4etkfh!FgO?e zkLcmuNgzyaqZNnr&moJvUF{1Dx-TEXr}a*p@FTu4aVXqjLmKRVr%e1OuE0NsG7G$% zaXf5tND7PrqgcMsEfes*uh<-Ftra7-SQIXHcFAVE!ErWfzlXu`N&;l@P$%%J^Y7qc z_zQ?~7V1_K3S5&)bn*{9LTUz|o6<@~^3RneRS0!q?eS>UwI=$a+0)mWc?L4`Y-z{GDT&Wu6d$66g;xntfSSWj-ub!{C`qm;@?CbY6 zqT4lAe;L(wv0M@vwcPSxP=al)*b>~dGO@vk0D)y*zSpPA?UMWTL0Q`mU&enN?MHi{ zibr>=L?gub1iP3gC31>bk!w@NIh`G3$uMk^&h>~%N;blEZ40?z(a&?UBMqN&?$^_A zD>auywf+6qb@B0H;0mjlwqlSn^jFK^c}Yf(bk~eOprb`#gk~7S0=y4H_#!yr(7Qzo zYF6&*N9UuZc3_M2vxmcPYIaKfHvKZfA~?m>t2*|L=)LPk%Nml+2wgzlGj@xk22_li z!(LTOm(*76F>s_XYK6@jN0oxQ5NDOSmF?q6MjjnygD;mw`B3U7tqPxJvY`*mrY32u!s25rFn4HucCrlryeHI`69w&K=E(6ZD(N%#}@qNn>L^G_$F3U9956N(z-!x$R2B z2*=G(A!n4Il91d%V#BPatxcg|`K9i^==8v|3Kti{G@P}Zky2N`os>^A>+v|ADjN6z z{X*aKV5&0Cx5+D=>dI!${D4nllpa)IGmyEyU=558Xw-AkJIE;Bg_lj5M{ zM#g;+;aj=(`zFWn+rwAgT3)E&=&zpM$K|z6I#1x``~B9}gT?E+UVo*_iE|mUFLiHq zZz*mY{Cb&5{jOwfplHL6W(U$Dx1W$<=B*5g)pgZVKb1A3JU-JuoJDNh@)iZ|2n06S z^uXGw0{`Jr>I+^vWI(0xjZzs-=7&RS{9EQngPRKElU7ja=$Lz7cOxH^nQ@apf|P>C zdi7iPhFG5E()Z(6TaxAG+@&SFr1{NSif*a#w!stWi{L_0F%jWeHp_?A=-cN=G#>k6 zc1jCc&2~JQFgCp=RGY%4=qFu24;JrWNQy zHdhp2aFDLCp|>2(y9SRkqpbvS@Z>xC@LA$IGJqR|s!N{nZ#MS#G7{lw_fx|Z+_;m2 z2`6g;Ijh5Qh7EL@o3KGVg2e2Q{r0DgUje%~w#=zB<~P8yRR!IlxW(r>gc-{M<$=|2 zzqk^x9V6dGgXd{0E0C0$%Pg0~AF7t7u2z)~MVG4v`0;>P30Uqt!_Oo&eg) zFJqfjA8U23n$@tyxh%y2`-)E#XW0~Q(SAG^w|iMWt2O>O-oZ=ZBjke~EY0U$bLjJo z45xUM9wD|a+OsY%@uDv_YvJYCDg`IiXSoZqEuS4DDVlmcrk=tO7Q@T94Gnff&R1zY z+~Osl5~zxA-I?w>V#yO+1AHqYBGzA~XhvSZ?onMAljOAG{mIy=Xnrp{c1PZHu?<7dG4t3#~&2ZjdZ5@o@*gjsK@7_HV5p zdjqnF&0d?m=gU^R?-=75?3;x>>x9-+e=aQjEuaboXHL%l==TOBs@R(2Q!e5(ybt3N zBPLY6cZC_JL#N|m*$Z3It)!$(WRg?2c3S({cNAzT!GUl7j5K+sVwt*h1KOjIN??MM zGT4SrQltFEM;6$EuP#dUHQsBadw(6m-1OxC)M+(l*TgU~+IIl#y zA(EptK-K(Zn*@s)^@8J;AB&^m6|tdsfzK(<4^;@0DglOsP^KulLU{oy-!3dQB-93; z+D&5m6wU#ylJN;yHp-VmrbJqJ6wFZWK8gBD0%cLEtIGY;O*R_2wDxIGjU8v>1{%mD zHM0dICADL0jKaAvrlo9~L|qs0RvPAeJ!_vIQH@~wt0j?ie{xqL z{b<)td#b-$Q*~+;yW8Nv`u7*>u-f&1$4+##A+5cz*R=h{3h_?&lr6uu!b^>9_no;$Ip{ELKU+N%#>dzkC{ zWVV-4+`_Z|rKypT+7>x|6IPkxHZaml4i`C&2Mo75RtfmJ-OaBoE`^2!x-;#?H8(f- zUKZ!4#|J!~UN6p1C9N=~3*@-Pz?f4PH1U3a{r2vQTe*y&^Q+IkN4#6$P?5n|_gjFUj>;)V`jDzH|%1s^FO&iZ zUH$nJjxes=PfuSBFO51BnT~#-cWFrHa1LFzgo+!)jz#k&BqLc(Hj1_+WjW=3k}QM5 z!%0x17;{1Poqd$UwQSwcjQJwcTz^BUZi8{LMM>XnPei72lJ+Y)_>FsC0Ql7Et<<1H zEkX>f@uZwFW~4ie)($Z7+`O_4E;v!Wi-Ee$7XxFI)7GM`~DJyuyh38@2- zrKG77uroQA4SkcgGb@%1xg~OK(B0}&J;lIVC}#;hFcI}DSM+>BbIOKuh6nQ@)8u*( zZZde&r$@ftr!+MI|2EfRk#+@PY@Xx2Z$mi6P44)N$i)1WKq2MYtULvk{&Ixt; zzV5H<(u|Qi)O9ChT7dpj5AIa}{p6-#3TyH2D>rj-Oea z>qy9s?xXz`=EK?sS>qhXPG~fEN+X>ZeNr~EaL^0xWA(6OGZH9L|8NkGvBe0{wq^+R zl$L!BPluq99adfjuQHSI4!#~xf|IelCqL4@TeN!UJy(e?hn5 zkuI5ezB%}wC4Z~SvkE)Y8u#gHDpSQ`N9US(ORyE5<8Y#XJ5?2lc%9H3J}OReQ@t~M zP#PCcnxKQ1r_?a~w9YZdmlsnvXICYTb_{MuSER(=@z+#VE428|B$Gl~p>yU}7l#( z6E33Yf1DVf0$5rH!Ymbdwck;h!;XfSJUu=YfR}s%oIs5W!BKioX@Nb5a0g`VI0|E# zYFIOlP)#_iF-KaHMP4I@NBMFiOG+jdEs$hMJ?x7rzS2`u&-5j|=TOmXFUTW>o(aM1 zqiIX<(2UOQ)cUvZ`g0b;(xpy_j_Bp*n$Z*VC2Qe<2kMXV_E$DQNTIk>W08C{V~J#8 zE~7Cp6V?FPJjzR-tby37(GH8gP&^`3F;kK(LDYs=V=AakkpW~du$;VOzHEp07r{?v z@_2FH)mT%J=6&8j`9E^a(SA*B7}Crl86X+@OMPx8f38q)ui~TJ@Y`*}o7ui{Zm)^5 zw)L-^5@Fc%(R)4Qu&Ykh=Hv>JW)i_fL`Wb3lO#7I) zvoDEBuFSv9X4r`_Gi5u)=?q z6QjlzSQf8{{WQ306#rChWNShwh*+{|tpL525_6mU*1}CReO^ zU`&E*fbNFnz{MbmHCd$l=-d|n1COVq|6r*~OjxKHTTT)gCN6tP=3ASegK*X8;UQ~x z<{iw5|E|)*&dEP|b}zSg$A=;I#O-3sA-}JZN?vYFI=sQ?57Ha+o*aw*!oMGbI3epq z?u0P%!eM_?JkIhD;lvu2~|R2(#0<(Bt=#%B5LK}e|x z+N^J=w+2}y+5ZWZv#~FB0G0d6FT;|?p;ao<)%%c!o>&qKNE|X=-_e_>lC}0sqxj~9 z&fcv(b0sBrKxFwv#E2XXNg%OY-cs*d_tvvTBvpFRPA3&9Ou*Es`g(x=_CFJYPZ~o%K-_dsfBGP#G+Hk%bug3ZR#nxE`#Sw1pI!FS9pn>47K|-+L?(XjH z5ZocSySuvu4^D!+yXyc03_i@jz+vB(*}LTkayhUSoddezfmjTGniJfMLSRZ zoRD@9&o@7uNLhe?9vp=$dXfBuzM)kZ&>tCckVwtFbdUc~7VL`^H=8UxsLNMiL$NGm z-EnQT*S5v(BQ@shc4rT?+y6~UBtR{D$S1@t^yi^%gv1nxIT_W^`KHf&uwh9;W$9($ z21fw&nS}TOrr!5e?LN%h1<5e5S?%;!+fe(AVU_@zPXjKG4`{ee zPDt!l)aOG1G9fX;*ZKu>eUy=8 zJW4WdhYCN?zJ~YOXY`B`3_@wAtq)_r9X~iD+*WFEMG&!oJXVypuDQ_xb|}`;&~LK^ zA?FZdgCYyyLU^+57=MGg5(DfHfwt9SuPDsPL_N%BZp- znw{#_rB6V`_C`D^y*bJkbfShq0f?`uU!#E7n<}QYHOL->BvJc{l@zZ((vXv%IYo+} zn#+C#S9OC-%VEzZ5u{9Hfv-ZNVx+e@aDM?UP6ad=UWcwnC9l`I9a9d9!m`dQS1ffG zwtZEropHypDz2|ya;rgR;covKMg9-c00zGX5V}$EUPd6J1^JBdJ)Ev$NhI0xJnk-o zd||!a&rUCAfTY3c|Ds6p6+Yb23H|Xn_cVf9b{u!+wH~~2Bjt=>PB$k;-dTRPe1P$F zYs!M>&Gw@nGRQo0(&AfYy~=)MTA~!s>H*S8Co{sFXT0=@D6s?ZUuQK` z@_79AKe%f?nt3@U5n8zOKgiihUg-7L{CN%s?)ATd{1_qaobgpB;WOUhd1BDfC756* z^F)HI5*0Aek*d-C)AzcN&IdQ(jU)9sha7UL%&xD!244KWC~_U~VoP~bmpJe|Et}D8 zy>LTjyNno7#Ya0H=W7mw5`6OgE^>DGgugyP{*#>VuO5WQY8Xyfe6s#H=dI(#hO{4# z-8k@ENZAmNckSGUr-8y9I>M8W_A!Js^NTz{+71UX#)godO5Dc>L~3f6Ts_qdICZUW zpq^{_m8qO+k%wJq2))wEuGB@imSflKZXVH!aVc3tZIoF0cL2v|Z09WRT%gQKH0KZh zN8HX&m)7fd#`B9`hORZ$5@41+u{QL^i5jn3tddMk&GpZ%ueu@@p74-rjUM)mVAEmn}(&z72-k3BZJvA@y_e=@-Kw}y=8dcvoB_W6<`rsrgj7BRwJ4^Q{nUGDXVo%6^|&f5%Uaor2h0J+=43-) z;K#e5b-ckt&dTry42k(#X6DM8QL{Aa{%iB1(D-|n{qC$_eLGWgk=PokW==#d$JP3RN{m*yp0%tVzWyq_ z#9@|30d6HvHvik4(MqOW2t|~z&qXs|bw$OYcdK}eH$D6}fd4V3)0Ru!@GlVm<0mgP zE!;nI#KV*RjQVD(^uiSHLo)ixs%Tr_A>=99E1*dSW$!Ysvpyfr3i)&74dv30v#m50 z=aM{2$Yd(IF?v}w`>4@Ihg3Nsrix4cvfoa?i)#KZ{#UiIA#bLesKKo2Gm*Z^p`20( z(j0r?KQN@oXGV5FmW*gF({h->p#1oXIJlCr49ok@07rR1m>_!@l*Dw_I&hW$g~f28 zkQ$>Bhjsg6sn-j$+e`=+_pwR2flDCwC{0vkoCA$uv>?6fJ#znUkO| z@Q&Y%87Yd9^`0r(&z2+c`!;V)8f!b3d-;P1kw#q?mCK08d4HF|vY^k3JtZo1OLH)s z1jtWhQ3oR!&a{Ek=Ad0HJfHsD!%EMOXK%bx&aRmBsqYiaUX&^vh_ zRtZjf#`2zfQJzUPsz`aTSIL=@KE4mgv-jtrsg+& zy>tgLEj&#(G}nXCr7%0XOMi5v`Ym+X%7m?*doLv21eJE$b+UMMfktHbSXC?jD7(M( zn0CGfuLx%n)&Aj?oun|VGh;_Ygt((x7~3e`TE7FYf@b}~ZR$G&)wi81vjQf{-n-k% zu)>{V!7t9IbqRA1J&qet@{2dlOl0Kf{5>9lw`m*UytDkCm2Bg8v>*AW-01fyUugiC z!1XOUEeSbZzEKMj=oK_WOC;FP(Fd`#U_=-fgP6UZMt z5{cgQCe@T{Fs zXF?LeNqS>^7ZrsJ{h>{SviPFfeW&X^r>g|e|Djc>I-^~Mp$q@eXJ4;mJEaPc*5(45 z%zkXVd{BEytlwYW*z<~Q%EgL$e*mTY;Gg)%_FR99an{G!Jl0l6@1+$@+${ooah$$e z6FgA9{-pw1tNqjrr*JC_)GvBbDgEv4P8jwSDu{QfgW%tnfA=mJX*sy?wu5JcV?^>j zPK1v^m``MziA74peP?g`ki$3q0;_eSHNme?I}(swseo6wZk#<)IUHg^wkq-4U)wUw zT7ODRF2LSV{>+E=)$}67n~MmFloLVa{22~+#adXc{W5#r%R)SYbe&yI(u*t=Dpd3= zx7Sy5A#h_@!VxIxA7ANr2cyC;(^$Fxa34@U@VWAR+xrP zgX`|D5Y!OW#99QN96`0ce z6VrW{m-fXyl$&|Q?ch_d7njj{of_Lh?g-21yDPA?&!3K4q9cg|PDK7aqD6$lI1$!`eRwZRb`t?(r9JtP0LqyNN1gvrU zP}=@Ls&^t>IpalF5ePot?G9i`qR;OzB8X~)gXPGm?`NIgQ*u-{6ar2iX!EWf z_F9_StAKhsNQ|jyf1dg0IScG?N3~pTyqv(FLwMq_)$Ti7Xq=QR6c6>yX!WxZP_W)v zJ&SJxM5z*1bUqAtMYcM&cCh(53;}+qP z?l#7e@lYheHpgc%N+a;Ggzznj|E3OPF@jzdjEP-(Hc^i$Je@w+?Q{*ksy zHCE8zA#yEFT7D;yxLG^q2OALv%!ePFN`wP;>Tm>{jE~Is#0^c{gt zIVZ;Ckyc(t-W2pgA|AE0^G1`<@8|=NLv+eJ9(ex zbEYK9oBB(l(EL6D?5gD7A%K1|7i7t%AE&ruOGKig&FEGVa|qIz{*!H%xHFV%dFMrv zT#f2Hf%=uX=QE}HBuC&|9i?og8f(1xU723EOt?4cOAM+Fb)vQ`v)L1R_0Mwg-_^_} z?lH5FNY9gQ7Vxw%fwPb~bLnR_UevFD{O!%&S{Q>G=sR4{_E3I9Gli52x-gr~6eZ?w z+zAAgVqQp8{&fCvpB=YIpsEUHN)v9yD|NWNVB9KfL(3*bjeQkSYIuA`s;LRWvv+rK zCPLODS@p!VjkmPL1=@X4k|D^WkD)S_FsSg=%#cTF6s(Wo3kk4#9< zp#*@L$X{^pnd=`|8U3HP#FyOJ%Po~_#l0}JPRS^@WBzb`x^~-U>@=xuan%fQt56?) zoIL#467!$!a^9=%g)HhgU#)`YRP)bS~=(YA3l7$}0(oNPk2&k}) zaju;v1$siSE<}4Y zxPvOo?T_TknWyY!+1Ldzg*Qz|$5U|<1{3}jGkE&@$p+rt$|XczOMgq4NYr0Sz#>}G zX`j2aS%m@!s;UcXgr*5n!?l$G|GQf>uke^@c z`@hGqLzPpA=G1-n<6DvTp8nUJZCo62;6l7PwV32E%;wn(ivY z&~EesA5FcdS%g#+giaQvO}{SpK-3s z7z}-@)cVb)gPzb^&>rT02Qo%TO+dvsC2f7;8Rg>Ua_bNTgm=PvJ(aPA3B`rbaAViH zYmj>G-=@b8xeWj@ooqSE+rO>lii-g`DDO``i(LKUMe}#69bpIZwF=HSp0!#U@KkTdYmY4XV87w&Uko-(d6m$JCE41-)`QYxd88PTZhP=#I{sxV~TAE5plo>Wb6Q|S<_a4J3TIE|5&NeZC%JhKSF-I&c8MeEjfB;n4 zetDThooWZG!a8iWAbM=&#$z7fRC&op8n?+xdRF|QNS?O%zH$BBf%=jRtwcF zYWC4i-LJm$9~hi6v+q9ZaQ5bOaX>y;EXM(6r0s5NSJct(!TWl86RfQ}8tj9$#Rr#z zCBI4Nw&de|miimXe(rD=40I5Z6e3a>Sl$V%lM>Q^!y>k(3WB;{C9|v;Q4sEF~%V z!Lp6C>h3S|qJDk?-eUTh?co>f$kvSfR;u+8g+lpt()VoG4CGoi2Nv<_Rc{49 z?#2Lp3&I%$#kxpBYxK#MS{_H)x%};4_aY z???^w+n_sQF?#JC9@#uPq&gf8d15ozSQ4}%XP=Ks7IHQagqvc*SkRqHKVF%E{bPH2 zUF8G)l&QA8=+jtQ+M=7zBCyOPV3Wl`6c-l8`h!Om9UN`?&uhtUI2U(_xWN$j^KqmO zq6iL(Bw`SpgbQJ&*YTjD#yzZYrK|d(`Z7x->F?8$4=MM;7Vev3V_?1O;Ym-O3WKn~ zMs`faneB;7ro``*aZpKxK74XQFL)Q#aKfrp-~D%{vZ%{q7>PcMzl03-$OHmzc^U*+6fvqhiynhkC?G@2Z^g1_o>!dsr(7|JD!VrX+4B4JxP+ z@TZ8;y{oJ>Gry>o;k5A*Y}dYX`lPCYW4bX!g|LGB2U)eW|H%Bi4dY(neQIY$;Kt4= zrvjJt$`2P8V>D#ZTJ2sOz{?}5*L2KIY{APjJZKZc@6c`Y=RqAeECnRdyfzB(v-JoX zdc5|*2SkQYu|3z+QF^Lt>R6;{{NDu@^6&!A4d-(LA3XG)IXL~@5PGJt?=l_wVsHTW zbRGf_c!hA^RF+gfPW<;(1?|_3N%NjN(7*4k+q=Bhas)#>11evK(ci6_3>61j%rn%S zleTVLYO6sn1<0Vu(SD3ifZDCo{iYhnn_fPz*Qe9InU|#_ZAiOb_s-5!8=i3mNxJvW zMZhZw40T6r;U>>xef!C8!~n?3sa8&ac?AHQNgw7$CiQciwc+rY&?D zk*do-S5Q4$xpH;$`C-t?&>vZo>089Qbn+*moG}pwsON429B}2z4@IFfn_9ouqp$$yI=H(?;Ugl4kXKkI zG8mgy4tI_cRGSv`rR~=Y^bIGT6P6_B1$NX3X?_d|+zLIz*!qfFHBP~Cs=*s9ot2~h zxSl6STW@CR@k534Z{B&&zj|oC%0q_fZgvl^Y~1J{wIx-y4}(Y(ll9lNg%7_0_%xpN z_Gw$1K2ste8B5YtHgGh=+KFTCwNfk7_KhD{8eAYawc@CIxYE|Yz`hAMD6Yh5C4_qI zrH*4WCA{e~c{A)wKJqjuY4l94ucRa;lcO4nSM@nH{%lj22^!G2^Kh!A@H5h!w!sz! zhvqBLRHwYVEi*kD_;nlb#!P5GO*wTG z+N-Y~MpLah)_xs2znOs~a zw|y-b<=#{g3j9i;z`>ze%2!KO8p2y8T&`qlq}~om7NocCg3js&mQ@d~Nva4WAJ3pAU)mL)e=NezqvSg7A^OEc=<>|RP&?Q)~fRX*n;11@S zyUQiQCIsDO*Uz;#N2}!37tk+uw`v%pe?rmC=EJ^KI~P+#BT`WQtQQut=st#qp{Q6$ z3KYdIeyQ@w^nY$;Z1`&>1hZjbc_MzmB=)(H`Qu|tl8)Zd!esZSsiM;>q*CWG)s955 z(Bf0nR(axg!wND!7Dc=^7;C+*)>>&p$>C^6XGy zZ$Q*q*KVl+=w$jdT(_BKHiZaP7Y~8f?PuiqY>z{M$CZ;NIQ6 zml#Sve(}FNn6JukmhlTNWwsUk!-gh}NS-7bZ}qAu)MJHA3gI?XJ|$As%=(7;@LD4n zVuVhk8d2tGADw6$Z}Cd{)no>|LRrQFy+h{1aH59`1yqE&-5I^qPsp^2Xt$N7qB0=P_8b)r zYi4pnv`*~O6~6xZH(F~q$6eiuz1joaMc^|!<8M2+>b6=rUB7YrsAIYSuk<$e*bwZN zScXm2SkVJ2_9|I}qwEA`mM)K#uZJ^}Jq|i+oE@Yq87J+o7yk;B^j^c3mHZnH(II+v zykxAY9ilO=!e%gQl%bZic#xPCJ%c;kiLR22gVWN)yL4uPftROEj0_|9o&fOiLT@lv z?^A3qm}Q-XrSn7GGXI%#-QmJl0rFGO%%>YA7o+Bbf}M-XzOu!XE=aMQKYiH^)YdI; z#D?^Sx5C!>ktHOJlr0jwIH29nWbsI3SEwsskW(XsXdhW=Dpsg?*l!(_bqptWHw3M# zD%$mTMMPat%p#*{{#2NU>u((}(%4UGS0^sTdi1Z8Q$Woz%uautU3!TXRDFnrahjK^h^~{q+xaOh)wu=CkRJWrY{Q#fBPpq$fT3vLIJ?dzLj|>9v95rVA{gnafj26)HIjK_O;*LvRpyXH7;Y@*131A zQ2;C_Jx%E!=O91Nf`c{PJn@p-=P|-h``$F;o{mjyTs>Izu)X8>Ua^={hmG1jbmW}(@^?};ws zr6&2Uq61f6^no0?A5(cm--0{f&t~=tpMM*P(A8a%(G@!$Sq6f zzbDwxj5l#{Ww!|(XBS^^SJdSrjO^Rc9){2tzAcal_sOi(?py*N){M_|-~p*?`Hl&-B@5V9x zjFlQK^rpkX!&EAY&kE&WQgzS9qaGZ4BRX`VUOc)(80_*$+9646d%bx6$1;nOwM$Iy z5WP7^Kak~8vgr`c6|IIN*{?b?NKdN@M`{MmyVz+e%_)m?T}AFD-a^{!cMnOa>w#66 z0$ohVhY)4E+Q`x;axB4PTif@-SiI8cky|FK(Az93u2a{x5wHckl-f{bQljGQCf@4= zBxrlIqO%*>w^y@q-S>!>&WS@UQ;9cE^-xU(GM{3qzDL6g6{sUOD{^v5))s_Qj3K1S zC__KA$4xk%G0g>9mA*G;>yOHABjzGB6#QVZI?pW`cEu)kZ-iNeEl<|)pjCWW29*k^ zca1nZo;OyH(j>vB6doyGnHMfZBNE1q?z18^oH$dMrYNAZsBxB6mWOqbu zX6bvZ#D*lM4vj(}Zptv-k)_w2Rxu)i2dZm?Q_1gzKH6(H4HaV#i~+xyXWMSmW~#1= z@jt6iHD`rPU#mX)rgKxSXr_4?tdj#f7DKY!SK!|1vcu^7yB&#THqe0HwQn{;fAj#I znj3?S8WrSpX_gnz1%Xt+$t17E#Z~bZCoJn3O}ax_7tzVFiooTEAWmZZ-iu#URPgv9~HtWd3|@}stjHFfbJil}*GjyxwN@qEo8(mxJ8zKOfcD`HCk01*WD5yTd}y(rlR?;w z-{mKLzcI48i?T{CLg{&&jF;<#-yZW5uS6J|c22dp@1M@RR}`g6nZIE&jOL^2L+Eu9 zQ_%=QWC~6#2x!A<8;h82F`)3AT2|6LCL~>A$fVEwgdKw6g4j|lP-kjS`Fs88)=30o zdFX6WX9?fn9SPHv+*^KzYZKIWn|b`rA&)?cVXiSkHn7@2SB>UghsJR;s4`|gOJ)8W;RS;l`^7^ zNs$U|C?ViVK2NYUq`+4SqrTzY4-D6VgmPZB9GcpUSk3nQj$gUrJ_`xU*p56EA5teY z3(!B*X(-9{DfZ>{Cf;$fqDYu(q8QX99QFnY(#4lqI@+r9-?K`+C&*~Z%jq8KYJ+|% zE{&C172I@(XJKjk&Mn#Lnr&8}P-yhePRd<{hk#eH{3VwrH`Kc$rXic4EOQ#mOIy?w zqk%%47W*^1N>}TOw+*trP*PjVPP|xP?vyE4fW#ep)$rjaxv@MTJNHO%L8%}^!NZ|w ze`ufT1cHoeK@s1xaSm09Lfp#`Q#q-rq{XW)7hr zjjqaF0jA?38>s<%->4uJ7$hy5_#1g00~2QWOSetzuARwjny=a3rHU>c@q<<(XZ^n{ zZd)`AG1z7#mfCyTGA>gO(M$YRAmx>pY48@QZOBVgs%1^p@kguKv*+CP&d>3&M#C?< z03Ca~@4Z?@lkKTzD#aLy!jfgbH-W!H9)_FqZwemAJ*R=b2J3-Bz`e(Xf`)$u4b#ro zWV|qb{J{8VsjaH~e@q2E5Rpt#0l0IOW~FNh4i^A!T`v}7__Fb> zOmed0em(7Z2;~CLAOfqmn-N4Rp;91z?0?$RQR2ZafbOqlX5Ejx;X7^+Vc@X7F2ZJ4 z2N*FT9I5x>i(@w(ZqOlmR|l2b5sta2H_AzSCXci707l@QD?hN~O-W7`!t@D&|2heY zT%j{kzV`x3QGl1A+hfVB*|2p&(!4Pp)1t5(x<6)>g{+c3;^x0n5T~M1rLn4%%IPi8mzJE?$xm^VCG2t+aTB zCT)Zv+~b^_fr_$CtxzgzRX3|cFdQ}O!Om}?2_J6(>c)X(W;>P5K{6H?qow7>Hdu-R|1df?jyL4t~umKzivpCk$dXdKbC+ZK^BDEMAZp@cKN# z{bIv1P}hFcJ2+frLkvCy9DF7VKvE1iPMHW55KGW3Se$tHccE1ZW_3&_P9=U7<336Z4yxu2JxBpj7 zIR4Vtg3$*I({A)5@_TOH8S$%mPIfl`6Vy0I(Y;R)vAgC3Eea6g;Y>&fJQeK*pegm@ zsp;uhyk>@(M3S`WMe8}uaD{-{fav}Sc?C{p_h(DmJst>`-L9PfzW2{Nd@(&uMyb`F z6DEFr1X@5nwx11L%(>!t%b?WzT1FDq-IlDHi>Ys>N%@`Q}dTogmro znRx9^FZ=$v*MNs#Jstz^0e%Qd0mBnNPc5`g!99i;EZ#$UKez6=_8gr2srJ2;tyiDY z9qyCJKQHl{#o9_sG^+_Pom0-FJ@rx+_=d1GPvtSdRj)89P^?H`ob*>2(-&s`=@MuJ zT;;2SM&rv&;$jX#XDg3DYD#m#>tD-^4qFlPxT=rp@2hWu)p^Z{Hnx-bn64?=1A}hj zeLn;B+t<_gm!h%hs2lqSBjyjP?7!OvcZu~e42iAF$4+854t{%#kUcD1TP+}DPtHgM z+36^vymJ}<6epYd%%g^uOlEcfxY1r-c-x5OCM%AxEi#SaB_PzNE=8?VzVe%aIA5#` z5UEU=NjooTOURgfPhexBw+iclK zC%TQFtU9b?Ld|SFUEQFq*G}=UL8Yt)8h0jW0NMppY>?AD7eqFv51}exkvO4rw zQz$HG&L?0n2lo3jsu;P`i`Mcj0erD00H|q&WW9;KUGQ)*cgt9I8t4<~usNOIp?Ixb zqlqlPHAgb4?t-_E+i5SiMfh7!8$CHtT8P&6*YJrdOWt*2WmJhXNVg}QO0%W>67d~1 zGx!sJjOy*+>ey6pwVE`)#cGBMh0U`;NoGx)F94}2C$~D&d_9S@QgQm$;Evz}{l2W_ z2CGux#|;4|1{G74KPyDVr7K(WJ>5!QhaSQ`Ys=#x=1h_c3C{Xz1=Um^7q(M9{k}NG zq%Xp}RhECo(q&n3rzpS)JY>-xx5v}mbPZZ1(8L+c|MLODz032~5(*|Ck0P0uRA)7t zSRCfSGutS(P~V7V_;pAV|HNE{KgA^Bd8r7}$41tk3E6Jh)VpCcN22i<1=pGl%p)nF z&{omlM7fvBNN;U9d#R87;~{8|F!P+buj84NS2{3r@<-}@jdSwR1~gtfpLe~!tb+QH z_CgO5MgpEd2}5h8PtLk$&GaL7V)5p|+mhWE4o+LeaU@8j0r=bCp#>E(4@l|E0Gqum zK|SWsWbUl!Nnc97N=hS5`gJjBD1M3^wY!#)l|*2LSl&XTFiXoyD{j<^* zZ>qX;V%y*An2%|f#8K}=tdbDo5?RC5OX_Dii)WRZv3c(d zy}ND5$-uw54b(a|nghd5{tAkqkTU6|3*cVKy4<8i$jh+~cf@JPFF0HMI$WrByRgzA z14m(zX}`BSu1S|=t#9=vGLkuEt3PM2>Y|GAfO{w3(D6msP$>BOWST+t4&1FI@JW`> znrV|r*br>FvO^EP0&1V<{uHwVB<$6UNV*uWsDs$`r@u)^f*fPYg( zilf6yIOgb#EEO(UVx00Yokm#=uR&_CNxjQBcf6$Jo)Tf+-2fa&qO}o|fUSsVh|^PF zz#T9Zv{*aI$5Noy@~&o|P+V@ValXRRJgOmd$)_-tscZOehjATd%obUN+Vq~=#TfLt zE!UduWdcm1GT>ZKh6|;~mS#)orJ%6AKi5x)7OA(uANpg!xT`NKLzn!(r29+!Y?=co zI$eD~5#p)~$r)##wFA0pM73vRB;K(z)HVC%r>U6@^q#KVe_?2s@%v5%2Z%JwMTis< z3pTCaX2$%iv1Uhy3}}UYn4R7v>u>`J20H_u}K`3tRJ`Wn&15e zu+x&f!zpawl-8<~ojRN-GKkJ8M-n*>j-=d0Yw%*ozdn5JM-qex=>NPvI&t$s4t}@6 zY!u7Yb9QR|!<5)#x87?KtLvb4N52LT_?pXjm9E84Iwh$;uBF(N1i$~?_>{mj{U!@{ z-cjR7)~%4N-Y!i~@<&j0Y*pJs6rMx@Z$%>14wLghshTafg;&+|Bmr#{ywxZY`oWII zC{e*~0TbIZpTf0tH@*`yxc#+)sOBTJC-`H3lYtEM~3~ z&ZpG??%nYoakyUTGl&2^@A~OKZcH?uO}A<{dmxlseV7r-nc^h{S4QkZlmZda17=BO zG0!J?g;GL47(9(ZtUr%<^k94Iruz{citYQ)u`ru(#kZH zV8Tz$LUhA&j(+<(`?E#eV4f#LgUf4#|Lg<-E3`;w;Iq(tZ$d1k6jRgvHp3&_rb`VO z!~1t%Vlm8eBv60q!GBKV*Z`cPUEVK8Z2sWPAzzX~@()=M(od*b7Fzf$va!8S1O3MnxE zZvY>f$y()O9!bV)7J=vHy1Qeo4)uHP?w>>~XevRavg2arC)GvExC?J~jDHPuCg(Fp z7waNPl;%%VWfnh~>L#9;nk7vN-or5EUJeO^E?Am4zTRK>UFL%IbZsypKn4M^>!xzM zyIpPyP|z`*t8Ln538VS<`nvS_91CtR7bG0%9rv-x(@BpnGvITwqU}g&z>iTbh9^8h zl4w|WI_gH$doYgaw?No9}xS4jVW zr@5g;Py5d?^~7G2U5eh6UlbmTDWlaE7s9vhbt~1v;KFE-O(c$W%uxr_OE3tR-2+!hEeYnA1r`IYrc^ zGc>q9sW#ewtJ6d?iC0?^R=}l{sFs*JHw0v_9nDyk@XSKR&Y~MU=3s_8>d(zvNlXyR@|%>uGi4fQ%&~DH}^cY2Nd!FM+1h6#ME* z55A11%8;}nvEnXI#I zvmnAhj%Os#|D$&l4KZxyySs>Y+6vR>pQazmKA6yl>Na)djN6wPjVa#7Zv+W-UGlyY z_O*?pc20!XMW$63lMHh%U>z-Ey7=~}?>^2k>YDl^LmH+;{mr`vkm{gf@cH4<69+RSGq`+Jj1Er~8-9_CPguoc95YvtG3j4DRyP2!PFmQX z${Jy_JTxqMz(i(Uj&P*DIh@-$<+!^$g2L+0@gi6QZWapmZ{%2pvN`R(w;98IVbKlk ztM4YXf?Y-Eg^Ira<>U_oK0=fNLfd=xB)tAMlg8-Qg)@18jo;+9;XpY6iT7a=_e<#K zpIeJQO!`fTe1bp8?3?`vF3;P3IifZjfc-RQy4W@>Hq%C?iye{I75#bbk@3|imOfwF z#e>U46-gSzPmNfF5{@4egM#)z_UA_8+RNf833b!g_HIwm~Ti-x+eL0?Yhe&v$#? zIDwv<-OBNm!cEi+HR{E<8r2s2(NXSGeWg{>P?T4eNJizkJRrjHE8W3AWbp zmbWKMx>mhD!=Xt(4%4nSd1$oZ)La}Tgsl)QmCqJN*QJo^K z6TikB44XyF{rvHLcw?dhY(<`yotI42XbJn>g`Z8oZdl&PCmEwM*W!9IMnatiac$$Z z|{c7A2V`JJbOyCj9zc z@RTCOXNwh4-2iouzts{-*!*q8FCmmuJejm?Rz{EqquDP0obTUgWxR}F5`dT2AYv9x|_KV@Q0pUfR6I$t5<2?3F7MGap)O z(UTkS7etL_P=7m$k@T>knx5`c+#&^`@@GTQkX=u{=B-Xhl_9Z3u>Pw1t<65`!|~r* zGD2+m^vN^*w$i3!Kb>JlY;Kpb?;~9l9WAwx77ItYvk!prI|q~j#=D&LkqK^gI*YVS zXI`a^O3xX;T3jIoKlGf6+(@`mgJ}`K(dJ?oty_MFO>mehZ7c)VZ&zQQ0PDXVCv6qK zCsrx>t!EsZPdPnLIcIoZU5MSKdAh@}MSw38N;TBwYb}T7k)`}}oat&Vu^3gA@CTp2 zI;8JlXbS5|G}HW@>zkIL312lq#>*+IyMqWuLdi;IcrF4J>hR8eb{97O7^+oE4Y@Il z4b;XU3Py$YzHdm%@~F3I^8UK%N^T=w)2@=_G#q{JWABD@U`Cahoxe4yqvsgd`K;Uh z>|iv)CFr7hntwOZ@={p3CDS^N3rZAV+LCNgIaBj?h^JxF>NEQE z@4J&;_{m0tLhefuo(XusPbQvOhlbdPJmgw}pA>Nln`t0lC+L3FC7wz+H;m?Gnh1MDTsRhYDW1NPi| zIl~AN=5E0+swGO%tTO+Eo`MA&N_@#tFM*(m7Nnvv%m~^rVxk9OwVk#AfU^yOHxNfX*1|q(tBh%nF!$FgT`OVD{m{QLD0nIi> z_Ku$T^C#rizt`ao;Rjs?6UMJI*I8c({?Xs&xmhNmOl=;EJ#j4Ir1ZVv_C2JASliJtdtJV<}usTh5AxhUap)2>eF zp^NyWw_|f>cISgoDO03lG&WBSZf7D`RNnko8LqZ-&3oSYHftWT+YU3F3Csw%UkV&p zD1cEi>HfhSA@O9&cY8j#`W-m9dH3~)rvKIdTqs$UzNOVjY!`ThOVbqk#kb^&Z344MOO zcUbz|BEiTSuNzICA4Wif6eGP;PcIi0`~)msXM6rKM2zW>BI`cq%7yc*f#>q?5xs#n z=yCifNAMTi=h!40jWcyn>jFoD6ocgw`m=m1XTw-#&a+%>qK3%^04wR6vM?PYZBMsl z=^{o)1Thz5SC^R>_bw-M+JJ7hMOkLyW*Pp23|TY{>o5Di2UK034eH3!`gfwLK7J&s zXv0C(@c60H!$UvqC&cOa)uT@3dbVxWlWtM(#s{{{JECEZf=&v~>$Lptu!x z_u>*DMT=9cI214LPDpWg*A#bmONu+RSa6CL4eoN-_tV+u{(!ZV~Bzz9pP*CnLGb||AJEeMUYFvS*$PJX0&s>xD zw=#;{{Pf(~t69Na{jp5NWGmOq)I%mMlucu<{7dH1R!4T5>*C?Q_H3fsUNGZT4a1w6 zXj0)gd}eS*&eX(~#OWh#Ij@ZH0L9XkXvIkL2xgirBoZ&26gMwomb1L+I9uaPb!E?# zEY&06pxTjT+7ix&t<_>~ZR9CwEM}YXo3DuNi#N4(OuIus~`l8!l zQz>suU}_^;xnGG-c4^6NE3E+I`#EYFi2(%JMthcHgtq0CtLy>eqTYq z;&P6>7ROghik{uS8GTE%K}U5@P;KnZ-#l{#xS{qtKZpEM0UZ6%s<3+bMhqhr{jYuf z_M(jYE*fD-9^13<&DN5xcO8dk^4Wf84X0yP+2JAh_%{o}L_TByq;KPy{5SXeTIm$d z7697d)T2;YhR3mzik77a_4rGPK^@iz+v;pN8Qam@3oy>krCY#ql%~WTk{EAj@jw`L zotZ-NA-ylVo!leZ-6X?~uc4C1vMW?3k(c#xLLh&GG{Mhp_t*_i%`(aikZ%(nacqk2$}Lo^c5nLnPn%F6LM|CDdX2N98&Z6DFFA$9Px4Z@+~QYLUS@a?s$hv%)2^l}q zzb}K)E4YX@pR32pn~^8+l$p_s54pEXygo{{A1+befu7Y~r@0zXT7fF7nLE$j-|3zw zoewOiHad=%Wp#$#FwvJNhx4e9e$I6j+S**6ZKUV`DrCL^YBcG5d1UGCS#hl%^cJM= zqOYto)M0@6X)dOfW$^%&_oUwRrVFmG5A?Of9jFUP22Oj_TniMwV`wJ^D}yYHxo6$J zQzW{!#unR>WTT(lvgFCm(G=z|)95Y+;o%kmzg9q+b(Ko;Uq z{0(QVq!{d{W}o;=^lr>39gFq^8{ftPBThpu+VwvboPW#&9QdqiJHQ?r;Owu%k4e zu!)JLgvK2;va2OW?+MjJWD0Uu)eb+USCaOn(w`cFRS#-@RZG@a9o zS`kwz_L$WtSQW=(Vo_*(WXUzHjM*86?y6cJkF@-T7{5UbCS@!BmxK7DUoP9Uh zD#KrY3_KtBfca4|wT|^nq7g@tNMzfBX>*J!C$8y$@p-+GfTLQ*>yHLDiCprYRjNUf z2cSqqvOG{cs;|U9c?bV>Y1mw2?m0-YMT@%e#QWaD?)<~x_U_HhAXT8=VGYr#uIPU0 z>yx&coRYN~BS3_w|H;A4KVM@tYOJ=0C#M}@iweyaz6dHY*PB}A)LVIqtKSFTlbr}meI&EGRICdNANwsdA|(N-PiYKuG&yv#PZKZzfS#8vyLF| zE%t83|6WO?{I6%Ziqx)|2(0?x4iW5_6d7I7uLCaPacXa8#YRSwJ3$Xia#jo2h8^E zHooZK^53*OtbOF^Jl10J9pp4_#@xRo&QXD?{oTpZ-p&H_iJz4@VlU4lBTWL=k=S*G zL{F~4bFrd!R4>!0>SPxIS&607G96{`{ZT8b>p6vz^5i!MXw0zt0XKaA+i*VjOIu`x zdybsc&&15P^7}^RsKEYi1>(CIlvgg)U;)+c_Vc7=Q&_d^o-dL zfnv2jT>{30J#Sj*19Y<*tDtRkKbF`E$l5GW1U;{kIDfM--)jv!{3F3!zK##&=gc2Cc(1PUUgj= z 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�IdBGUY$nSZ@R zMN;!sJ&s+9VgR`z52-si-zy8t5&>zB-}*zocj7w zrtwD`1(33G#-UYCx+tT!zsuw`8yrR7IPnO|z0*AAcAzc10E4k9cz-wRQqQZwqD=Jc zbB>{}y-rIGo}KObR(bCd2Asw|_tX4KLjQGIf9_L2EMF& zG5cXlE{Ijbn=8Q254cY^I{{cyjny4oRXnB@Z4bAued?vvs+^@K_lVOOR<_==5@ zIDYbp1W<-i=4k{wM-`-xC-`(3J(~G&swmCqR82%BG$%URZEhG$T(Za3I+oooRk39R zeWq=LINKPyQXBX1s3IW5M>Eyz?mqowNLSO7B%U*Pp*IcwR`T7izO+WhB!G#6R-Ti< z_*jYHPeB@>;nG*vj5N`qHPTY>`c z%}d?j-h#r3G+q1kvUJkq&)GtaCXKRq!4#S)$)x8cBl=FMCxPw#zO%(0-;3?$DYAuu zxs=SX=Vvi`KL=Z$k4KjDPN{d{^sb987FG*m=L;Ibp5hlzjaq|$-hV?HyrH+rf}sR? z6b>o{=c>fKQ}w8Gj{1em`ztX66nl=-`ex~N>49fs#sd^4eBUma8|Lf@{v6ejEe z+O75-uoFIx)Kn41c%ZED-D6w;D^c}LcLczB7AB&oXFDQF8!Fgb^mN;7kM4OMo~O2K z&^L)6%5rUDjgrpsK9zm06Z5y?lO?tCJV`7o z015thw@i4lZGa{KJA=6D&A`bVqI+6$s|pLKb{=Cu-kHMAGyOOcNpeTf;$~!18%7gf z%q_W^rHxVzh939%mxP~jd}f}OU(}pD`p@HE^YB93H?UUUf|Ey4GCc!+E4dWT=iMlP z|752L37%leubpW3QY%N29N=-a!;VgGgS6rC51Cb_{s?17Be@Zt&1RdV8TwDh)J+>^ z^5l<-{F%N>Eh4Z^A#B}|_bU-&TuBodijs2RQZ=RK=qeZ!!tV^XlCRDDf2Y#ACVq`f z;Uc+n@Z%D~cv?|@&fWs?Kgc@9LTjC17C>h_&nc~|nyCB_qv`QqF$%x#;7Ugm=H)qw zbD*9gc6^5bwN{%-%60=eash1fvH|8C1!Q zHO&rs1sS;XPJUxJcm3ZKp}&$fcT{CCm^2>~9`!GLcby+JC|*3Gr;TrtQF-yu_dV_F6GlKHS>sNJ`tqnuko|cE*E@-Znx5^jxaT|De@;kT+5-7 zryT=~AUZutI1B3>WMCZLG6iof#J0TP*U9YYjISA~h-_4s$={cT-pcM%aSRZe+t+dC zT;}(W0R<9HA>az(4E|+A98jU zLLs4nDOt)e{!X8F6&L9X9+unBYL!VX{}ZuZy~G20D-+M*e5h>_PCwSQ=qm3Tu8BE= zAj67{v{s2Cb{wFhH-qOO!2%bqG0&MuB)Zt197RO4G1kfKvDe*@`V8GfrGdg>I^38` zwFOJ(l(sUOH%<&{A5FhXmN)l+0t&g{UKC%V{jbCJ-`4W~@!Gn6N?`x|`3H-Il^@T# zx&e9pa&Q5Vr9Ijc{elfzIBDD5#p}{=f3A(8h+e8Q`tg?Q{B4qXW_8zb0Up^^tw^N7rTVrs>le-T{Xk0bFsR3(!mQ zzX0m)8`TF^AIIwt#AF;T3^?7rLHW0jL|y)DO&*Mtk2{jAxz#=jJ_p+@oGiJMS)J82go_|sHxvGy^U9wM@5aNGpCcE_(M%C|KaW1a3E=wTk4>hMi>gT$e&8efP~-2cpvIfY3yNLQ$Sp>qgs zwk#0SlTqggXLN81wXn*Iz2)cI(MqFI*i$1J{hG}d^7rTZJAVy$DG4uFB6_K|K#4=9 zNr|97F@k`?TF;heq=XE0-*Gr$$oP;k@BR>ZK;|2^`fx?xuT{s|Dx~i=I8w=B=1kW+ zSSzr~?3!LNGDSQha~*)6Syx!#9$tsPP|+V3sAtn!^?mm`g`M8)wDi4$AYQnJwsDKnjn_jywH(|@Mg ze4V@T$u`~HT0LY03t75+fnno}wj z^=7DPOBI*>OX@0U6Ah=Vjj{1fk~K5eG274e&4QD4Stuf~aZ;X_+hWgFR+J+fSJvyQ zo_)G>lgn{-N++)5bqN&W$OLE0@=m=>)g(Fyy$uB%aGT_nYwBeYiS!xO zC8|c|wuP#s=&W)3;+Abc@s_Eb6yH^OtjFM3I>oR2VsM&)+RddB7&nHqGjN*94(5`UDSJ!${ZeYuYp{!) zKglv5dqr!HCoRvPZ|TcxD_C;mp4#5OKP4ek72m_!U)ND3u|qXVx2(9xLBm%{*gclC z0BPf{>FZ-W%ko=ZuKz6%Wq2ml7xPjPe&>lI@^HawA2;yXgS=)^Uyi_wG9(zp;y5i>4^^gHVFVL8l!tH}|{p z0k_xmj6Vm*3LURt&w>wWsU29y)kGtUXI~3!jEq~}D0F9j0-{qcwLNE#DHN3KvR-LH ziXA~>g=n-2U3K1+ND_YPec%vIbWoR?JzwfV;9 z(PO6q%9oG*;i4Ju$2#L9<0THO&F|{pEWF@6UfPsPsRt@IEs^Fdda{Vz-}r9wciqxw zdz{OUW$~a~z{Uo=Z`K2fv)!jbux{_0zC|6poR@8)0(3{o45(2;Ru&;XJWf7X!n&R< zlB^P5bDI`tW1F@nKFr~6Vj?tSBhS;(-BG`S*2G13U(C-9qcE6k8WAzop;xfA0K61Q z{E>rwn~x=NF(y3#hwS|L)`(#o^~Wch<+xsM-Zz_tGs&-Hk9b!gc<6^ok)SOyYTAxP z)8TVnhZ?H+{qKAI6_l!|Qb~;Rh-BW8j)b-e*dRJj6o6nWoz*;JH3rGJhh2G`wB)F< zo?chz-P${nLA1DUi%e6NFJ?ABQCOM)N^IY{mXR3Z)RMFkrz$Sds_S+O=#<@?b^N_g((c75vu7J%r=UqAKM<#&Cm~ z<7SDf7`f;#^*Sd^@dPA`S6{H=dWDqykbdm_V?XS^avj%cIqdldhUSUCIXxqCR|BL8 zd*Z<$$g}gQvS!%)g(NIlm0(>OHnt-*i z=wF#>;lWDQPPpKux!YKl#USu6w9jE>yy?%}rD1dpCtJm)ue;Vx`uGghzDrqq2}-**wH!N z5j8B@mtE!Q_>gU5@4jewqjcT70Yi4ry>+9us zG2UTKWZ?dB`S|Rh@@0WO8sKHuB+BLc>~(sd#z+;uP$2*s#xILkNRCC6_?@I}K(KOL zPkSx?$eH<0hl6J6cRJVhDvdZ*Xw1P=VI}eC6;&lA{t8OpvJfE5Yn1ku3J|Q>ocUU5U<6>8q zta|mg^c?o|FTTJq9yc|OfalzlcAzb>q6uWrSY$4gx`6SuFmIDbXZEX8*Qk_spy{EB z$FwlFQp7%4cP>7&RCZ1FW98Luz)nB53ObF#)ThCzsP#uAlxAGIE)bUC{oT>wjyZOY zS0gYMaSdES2K{k_lDhljVZ;k@$rWxSZ5;|p0r37?lG2gMqrzuPz@3VZ7`9YPlDb_O zYR*+66nmShya;F?sl@txif1W2|AxdiO(0dPZ#HH@unP3~J#ghg(Y`e~F)cJ1$HR-s z(%KMU7R?21&1*pKi)PMeYnv}M%?$bF6aY|?zzioI3mmc!DLGr|i_(1&n(B~J9gs*R zLn}?;n^CL(sLO!v*%KZ$Y7q0ax*}3Fi+UjT4{zwamHf&z8YTxP>JhYsRja4)Tf79?==ZyKH8;DGkE(*t!X>fcBKIvadunGSIr z1{NU@+-f2K81WPUk;d@>zg$6Lh2JyF1Rpi@LRVZcqcOOi>>BI6q4xpJq9x6Mp>$X$Go{vdA#Iagq2WNi z&$O0p-9#il=<>rh4;6Zf;tk+N?4b%HevjPFtE9^SDaAlw(P zVIb)IyUhW+>Lqfy1!C}vdom7hRp#;5)vxphgfB5P85H5Ep{ErT3{W3_gr7g}FA#orGLM>H8U1s= zuGbaL@IO(LGI((o_a0|-a3#<+9J7;K>7yyY_l6zO%<0EN8Wz?AUv-1_(5RNaU9fHA zWAhB4zG;^O=3`|;G3q!NUor}vZhlTvF-a<`DZ2U_f*zn|+oU}xqY!~Oh zHuDXmZCD(oTOQ4MR5XlJGC8&*4y;dKWY3D?APVf4&`!=QL}s7cSGaynMl)`BQ#bEDxea^QE#x<0uI2x#BbO7{)cfA`v>D z%wUo%$~9`v8?))0w7QpiB;3g$fdDn1dG|&0j)5*-acga zv}Q4!Ts2_;Gn0I7jy-qMt^mU{&6AakcA?HP-fWVa%B+-8lKW!VP~5h)F}6Vpg75&X z;fwl|kp(|Px5@xm<3CfDPPpWWZX^Xk{7geOiA&FD1J`DLRbo5m8^f6{b5D-(ZrVOW zd!q(F#G-GV^j5+|3)DE`7OLCP?K-v* z$X%x!n(vD?@JIeF;z4)n(d_!&?Cy{{edlm=Jm+Uwv7#GrU4y(xG_oGV;#^M8stlbYf2Z{!9|wX$}<%Oo@B%xL7H9(v#TY<)(H06~3>HFCFJBntNnhS~)_vmTGgz-vnsvmcNXlXY9N9YQqDn??Z6Nm65{ah4C3Er zwVd=>@a6~uXu}*Jdwra%4R2Bx%%@7?zCxXWagXwyH8@2NBn!huKcd8oi~hqFYd{81e(<78j#zv>s|yyiBQX8pQ}8Pl8T5sPt{?-r2R^AMGFQq~&L_gowoaTgc-s zir_3`0Wb$SMZinrG7SXHW>_nSY61pcBg{aO=0TZWJH?LFA_r!c&^eFfVW93@(_gmLBaz;iGO#zKkp8ys)6~5p7-NT#F6={7h1dlYtRtX{zw2(t`g8 zhyQ(e%nb{#->I}8t({(1c^_|+rKnjNQk)-`SD*P**Yr9H?9AdRWLFi$zt}Fpy5Id8 znvx}`62G{dSu~p&e_fD0TYNp}cZk$f6~H0uI1O-zSZfVFXuGqC)t{F&pKL1{9nxt3 zkBBSIdoy3`mKK+e5 z9QeN_j_ucV(STjf#)mqy@qgKEM?`Lb6^fg$b%J6w&G8DqwGo_T+0fOn`rgv?fw9%T zB8E#bZvQSJ2$4zTRe~!G{-&oz5bd&xi?QI41R?)AvkXCh8}BgtnNkENAzr(BcN{!7 zb|LjaC-5!3gx(jHhNxn^BlD?}bP~oQaYy=o%@KM@Q8x~+l56u;H zifz*^PTK&vh}AlI)t(bN6s6mx3!CRViCpFR`}v0E(gOQK^pdr%sX-m zV?Sy;r`dfPLZf{r5@r?c>E2lRiOaHrzWT8RAFF-&0sLwjeeJdh%}2Dv{CpC_96l$R zT-raF;#$HtF+5?Ic||>IE{k-woY)naG44Kw*|VyPCPd+377ZH_4#?G~SLBYwCp_)7YNPr4d*hIJOK+{3JVC~mhu4i* zclriPb67mLb;D#gluXcPpUz0FR&KYQ{kkR`)}zbVU6?#i?cb=UoZ7bl?;XHrI!4r2 z;Omq+za_@Ny1V_|$Mk+r9Z;mLHrnIjwU^4>2fCO-&xgY2Z9fi|R0`9xxus4oU!PU2 zU7<8Svw6?mHP$GuzJmEz{3GY05SxeY6a@wxld0Rifd@l>_cZXT;PIM1a@lf6{!*%W ze3~(PgFyO`B;~Oo7EoH8T`Py)Zl;J3-Lrk?`z-XpcBPu2vt#|q%Fkk@r?CCJaiH8t zof2Y9E=c4=I@ z&ClpQyeAFZk1p!}TZGM}e?%pvZfc8NuNQm!-U1ka3fE)0!Sl79^G=;TXzJ=%Y4ctD z_y9PZWhOyQvo0_;w^m5E($476U)e9^uBRCQ^X$@d)>x0q1+sfMk7gV=QNb_j4E;WNX(+KzoO|Aj(y`UghPL^qc?$skiEhC23s_V_aWNMzaRcc7vrep0;wdXiV7VoiS~^If~2x_CmHsC8Y79Q z58Bw*t={=yi+eu5>*BRh9kLWSE-*3-4ttH>@v(sl?jnBx^m>2b=mevRvq{&?n0?~( zO~Fq1av(e|0cZ|MtFm4tqa;Z`8?&dVR@B^n)-OP^$)-og+62e|?zPpRx z7S=SK!nNP_#aJ4jXm2dvqL^yK7B8T;(}7CZ;A)LQtlA^nPWjPs$*HHlix&(8V*SU8 zlw=A>^iuCBi?A7i!Mbs?YGPVp;_<+Q;EG)`2Gc?p(jfUnY5c`@-XdyJBpUE;ya169`K?SI}-WR*w)fdF5qY?rxqmczzC`GB{wE=DQ;;Ca7bWa_OS{TEvG^_^ zK;U9JqW1az=pDjggAE5|DF&l;=a?_IdwoF^Aa;pH*nHV;MfGFJ6<|R$nw8U?yk>*Q zVf4lxl0)?v;r3Ju$>|2fh`-s?>L8TH*)5#AzA+gU7y%q z)`hy>@~^Ekq)Y)h+E8$`e6)q^sCcZ&0oG73JLpML_<&yizf$M_iXQDh6%TPPs0)Pb z6()1Z^5hR8j^!ObN%Y@dg^|bAkV@NW@XE8l5`nh&1=wNZ<-v%O zy~Azp&ZzGL=s3c)Jcv&I+4`)9<$pfWu>LBe{-eDzF0s&m<^*6}R*9Z10-FB{Z}jv)sPuo1|3POnj11_kK_MJ7nml`s zh_h{4eZL`aQ}Q+dlI8Y<+I71$E&$)`P%DpuR%1iB4qZ0lw%eLk{W##>^)MYnd3vp6 z0Vi47_j4>%ze>C@^&>J6!%Of7Ynqs-6iB$`s(I|<&%B&VD2WytZPgMkpBYPIB%R&N z8r2>#;&}gw-oQbaPdK}&47brajncK)em`T7@#z6S*$xA&QxR|@ccity3>2@s(6GqR zSy1A1i;k3SH95p(vR`h$gN>$HJPFlxaGr{J_<3A2y)8%okWt8+DYLI>iVgsfR(nh1 z-m`uTf3HH3CzwMN1b@q_slOU?B=LS_7x!{sVL|x zqi-c(xL5t{p_vsKN2dK2Qa(!=;q_-ngvF(cOt`xRdls>0 z35XwTml)vgSPeOnN;s-gc`2cXG>)OYXJhq8VW-2CT-^yL)TgN#X?HNnl&VTs`}vk37tCJHl`>Y+jHBO=qvIRY|>G-D{({Z9<4dHlhD zm)@KjfQ@+3^F-NdtxFOF`Bqwd)19CJSmG}CGfounq0W~Idg>Bl8BRAAB4C{E+v4^lhh zErPu{vuzIRjFKbe)+j{+WU9}&--Qj-ATg0@1FhX3XrWh=bfBioUSWAv*+FiCVPXB zj8nfox$64{`?HYZCtRdS%JF#)U=el!i~2&@%P=eBDShP5kJIto zG=)h~|J5kSo@Yw_I&jxvP$Q}DZ~wqoq~Bk3nDzS3eS9i&DR9jAPD7~`;HjHN+;g+( zsz6Kj`*Q6f&kZH!Ox=ZVZa59gzvK)J-htgLUWa|q^^*Bd;1V!JR&;!@=U+Rz0})>+ z=fTCZ6-5|nDI2ntGy@KTTpv73Dz@8d^z)ESd{mmJsPOA3h_)H91oFS8R#d2j2;?GS z;4j{U;xQ$|K|o^wuG0hum%Lx2Bfda%iiX3mJ|I{ibbDPrI! zntQRXuB|0`g`exiv7w9$*hvlpdhYix=2|03oQ*U~tW0k3t26YDaTzXM^m=Q}fb~cS zYtHFM~ zuPSf3_Ga_?bTxFP%Zb+W;bCh3pl1)|IO4{xm5b6S@gROfH0NM8ZL|UrA(iG+h+J>u zlIC9s*1{u9f2ozHg?|RtpoZDl(!|2aGS}XfOd#iyAc2^0zy$65Lik5bZQk1t+WpxC zj=LP3F#N@25qP)$Q*^XW`{wh~Q!Ve*MFkf?b(uHu`?o;}Wt*SbSjhR02wyVNBMEe$ z;D%hFFi(9Oi-O5EsQ2FQ{et8Q=BpYM>|o{)nnuuXZaUr@ zyT~(@y%@unCW8!YYBkqtpHmXwDe<(S=3-@x@=zdrKkzp@;CuapIY_3*m0B|l zOrCrU@oXl3wa6 za3!SRSPACz!r#gXK(0}0P|wH8>gQT^&Tbmv>!p|0l?sRPUXM)}YV#R_s?4A(sP{SB z=gtHPo~arzp47XW^^&U5wk?Y#&%IIeli5jP zgRCr9Yt^huqos>rc_ucReACrHX5}79olv2!p1pqK56t;>K7E4G6C<8h^NiOtdxQ3m zu!qy9j?RmrjTb+%t}9z|w+UT=txd^Vi>=LmIBlCb+k;u{u~vr3);1R$G#Qo-t@Z_R*tH$IlDlqF-u4e3ZKHuGqBk~fiobf) zcgp*}B6{dpZQl3a7gqiKo`og^9K<_{tfRbhD!!HirhX~u2Rrm;Pi1&BLZwmB*Eo-| zbR&j`66Dvs58poR>NnBObJS{1Rf&Ixxfn_%Rd#$PMo+0Yad-f zf4eiJG)W@3C2Rf+GksW4b!IBE_V!`#jOa_|yv)a^rc&6i zf6*sB`ljer2G2~06AAk~Uj%^|DLr^qo=0kt_M+?!?#DmWIYeS&0k+ybn&Gkezo1w? zm+^@$|J@+bGuj&f=rbL6%{#2~0c`O4g{4#;`zhQ{-~VHr4VUY`CT!U|_N*$v%SRnY zz8;&<+v|ap?bn=O5C>(AxM7=BT;VySX)PFSLg0d)4LXstj$W6di^@}^I%ge1?m$E7 z`}kV;6E)|s5M$q+<@O`yVnXaycink-y|$j7t;g&Thfsixw#avS745s@Al{WG*X0HI ztPXq82!0Yv0+*Pv5pD|opc0+vx2@?1OnEcz?Siwin9@;>G2Y&)J881v?N}IP8gZxo z#HPBQl2pp}(s2ZbPgze4=U;GS1TSSxQs1*`loc*upoO*@1z)T8EBJlLTUDLnPO71l z>Ict1ik8V~Fw-vaqJxv^OOUE46B|pb*)sA-l*sgC@bYMfLb*+1EysDM8Yv?8&wwUB zt=i`+atOYcdt-x+d+%#w4hNZe#IHlsU9YZZY5=sn$5P*1y10?B87SAT>!o%Ad;(wTykMR%-KOZl-;vMs3M zZ04(hB_&kN^PwVI9@g*R&>dR?2n17N;YchdIyLV8l~3C z)^rt51;<`y2oe$$$$vW!572yrpm=o+vW)W!1cU)f;gE=y-M)~ z2LISvYl`Df*1o0eCf zjb+YcK16bM<1l9Zme*w1$<$Q28u55OF#p-}cqedPP)$=2w>8XZh_JX5+E;TK#iQo0i$(ovzfIsXkvE$B#oYA{DK-J_Yw zT&|Yas1XODJ9czOGI&_5G{DS#U8?2GaCL>p?>^r6lhlO>5^qBs^*0@1%$V z^vdC^Z<5(0x6Xp^;(<0~$YfP%QU|CHa(>3FMxDP|8MAfFOg&#NdeaE1u= zJFu_Cd+N$yg0cFKC*bd{mFR5mfKBKhzho!zo@FyjlEI+g8<*e#uSs>^t@v`^yah_o zxrf)uC~QlZh$>ibywDh`(9Vl7Qwn6CI?(%R!>JMcZ=oe!oaY}@i=0+HrCyLoIwhLT zhwp(IRH8N@_pYkW^8mF+=%6l1e2V8;EKH-uH?N`a;MyMm+r7e;u+vL-xQ7CSm` zMak&93wLcwb?eNwM#${Dlc`pSV?^&u#4aG9(}5+%>`*+YFcFB{i#pq5Ti&L2qBS*T z1trI4^@IE9%0_g97#Ncje8OicPEr4twVTr&F?o(Py&|Yewws=aJmv{Y^n=sh1Pa1y zVf2psi%`Z^e05H+%fb`(`qf0!w!x`qQ}UK1)t$h}(-YODTRKDYVFt|jh{9F2?RX+g zMfgUcqP`d1%XKB@U`b{r64XW+sGeUl5&w7eaH*f$0?G8{AB^&%D4RNfJx6h%k>&yg zA+UB|30xHaA?0F)^Rs$NGB+VJAW1y?s-0Qfs31p=7CO3dr%P&@b^_G_9(m8S*C+V} zUU8!{H5#BE^^7tRILZEhnVnbpLn=RmuWfi`>t(uzQR{vd>t>&l)u{iq(DU!8f%wb! zk9Ma=T817Ul8WLL3_EaNuf>SMvB^DGYlA@A29C`0poR42L$Z{g&ZdZDUfXy<=04AT zRL0k+ESvr1)!hZ9n!M(aVFj^{P^`qin=1F6@|N}5kr$D^abF&Pofn0MeSdi?;u_YgTd zg&2at?JphnknS&CZpU|m7c~dTZJQAw3V-vQ3kgDd?@`kA(}$A>TE5jy+UCnVo(}Na z?jDF2#f$sya7sl*(&I}k{lw~&5`e?i(;_w`%S%3tcuL?Lc`_AIj5ff~+y;afW2lSz z)^jwa#-@Nd@3#e($DBa(XAFhmfqqkUj3a+zcQvW-0!3bH;>7#@y~VeW_k+k3-Srvt z7Swsswp4UOA0-AQV%}m(tG*QKZ7wvG!f~Xunne(P{zj*Kv-J4~sf}V|E?YuljIk;c z&0wfZP)?^l%>p214vJ~Y|SAIaVCWM6Wmr0G?TUc#e>rm7LteALAceak%aHA zDk!CV+2SAG;jUzP&vIKpT-jg!o}s#qhV&H1)8ZnmAzb4n37UN3;prY%3I$COj@mxG zZF)h?$+LbQQ8CBpOem*)T4ZC3#wI1EBx5C*6Dw?KATtvq3 za%DFWG8bH=hoXlYD>6y?tI$;yO6N1BSN_JinqfUlE`HXWHC+6XF4sZhiJ5Uby**Bj zBznG3RS8}nPIbJmsPl1_vr2ip(4AcI#+3gsu6Ceh@Qkt#8Zgb_S!Fipy|Sny5nEgS z_o(mOLCmQt@;zES*o|bDHiuCm$p**DI*dK7^mo%;!Zfn#umPKF5V74G32u%(f_@ghzpp1#P*^rQuRw84eEy|&8 zhs^H1Q~P?5axp*p_Z-LLP2wF--iD8c=iIV;JTX&BzF0~l^YLQ&C`Iy~rc0qf<#0rT zB?a6_RMfD2K;u(=Hmhn?(kPahHOfk&t76G(Aal?@r!z5Som_%%_OSX5f|4a~9Q#-- zfvF$y{wwZ00HD-;WoT6Fi(n+hJkelJv_&)rMW2Pxd*Z|!?pcH#jSzK;+5+`2v&5!| zIQZduo1}d$@wZ%(@4!f5b~FhnqoIGcPKJeDXwqb#nJuxW(i`6GOyJT_341hdVEX%q z_6``Xdx`yv-IjXq_&h8S6XG!06SDJo(S^yfVqZtnxMt=={T?4jQOc=9VIw1WJv5t>$avqsE znW6b_vVMn2)!m7k$$3F1eCV38$>2*W2J@NkfC zN`7RK2TqxdPBd#`8m6|=y?913qa1$@YB@^w3uAqww6dpuP3B=ZK@`UA2s~FWtDSGV z=wwAdzjhQ%Kvg4`oFTo=h~hxF>WuIE0qnHe2b1~nJ#^w1rA10wzeKl zI0`mMNd{a{D8q7PsUVQf8WzGKA?3kv?r*g8(q*rW&){)nMspzt?xjCNq<>hMD`M;+ zMHDwA2t%&v=K8U2d4WT)$no%Nd4ol%Bf%n^?t?|TTyI!T)Z|3&vGGGkQc?8qbY_MQ zAx33T0|c+Gi^kgjL>9b}8Nob*MuV8An010rrn5J;8(P5?r+vy5QNSBX(a{={fYO}J z{9t5vI&{Hf-0z6ay2^oCp3h)rv-%d#bY(OF6vFfi`e{^pe`Q?a=R9p+XzKD)vNZk5 z;(mPps+GNR{_03F{`bs!2#$ixnP>nO;sFZL7veu|C^n?d*!%U+H zfum!xy7>J9Es&ksvr+NKE%iBxA)Hb?=5WsaMg)4?ySOz$}h6(P}$Qc2dxW=%?+R#ZVx}1Q2yVBhJTBR=FKcF%l!4ax2H1jKm#3C1~Z@r&0 zBkX%t*6X>&TBaU^iii-^{T%XQ%|EBTxp1r+0&~U(CrA3k+iVRxNQS zM~A$k(TPjPQejf zF~tsf9c(-+qGA1<@9`Y!+si%59-(_Rmi>v761M0qG_N>l(J>Xm!Y+nEX4i`Md^NOl z;*-7&W~u6n+IV_%eY*{->2uWw6eel)udLr|A#6;;s=Ce|+&4fQLAFin=&&3Ya@Uu5 z022i%Qmbh&^%(ej^xZye-?5#tU9i1w*tt1l4wynGqmo31Bzin5Y71^E) zjMJ3k6I0EUi&r9_;WR+&i_wo9g?sR6Dp_g>>izGZDA=maJ3j3a=Vs9jgFrNFD z*8eoV?VUcoo*X`lv9AwQcwIfc_?nKYp8n?HxI;d?^4wZqh2=nWAvybkuDgFj77Sv{ zbKG$meQ)%BJZ*Y6pHTS@g7|-Q{adYs8HC!yov?$6I8I^}{;Qv=k#(*1@PBySzcS%> zROL59aBKJdY7Pp|_WvxOc72t#bZtMA`|hQ!H^glj!|N`ph)E_gxy70mQfi(V$cWaT z79Yezt*e{C-M{WkOoRTW3~-z@lV*6?Wc79gMG)@vCrosc+Iw?cFT<$4oS$4!&50hU zoYA#B`aox|XwnYPiv8Bw!@6x(Kh)1H){fai#YxL53vJzKZ7L_^*Hue9vQeRnOpv@Qp2X@7ZN**!>Ih9y-McFES zS5}=Gt-T>H#0f-IG4&Y4zpDs)hj8D|pJS2o2Qlm`$1;uQ90x#Hnc&0db9DXVl5qHD zvN&9p#(#<5G~%adFr4nxn%6Myg^II>PR!8EqNkZ^92erptDn)a$kT9>iA%BS{q^3 z=#^C3{ew9~(T<8g`DD|4;a#yNb;q|&xfh~j923ymu{HrW`F1i|3e6=WUBN^!RqmqKu-xCAIp3GQB`KwI1`cnR(e?(T(6ab;J3eIVTT6R7>yTrU%r>xOU*Au?FMG-$i%L|n3gjZTUd^k+ z)2MCTgxJ(X7bP&oE$p=q23&~GpRz{_BG~J@)PLOuNY8-Z$CkRl@5OiNDtID420#i4Q8t%6-ILA3n5VDREIV$Q>Wat(q;}iivZtx=?cPd8t`P{9CT; z9lfR)9MACf1X0-Ylb!x+*BT+Gx0@8e>LTOA3oTGwNZt7mjznyuBvhR!~n4Z*){Nw!Z+ToEg0L{w~lR9;-jJp1Br!=G0#1N0< z!!)Ou?+aVh*_`#=@V>RoeeOR3l~1vW+qr7ok*WAH#386&Kna6i^7K#3GXFA~WFqcL zhYc5K|In8gf)v+vT8{^F!TXQy5Usj>hzP`z!XlLOcQ+B*6a$n`KoWG!+wnY(oM(V$ z9iiWOE$37Oh4`&F6iv3_%kVfGxE&Oxui8sxeoDP3%4cD##3k*p(0N@M&sNO=`12yy?&W4}^=Z!-^nkfl^4bD)V*i0HCeV!V%Tq)YDaEJzx*4ok7Ko9aAS zvddQM;A8B%clo_>BiG#j6=GgA9~A}pcPOR#$7P;2+#xkkb)8#)c;WI+9m|2QPWnwj z^3L+&I##Mw_!yl-ukFUIj@;wR)B?s{TxZS;A%ZlQwI+xUUjTSa&WM!A^_#2QPdG_;s-);lQc3#%51)SN9T8J_T zI>qyhX7u(Z@Ml&FVE+gME`1E}JaZdz}>_+bL ziasXEdGsyXJ(CShoshx`2|VD=iPD;i_;*xJ9ff0k=2Ii3fEeSFPzx(B9 zXz&Q9s8d`$-X$5UYxP{=SM#PG_!IYcWTJZ!o_s<=cyB2z0$wyPSm`-;pO(Atwp7E; z;z6LT-{l2~zF-*lf7$@|COn}(S z1b2TDgOHP)`gz@VL-+-Pt39!)SthSPK9f&@TO7ywJBtlr(F;J!L_iev5ZZAqe2PS;VyUV8NGFe{p(;!d%n)d+t6hpO^jPP&_Iyilt5Ouerv4C z*w^UQlMGr0)!T=4miD3CeIc!%Txz>MKlZ$s zNsu+!+|!+y>Taqm0%zkHI>AT3&l6XQ$*qZNWpswG11m-X4B1PQHhrzpZ#$>TF44(l z$S=^fd6({(tg`S)@Vxs8(W5kZxyFi4?ojQrXGrPa!_1JnWzE?SH{Yj;vDguAH3yjD z9!UYgS#z0loOl4;%y_c?+@a`^0Looy^ipL5*1N1>ct=hf%BW9 zzfpf2XJwU_z8HEr$;_rnQWG8bTx>o&Z@ZX6eqByKa(Q~Sx|6w1#k=HsY%PC@&N zBOo+wp7WB)tnbU*v0#H;&g5`|q;)+*n!mRE(l&8?VUApNV=Q!`FCh{0h;py|)85G@ zeNt(l0-}k^lRV9)f$XN~azCsX1h??q06{ z2s~S}qjb2MN&?jGITbwhqu)BEwMiO&`f@o$b~r$@2w>B{k{G`Vf%CY1tIeZT3xY_=c4gH_0e|j!!XsGqGsmgGr1sD8EAnAf(-G?=!IgAQc2bJ6HCM7WSqj1l4x-u!I^C^ej&D+UQc%=r`@t~2Gb+G5$Z#T6MeSDx-^M9y_ z6Zt8FKN8^55GVYhv%XCO^K$~_IxG=~b=maiPObR5TZ*qJ+T{-4|M~hsy62rz{rFpg z+QavR@S&wX`218%dm{6)91b81&6yt1h9XZ&YVLDT_T_e&_$uWR;9FA-hu7UEyp}>s3QpP; zaCx`}U@$&LH}p>b)R=GL@XJRS5?o%JZVudWTLcDzZ4rdfi{~7{bG)UCY^F*@px&En z*bH}LY+$uB#07trJ@oP0SUccP6rgVY*p{Ml0*~$R)<$a#JkjiI1T@PwRIH01_tp@3 zB_3fh{ODL(!lOyIyXiExsEox<;bcLY_i^mwcCfmfP>!7STQ}fjhsWPU*FSC{7iy+o z5N$VqwStW=OL=GQCbXB!YV?&XsuET;rRi(@w`tsWW6S**+8>eg-J40=Ef6CS^XSF5 z)uFZmQIl@%Vvi5^_gC2tg0`Rcc~8H(_%2dO0m0N~pbz)4P8{=#KUjkOa&ghx__!~N zALQU(nTfAXg@%()<4W*k=r2mT4}OcEa>1UJ6(kJvKA}tfiK++O0aPmQKR-6vQINO3 z?`WKmvNT#yyLFM{@2IViW$JvX;Qi7bMqOU%(_DacdO|2uWuqCbb8zLauPA8v=aA_I z?P}Me))Cgi^YpGm2(bh-N*(9Y$4!NX3EodL^^--I$%Qa8jdKU0Af}{KZnG$dj!oH| z{)fw>ui>7*gC+FX={s7=`TS}4-r>`su^_yEJX=4Z}qHjDM= zv2_NjjsV*{gqs-}^98XxuLf~@t*$Q{{e=jQwD0Ry&}2qviveh|Q@iNxOUaVFR;|SA z9z9K3`#!~`Z5|)7Y5E&PXJ2cw`1riU?jtN*4WN72>|9u<{#kXIf!c+MSf=PGIuHLckqVAK-^^o8ulM$uf zY{PH)vOUD_M4KW?N3S;2+wb_b4FjyooJ-uhV)pwq6%S{3uvr5S?lXW~WT}d!%Ui6J zbY-fkYOeFn)M$T${w)u1aFL~<>gdX0fQTI^rLmv zt9j>flbtsDJ{Vq&Mv;@j<8<4qD%+R4*AXX8&ud1030$@(nsF~)=?ath_FLyqOHryzWarWQIrUDh+XCc9_UDjojoWjLpmf+^2d|liZ z>uETSzy6<7kN^E-B=F@N7%~XHMlN>v9MxHj&`P}FM1`sLH%W3`zp|se3mw55kAd7p z#GiDBpDb{~(44B_vO%Zqzdgpa&U>PAUBu5|Z@XM=f^GmbUh4Y*2_W^oMbI&*#dZ7D2NPLib%GlWDy(=E@U!lBqWN-^o%u zDpj7EutMY(0vlO_cR!qZ1O-I{E(Y{GGqi{Q8|^Y*omrlb_T&LmBGJ1%dp7TZR8M#J z>(8v2OxLc9nrf%n<9q^WAvc5m`vCBDC&fDQOk?EBgO$zVqS|&bOsnsH{l2;n9L?}Z z)5ka9Tiu3dU;%#>WR?1gh$=d+`w>Z0dD%{ed(LCnN-5KJOmFJhe<})la0A4dY4+vL zuhC?hd+vvr?x~tfU4FAX<0x5dR37CUDw0sAhRQ4p=owzUBZ{^*`|c}+?4qKMR30hd zo4d59#4o0BHjlFTaO^NbPKm*}Nb&*QL1mWt%Qpw7p_u1Y{#4tMiTJe#Sy8fWjk6zT zn;Un3!Us~-xkgluAzf-fR{qE$Kn&vaRS7K(CQ-5cikITP;Zi_x+H#a?Fylz)@bhBQ zWd9P4Zvo<^=Ph-*r2TG*tQSVb7y$KNo&92z-#C&UtMTaF0DlwyqQz9&wOXW@ym;Y@WM3S;ED2FcXMyYPl!bwyp4kvPh@OhtUiBRt&HDP3MA?d*d*^iTDVxkrCk(U%d zHzJc+;1Fh)eDR=sIj{n9%8rtrxv9$Z_C(Z9jGb!l74)~(-t6gqx48H-zCJ@-kz%DCBG=M5A#8LoOoa2o-)!kI3)T9 zN&U^Uc>APfz*C1apRf4QdeRMoM@Iv0ZR{)|E;2cQ>VMu`M)DsWtylR@0n@FP==Rd+ zXsU-)5wK^i?qZyUY~fhT8J5N|p&0$=)`uRaRaA~g8Q<-X(3zgw@bfzvf7M;E`{J?d z>oY$!MJV00EA02L7Yke}-3Yy}MTj#z3wv)gFqk55bZ4BQkP_Hkz0%*6wgnOAllSVm zh`;BNi*-C0ZZ#pmJ}vHK+a)P7bp^`15?W z&QwdZI)2r4#86&)e%!NYboqcm;Vgx!$~jWdMtv!}N};r}UXV}z+nHEO-<@t{G^ma` zxz?IIJTs$!XyK~$iFEmkB;xA_`!r%q0*bTV_3h>LAPd+;w_U*}(kh%u+2QCVp;N-SPrv!0Uc~(wox(FP2Z}v@-sIT`}-N5Lg~HXHtABIaE>w2UXAN1 z=1&U#%#d|>3s71msIf*#ZGNwG885OhM+p-T4!HJWlY~^N3q{04JoSL0J_n%v44NUzV6-EAo>ER1bJ|9fcQPT-Fq{7`DS;# z-Fl@9IPkkt&5+O6aybN*&CU4$5M1SkS_*6l86{3&@Poh$<<;xOjmXot9kF|q7>00o z4Ftz7F99gsox8?BPu-X4v-DzqJ(vcS8NWc%xb@MJ)C=)y$=s4QuW!!Y4BZ`XpXen* zoGdzsJ8RuTf0yG7pDuKp20-Y6jL=p0x4enr_+ro5^Ml0KuDh;LbZZDfSLeF4=;tS3 z?IQW|=B=0~ye&iqK`PzVd+$C<5F+JDBPn9{3?F&q?RU2rDf%$FTm+R+S$ds2?=2QL zt=+cQ5nA27*I9TiH=V8?U3@1|Zj$a%F3;YsZ5K?}zKHGEQW1O&G0hn~I~lJnYft(V zm2Gs_AeHmCKlr@X(wG_5_3$fw8T-lQmcTaT{|nWmKlq}EwG~9_y8pa17>-%-_qC$; zLpR5`Ey;37fYJk^r5j#d(>^lNdNPAcsJ>uXgc{o1YFY2jchlc9_^Lw!4f3D7B_C^) z27w`mWClIG2IgU8`WB}OnU?k!R(YN4&qRaDCoLagyI70=!_0IB)vugnx9)yp`L{%t ziKv!A48Rtw?*aeHqv?$VUi>%Z<)1#v-m*k&a#{=aJuSkb?@?O2b*)4qe7A1;@gZ&@ zx1-gE4?BqEi|XqjYzwJFA=C&9d~n%$Ep^`WczNLN3l>$eZp>A*OZbeVS2&fqi5Wo1{r|@B+xSA&|79tvfOC=yX&ZYHX0slWC3#I zqtyb=B?s|7&B22DrEXMAk)3JHMROyFQ5fM7RvH>J<=0eJWhC6}0p3VYN4iufafp}A zezf*@KKTBG|AgTO{Vz;1oz#$bukNxc#vF*i#{wU*0gfVJQN>$kE!19}30e$P1D6d2 zO;cLYHYai7TD_tgW?V--N@?;-yo+%DuVV2SwB=hiZ;b{8#_7W2sYUb z*maNPm+kBl_B`IO3gm#;zCPGqy>%&!7~=|FBxmOwEDhme`D2Z0f3J@@7kbs8lt@KE z+b)lhNHyTD)ym}D$tDzy*X466Mp%a%V7nb zt?MQP;&aB$ncOKtjE6nteofX()a0lAiOV%QpAODh7QVoXfBBP!wP=F7?eW1-IEq{i zd50xn{nuLd9odvnsBEVwl%hVyeMRKeQ37&ay}`$k(ruLE(%VbUOAz8LsL|M+1^tlG z!T(O}_ibgV*wGJ_x^2_nAF1bcrjIYMxTbk-0Y8^Z4Jl~cUvT$JR(kucnN>;z@OFgz37Vr8Kng273rN#PDNVLVi+MG zNJxPnM8_*MSQb-X_x&qM!$=2X+nqJM3d>8cTL}cVALg3fY{v7FYI^gONI~QmN3>T%aNw=^aD1 zJDIYJt~&wUvgozQN;6{yfBcd-fwF`Ei{=7o3fK9bdsOSJ=#MZ~#tzKnM!+R45%yiC zN4^+=5W-WCB1F`Qg_(t#E1Z!?NYRTsdgH|xY!aiyH11J`lAy`MJ)OU&>ix{3{m)J0 z`|s7R91UU1ES0#=bC`wuE5_n{QmnL{uS%P+38Wu*CGQpl*WTv3UTs#okXF4_o{#YH z#rd)@rc*pdrc>}bL5Fq1ISe`!g!aD5V8I2|fO0S2nj2uB=iwky4PFW!A9A9IXJ0ps ziE)5Ntjc?X+|VmXm!#!U`>2(DnYd-Qr@HHddK43o>v}6~J8rH6r`<=o(^8yNUu2P+ zgFu;Q@&>ETtG{QYfjB)D!h?d>Xf|9W8B}xpL0*b5@3>HDqRq9h&FJ%kqNmP++9`7& znpUJznlY46&lT2zq9>#;W?P&wbO=R10%N_7l}yM5guMZXgFJnz(d^0hGiktP3F-sW z=+gd0vR!nVN(S#8!=`>=UvtMDO(Ey1CSgLV^0au$MP{`Y{RbVHOVDY)|Ih|qp{3Gp1Q@!a>Mrg&VZlAkrTmc#s<%r-BQ zJ6m5z;`9H!m`Q&>RUi1zsNg<1HtdCncBna$$&g-L>Ha z{paHG0Z^g*WgO??HZ^)e-E6_ztm96XY-Ri7Utq@(E5MT8C8z}7h0CAyu_J*~>r&GO z{M*}>Px%$g^F@}lTb6mUYHr9myPejk#po}lYXj3@s-W{|zn zg(Ga*kcET=kK;ryuMutu8BQ9xYV~5QacSXSCN%95)Qfe`7d=U{u|^&kk=$|(u9Qbx zKEg6vw4O>-{cj_;Q|z4(1{~|I&-svYVmrbPgPSH4YD06?jN0`c-99i$`P4$5{~0W?t~{ z+v=+Q2- z{Z#D>T)J{--7L$k-TL;HCp+vvM?e+Cy3qSRf0E_a1X}{1+k(}T5%KBfCMYZ z28n4$a=QBe$)5Y)T4(<#+st^IIGDR$o8k*ZyMu0+^8ZhCt7FG>#jAh!X5)Tc z?`}maIDp-?{cx`jt~UwBMB@uQJX{D$ZK}OKGuen>=?=AcJou33xkd3(Gc=GPfame{ z;AVm=y|aCntbawerb;{6D|=l<4qTUaMM;W^)?@~Fj|Oh_{#d(AF`&L1e=o#9L?ch$ zJR=XY4!Y3RX_S7RlwhQ>ePO$;Qr86!i_Q({jx7zv*rXx?W<V^EnkWk!Z8fnIldI=r20AAUjqcbpeIYKW3_sSvGxW=nj|zK{N*X zs$RyU zqNlX4k?lGS2qS>wo?8uLQEX{o5ZAI-^;|Af;+>_ow5%N1Zy2+rQd*ons8nWjP5|zP zU5`iZ!MM%J3?PYdRZspFZA*JIRAebYMQNS<*4HN=!25Bhw9!C&Z@Lh+@h*nFm_l?u zttdKXGYOluC|GcN{)oOAP{^p~cxk>u|4C~};7TsLrF$*&PJo9~RhwCMJVZtV5UtAS z7^zY0ue$UNbg{_tvbf+7>bcICuKo5}$l$6-HfKTdi==k`K)pgdD?g^h_k;Rqnmc{= z%?DpRIo&W*JhvWxHbY^$-|J|Q#`g$x0d}~u%uk-(*6Q2#2=TUTImVP6(PF?@=Z{R88VUW%%98}-aEl%3 zHYJ8DDBCX{A9%pR9J_@ZbCq#@XA{JsD{u*?KYf1sn{lcscx-z8yUC@iX3Ks8Kg5N8u__Df_oB(>Dp`oxg2 z(x~RN5TsInRx6!cNjI6hkd6cCn9&lM7)^9TaBo8eJ!Y z=JJyT_-}^z218#5gmwpFW{!+7d#J^d+v7?o5w~1Xdpm&L91)d2d6OwMTN7-KzcAZl zM_-a$Vd!+#J-Djmr|5|8B#oIYF?bIa5P-YYM~i)jcs^i)wj?*j^O3YFRxVE!(8TkV zpz_SMsN*iI_hCXWh&kO18^os=YhL<*2lOKo858<2b?a?o2vvxMY)+IV&1~jB10$y8 z*v5lJFoQngL89Zy@X>St^^!s-nFb6=QgOBTmu_5u^9n$B7%p$;P@v7{&uH^zAyWDw z{YUL(hDhO@0s2|A4QTfJ=`AZ*Pq(J+dl!!enFgzx=&ygWBs|m$q^W;b9k&!_$hlBO zH4!&Qay`wMJR*52N%Xn;6+Ya3T`8yoz3ZzF9WWy-K}bRRbnxSH?dgT%E7Szt^Yzt9 z<}ZY}8IjI1-l+mmLK8Re`m-0KQgHy^tsFxCdCm;rOmXW7<@*!Cz(RN0oGS9OHrRg5 zHf2Lbemu*h43-1o_rH76*#NmEZRku{B|y(^qUX^<0xP8Lp8f&eCG$CZv7rtnQ0;Tl zH^$*yX_v7%G+MA-r)xY(Q(Q9lOC9#ATx$QG!lnF$`V_+L+<36iBYpTb@3+eQtVpNky=8qN80 z(Z>k_d8Cw*tS*3iV}~*v>PideqfF`!N%CCIy#ObDx(?{Ce5>o+wr)qQB)Sg^!ka86 zW;s@F?ul!tYuZs9G_NB@inf>267A?QpZz|eM@D!OrzfukC{b{bZqg7}o?9cg}E$-YgI-<&Z>sUJ`FH zVao#a3vCK!y;8M_?lsrRHi{E`$r2QAEHghz{L(1v&$#ZiQ7$!X2C?#pi@cF2lFg?m zXt3E{sJ!1^S-lQ9${HIVr&un0F^C`7r9Z_+xAeClmlGCh^!8pYZJ3xk=Bd@w1VEvT98 zmQV>SMmo(jtVbJ|0liJX!2jNikr(zI4Be=cp(U~6(BDF2{^iV9fym4|h#&nX!D~p= z;fl!GBckQv@PxnxvuX6^DhUuog3~7sHMwR%)kZjNuRjITc0a(~E=Da@Ov?2!ur)Fp0gGD6l zYVAJddogL7wtN~}B%BA9k^H^;f@j=k#k+6UloMk6$CG@oHy~N|ZLD#y5#BL{%V#M; zH7hAk07?Vg9EL3Wd~|GB7-U8r$4zxOQE#9yosl7wAo1QmAZ@Fa78jhVCnW~>|C;wUE^jq0n^hMqb z4wqZCaJLM0ig}7l|4&+eIK-k%oxW$>fHso%q|sw@jU`bkJib|sc=_czxu2kgS|E?+d5zliO+EabJK(mW*Oeq5M=DC1 z2l*DrGu|K9K1;S{&UJO&``xHG;>JdeY0Q;EL~Z3xI&Tf-9zL@J^&3F zchwI~_T;LZvkK2nchsOF|a9eUD`; zH@*mwgJp zr&vfhop&#pOMp>3Jp<&#z=9DZ#wip?a4zL2Qx7ET-Gqg_fKH|RRc2hS(C6?&+y@C)}vvrGX-K*h}Rob2n}G2^`=8MZIVMgi4OF$%m7Z%89Z zA#N42li5o88GmN%!o??s>o*0#?yc96?_6?iRe||P+q}czqfkT;?<)k&{dX6sByU$A z1X{cv<4o3%Z9%{H)0$zFEX2{+0o`;oGgB*!mkjKg( zqt3j%Pwp48q%V0UGa)X3@;NWMp@Dc=3x)v)?e|&|q}~YMoLUY;a>fa}3Bb{_y=gvR zJ;wz^PYTcTF3#(ta|xGcF{RjkMI!EjRI;HLX5seA={s?IMi8w;rcuYDK8wo!yMBIe zWs@)XDr>P0>@VSVNzLMX!&$A%q=RS82NZNz(jTVkEjT@mT*vAxwt6gl^IvT)8^sOm zWix}wow>~bKQ!)HszYXii=Dis2v}?ym;sQa+Hfw`{;0}GpB3HxT2vJXJ*n-_pChlg zj+e5ffVxm&dO8?{;odEIM?;@Nz!o0$NWC!kPJXbWl*IF7q%d2+A}kq#hE@_+pgR~Z z1P8;07i0&}vPFdyQGH5Vplko`_Kj;0fK}BOc3wj+0_f+O8_IG$Y-1@yK>9uj;-9|lHXz~jqJg;3ErVbS| zsK=QMnG`q75MX}oe@YCuo~zaAFQoAB9c;6aa&~@zFEUIUZPcBp*Q)jlJQ=83~H^Shqx`T5?2-Tc}?$OJr#d|W?zZ#{s z#%GW>-$CSSfv?Xg6jyYNF->fj%g0%De6Rz}w&Xh^C9RBvGS9 z3kL3+E%<~dZe~chLawL;r}S8sxH}5>C^esXHO(K(e)kLyqzD}BJA$6P=Q|;uQyDo@ z?jVqi{&(-nW!F+yXpjES%n7|Ax~Vc*+b^!#raSR)X?hWX@y*VFORihCvLWPnh2~qZ z9aR>)%V&YaWMma52>aElZ#!J=DZ}w{poX{Rl1u5*o{#R#2@eKU-a1;YP`TYTvS}1@ zvSJQ2%$I|{-tdnw{j>+3n_qv2@t)Diio@Qt-n`CY$9HNX>3d^raX`00vnlS)dFIJ8 zqKJv+{Vrvw+lv6DL7q+67paq4OtgvW5>hIjJi1DZmZ+bsu5F%ZPiB^d8}#cZ-9r(U zHBMB0+NT;zT?s$*5PsGOK0NIFnak=?zo@y%0=ZI^lL_jVc&xDoGNbK;l%;M=F3b_J z;^d7rT@3P$9f^3{@4@D8^TO?y*eXd@vluy~q@+YQYxC=%_iNvH>D`6*c>lrS1i?WB z!T+`c|J}`VC%%H3kgB1 z(T{vEhW=bZtXA(w<+~`HgX@9v!|iis=Ge>fD5t(#xKI8YPBqTiKcdbwk#$p%*(IQh z{>EAP1)8=DtTK`r99Mj=r%#^^Hr+B^pMO4uL;FRlk-~_@U09Zg?^(p<0V^;SGZ|Xn zP|Zj`vYV7j>zOTGQu%XGlZ1R?)pw2BcOA%E4Su}e-jlIg!!9;1JdzbCxs*J-LCPfD zGx%XV`CW+t_qX15MJwP^Y=cOy2R7bY^iC*4#*C!k*?*m&w%Wp_%*UBW+Wo;r*TcV< zY?wT&3gTiP3BoA1al9+)qh1hEqYHLka{SiQ{(^6t)pjDDKk(oKKmguVi%EB^k8<)N zj{kq=o%9ETomlpQp26Ip=UP(8N?+tZbRWek#LSfTQ%K6}q$n<`_4HqLJ`iEF5Y(WN zp&k1lc*h_Gh4*MVx4f#pg_|v$PKE!&{E_B!xzJ7DYmg|tx65n#pK09iTkRPk6fXEA zk_Vg~U+_N{yqG|hL)cx5Q=kz-cz0ONa5LkfCL}UXYgC3Xy5GK?PxB#?37$Ni&uH9$ zeCF2)r?s;M?3>H;Pn6dy*A$YsSFo7v3z?Yz9uqy(3>zO;hUcC?aIPV$eY+~I=lx*0 zb?3cU+Xx0EcF5f}gT+jJq-Bhox!;?!+duK5*Hz5gSbqGfxKKk#@XG>BYQrOD!?*v0*Xq%Un@!J&U_(o-S^e~?88Ws1FMMAjbB2kZ! z`K-gIXAa9Mn78BQS;5@cD8jOcd>eLm!b$riJSQ?L%yj^O#@*G-`;lGV_Ezjot&b?P zaxp7fcqAnn z*WG$&{+BWGP@HIXxE=-vw3|vot4(fjnUc^#l@;q zRb3IHFm{-QikF7<0|)V|ExyW@i`#Nfe<~|fvH>Y#e*sx^ne`bb$vR}Mos@OArSOtN z~5Fx3`_QTO16Z`{r7n7ZXc}evN?UuiRGj4$2157TBEkD<Job6A@2)%iNgh6Qs}4GPxQ_l?cK*<4mx7@3KIEeApx>r$@UKS-RS2<*%R|3P~C^ccc54K^OeQ~dTRnv#2vmNbt!g$nD}at#pgVi zdRs5RZcJFkka`jFaNi-DqrwSLrZ7mwY^%{evXKmMRn-ltvZJ>-DQSMFt8+4?Bkl&h zu$}&rLAq?4!Nwwt!2JRrUgu2})kx04*w#G8rkZ- zn=lP)q9FUx^aDZ6r@R`t2Po4fR~Ak|WKdx+|f;HF{Y)J}V| ztb#U8hq73x(?Fi!)!%YIhuWu^+DSFJ(KCa|y4IsEg_H2mKuZxUu@+;xLFo-cvNTyx z`=@LSu`@aUwUhZTT$X3Fkp+(KEJx3m>pu!Agnpc$)E-?T z$GNVKLaG{8+mjf541UI`!L-eI-Qz#kE|4-LZgWJ#x)PleaN+zRsla-1YI($&>69{Y z$WuPS=krjSU8RP#i^5ykgluMBkb}kNAZ21!E15+Yy3_2X7_*H(CLetunYQ(FmA00b z{e6paR)*5dHqL}JQ?;pOQN!{s7X8xH&^z~QU`RiIIS}jmVe@=VB91(9BV+lz|H95Z zZggT1_jgTwo^LlOn}x$tpzh?2zma8|Q7in2#A9=}Wup<+9WLfc;S=QA+sCxxK5Evw znbC1&Od4rudq`#Iis0hv7*p@GY)b!^T{RE+(qPvnYVMzC^5zaUB3M zt~VO$l;tQYBZ{dP!jMt6y^Qq~nlV_4rXi!}*V3ZLn zP&Y==T)90YPbL0xKkl`QP4h-1k6n*B^$M>qrT?M_%c+_x)ZGX^|p2= zSnO?4H^HYcd7in4$4on1*-$cl!)Nmk8~3eu^`jn-4&~d~g-ET&$%?4qo_~e_oKgl`174ri!mE0DCl%T@3!!mp7hV zU{8=b!+3bumx0I;tb`}J)Y4}Z=H$Z?xhI@`k9rXYA5^4!k7`V)Aq=flT|p;J=5&vU zL`dCP+5Kj#oJ`!LbCbCy#Y;fm&juTWkDXXoaIKTa90pyX-vO zRQCD03g7SAuE^#-!74<|js_-YNEHWP^3)k~NnL%4_7ktzBj{V(f4i&xZ!fb!#KWmu z2qcOI_%Z~!ECW7vGY5P!^<4ak&91ES(9Oyo1or|Zw4*0-gl)-^p*=%GQ<0bb_|5%* zD4Mqygu4}U^#PxBLkj6!NIQ~y7&;N|?o>DJsAO-zA_xXv5A$z}H zK=9E}ra~g0)pOH_)KP6BP??%1tI{5S|B5#>}0sj5U3smd|j5&AqmX!?yi>1X{5GzkgP zK&9T#vLuIn8R_Yqte#4E`b?xdB{apa6;Z3SI07BL;gn32&nI~(B)MM2%hqy;#dB#8 zP_Tt^XTN|`|F8p}*RAs@+YP!|*YjimD<)0Sbm`L>v!4w$U}2+&-M~{{$QvoJy1KC( z1u19Ouz;VGVtqCRL8x5aTUg3zo{!bia8YrdJSM+_B_2#Wf9v7#ZE;u-D}OTHRnPwR z7_Y`xj;s40N<<%mZ@FRE5cP59Izz?VRoz6fVj%6C7T!znd@SF=;Ra_RuZesq-{^+{ zwq&8#TBmV6@j7dIX}Vi2W*SpmN7>2|HO$Y!GU5q~u5y?-xz}aRZ|WzNq&am>sy!1L z$)<66tDXm_jqtrMeI7&}6+roG-|F+=yXEkSt&yDUF0NU*zjhxTz09({EaS)@or`)=VVx4~GLZh;EC!vDj?+VwfrB z-cw@#S~>t09|p?G(|xmoH!v4a70xqeZL%O;Bv~+jPZi=Vvg7ESK@xMMOY}eiA4IRB2VYVr~{XY zw%b#3^4Q=XBD~&V%-u1Xjm~D6oaKL+^psSAr^|$CgkKwycc`=dxQLBwuYR#8+N^k1 z6FIG|D*^Ly=D#uxhqhO+<9VM_z8flRuFH2+lzAJMI+yBHv-Ez?UqB1496va{UfOP(9s}gZY}K;=-gmbARb1dk<0VTPK}ilAeV5!` z=Q+-t#@o{0TW?b)3%vVsXQj%bcHgEyIvVbxs7PE4 z$>%HGZrk>fg z4dJYwoAVps2u-U8C+f!Bdd}5@=Ey8lk0q8=XSBGw--eI*PNVZbfJ1>36W5KePiVL- zSCvZrR1UYV33sw)gX%yr8HasL z<)xLj^m2Li*UF+<7xK>8m zp6Uft0SD|7IYxiy?yNUBT(l?*HQ_aNoSBWoEA(M&1Q1TAI+r#Cm=7~?;iR0i8Q&x4 zsKLhH%W1Z6G0NtOV^~5Z^@G35vIwBirmyU8m9xzlV->5u$@iMU1{#^FADI^Tg=_n2 ztp^dQS4+U|d%EhbJ4fd(xc*4qqPqXu`zv!q<^N&pEu)%#;QnuvRJs}6i1g@|P(VOH z5SU0aLP|h#gfvKZHxe>p#ORRj(H#m%cjvh0@4C)?{`ddfPreWLU}xui-}}7d#mqP2 ztxW4nla~#(&Ha2eSs=*o(DSd5G4;yj8gWv3_ z6|c@w)jm_$xFoG^6S{_0)MUkEC-t}lSlhW{v93#pWy+9TCTOO@zVh+9vBu@r23xzo zX0m5=;WZtC-ih6$HCN)rejl7B8A3;9ufme!0N?hXWu56_MbI_}Efj==&jE35s40Sf zn&{+!U)jGZnw2{@Dz7{Tbik}N`%iUV6aWgqTckJD6ZGph0YHZ-NCaPg7!^A%dYVE0 zN4|L0Un-PK-_Lkk@eeM$=g53-hp>+~v|Uiq3mdPZUoHDLxbrv9FB^cligZWCHj%6j zX1RQw{>BSW?~Y&quIwUdPeMt1Iq*TUk5wd|__`{lyvt z3Dk}Z493x9w7ju=9=+b3+aJE+X2%o)8s~R=(P_YLOdWf1-XmF_a-e$fP+{epotT*d zlFpJmI$PnYGC#gKIpxMHI?37l9$&wc*Mi4KzSh=Zq2*>d{|6ZaP}x6YYd4N08S4y8IMWt;NSh@BD*Y&GUg41XC5f~C;1NIIwJ+fBP07sOs9%2-`>L8XE-G*G zd@+6x1{70y(5JN3=6zObqVaz_V%!2M@fdwR7qoVb2fUbl(Mh_WC{`aw=k-6&oU`dP z>A|It*XzLzyL*1;1*#=N|1LpgS~3?mhE?}(xjbjziS1f*8`YMa;r{o*k1?$3+O$rC zu3~%nNA6$u^_?0)b50o-Uph^5c$Of?l`-wKuXUbv5gmE&?K#c0E&kc6&khasgr!u2BQOlJy6%cVG52kl)JPWU+1SeQ9}b zXEb0Yragz}pW{%fMu39jyA<{}x!xJFBl@A(akT_9HRC`iOpPqu>oGz7SJm5+$$NP zx{Ds>rR9j?*GW_uDx{;P2$zXE7`=7EbJKmOK((%TR{9TU6#`yw>05l7Ln|I465*Rt@psG6bV)vZF(VEU|_nraPYZw#AftGlG9w7)59-vKB|ss2z~EP)Yh^n^YDN9R5z6fqi%s^<&f9GJJ~Ad zp1x`u(7UEi3dVJM_;j{AB4qB#VW$2AD!rDs6N^08L?9yvpy~26Si8X%wat{Za>QlI z#=_dBB}~tAvpi3+Mj}fKgfn}dN2)`YO9V764y%R*DMqwO+O>#rj-_3*sQ7Ler^MXI z^4;vDmS4@sLzR#it-W=?`0Qd)JL>gL1#O5}kal3$B!QL3%bJMq*tl^x> z$(WNfFgXn8;rGSwuQrK;gbh=`>Y7E2t8aD%)v(BbRIxjz@9c-05m!a8X%k-41nreh zFY>-=^jl@JDHRMex)`#YeLdsH8a~d=_y}~`OEcC6rvjmRpA;xnC0}%4Ja3nS>1!1coYnJ(dSg&;`EjX`F_oUInU9~m50U8-(iL>01{WSc()r?D?HjZ0 z=u}@{X=;e9D}s0}pVKH)ReeG}VVdQ$CDr6^eolPCvX3qp-9PiB0h!A5*J9s`ITu6$ zOb*&?P4-0A`E=&UJYj-fa?uAI(#p ztk|BbCu~)(Y6ardN_2YZR`3t*=6N-Uy0|Ih@6J^#=s(2VH({WiROwN?{f46=sVWJ@ zmH5>yWRH4z4rLU99suac+c?bpso0C<0{rM_l-#{rSCUk+*MG9APB%p>BFp%0o+qb? zYc}+Gy?7qfkGK1Lh#*;E?CZTPK>GI>KucPIDWxaUbz7Ka!itx#Yc7GRD0)H1ePox2 zIWJ1aU>()PJsskU>0@wbpd1z71`*7%^S0+`}wJ1Yj|pf88sSUmxIx~o9~@aW0qX--zpNx4sHUY*XOLpyi+qLb)yqPd{l+h3*#|SArv;ICz-EJ4vXwlcR|Kmf?P;C!8_-6dT5j%yB=<)IyRbl2wfq? z?J(Wo4CkFgcIBM3LR%CRX=Z#J1HLqw=jP%X7RN*|-F4y!U*8c_f5Zq8AA&V?@MXYO z4Q#efNU)Irt=O6_PK74K;KY6<0^@gVmdYZ~0wCndNm|{$rL@3X@i6ryUTMlC@~Q~Z zM+&N!ul~cj;!<1JP>)zN;k9C=tmhq?=SN(?uhPG=oDdRCDjrUx5sY^M%-1CzdT z*#PBHYloH#z3XKkzey|l+eP#1e(cpS{^KEAgrQx|HXtm!GPP-g=ocnM*b-nEb!ns(>*2-OT}MZcxNWY>_Sq~-=|y5U`(-DK5Qa|C z(K$*K>wu@KWpijR*~&B)_I3Ot1At+3zo7qj9{GhIws2^Z$i8fO+zr|{H`L18ml*Ra ziR1^x1cC?8+}Rr=hlS?ay_VPqo=EM-Mh9*{v~Xd6la@#QhTTH7|5sA?89nC;5vI*h z-lN-`YzZ>6%tCwWahMqm@HlMnVk>KNI^sv zM=E9;1TKMGpFN_t3Vvm#LV$XeHUs;07{AtInYZ0zS82X{%>tQbnzzi%xzh z5OkvWxBl&qfB!^#X8P#%UJKaY3hlQZwUs0=k%uol=WO>31@BdgD-|Nw-4i9GOoQXn7(X=ZqZ;Yt z4C$JZjKauTa&GevI(uADgO^f$Gr}vfR~(S{{f@glIZu*BC{{VTULrE?VZ*K}ZAVl0 zt|^|$Fe&j~o_2dq%r^;ajQ>9i%zVvK=f^DikOXN-2{*foftcUX8Ka%whxl4C-d^s; zp{^$A1X5$omu1F})b0=G#i}KW|0`>*$1hG+TxTvT44sjf*Neu;YQ@3-B0Hzf{lR+o zIK`QdhDRTXFy>sg)V~}ZSee!%yz!z!x_T?x2Ax+>$k+9Ol!8>!WPUu@IdsS4&cG-} zUE~vP^Fb}@gf)|Qy^P@PyWN9=JdmTt$S1b;)qK9LS2-!KZ2rqs8-c11-!OF1T_FkX z=$xo#=D(oR`kv~+P)A_1A%rirTnc}<5}gw(f%vvbWOQ=qN5lAPWavsj2A?F3`^r}m zw^Q()m-VPSE;anAWoH7k3Z4C!~?T|E8&;ag#Vkf>rJdeoM!brQn<+YpkeDmCNR?;z>2U=XXXiP2(QIYH&@$xGGI`_RV_{m z-(dv{nE--aS?sq4NxlAlHkiU1=Z)l4g@*@OXYD`ulN25Ye3IgpNl-#Z@A$HBOB2z6 z0>sm9se^y^pH^9mGIu}%s)(%Mcme}%06}ZEBH^U$s8L}6+SpJ|xLgpn>67unTJdb2 zKoyHko;~oq=^TCelwZt+@tgw_*&Fh;Cjt6nFQ$A-$5s7f2G_(Kf)x#M1B`E>?J_J7 zbt;~x;P2MV@wNS8o!oOp6e=Rhl^+8wOJ9|2xZOmt@CnA@GwrO|Vu`Nq?z7J3|+-E3Sd)9P?Cr2)LLlsj+c{NSgqZ+_)~&D z{6F_TSOwXzhBf6aZ7_FXfduCK%EeS6rnbY)Miw?_#CP0Gi(apG;}rw&${XD_ZkPDh zYc*muRIl(oY)TGDZ~BRWju(U1hNZIRz51WI70iYot{iKsV}{QL_nC-FpW>M-4bL5R zV)fo8zyjazjX-x(5z~}tXuA&pNdwR$3klKF3*XpL_pW@b0Pzz?gUc0=M6%om3)lBJYb!RfsXf0eFGD!X`l z$Ey6UiC;6dIsYcOzPFP3&S(^t+ztPr_=#1Lq&G*{+Mu^aJqlkARXrHCt_}xV$1>7u zUB)mb`zE-uYZgkbI~kBsv^PQl?D^J*#=xu>bW>7JMLgk+x5vs`|#GQ zg(0rao2|@1*BQgv9XSg1O^1;=`WtHfA_7~bJsHI~y*7e(I;|PfJ!05q{v!n&jp_5~ zoM$-m%o~mH!#-pJHvZtE@b03*!xbz-qPS20*HI z^bZD=u=D%g?7U%BwZ6n<0WXYT+gr(KTm+hUav1M}j&7&}lqD;>&TMF^oX)I9?{kmS ztn*j1yZ+Nl0O8<*+~m<10j+>y*5u6kccMY+G^~!5KDt1NOiRVYThR=4J2(L2%It@v?V9i^%;=$`3h9q?iDb4F#k3ofDIv-%G{jQ-d^N0 zQncAtMrvSKECKeswIZ`x4S^0bXDoM&&jY?v-w&kuYJ_EVKUXv`HpBAu*Ydg?Rtd{%}eg0TMv%|K_p zz}7gm4!+;C7;a-!nrIwj^ieU%5>AvR{@tdt-C=>t9$y87&!t420>n!apg>9TZ&9R( zyYO&Vh7HZeyu=zBg(s8W@eSSraMR7*&(b!mQ&2pbOSV2=oios@{o4X?q&EdVkly<- zdUd^uv{L8@qv5z#x@!K-(OD{^_2j#Zr7h`IJGQXTt%Iyqi&L<&>siBvd;=1OCkM}B zxbYShvbB2Jc~=R$5AgzU$|{?f9a?r-GtVyezFa#QE6_FzoS)a69iJS;bYwR>L?i5K zp6Egz_MU@b{}%Nvr#sI3Ec|~dkiIk(>d(4n5N;IWgInEx>X$pU$d zww;rIXxyyEp}kP$JxByz-T_r2A4t-r^X`m3O4ZcBW0QUjBvdW7-Feg*IQ>Yfns-}V z3n^<8K5VAUbf@?A-O6e>&sgoI(K8fr?~{d8!E<+CRlBmMt+=eJdc!BuO%H!lD;Q2u z8;0qR<_0_wH%}5ik18AO{WPmOlbfROax?n>o(M7$X}sub@8iA8*88%bDsKL=&Hcb; z&ZjS`iP`++laJ*TZC(?c`rv6%io=H;q%61p|JYTQAe-qARm)rM7Zp54wK$NYYA%8m zTgON|J%NUQ^5c;qzKApWdiQVeUNLQ*pvdC3&yOP>5%^x=ueNv&W6yu2iUl98``-)N zRr^4}R)|yPZ)l|-_R*&;m+RwR;||P}rEgoC!^S|&@_$a7C+6TE4nOiEWXD~z%vA?E zp-DNRFA=5D`G~xJyI#Cn_EZewpo$44TC$vSB=>A*&4PtRdN%&bL}s;p5!xIzxCSOw z#XD4Yr%B3N(CZci(1MpVXbc1_)CTx<2%WF+#^1Er>Y5PwD@qgXI1p2bRDcsdVqt74 z&5PhLMzH42QcaqShtWcdp@AOL5q*ezx5=RdJZvpqnZ*3^_xaSn|Jno}Oq-829kv~v z%nv}K_9>w6Mwmb2J$<|GMq*MF_FMB5ucakibDP{+{EkshK=(x)`9d!y7OwG}sK{u` z#kRSGVziRn?=c#a#u}^8?KF13el!zl;sG_eT9`sS+sc`DyHZ@ITwV?}8~1a?H}%8U zjNhY5K6Aly$ie<-SczKHeracdFPnaQUGZ>z&maC_r)a=!U7v^<^f0aLV}T#H3Ki{} zQr_=tpc~%7tF*B6;IR4W`EnssjXr>SQ!JHpdqYmHMlJr1HbNTs&WqVtiHGQ+SB7;# z>UqGD$FPOImaR-i$R^W~vzi1JJ;RPnt0gL|Q9ltO;)d-qA_Q?~J<*cDFA#)guj70W zc`~X3540dpd&8h^Hk`u5-)&Af8ff38 zGft>u|4Dv6RsyQDr-vHS&p$^ih*zrQ7sjizSR(CNP_2~_qN~6c7JRe+@n^rRU?15t z-gX)bV^gXAc#l)g_kHidsln9LTn*fC_h-N#tn{L-0|kt#^~6C7y|vAoJs|O7CK~g= zy5ULhM&v%#mR36#!n%?~Be}_^TK-A2*iF zgjxdonw6GUL@$KrF^32eh_o1Q^;3B#nxNsGmqOLQpYgSZ@CU>KSaBe+&&}Qel0MJ3 z_I{g_2-s$gA5HqDRJZf@5+^&S8OK;=_r1R7U54JT50rGMIrOYxp(yg3-S`b(Ez}@F zU_Z`q`tHgwQoo4Zu>H+LyN!0-pa`5NXrQa7>MEx|eV&p8u!}gGswyiCSi$ec!bK}I z%Mrt(xZ%9etrhwveYu3IXXr{Up;Cc7V-?O&)kz5s#dFD8K)dVg}GpeyL zwHnZbQSyPnyGh!J4_oeoYe6-3Fxu`fgw}Vtk&T5(tW69K)Qc6Im2-j(hJ@F^e^KV^ z#TH!gaTuu(-WvSvV?#-iEpx8AD z(<{gABPS(%Rkp>RY8od7dxX^B==}@t3RxT>!b5CGkbekwg8g)wFE!6HdWUUjNZKs- zkT>{uV9}6E+}t);nt>-x@}~=XiNaipIVvt()WBOKsQ~{mrLl`(p7V7B6KC0u8YZ>A z2dI9xr%FG20?xAM=JB)wNA97cp>=Aac2Qzz>nWl?jr3>N0@)t$dp}y!E0^Oqgh;IaQxsW{KB^!4)d*q zE!96mgar1N#RCvwxi}!!1qwy%Vm59@GG<5}UgH+>HiqN)s4wU2kO6))GiS4S;bO-* zVI+{~Gb$Olqt9#XRyzOee%ARcWhCS6nG&wv#EfLIeWj9;f zTosiF#MF2-IHxlaf46+=)N=U}a)r6ig|@M~cq0k&Z6!Q#pC!3dB*plM zHe1$~WeVEfUr9c?IKN!WvlpN zPNZha7YDPMR#lvB9W7Gf+>kh1&-s|JR#SlF3pE2!(!NCOqc7_oDdOifR8r%#U0D2* z6c`hwJb%aP*v``g`_W4KUcmjo*vkL$IIZr_Gj64D+2pybuzxIdJqfhc=r|o3{fJ^^ zT?`2H(Q5C`JweNGXqz9>Pe-YsjmHSQcj-Z@ZSI0Hu<7Jrh~M2%*PPw*#YwrU@w{^P ze?rOObkZ0(aM!xD514bW*NDCw&F^7pco>!SnzbBFJ$+Ak0FFx&ELyvDq>l=`T7f6> zR4zw5Eynmo&|Qf=sf64m&zV0?emQ#Z^Gs9y_%C98IvA&tz_GqJYZxWGatVJX{TRrA zB(wE3F<(NiHzS)d?+7g}KBV}`As+WBukKUcz8}C%`iH-IM^d{+bvd42jQT*qVo+XoAlB)HHT*_AGjJaI(-j@X4mA>lQTIy+vYfs)V z*m@;Q*7)Ul{T{m0YW?Pxs|_;LM6-7aEe@55dybCv%*OmPg}xZsbkbRowhmznq{Sib zfnu4Tc|~6__4DFqJIzOvT#hZ+%U6z(03ja>uhjmCF~*m3dS7oV$$LZj75R%wmZ((Y zpGsowm7S=DV=0G^rAXm*=J(YK#ND^Pf1dw=^Vxf$4I|ZmMZW%)xn5cYA1r}#8%$b4 znJy)}7K#k zlGn?LUg}z#@%s{elElaNLrt;5K`@KEf^;}|WH`J#a;r#gaD@0PFMzi{zVq^Z2nq4% zFDZx`JvgDVQq6Y6Ak8*0jrn|VaF3$yT_Oj79xLqg==&roAH}5C@9QF!n$AuxiOqGj z-=}=6^^MfoyJ^YQ^$NffmJY;8fXktgnwszSiBqJxr7n_GPI)CCS`hrPnCdx7JW*lK zgV&De@6U=-RBB{l1}xZ2-Tug?4U~xw!*Pznk0Odvrow*S@4Zg$Fph_%rK2!YX&!$m zC1}>5K8RRWp_ zHu2?Yep|NrA+OiQHTRzopDlUV6cnI%Z|p>kuSh&4UV2HrBT*wx#(wUqhTrD9?hHn~ zPJ*JSeH`t3r8=8_>^bZ5?k)n7FG9m)!K~G1%T+ju{L?{9<8e$RAQKzX>ZsyrL7@&Q z7>A8j6}`E0)`N19*scQ5ZHzvPbKMkRvsGFb68(d?D`Y%HLEBO^=t7~F;NpxKl~dY7 zMQ~Vhly{z(R{W;Y5vgOGYAL$Hc`J8o1B-k%`mCZk!6HW>A9eA$s~OK3Cnc2|m|KV> zr>}qWL6rS{%=ir;Key3CF1bFjd1go`$PnxSbI<8Ngqf}mFbRYmF@}w8XKWhUs@!V< z?WQd7I0Zwi*`B?WUmF(m<+3$Af~=^fY=?eR4q?=pA?&~X0VT0mq3%!ZKru-TUbySd z+@R5}twCgw=njn!GDq`KGd|d(Qf?d=a%C*1@i@5l5IN1DPuk?1dj&d;qqcn5mOSr+ zoODBTlj^wQwZJb_3ZOhs<>vT(#V+bA*eoiUx%3@co_>M3q`BMe({9ApQ~a`?YnGon zzNqxUFV1v+2_4Q9#&v}BNml(~r!ikd;cPH${L8B%?F*ttOMZqct6!ysrxbyYe-91{ zbF_C#SVo?;vbGln4>ca5YL=^|Ac(@Zje9NtBYmh=edJcNs zU;WCm<;?(n6;y3ptmFf@Tj;MIyCP6Jb=Or@a;7@uM`Ubbp$XSu$sgp@oDQF&U3(%d zTP-oKJ6T)=9t76g`C4fJGE9@GF9$z^-JtcfeiH0ou?g~}zB!o}75lc%D@JMSd#xab z>I3D_%vT5az57xl~cPo6} z*xA=*rBB&Kgkj#SrZ%)tntJn7aJi7#+^Yrf?btcf**k4_x7RdQI-{7DZ>5gs3I}eM zMQq|d@itrf51-xkhoyH@CAX9md-#5ois)CYPB~GoD*i?6>l-#FcZxzKT}QAfIW=5qTM?qCj=)UvJO$7 z>Ax4L-?zq+*R5K}uok4@>l|fq+lUUINjdTrDRuKsekWxpRkOs(w_{0j@p= z2fI(?gW(gyKax8|9UF;54;n>%zfO)Au0k&7w@wB$4kD%9ABCdDZ*k5qFY}8bHy8Wg?aALbXLH!>x#J6x zK+AQCY;6Lkyf|E29-ZIzENT5;)vE!&580l+Oa!viviGSnu0$MkZ3>)bRq^>t=gUWH z9q0td6|c2|A}X6m{7|lv(@ov1Ni)J5%an3==YK^@xIMhn-S^ZSisE6AA7()YOFfP? z{C1CZ|EGIpkUk>fdm$-%KDEH$G1PE0oK}ys<-HDBsf(KRqtrEa4NhtC=BsaUs+;}Y z%_0*TderuOwQ*nH`}1Vo_~PXDwOu@`*m1v$(<-VXg>r-bSJ$*5eKXIaD%X!E-~IZ% zF+l3IwNz>7PgvQ^N$FE%vJw;U_EcDKeBhQL_ z97{pYVEiQ9W7hg!MZ%N)(7vNbp@7m`l82e}w0-A6=EKvS^RSe68bqII!4YCFs@VH) z3VSm|R}OD8Ik|{c&pnEZ|CNoDm1$gF>J*~IVF!F<9#qPnol7VppvaqkrY_I-kp9e) z_U)J@m_LuBzM9~A`%``#kHXWVE6!8t_fuGRb#(eV-(sp@{nW|+p1o`c8>!FC)Sb-n zpS!!?zZbz|eiNx+%XM+r-Gen!5tHokArvRkiA5lciKcn(axqoT_Dni|s1I+jAl9Ae zh4U=4B^3+Ylpunq3v+RV**5H>nPhaB``Qw0IMBgu73~&dzU)K?T{h}T;73@$@=OYS zjNLltnvarBC2%n)P5~V^pu6`lYDA2lr1F<1N?m_FdDUe_13SpGzUvn-B-2!VALAKd zZ8-QkxLJBE7KSQE*@I%sLuUywG8fAlYq# z@)S?8-~DCnOfq`t-0qOEWd$`8E68=ffB7Y^Eqgel`ta`{oLu9+V>VpT24;x0LpKB* zSZ(@Lf0nlMf=d%k2vhzC4pCs@^SNZG)oy_sr|WC`_!GvmA^Gv11c+|z?c0@cQtOCW zTf4RCq#inTjM8_Npx!>!9#T;pP|?0m?+n|d68#PlRZ)P{(;{=w)ZQ{eAx+9-IbVjB zG6kt#UNH;tt~d0+?q_LBt5-2NwOr)-$)|!^QTJKP+{L#O_j%UM(i}R>_5M6LP1OQq zBI9ZKGFl;;cvqfABl9-ZTBKPf?pKI}^_zlOuA8%R0Z!E!f|}jd^bmN~obA+PvN{V> zVCYSeq}7y?LfnAq&+U)9H^J(ZmFc#|0&gT+R()t8Y+(+UA3mR_=R*gE$w_`>zXZmk z_U+3`I}b_8=gcfvbY3}8u~XA+p%xzG*{dY%Ifv!zFspCh#j%#tb=7@IhV|rpj(!_n zeck|%+#G9HB)+1_7|XL3w4tJC=Byrd*_Yxl-|^hJ`il*GV92=0isZqGc<6V{xL`7v z4+A0CT@F*ldqfU0E3w%YM@wLOeTqq`O_eT0LHIVOX-B4_cqyf4Y9A1+`=y3D{xhK-!J=R zEAsx^VVB^CI!_oLC@HrAxL{RBZ%FG;yGII;`b7WD;Nr})Ya1h@IBIf<$B`>pKv4En z{w%TrC%xFOHPy`u@1ynQoAq#@L)^uy*(N)wcG^C7)DTKsXIhZy@e7|(WnEDih=a_w zRe?I*$}WxHqMQjm1iT+MSu3P5)RG7B?Xp``=ay8OpI0_IR~zDgy(=DZ4>CaBDPnrbo&l!e+_cgP)jzFyCh6)~Ubd>a~nj5k6>3WeC23Ib~i4fgQ zlJ;!3kBfp8-qe@SI$ryHDm6}R?n_nX;ZK9!FNf)<*R!_>YZrOUEhaD;7zn({%Hh>* zs=9As^%lk-lnv~jO5?`1?D_z{i$>L)6~lTEO?3q(%_kwIq?#H1)qx-bWhBYP+h(Qx zMESKvF@5>u`*Wc22rYNf+X({<^)pcJWtDFT45>Vp?LtiGl z_BJaee@dePU;HRku`(!7V0vgr`~Hyp)z>XwWD3_LuFA^owJ&D2V3gxtQuafuax;Pt zU69jU#FO*K{!&%MI#LlJ8So`jWJgG3Nt8{x=CJdN)JpbI&)fN{p#ZwB{0+Dh7?m<` zE*V@5KI@FXRWuDNce}@vY51k&wD3o1?eb`4s*axU{)P!!H_Co}J3HD_44=buydx%3s+XvImNoO;z1`=-C+AJ0>wDx@-tu7uY30r66>LDF z%!+MLKphNzV$zOZyCE4}g^+1DMD#J@{V&h|$PE9-USuu1ZAx=aDp?yj$Fq(Ay})}``A9VBJp z_++5MJ1Kf&vEM^07ylR@-djH%dGg->*W_*y`&X$;l`36rb~|KSI zJZbqG#nrw6hWzN?P(hzFExR~*u+{h#*xpK#GjFMIgjDeJ!N@rJ!nnz&uM;o-pYnMO z8UEzOndvb;`=$3oNl<=$CAf%zPGmhZQf`^p6{(P8a09Dg-XvK{i&Z5~8Qms8ZKGMnEUNJ_iiW(D=wBL760o>bV z1p>a{djb1iq^_f1<2wt})U!{HGzf-#YaXB(<8ekR{gwF)i(xCGx|-B8??lEtg?Ief zuDa!z)HyzT=eHLtFD{1G7J72Rpcx91Mc>^RojaM$A$Z?2XO-|sUayaR20XOjt~08W zU5wreM_w^b+NtC#a?M6jvMqM?PVsn&^`sM~Va-t}e%F8VJSxj}?5TOLZF1Phd_9^X zx-+JN@F}taihsE1-CA$3P;+~xX!#B#?DV^6M^`O!mm$dSgf_c zjr^$1y>3d?^uW%;$+GNu5MbUn^9bEJr@gf6v)s4zrAht2$BVwB${{O^&eFtv!~S7L zzd_PsGIKFZ;}s^WRIUsnneN5om=xmuw+6j*5jPvDEP*jmZ2PQRCysdjjR(-E#R(3< z&uarJLD1e_wh8g(%E%||Mgrq%xZudqFzQ|WpC_(>MZFa^`K_Na0UKl1tZucELDD3ua}4o z_>suMl)HpoZj&fYGb~&cu>=oy&zL2h&=ujep`y|ep4Ghw3FQM@+wK|i+EyMlB8anY z+lS18vD!i_cu4Q6@Qw9^<39U2bC1aYlAsV98Y+=gU3+RYO7|wy)Y9F223j-jJ8cBa zAqZ&MU)wG4dWNO1kkt-{T}Emaqnb|355}udU7=lx3R{;8IT_t&<`8v5-ntRV4fnKF z{JCc(f%Ce=?knTpEE^Rax^G?!-wFO}Mt*-8i%&6qnx`5KM{9RLg$Wi{D}S(VF$( zQ*^+hXoWL`v1r3u3`vfMe40!m9-d<1ZyFcq19#RXd8*l1>^R#wc@I-2SFV&`33(S0 zSFFQ!7%K3iFA#{u3WP97yQIvlU^d^Y((6QfHvk1H7uju9HB=${#=K!cz{Zo0iHSM0 z$mjRa^^Mg*!h-6y{3%VWW}{HVO+goa59MRFfWS)t8fQ!v|77<2V2Kw%AQ5MZhabpzTSh7J-ruuE~IU)Ia?zllCH-&WW_~CsfjPkq=Bs&aj4G#IFafy%@9Ec^~v`I zJh9kvkUXlPY`e)WHL3k z06>j?dz${iV^Vf;Sz2}EG{_Ra%HP<_&`3B-_nbJ)qZ4FxTdmEO;bE``0sZ2&Lb+Yh zFQB0u5$eLP4`|v~`@P4_N1LF&P;ArEPR`$cxZG}OwYU)9Mh44+jt3QFWPf$suBT>a zO&a%z?*iSR@9R6;cs=FIXAxs5i+dZBDBLT2AZ(wIqCdMn6p!H9RhwDy7(FEx&!su* z-GrBP&5x&`)yeMJAUT2q01k@XV`1|H8d^x+jZ`|)x%7(8($L51emCi+MfX?67I$_V|<(QsCrq@*y9kXcmfex(5eUm%9cSt)nlj0TojQy`2<%|rMIe*#fxLuWc2Cv!buV)j_TR<5W8&2+4p<9fE z)=pDOQnl}K!QP)a+|eF?$-{-|le}dI^X3D$?wG>{bXzDa1dfidrm5SvW9Q*_c-EY> zt+{|TSfoz#GV-hanDF>^^gC|Nvhr%FcQVdaXs>YbmmrVEPV43Ezg$IE{4sGU_i6~G z?{R!xR%K6qT|u=>aKa4=2B}A-Zg}M91tP`QMn~>--M}eFR;LH~D}rf+{gC@jfscbw zvgv^@wyE>4NjM9#Z%*!VP3>NqD}qQ)&4ld#iHQHtu_keFYz#+a)s1iI><+tSkBeIr zSJcRh-|}5fOAsm@9IHSenLh<%`Odt58@%k)X;bVK{XZ*$kLo)+2R)`7S=)vgvX1oc z?gnB16C74y<9p|}=wz;zwKm!4=M^*QG*Iu*x1IWEx;>0r(UsYV+hQBK-`wTb8d=F| zBedJFAJ7Gxg4&=+T90Q=-W~5pd|mMZIma54Zai4ZPSgm zQ%o<9_HeNcR`8KiU|wuqqT*#{y}b-G)nTDiB_2J5T+L`&e4~`TMm4Zz6lxaX%1o5% zZqb5s0Kn6H$<`s2mOQ;Y?_Q6}|!5l8lLk?efnU-uE_F1*_o zqWZbq-EdzMK>Bx!frT~-jNKm^U!U%&cXzxPpJca$zSaa;#P4aEH?t!cpofr=k(=p+V_ z+Z9@J?JpL?YcbNEr0Oxi^48`-lT?EfqT{S|G_KWn9ZFeeplRVEsNJseh1;dU_)Ulp zcz9Tvu~HCdGsGivm-+P^+T@M)+3JPimSLJ_)Fp0yvfWdbBEG(<^~NU{$q)W`w1D?7 zk3%*6RcvCN{$PN#MO_k0n3$J5TSf86cY4^V1lFs+b#8gb=6~${`GQR{x_jGv*s-xq zY+!VtwT+i+C{f-D^LM7&uxDP6 z^$+bLMY%)6elaNT;2UBqDIUh=;O-0mP0o+=?4i^b`|&W6emd1db>A% zOb<=?qevv^v|G6z^Zi^BrC%LVs2t3^~xgBYCE*F2bUsp8?$bQKpq)gsQkk!eEny~8fq{CZUkdKu|VzI4rM zmq-g!($CLoUOQ5e&k!^pp33Zrzr7Rzjsj>(C_#(t1qd{>bOo2{EBv=mD-*2G0Ytg8 zGOmV@DJ3hB;EVoVEVlP~?zVX8)R_uMa=u&O0?6&gV@e1uwjgW z2`D%QJZ6`oi@xY!sA=%R`0-X$?5L&k*WjKm2y^kE3LQT0E6H6m3=F<&Tp+))@mgK2 zpSXPc$F32cRu6tZ_}g?$D;W6Yal!xZh=9ZO5!w6*E01#%SZU_qwThcK*Qyl`kzaJ8%|uwUM!sw^bq|*wcj-rm9*5mh)OoTQRK_jfeMI%m zCvV>7!^<+-!0^Lj&RTMIrM#3kLy>7sfc9jzF4!{o02nBeFxNjKHgkJP9b#M!dC?p( zyjs%7*k)r8ER(>!MLR^sHJD)Qv^HudhQv#NnXB$Bt+r^mnSvRC&|psy3d!|Fl-o37 zM!$l$M~;Rg52U=tqgz_6rt3GGAB^(LhK^e&4aDJi)mGP#`zjfg)0fNvHHz`0vr%Mo)Dl&KbAq_#A>a|sUTmOY zEx+rag}2SD#@gIu#l%*&$7ce9`$5r@q{#|7V`IV@&|cQ{kTF9~&f_T`Uu%@;HRDSA zCQV!s3@&gmQLxy zB3^C3RI_XOE8}r_ff`%n+|-ZpueG(B|b4iF3l4^E#W zSuC-&4}qNghR0r@rY!DN63gcgZKk9djH+Q^h4+_*C&n52>14aQE}kuJT;Rtls|3SB znh0f%q76#hvXtp9wyKBbgN$?WC|{GS+Q0U6iBWyL?{%^IMwJa-kbVw4Xs@E7f1G7$ z@TJ9iH<-DV)7MMm7^KSEjJH=&pS|F%{^!VcjG}65qiB^!TZ>2$Cx5g_W6yNn_iHOq z0)y%H_V&0D;rkSu-m{|Z@nP|(cNKXMKVaMPj_?H~mcDvskZo0PSnyl^uJ%M0fDI-14hd3W^ zhW}$sp`y9}ZvvPEZNw5cXXQccem~_3GHW%G?r-4OfX3DC7R;tyr5E?<%dgqi)u=b< z#_ik8_sF)pDn!Z^m+OAXe}PL1yWXXIU9t?Agt4x^S*Bh2%noEnVf*DB+sVYv#P-CtZB1<3wllFh&cwFOj%_;=J9+c` zo_Du)tM>oy{#JE&-S<`J`kZs3CXwYwYx}U%L|c*3+U&52guPy=v%-K3LZa+>3X5c$ zp53L8Yz4%hA_ZyQ23#ZQ$++o$Dq~$%)2YSyZu8R6wP6j~CS;qVxP%<#O^UR>^Ge zd4lt6ClOOm$(W6$m5?d6zT-`i05o$dXeyDH=r~w%rmc#q5%GUpE|*0X$dqkqm(r*l z)#J$#hOEX7=lG{DEaZA zjtvbo@8&4LT8$D0KW_08#TW1{8%tcBB~eIIBGI0d=DOGoOxHAl#%AwNGF_z6w_|mY z2}6Z;w@261X#Va>GxujBZnbqGMn`XKvs@;Szc)rfET<=usWq3-Z~TgL;YKHgIS{5b zJo_e*Cw7vF7&}WQ*+e%qMGFu3Bwl}e`AVys47=N!OX6BZfeLlqCwfvpCR#8Hi$+Fv ziT{j=0k#*ZB{R1wqHtzo@$DgMBm30YpQ%r4k^XV%f(fKD6)-zj%PM0(Q%6_Ob|rh` znZ80Ez1^gpL=@ts=`qEJwaPM$2!>HIR!@Br3}dp2{Z#~+aJNu}ewgqjn*(pq-IyHkGw=TwTB6lX$9u(up8^V zUSzx<&<@E^6st9CgB#B=_OpvyIKa+7e0cDlWR?$iYWbEKD@i2~?hr(|Ry7Ji- zYL-WDNsWUgKiK{~%;;u#o4D_x9yzn*9Y-Td@ug-mkGrhM^BI*jUnK~sIoW77nr;$@ zj389}5hFt$iusV;JRgRg_i5(ZE1tEu_j=NTTKnYVR$x^UXMeQjdsAd*a{h4;`dYKa zxyVP`&TMOffYIb9U}x^@5cuRRs+k-CR`&Ed8a&A4fue=Y8IIw&gA!jr7Gfki3;#C{ z?YgGm?%vm9Sx*eHLze&3De|h4vOgDuAEmoDP%e)H=leont9*Q2JQ%9{ z{J_x)WAQjRuc?#QG8CJKn-}LbuuE18mL(D|O<#H30E3p|@WaQQCV^8ix>Ih{BSDPr z{?jvuB-tR6-L4z2vK9ixHOvkLIy^k=u=D z%fv)mRvS`Py+mm_0#uITLypMJ`6j7eSPOk3;# z*ie6d_-G4GL!nU{-($z`ii{a(LK(}SzDN!_W9Qa@&Zp|*b`ZIM$P3(B_$<2Bv971vcr!nDvsYDFYFFaQZ}||ATj7Ta$%)}$Trai5mi5qA=5&iO z^^OTrv~6oDpx5E_24#6Im|}Su^$6d?g2lbUSwlBNp6DVFZm>%DA(|m#>a$Sd z?WwcCJsNRAuerLt(7lUzRYMP60%I!`Ybvn!_+jY(%zWbMCy3!l zXPepV)1k7u=COtHZ5V0hyY~M57-))sfLT=;I@(=X7*p6CqjOm;*oyl(c9vHlJVx~iL|1-ic ztv`(lb+Si{{Z5Y&ET^h=*Zg;MZAk8U=5^(3D(EH$OiwDt@i(0F?xwrBH7c2UnoRnJ z8Byyg8y|KySew{f((C07gn1|(VSeZjK$^>sz0R;FeH@+@c+|J98V7^V)Wei*vbDRO znH5oV2J|nZ$L~xj=v+^7m;sc$Rci&C#zo9u*RAM|=S|zEnV&eNIdQD=JOn*2A6*`s ze=EQowvqF#O^!d0UQ^98o7&};A)NKJ0Nk~lpjUMMXDL$8lg&f}%M6a@KY+ZPM0qcJ zQhMJdsrG&YQJVIbPrujcmxge^-!F`>{JxZ5iL-GRfv9+$bFkQ8*m9!Y8km{gl&F?U zM_OK61qH|p)8nBw<$%nX@dm3;Ew;&Hoy3a5O&u9sBR$X}tG#aG2f_ z+jYOXbe@UHkB<`?X4H}6P@N1a+x8UZtJ$N-+rP0 zd}f2qN1I;l-Z-uIa&_B%mP`rL|A;`0%IeyVI2(TZ8Lba%Ds_+0TTsNtrOyY$L^Qk| zTWi-tHxic*ENTD#Q2YAwwH|bmIrJwQh+1SLf8OtG-}E86_3QP#f1uIm{TIm*NzLTL z8_9O(uzOzATe*?(HY_hryLz4W-oBB4KaKbI!I6IIKP(G&A6nn;lzxKd8ON-uj30dq{#~9^P--0<)!Vag;X^ zU8s|FsYFaiyNGHwlY`%z*7YS8rxi+x&;+~|2A)(matB!YhcQ^p$C^86oKT8@Hefi2 z&2dP_$Su!tD3T3epg~Ni&@^fuGV?lL7%ORf({D$r6oQ4CAbK%R+ik{+Nt08;|{2dyc{5OM72R3=g~ zK|)*0>;v!??rN6+rx#RwQ;a8i{9v1I4oOUZSCFiiDkR8A465hG48^+nGgRd;-H zGxjU|UMJ)<4b-pw{NX0OtuLsZit-ecnfvdD-65iO<+T z{*Qx~OplRf*_bvk+%RT0vVj8Bg#{JfN}P}YF)e7(bkhfby90{WLa-RF=7QjKR(3>2 z?=rciZBi$xZ7Mbd&-|Y6qxDIAS{QL%=B)>wB?)0;f&4Bj8-k3$qjQv$4#$+&^>DcT zlj7}9{{;d$oEnZJ_Ms`6Jh4K(Ag97#A)G~(qRb`Zogyx|mjoyK*Nq`cS~N9BJO?&7SA1^o&j>Imw|D)KucgG1gEA4i(nV z;1h|ynhS8{g0XD>YxBS++s$tb?BQQ7w#1S#nDHy@&c%4419KK_d88VMEGmppihQER zeA{CiT`gYS4y4j4wej(Y>l*{qka5YIvAt`az=w*NOlvmlJ7y;UYA`{fA$@;I*#reu z*{?iT@@aE6BbR*@*%u6}woR_AasmA&uE;);#97b}?X`h3X@Qk0T{6JO?Px5i%-_pw znBJFy6cO6?=nvg7TR~;Aq!9G|vm_S3h|r|Gd%fce92LoPa3@gQ{@N!qO3fAEg-Vrt zUnxbFQNi(Tqw7Ua4;&6toJhmCHCbWbctwGEeZHEtN0!H$pl$lE@f0xWu@iUSY5)+re+@Evc>lG?LMOO#-(%){6+hO6k%~iE^SdATP3NL* zLHl@GDl=DN43IQ8gpH5(1eR>HRuAwXyhclOfntgx8w z43_F_)y^{|2Ntq~A!@q0jPEC;&JQn2^d|mxCXdF6*AfVvYrXS#D!^`Y-Q@Joh5Jn86N(y6boids6^H>`;k#yg-TS!HpM&(H((6Fst(gL5u(Oo5yR0 zIg@jFv7(4$<@&eCzQX2B@|L@~?_^+wbN<>7LuR6+O2O-Kt$74D(Qci=konr_UvfMS z({^3$sNeqi_}1kSHws>F6T9nk{33-By5*Wm9dFn4ng&!BP=e;5{ACB;5VoR~KuljW z5C{QJ>jk$Dy8TyWC?f^JA)K}}LNiA$oKGJn0sEiZ%pPv~vtR>~LS`YN>2z?CM&A6WVUTX{Vy_&< z+5ePACX*!%9?iR!%`L!%E4v=UZ+4ruLcOoNVT`9B;@{t-WV84Y?Y#zQH1`oJ_G0Zl z_`dxqM{5xX8_G~?#gHYu6Ewo&Fr-CoScW)=Z_+5DYoAfose@Ynru+LmYs35vu;ffJ zy8Fz(SG+l@e7jrew_0DvpD(T?jQfOJ@-kT6K{>XZ7_)sgy8pxcwk4azu_L+Lz349$CgzWFq(8^kZkS-u zSjL0R+m;_pnBh=LuMokRW1DV3FGZQPA2GZ0BDDA@xx2|^0TTga4}R8|UfT*pLZ!Do zg7}?WcvSD73JwpZzBBX+Z~6dPf?Q@#2-7FE;U4?d!x8T=<>8rGIk>k4q%6}8|Ap=1 zO5#t>xqkgP&#dh$?o0Kjt*DCXD86`J{5L{BY`=&3yxpaWnm*g@o*z}}$D1>L-hQwG zZ!dZbYFrvF$3ePz4cmz<6K+dyo^|aaI7MLomyOn9nLpFnX2P?od69H3JTUy+w+D(n zV<^A#eR|1&Lz_?h5#Wy-XcawvVtmlbB~@Xy0b|^~uUx4P)zo3ITxSvsOb_ty>`Gc% zhczq_i#mS#Hqg&*$YL3abYxc(R5BZFWKw8@QpzhR$Z(tZ$S4~W(;qzmHemg!jd`$F zEO$1z40H30e$7~XGH0FLY4@sKa7^~${k)J zXNy`lk8S|j^OP_?kezGsv;o}ncco?IwsK{M_Nq+(XTu4gntuag@^m`x9ZR?8 z4vuK%NLiTE%{k}K@d`VB&6S~;g;;Q9SU-@xa4YXJC3xXW0d7|Eqe3EN2#*}+=mMf! z7xXKCld)AS;OBl_vDjj{0Qx&`a0~-0-6rS{rw9v3or^sf9+O131DHPx3mm_FYpm{RX1SU}2HkKU`?)1yy9LZz9 z$=;f^tTQ_=+y;`&(l*wWxv4E-7>j5i-?aTq;R@dLb)lFP>S^Tzy!Wx+qs90(H6c6Q zF(z)>-0DWZ2^q~-AuSf-hQ9j;b7n9>M6Tm;!ORQ(z6z<+R`ipswxm?T#P(mvtbRkM zly3_Z38RjDt917N)-x-t%l^f2b|)aM_%oini7V9x2X%57>V^ZRtz${N1E`HBH(Rlg zwBW;?uXyR9{cZtJeYKxPsCFs%ah^zLMS4G~+CDWiSy8rYjlg|(f+=S>yFVXrW@(zV zW4G>0IA=PxZaC%@OuqV*&W6;Q?O2IoPU8#nuj~~qs2~2=RU*3ihMRYPL@**Za1A!x zw;g(O+YW;EHEqF(7cKV)F7aA6P3DXo7E%c;U+M9UEHB-w;>8}YgtI+BtIA>71k$rE zTMCGTcJd%sp1vX!qNH(io*ajX;#p3(j!8rWW#N!NCsZR!D#q3Sl3NoijQhRDn#VP1 zLadzXmVt*$prL>xO&nHsw9I!jv{Mwc{JEs!%W-3k?-nG-_GCsmDl*2K9 z`sC%Z)(1=`XJ9IZ+#F)z1jYYOz8O(dzeKznc`YY-GQB|V465Td}&dC zt2^9tN|N+0v*Y;sV6Cj`y?EX4A`UU)if4__Mi0lNsZpQ7*)_pg;_L|TP=p^4FZP#^ zxLsm+&VR$#DpvTKH-?R#D#F#+&z-4J{Eug#A9vL&_!8B9vV5jyLTV%4c~&;m;{>iI zC76f;wyOo7m_NpV7g6XVc)^;6w!0%$-ua^{|4I}|x%Ey}tQvUJ*JRM>nfSZ>l5Vfj zOHAF!tJZF?NShy63q3?|toFR8{t+ppbN+2rk3$YYhSxA2k?1GGC#?sDIo5_a@3-Ic%yXPX|;Ej zhW*MEvY!UASvWsc4y{mKi#CB4#t}i^UBe<+G5Dg0SJ#vD6KGz0ByTfBpWBxAL1{f{ z--Sb%o~b`!yY{>C0RH-(h`yiu_N8VGi9Ghst`^vJrBWJeavKfAMDK-&j5dC@24i2+ z`VU&_yX&4el@sS@gHNEo@=!lKrDXW~E4V}iBvjk|y%E`cJP~C2Px#`;d$QYhwQ-h3 z8vCN?0BtUCZ?vDY!`Qx}c&-@5m2@=QUIJWr-_9{r5VA@6#mubxiD;;v7hNUvKPl*6 zW&G-9*evj~-u<1-W@Y`Nx#M$=&!XquYhph7l*0{0K)=wGAy)Uuiv8lEpS2>raqW?hn% z9*lPfVP3@zm!=Wo;S*b#^pa~{;N}O_o!$S#`_tm{Ty)K~yl&f&9p^dad_>KOEX(z@ z;e7htFaW*jx~x|W3?|OkbKm@f#C2fkHE?bqvToNwKlLcs@E^V6;4^i$j@hffAy??z zpdNp)rM3NvMDUPm98y>IUu6Wkhu0*$>a^glM_QumX29jM=Iey_Z7HKC-}=iJKIbJC zdH!f34XA?jpM{Pzp(abS1!~vrN#W@q2_iZFuIKpsm{R^f5fR`0mR-lbBU8>-W~sA@ zLa*aeBWS2fD2Ts+?da~a3++0Q5oN4RneAO6)$NW!(;Yib+#lgRjDxP9kZCh7Sh~XT zYi}g?uVh4W&;n}(I=sa;H>yA_%RV?b7%(R1UO0Rhg?G3$z_dD7 z=NJZ?*Y8ACQ7#^Fp(@rnwkAnxO+76uEz>?RhI+6$vkG!15SJHRjIP=+tO_SZrEsM{ z2F%9A(L8Z>%LGim3<+ecaHgSO}ZpAebT1r^WGsUFHBWwg%4s5rPoh?X=E!TBWhje_3r zLb{{#5Mqvri@n9@|udi9_*f8b_hMb{ZnO}K^A+Z@7-!e$@!JHyG;)DeOc7YLE{U=(}Wf4f{aWL-dcE{}%b z<=|7pM&T)VBu2L|8||VKM(@oyP0b;ja|?n`KvQ_CVmf0}co@=rHPTix#Zl4)1zW(F zwKE^8R`il=-86=%1*NqZyt9# zsL1F98qHdG@1PgZAT|V2$1yVNqD^v`}Va^=4;A9F>Az~{4Y##iaesU+k6CD_C zjVM!Gl@3%yHrzt_kY$dyWUJ6;^_8XCWSgq<0dI+a%M#MmOn;$OR}|=}O-7;0j#pwb`p1ii#gqWztgPzaURwN z{*Z1ODaK!L^W(Nckhwb12%8olEc^R&=>l)>mMVNF$&JdrGzE4bCEPOV{CIlZzNDq+ zj_>~N&QA;1CZ5l&^r`2%%Vh^Nz91AHtL$l!8)D6!{fLb$Jhz748dsB=DL%{Nw{%rB z=H`_knI#8oG0&rjx@98TWRO%o5t-CiqpH5?wstd=Q#0?I712Z(1N2_9mM@aa0LNv< zHa(L8$Obq3FdUI}*adCC!P5mT=~Q$1%0NggaSLW-Rw6`um(Kae3b|;hvgaHE)@{~0 zk97-cqBLnrm!YSpH(?s5*R_E zG4fl-mNyxU{^iCSj?0%V^Vgr(U|#RZNG&mF_5Mz)q;PM>$cW8D3~7bD%=&aHX4-~b zLIhAOPmslqFX?VkS6Qw4qqbjc6Bm9;k6?B=RrY#z0+!joahwn zi&!CY?5b}vO=iUOzWi{g7Vwu z;mpX^nA+aM(z*V4jhD_l4_qXKBxNU$vfYrcMoL`Dmh;(ZZ}WHTd$ zUT!JD3}O%ZeTegrNqNks82&$xc85+>sTc>LIOwe64^b5uOU~?GoMp%fCnSc7XXQ?{ zqiuo0??)dtokYKNQv#P!o|#)&MVD)_gM<6;EHR zs{+sO1byXClKp^H1Kyln*&g#$6|6MVcTPlK9^9uCk^DrL=*Rf099J?KfMGEoZ$4jx zJlC@>J(A0f&w-;6zU%w8kBbTMau)K+>=%*RB>Nmut|kBOt;>9gS-JD&namCi#V-9) zv1qJ$6Eu}c=UFD!p5J^R(HPSX4sk)-#DCY@kbeFpY$0vNW^G{qu^D6Zu?Nyw!Xe&_ z>07Y8Dp?g_(X%OG*|XV_5B{{Bv-SE}Gk>6}A~ycC%&?UT4M)?1pddm__=F;cM`rsY6Uk$p8qie zeZrfhbv+h^Uwy(HZ)uhKTjx6c|FzUU(%4}x?{yNh?mcc`h)Asr(O2!>>;#XMoO4fp zs&*H;{{wjRFifN}{^uJ4x^rBydOoTk<@f0QRx;-V%d6PU%Ldr(ja41R<9RY73;2Y0j1U@j+I3IMQKrxn&~bYT5*e zL8SNu{yWw<4_+G70xMJ(OGg?>BU+aNZ7jJy7yK2YM^GAx`QD7_uae3O-8L~l>e!fw zidGs}QzS2F*RRCh0=~m1*yULACoaInC}1 z(jta12*ZqeB)xzfI%>hFGq3q^k>!7zh?km92sQuqaiOgfQLVFp)ETak&1V$?O+ zxUY$^gL=*{1I_gZpU6;z37&3r_h@``d(=k*$_|J13=tib_)?lS|MBM);2iBG_`ujz zrNaGYb3W!|{-x0#47Bfj#sDhglm@LF5HV3{K_Ra({}#8-~Z&^y_b=sN0n z7>+-QIm-Hqc^lJ=NJVv`hcdZd##;4efms8XSzHImz@TT`{#hIQE^wc*>lYt(EcD6z zgUo_-1n0*e_9E{S4lbnKI2!J6dnv&4yW&%Oeli1h3gccq<9zTLLac+7zSbH8TX)Va zP)L%DjyPQ=IOU^~RYwverQn#47$qUC)pjNCEO-j6oo>HLMKLroLj@})Zsy+%h@9-& zr_XX!Q{%iyN?`bJ!@uzRpoK2boM}$xuzNIN_{S$EmlQ=0EC zVLFTP%geymU$WqlI~4$43CC^!;oV?%k-%{?$tA`Ct1n|3UxULFbz|`u0R|o!;tWLf z;luON)Pz6DU=&E=|A=#!i4gca5^g^f_ySzEtj9%MWTV0)hw~phqb8q3eg7wPhvQSB zV-bC8zB^_zJqt^Q+fu<~s(uroV*W5Hb^!vDh-;5Q-q+tOizS2Fu|?lXGLlxv;vC}mov$+ET7OcfP}_5e^Fa) zaH&kZo$R(xAjz0?TxOwsjCl-y~+A@eyWTSq}v;x*mq3yoqQxSB}BIEK7H zUr7sgWGa*5>Wfi_r+n5^(LYJf2-g#tMm;lhHs+%c_6!WD!rBwT)h1R?+djOc-XS58x7OD&1V3~6P3!s0lTT-$^&V1;LFMA zMZ`*b&+!OmS;H11f5)XDkm9!HsN>Z)u`ScG;nosz?$YjZ&rAgH*L7s~J@}|0iK2wR z4sX|OXK;NivNIRYS^vlDb`0Wq6>jlVP4n@HCjXrSJ%wH7|FAXE5dN=DdNN(&;>F=I zE>@6r_ccAtg>bQZNKT=_pP0_`>TUW9F0TlHF|C{JN@3DZ zAQhWjGmm|pNvSUUUggwxopOp$z?hbnpwGR!W}Yr#^I;vn+}_1#M?gd@_n?FFD?mpi z_iiLj-(uQrIF)zqz;>}%RMd$d5|f_ETQru$s4L*7)yHile{zdl@120=AK^vDe|WLl z6sXgAwfYZhY#ej>PhFSf_eX_!a@(Ibq4$UL(_tP!ibD(>U_!_K5K|yi)9+Bb=(1ys#K?Z3S#JWH^Ep*B zrvUr}#dG^_eW3dU0O&=57#}z8nreEVZ13+*=&bsm3toB5XM)Vl7v|&YUFD<>`bx{g z?RME;7|4Iy)#$eQ{E=7pUvT3{Pdp6zG<}&jzWRN><@An-CkzTYbNUWB&xJzowZ(r* zs& zom!LldDY6Lo3MP@7wyU_jD9su$ie;!{BqEvxCgyv7_+bdDj0HGAwbeZZUR+E;PVeG z8B+#>6I3CzWs{DBtAbd@YOBWkp zL*+(H%iBT~G9`$8h!P|YAvEaOyTLN**a8+8d~RoKab`}ys#bZG;ZR5)OcnV%*epJ% zT4ng>M3L^f@g3daixoy0VrUZbAg*StH|Axu!aV(sAES)(U3v$Dkt_&3@`AjaqKzh= z)VR_3$iDg!=am3Xi>)*KoqlIrkOw$4OA{VQ71$suSxkVwRPSDhXDq$85hOm9Xw<`g zg(8%eETpSQRYyjN_CS3x{eo$%nx8Z|KZ6X;UsAeu1P64L7y`gK5XjPQp&qy5OUPj| zIf1Qg3@a-IXbzfY+II9G@?vyOoYPR1PoitM`|$*O0O>o)@|dpLXA*2(6!@a6XwToA zGdZ26!fW3b|Bhz1aB6zR8`31yK)=H^*Y6YVnqP6GIjUGCFY@4JnKQ~gacDnchY#6v ziVwO(N|)i<^a-Gt+h`T%T926?TvJBY+Z1B0rH4dAF;3QUtL)KvP6#2gk16%B1_?c# z?%)@~GqlxHwf}WpV=izY$KInb!A&UDAqd6t2>BN!plbn_lpPa$#pjUc%upmVkn}?)pdtF13)BQm5s@*+q9K7bD%6yZ#eit( zL0qz3zB1V8%8R5)+n5xQOu^Sk1KVkHdM8v|v{KdI%T65MX-1330S6fhj>)^Q=3r=b_8TUm z8FadHZ8lmdD65EDB*^L~lQQ|hzzBp#%vZOg9`ess8w4!Ghx*?#LBWn;tbj5mvlfqf zoL`U}ky^Dm2m2Y3>>{_3Tn)WW%q@hK>mZe3=g=v}u`1UNBzKwS*04q#>-`ucsPekdBIMbj*6ae`+=7Fqg4D zHOF@%7oLZ@!ml+Q%ZoN5j;Z4v=`D#pxP=pSEqx#PBScB)-_AC_xxi@%8a2h74S&(J z@ipaJ!N+{~N!C@3Z`ZZJJ$tx9>BbwRDDUB8S#>UEnH#1V8`DH+R3OBxW1G^Jq{{*6 zerPt{zi#Z~e52;Alpq|25CTwFTv5edj;LGQ%euL_T3#cYTbr<)vSfo^G>4Y4ZReS8Dg$ zANN@icTG~yB?SDrt1@^OgKYHi?+|C#A>KlGbCTC+c;pVuaVo9G#ICpD;$v0LvR=f$#5XAMsoYWPp8KCX+`-ABbU<{3u2 zIX0>7b|kip@T2%^ZF#xBWJ-|i@6XbCi+*1}<^Ztj_g38=CX6)EC{*8QT^NXh!w(FY z0}ke?yqTwLa36JAvh3dokhh)3EkzvcFeQf5y-w#OGOYD$dUsF{m#8ZLxEf^|rozWx zkuCQZ9mE;<=Kgf1T-u1haU9@=Yzn2YLXW$|L*>(H=lW2x0M2V zjd_Q*vBaT!omvZs3cNmk$2Raimi&qhCVOp6XNUmAZYT_4yZpFXkMlTXW0Vg1R^A z#9Y@C||a|FGn{}FnF09px{^Rlt zzpc>In!gci^xxNg;oUxu@F#6OlwkdOvg%?uEJDK)3jmcAO@A=yij(LjURXGv91zdlYzdI= z!7BHfuAK+L&pkEoTz-&}RW;kL+MFGuedjtw91u_-i5!Mj&Fb@0{Q*3cH-@55#3bLDweYG$CScpqu)* zB<6Pt^0^SZQ+;TM;VmVvc3Zfpl6pdRci%$?DrO~9D*bTdy0F2N;TE z7JH1?8%VqyvE}k3cRy5pv+)+>xY#Rh+XvA;>0x7^VgZhgLlur-zb-Z}js~VXVyL_j zK|EESwwEe0l{gCCbbZW;0;pkw5K-cz#GB&_D(7pP=_=C=$mP$wz_aaSOS+c?F4go1 z-A4JcqbgaSrjENnq~qiio9$T}rjr)Opxg9gc&n0X=r-&^-sOBfS6{JOKl7gxf>Zex z+(x916CHfj;bC=y_#pzTlcpvSV9&1mUL=$LN4V$U!|F+gP(LzWg*J`+{&@Zx@VR24 zgguAXC>JXwLvCiwFudwynpQ8ZasXu)7AN>FlfdS|pb4k9GetDQ5Op8dZ0=vfbgK-4 zL+xpX(@K&+_V5*JCsnWi@Ch-4BPX_@^}!KU)iehmV(O=3gj7xX3}rMK!c9U&q6{d3 zp*G~8oUF-N$mv-Jp%2m%NeiFYgwx{)9b3rxny^xt3HRZflKqj+$|PWXB}$Za)CQfp z$`?8(n(0RGI&WQC$5af;*HOSXc)%Ri8s3XhGR%5}qS+W9oD=B#eOf<9^e3!k zyM-SgdWE#RKOjvW6AbY}3K0$hJs^69>{{7qtA}id#KV-p!q_G=uty9ngx3+^2ZC`5 z1ML~68fJP~a+NH=0b}6U+7uYhOWIe+UIgIDOXA{+RtNX3+F9I=z{e&CFR~gQ>7HJn zO%*R$D(*kOBX`+c1%Rv*BvpaEId2qbCs9Z3fSG;pN^=s0V5>MpsFuR_*o5U^lyiN1 zM0N=T7UC=H1rhI!XC~-Z60?H<&4*pf=mh(&hdqv`pc76ycFg`vv-_7uRmDw0VF0*L67KyL-@i$9*vf#OF6PWrU@!*x@bC!v zrT1dz3}G(Kd9qMSGO6r0}idrOUNIUOO3fn{&VU_rM#8n zEBT330ot!2UPZK?lkHAF_973vjn_1ctUon~E`&*RcPvq&{cNaC`n*)e3L9wg7v;PW zN|Z=M)*uGu?M;U+t|A9Go~vG^1eQQ>Sd|ZxMM&z@yqfXssMdfGp6~OL7b<;KGaKjm>2edhU=zEq!QwbNNkZ(X0acpcZ?$Lv0Z7Z|a zuYGevCRo7!qi)_)LfKKhS%e(|CLDu*YI7OIDobq^a*BP(5q^9uf-Cyl! z5YeLh(HOBhydQNTLX`zs8rzZ9R;n{tuaw_&zSdBB%csQuB}%^SX*Oz-zc<*FYwP1E zEHK31n3KunnWhLjAruJI_SvHS04Ui99Y{z%%{!j?0%bKEQtPc`tfnfvP=NK!uxs6>7Nfy z^ric{GDFlLH2@~&O}cOlx9s#V>U%y zWv8%dpVWgrmpr9l(b>dfxgP#+{>A?hr19=P(Fc9CtGnIxoBrc1Y~A8Gr#Yh(e~L~k zb+)QyQ!Y)z1?0oP4V|Rr~JmB+`OL$EP8yO3VfedqaQcT6}(2{ z)>Tj-RF8&B{s)HWqX7E6B*r<89QwC~d}otsubD3E9W!hE_wl@nCOr~Y(mh{8WA_Cb zz6qahLp?d>d9QO?_chk`02Ku&BAkKg~5N)GzIi7Qd_>~}k_VVSs6>zbZow#o57 z6{qElys7$bgUN%n!4Vew&lr5?L)S%LJa5U3*ZMr$23olq8BZ!DAuQ5PM~;}Vm!$n9 z%%;CT5M@g9fjokx2z|H`B*?-;2LM#52!TS`cRW~@UKNJAX0-b#JQg@TT2D>mJ~SdK zNzE~Z(#Tr)g&$5my+HuF8{Z`AvOnG8TV7h)hCG7WtpdvS+(1Vn*UZEbFnIt$Nvj_o zwc-b&5YkD|FkBUAIVdPpks{GBypUn}*?5CBo9YXD2MALdKLSDmW#(8>#hSpjSrWxI z*c^{~>UViHO{=9<2~=#+1Vmz=WTPs{qSJ32LS1;%d(_5j;wPo&+Uun$=lgI=WNyxQ z82!aCf2D)<5Xg4yve;Z<3pR+$1AYlTiQjYQsZ}`Qn0@qA4Ktq5&Z;U}j_2J)sN|VH z#K`9RzlBbX5MB9Gvb7}s#uo}wtNy17#SMq81NIjG14u^&rw`*eAzw+oQahgY)`ix` z`~Wr;IYt>sHNwAcLYnt~pq_vfK-j9u`*_G0_3H&)3Ixx%daIjSspo2iYipIB5Wn+3Z|sdXE&vcphs1z zJ0=xtYZOfr2EUuiFuxsAUUr>Lnr4b?$7g0U+=9V-@aF)A`&r_oZZp*6D=)nTfNbkS z|BTUX=Z}4+<$72Fltn|ezD^|ni_4!3`C;q$#{_|6jBC#8Gcn|o)P}GbDE@bo{e2H$ z>WBzwjzbPC7)ur*)X9ohQbTk>k9bFJ;c*~GnE@H}RIWJDWz%+IObY+r13a1k#=_B1 zTUB#fHV#jUOacgkg$J@>m*NqqRG&?uS&3(S1YZ`lr|*8sQNei2XQ$5g+XkmB^M_Pe z8vqP1BsZS_W!>u*8KzPM5l$+rm^V>iHo<-$elgp&4Er3m4MR+}bw040o=;X%ckce3 z*@UkN^pNbJY!Jhd;^7CgX#63C#Ih+0PB4n@d-2(`u@R;7QYX%(?*ZK}n|CMGy86d7 zI(Bv0kL&IVYwme`mfI3nV%}GEc!)@dBzIJyr<0lFq9`5*`u0ipM)&FD1vEryrvDVF z$X6N8-a-)@J@pO${i`+i*mY`1^Sk;BPZyh1$~ysx@_Cgz6&{@$`HgWawWfOQdvJh8 zQJg0;c~}fX4aWX6b!;_)1M-!z({o>fMF)LHJOkiv4@>epJRNx@y+HG^9axACLaIAKaDZX0+UH>y@AL-N%TD|xpRB$ZW zeesujnH%cS`LZ!W9g`q0<8ZAre&NFqCdSKiBRV)Ijg#%Li&y01`An;DmJ25UawK` zf+;gC6G)js?UGXe=vuB6Pog;{HPEI3SC0>el*ss*GD^iaeJCu|f zl3U$N$R@&EnS7LRGuYWlJV~AKc{HZvNcQ%K+a(eOj>5t3M*UD)^1CEo8 zA=Y6`=tIv8&Ntv{`J{zd7}DPYbIptbQ)x{Z=FlGdu)@I2B#@gRJC-1Rp!kvFH7=du z)1SD3n#_>?wgqCn#ETYKi-Ql4d;clR$qOB3OAFct$KoMa(&613wBT(UJcgZG#A-_3q1uuvb%sb-#Tsk*wi${)&+*l3g?zQLyTt*hozZx$iSpF$Y^-{x@5giiMxe1sA@GAs6Y$`V{ zAg=a0WXGA>3K4v*ur647U37Z8GY9t{-vg6gj=qFRE^3wz&O+~DHV5LuuN#OYl`5w^ zw8E)%EYh$z3HE}6hr~z*-qsT5$oNE{qhv_o3dDQlcJ%D_TBcR7RG0izoD&*ULfqBa zCqJC>lb7Uy3JP+*(ZlMCoJ&G+y{3G5%_L zqF+OAcw^W$#?5D8HdTAA+1@=7qNXRPb+OB;oWB~KrCHIe1_h1^bQOt;_&9HP!*Wq= z6g21PTmyb{%=a!=yT`MF$7`wlM6`jUXvBnHJk9`<@FK<_*&mx@>p5EhAQH2MlM<%$ ze-Q`(Rzv4(2=h4-U@Q5UCP1uAHju*SnLBwBX@LD!|KgDTxIG~$@(wo&GX-y$WmE_? zBlyVbJ^L`(1l#d>nW3VRAs-X_39Xctg`+pY@=HhPKn4iHe2pr9Jy1?9U$o6d@wsll zeaq36u1^4Yti;~0zic&J2)57q#rZ%A_$-3T)XNjrBjzIN(!jx27Q&-dRW3D<8tC`V zZP-^B7Wql~s!*;c;7a240*wu8c9q}-#!m>sMmqltlP%wfHc0c$NCbxo>|yC_~H ze=iv=^I8nWlLCiB7oDuv^=w!ZR_et}yM8uZAC!H{$MWr2>A#00^LCmKSkw%CXBGuh zV3%jhU;5&e44LTm$A@yw4cMFv8zr>gG`SeWQq!Y)mcV=s4k{V!E86;krc0Rb%Dq7f zPXNVD3ky3K%G2TOG+gCmtm z%~60x+CjSiXEK;&^wN&Sc(B8t7+z1$k*(3zO`J!)<0rq&=3nl%$aV#!`FUb>&>JEw z1l$C)N{P=DE5|dS=#^i6Vvejlj`v1{rV05%bazEk}LMx&C{ z-a@;pW|t8eQEMI28^&GZ(fLys#=U*(bGbY2Eo5 zhz~{JjMcW8(_OS(E&Ap$zAH!Lc8+ipeDJDKSGXu7injJ}Q-Pk&yLnr?2-Ko^uBn6% zmX9T>CP9d(^j@1fV6@i>;dtOblCKsB`n;x!9hp_Xz*g!AF>Nh4q~#uTU5nl3J#=Jk zT#l5eph9AbGwtNn;6LMjdu|ZX@^s)q2o$h|t)FkF;vZDjyjCEI4HkXO@HW_1H*u3( zKs|hY6U5ur`>nJ^-~3=(Ee?MwS-;ee3dG|#2PveNHoVja#oy>2koj+tgjdNLJiW|~ zQnR{S9?imN^<1vRB~Mk{>}_uSplg2_Pv4;=kGtp2oLO!0***ulsni>p+2zuBe&)e9>FxZyqgI7*}$#@-tIo*?=Auyn9qfWT<0yw zGstEwIO%unHW`hDjq%;-mn-1zh=t1_eJSyj-Zo{D;>whmDRmu&f+3JU?y&as|%M?hnhRF49Up#@-Du=0!6UjDy6NV-{+U31ZR(^`=FhEvH}`!;jx z)IFb&E3wP>f5`@j@5p|)8@H=D{;~S6$0)P&i|gbMv+PCxNsn9~XCAtc02z^xp4Kk^ ztqpZnq#R{?zMAxt;#%{8={$T=%nLuQnfI|MBQ?UEsdWJ237qG49_>}XeOdP!`ZeR& zE?}#pU1yopmbf=bBFA=7qBbj|=u?BSr^&ITVu{AEf_JZsA_kVz6GX3qEx#~Sj%K9u zp7O6bjQz zeh=p-EhShFx-ckTE+w>wQ7pBhi0oC%9TOf3Rw1V8_YG649eKsqQWQUybW*7gjQi zc7hIzzY3YnB{;k6kv_TFi-rFFQBm26-o>iHHgN9H?;_HdLUcS!l5dj z5e3+9Gw+wMp3@ybZ7X>cR%al;ltP4#F3yl;xo5W)VE*mS9(326e=`2cg`dGo2(6Nm zK(!Fg6m=~?02R2a7>A@A%xId`AIJ#VWdJp)AsoE>I`xwSKQ5?EUMKH>BN*bDaplhh z#M%R9loG6KV}%S;J(%^p5mJz%f%#>cX)(Id*ztzZ28gq}MhObNXaRT2U=};jSdUif zD{Z-c89mM(d5eU|nk6(!;Z|LdW>Qj=Ff1*@72a|GkA!bg={%z5v#`Bufs5=x2vZzhTISQ?Q@#QF$|tJ7Ed6{e zO#~DQJ4V=q##Pc^QW~IJ38_&a_zl9X?dKwfGAdZ@-!NYj7v+VluC(VP$WIE^f*X2q zugQoOM>G1pkmACXvr&3eg6`}gCgtx)8`?5Sp@3(98;S$J?LGJS@c-#HEbP6#GD5uWdMo0ffpW3OM-v*ylu zXf@bGz7FWa>+QB36= zZiAl&g-GfIx)ic<6Ew7TP!25ROS4h zsS=J;;F~=2Q#_C)nr9j5Pd@Vh=Z(I4Ic3(rh4{j6$83lcxxYs5|E$8L~GKqz~ zv=0nL_ljJw(%OvRa#mr)B1Z6miOhVVV!#ZX&^WBik>&|$M3&ha3!6;LgQycMS?0b* zv=x_>O8Ni_rCBVnIJrI&v6OH1q>W-Ykdl{AiaIud-B#rSdl%BFW_V>PG{uyxi%Lo& zv#)fg8SpY{DRP>UP(Eb28Y-xZ)2xNSe2LyPC**59XeUjw*C$6!^R&Qx5Q(2{VSJIN z9kWU5`jK6?Cc8zefulo5YXl&QTSrtnK)J9U6PmCB=#S4P{(08HJbkT zSxpY?8g=p|4VAScUc;`#zu48BiE6X~IE&Vdr{BfJ*=KOk_nEVDq_cq3ENll~Y5cA; zZ_}B5b0Q)HJ&u=e7Z$tYiEB?3=I||N{`P!1yNU>($*&Ei2KSvHP8s8PUK3=qh}Uwl zQ6*jwv(5@n!!znFEV{|cNxa|m@OXX$(oDO1cUGh^p>;kF_ea*rEx=Z|Da%);TN9-^ zU~#ylXb`Xg?VY(<@&Y>Dz535|#Wk4Qg#PzYB86e}@I3j%vb{2uXOO7G|3l+;_U1!c zs{6L=_MVGB4jM-Lb7CouV(JR=yE+k?F{p0Io}nx;(D_72gja|{{Nl`eJY1rL z8-}??A8!AzRUv%O{a5+Z#}NAPfiu%mA8-Emv8S1*WkA=>{LYc8!6TFJZQ%v?B-6(M zlIta4LvmB#j(f#p^2rGDA7$e!kY!}Ex+PZ7`x<4hZS`!rr0Y_TMy^1)q`Bk$_idmD ziF|^)KR*iFlJ(3ENhge+=M`3{g~7*p^`XS1-x~k!@p~#vv=hv0od;H(H?NzoqKdZo zMFf@CVI4dsL*BqI5?eHTsktqH8GPFQd27>Ob;ES1DE3^PfSJ%nK8EXptVTXGWe99L zVVGT1oW$B%V(roQcx+DXEXm!ZZYF6&oT9ILYQSo`C_kGgWE;<)ZDM}j(LNRQAzd|r z(p_Lr76>$Et;XtTS6e}6sk~v7|iDyYV{ubC)BR8T|${0cwfi6gwZp<#ru<$aleSH$h5Ve5hQGyvr zd^kB`u|DLjSlKlc(=pNurZyint-Agk8vzEv#W&9HZ+Oy%NjMdFWyj3;Wt0rOuljuQ z@N)F1Ru&vpTj4mv_8{o%FO;l<aE9?>d1z zB}Ph1{kzZcP(Q?{C`JyfaUs<($~9P$=D3+z7i0c>cxAgxPcWXP&PmNP4MSlVfRs68 zP?f;V!N1!_*>88H82Ok_I0!fj#=S3>;#|q|iw?UJ67VgE@~yNLoP9nV}_0WSV1$2ysw1mfeb|;!MwK^Dn%UDTzEe zySxMdA=;2qg&@&0ZWff~u+vmwMk{h$f?5ZR7Pnf=?3Ha(+m_&)?uu)muK2;a0YA7&y$Mw6v^;+X(!!);b#5(#2 zv*-sBeposKr7T1&*FSe`AoEBGYB^Upl2(%se!>lw!xKR78&-&X>jvYK{=uP1aY;Ki z-p%v^cZ`36wZOcK6)|1gc9d8uwC!AZ!SWN?1+^LO*_1w2L_3@07{O1`7or>crB}P zG2MG)Des}{C-TyaIoRBFWgzb)r~N}`I!j#Ft5t8d=I0eW!+4T^OfjelHH^`G9}awZ zb`jx>--RlC+d>iW%g7Wui(D*#lzWn>vN$B{XV_+CsVK^?26XTyoA=%Ch&Q67Nbi=SNF zu@;KzA*8oe|Y{K;Z2xT#BhGfMD}5wq29?T4p4C$dj#pt@gN5g zZA$sVtl=-=i}ZE94wUg3&0_CM>kt3SO(L8%g|}BB33`GBz$w;o_s@6_zWTM7t+eMo zBd%zn1@BL=tlz-QvE8mwD%}999B`(Jt&sx)Bs2hif7eQjM^L?=O(|dztjPK7^euFv zQIp+}Y_lSb5@2h_f_(wEq=g&>8dJ1YZ?E*y=F78n$jP|ESh!a2%Q9P7;+@K2Qrfl zfh-B7^+MxmpBS1L8a5&l)F672Y?_Q_ICOASO>JBLo$h#X-i>8n8XjB)2`XtQ$0(@g z8|H}4gdmv(*%Of1De7x!8qKaW_?@!feZtqI+BhN>*nCm%TwN{e2ZStKWBTqC*fmz{ zg^bV+!iNcs>GSq8xoWRjDJ$cdbC7zKzM`V;N=-mR?B_9yiYpsns1d|prTEEPLO)^Lr6*z zNiQ|Db4K?h(YzwlTlE{Bz<$;}!^>X*uSHdluhoLZ@Tbk!zwc_z9-WiCx-JeMT_0y^ zh_NLPiJtkw71?iKp8wk7^6h{9iM^Fs$ESF7GxR+YFIYC*qd@YElbrk;Z}~WL4v(2( z*=L=4Z$DUn1YywZvgtf`O zbHJoJ0YXGX>B8?nWM6Zaq$b~&9S)1Cp8~k5HT1j@-nKv9MHRiq6sQ?%2ClvA()oK9 zIQ~k0DP~=mJPy`zcUPvv=QF|NSD?~3T3Py4}UU>~td#h@E5R2W{AZ8gO5)@gS>Dq{NMkKB|4rlhhO6^zA-Oc}*DZE_A znrvw%9#$4a6;GAqN!0y3*EZICLgtt z^M0B~f7`1Owq#bq{zFT0UAgSQ{!8ZJ0`5}=0Q4z!l1cvryO#*P*Bh`j$sHAQo^C9a ztwGP-%pJ!cSwYrAGSk*9M`(vSMm;4?wEe!gC0Oj+gZ#r^zd1(RSi;_qx^>-60CkxM zYA^0luf|%A-V`%U#tDQH4zy#F$QLeR8z(XP?7V$CAmOu+QGtIq6<-5)Bc9 zq*79FsF-YSpa{t&-tz=_z277lyNGc#5L5YvGWeP^8GgcJmun7W|1o5 zo}cI~ZfLYoE~^hU3MN&(gb1!V?M?7XDy-MyB1)HU5l+!6`Ti#IJsDt0jgnX9z74Z}4u9Eelmxyzl z2{^B@2J79IqzGqZS%Nf}C>?YAQ2WcArdnMRlZ6(laWEr~KEiGm$3^&x1AHPd>bTsk zu%VU6uBaE`DFr7TIVw2DzimViMbNGvRU+bj4ke%*piglkA#nw7F$p`j-6L5uthgVE z32&6lB$ABn0%R`fu~>!1FUPB-be(q9vA?~CU}uW$T%8HC4JU;)rjU_|>c3FqTFwmq%g8!z0cWAnqa(c%Sy3 zDUajN3JZrp!hlbvK)t~r{IxmycS|hfPZD1Hmbk7d!*gT@_S_GrBaX=e3rw@v!{!dm zct@YIbYjTHLVMGR$%-ktcyM~?`&annqMO`hfwLDEoKAnyG8=2blT^uH8#!>b^C&u{ z>2TIR*n6|}(oUrBn1q4@WYSrwIA`fi!SeGnbi@rCd_}{^VpB{K_pBo2afS{ePEbg!b^`rQKn+%gvG-GsS7?Rg?ZRGJYhQ+j-fe7@u;mKk@8I5j- z=kRgn)N<%qcse`_3KIXt53J-&4FRr~LFcNc%sZ82R>q0Q5E zW|`@<@do4B$RNJZ~DywtODuy*N@`ecfg)NPbQF z+>z%sH!A z4riRJ@aHUT4ZX?hDmJa0>Cu+!3CBDx`1j3R|Cnjad|zDSHfbfbcz;ZFJtTvfj?%w$ zeGoCcBgTW6f~s+cJWuz+7773BfI(LM^td1NKv&eRQBk}jkkGW;*Lqo9VmKzU%L5zBi)J`gNHnFz#m{2)ZL0zHxXRQTo1EqR>p0f|T<8CP zy3*8-GB&X*Q?h07&uFcK4`=7zVaevY9`jK+UY=L{T)Y%bdn6rX)J zRj84sqwIhC7krudF0NpQT17bBIH7qGa7QmMOrHyo|i+m zBSn5ikqQ-Kc4P&yixX-*t(EpiRj$sOXJiChXqOD8=-@Ly%vT)CFRuJ%PtdaZB7k&* z@kdrQhpC7k7(vcKPGSZ7fs{Sf5_c`?*70GF*o+GR9}p+S;z1iJ+TR_}PK_WSu!`gA z3ut9n<)AaLM`G7?X5bkE%l~T6mi96hk}|M{nZ>zk2!x1;E667iH3iSGt(Aw-P^2&Q z`k0=BTWS-dy=`D5qil(mdqH5NDLg?5w?w8pSMz8@)%+INK)W>aRh-DwXm;+eWhK*D zqz^KzcTiLv)*L#nbik6*Fj|cTtf)4t2;z0iXBuSgi8fBP;X`=Nbz^>vDDMZx-5f?k z9Ut-(wo{4>i1Z^3qJgA7?08eyqM$l5N8jFmmkP51;aP0hEP@J|9~_<6@T5ni3HvnQ z4`qnIZ1eddD>>m6gGnKGhlVy}!ylcWW^BczhA@4&HpRM3=QfOcL#pu!x*L>A737A$ zm2ZRrh-PECMD2l5x{LW}CZ7ITLmv3)UicODqP>SUCj>YZ#o*h42jX=~UnLqtuQOeS5dP)}LBR1}!qtVEQ|3SFW?BHkK6Eqo&P}o%>m6kw~o}uj8sTjkA^2&iC`>Skq9sbgI zT`xaVuvRNNSLa@Gy&}f0B&g10Fii%XkXxq zOv<#R`OBC3kitJunyhA1+FS85Cal%K3<)ucF1L+dNidSDJC-CK^tNbyf}Szu9Uz`f z2umH5eLH+`!mk_#WB*xrEh*CjzX4E0_SQsq{?HEt`--w!4EwavhAxwM3xRB|To?NY z#xU2ARfLm!cFp(@twFPF0&`cKTjjAA4z0yxrawUKvAbqc^873R3>G~cGdS7j8bQ%2 zLj|MF^fVn$WcaqhYhn`(pRiFMlm?O5>4*cB zx8c@kv;ew~`<0!nefI7Vm#@beLz1xKhJ`-97EBNkn2}n%KlL-coJ4WnpP-+YtE=-* zKI((~UUnWnx3v0a>feq*9J0P#Fu*o*oKPPoIT#2hsxqz4dUU;-^PW0+E8)%`Zt*#Y z;e5O%-kxmgD{!s1{q$N-Yg#p@BYI8~u(aK(Nqq2mn&mfk!Zt?BJ?|)ai$VA{+0T|# z6v?0H{^eRR|ErYfieRu!q*^EiO_tfEv`hm;&ZPC(th%g0!>Sx1T6P2cskgPzmeD@! z?aIo5AJE>y{PZ%fg;dZq8TE2J9&S8^%w`MGvSQ{Xdn}B~Dp$jLpCM;y!PO1?@F@i< zYO#II6O~h$86*5#dCxEOVSix4Tly~=y@k3K0{;8syl1F3gfUZ}Zu#>zE{vBjL8&WN zPhs1vqv*xHzj;$*dsindQwjj{Jwde5zpO7}GwFpi-omPBP?>i7{08mL$py^lX!1i9 z81tHoTVGG;sHgl+xvvQ=2T2=-2L%OmhAJOGQ_XVv)Fmp$D0jV1D+$h+YSq2>+50LaLW%rL}C|o-}Aj$s%KVj z%rw-}Qk)ZRcrTt4$-3qoMmG_e4REe1QDVoYLmrJpe#|ry^A(RVSAXgw4dw@P{I?Ca z(X!S2;lIl04hAW>A(>|uYv~V8fFE(=1O2fA8tDk_l4J<=bIGGi9OkPT+UIo5vddhy z;|tK!B6s@Vy)kGD$+lu}v5jXf1~YU%3lBjD3lm|PpD&)z>=wSFS&{El`L>~-SVHJ! zFX!m&;*EXX?co>RsK+@oQ{fpiZ&LQ#7|m<~vmY|iCwPb%X1L+3qCuZ(j0Wqu?I^0R^$uW)IqHuf0oZald8}753-YL@AIL+Y`SdUZ-Y(x5=5IT1S!@4g zBVdXdR&2{$I?aEN07w2qLwx=}G;ph)SPEs`NtXBeE+_x9r-I_4WR=Kkk3dL7L+^%! z_g$|#*D-}8@L#5qZUm6y1xxgr=6e(p2NEp-y_j5#h6K-}C+1Y_jQ&*?AYkT-4lsU~)Ttk+sV88Z@2PW%Qo;kBf6GB7H}SnUpW- z31e($CP86j&9jS;&}dp`>4QUN?<2mxYFceZ+V0#-C@^*8$UfiQ?`iyojN`B~+~F;eQ1cig0{MhHVaD#lm0;r- zdlk<&cV93eE9L_%hhOF@=x*t*k)XjfvF?|=3@RQ0EIe}{NA&9JO*l!E(Ni>~LLWXG zX!tU*-1z|PfKAWe*kHXc*j8IjdY@z>NsD1tI`&5cUFzg+SwTEzt*D|U9%OzVm^)aM zcv;l%h9{BD6Ig_>Om)RZG^L|N!)w$9NDo_NTFHplr3D6V!BwT02+?lgtd!b*Gh4LP z72P*Y@6~iI;|9`Lncqu6U7<~NhuF7`a{I9{wI>?xs61viA4cX(N7yUNb63yynWHE7 znGzhiaAO?t3?P;~Bt4V82*2p3L2?TggW3y=a~MWLMeeB2SY-98?ex-jUkB3g4p}3@ z$g8NX-$u1~vndrpl1V%R5>Kw4r!n)`&cZC7OSRO9{6AzQ0keg_h! z{?IXT7PL^!70zN>Qw9QznLPU>-%lLMy}{5{_mBhTL-7?MzQRmu6%Pb_^D4-b@CsSo zSe+p*RJ7^sl-fD{6}B^N98ixP#ux|g2kj{3;^Y^!7;#*OVnRbh@L-r*z*W7zG(@Rt zg#Z%(YoQUUhAEEgGBfWHrW&A)!pmSDo2}pcBsgRF%h^QDa05bT1QH-3$0nh76@** zpByml8#IAgOhEjQMuVnk8-we0b|)@AXSTVHHyLyJ7fDTLY~c-q9*H-fr!8q#i2)-U zNh+n-ePi$XzL<;K3X;*oFRV~_;fiDb1{U^cOaj@RhI8uJE%ElJ2T0p>of2P>x$MSwr!OkNPco9^HKAU^z5^P?%A{NKy3~< zD>K@Q4H;!5y0bxnn%SWu05;5JRC6xQDQdmn)>RN;6FeI^pKId5v?7*xiHc1{*mXHJw0mJ2DBZRK~5MY{xY#~Q44hnF?% z$E+7%8QtbfNT&>P>0@ZB-m8}R6S#~_iWodLw%FX4Tu8^|w%J-;=ZW$57Q7+TVM5*} z*=lnY;y3*awT#^8g(Q>s;erj-D>Qx8r_tYjx(O-@EmYJ`DY^k!O=()AK@7?6!tF?j z)3QOFFG7ni%nMMAvIGE9mj2LFS#RiZ{D_Shxm^o`ccD2%=<}|qTOTTKodqzh9I3S# z$PRxR!PU~{v?FupXD{6iht{UJ9@h{D*Z-I#44gmJ4mT+ncx zzGWCI3p>mjbSdBO6m-XIfXGPO0RoxO&|A%*UGg41j8z72B%fr7qDg8fri$sLG{{l- zBwx_g^Cs3ChtS8|3tym{ApR*ye6XH(Xrp9@Ks=}zQwJ%XtW)!O-{=id^ol3IxKRtX5H?QOnvGB za+|N$FP6QBHSiHajf%k^&q#rwSs{sy(_m`eg9H|*Yn$Q?@9iX@Wg zzyB&HU3bO|%N;oCzm-p>>lrL0c_;oU*lg!7G4;J>K;J)hO|eL-7aTpE#eV8q&^Z6| z&PsxCqARj*cF|&yMXK(fZpAH)@2iRH%Z9OIV#Pbx&2g)*z7bpk^;%l!wt;{crA572 zW-K_X$zFqjhQ{&q$^z;N-f^GQ3#yPski@@jRqP0k^*lGaE%xvWdUqQ~%0_RwUY^YP zmh<*nCN6%Pq0yP9@I3HB_k*0gCi!UrnJz+$597TF{mLUmc;eUUoaMq{(5C(h7jr>> z@a_I0zXq-!qbzLoWJOK`7e2KCi`smqBu>MD!#*MEK|JWHk=kJlcAZdviHrFmd2QC| zUrph^2mR+r@yEYaPR{+X^Z}&Ga!Q+cVy}I~amu}QHkn@(B_+G@>mT5tY}a36FolV6 zO{dxVj(_(*j|zXnBr@pmN(y~!NFD7-5$b#1mv8Ed!U&zk++GmagTk}E|Km{bx0rtT zl8E@fj-wp<4t4oWTx8$J0FIuRf&ISm^!av8+UhM2kq=$*U5|MMUxOIqAYPbOswWzrvF}30oot zI$|;;m*z0pq-Etv>+Gunx4|;t4pCyr%l^UoC`dz*f=84EwBtR96nuuPHx;!~#71x< zv1~#lGZDOhd~TnW29icb2athDLzBAVi_oT6dvY?;isNiWAM|x3w3Py5HsHmY<%GNX zt1W(-L19A{&VR`l*OF;YyzZJ;a{H?HGbX&`DTi~aT+0%Tt*^m&nK&YX^H&!*d#xmo zQXJ1V~d=IWAeYyR*Vglzi|9~4@1H#A`%5=#ZTRE?pu0(h;fG!}#H$wn2 z=plYkc-<^8u-7DiFy=j{6w+mRNU<_nrc1K57>iAT;kyeh314h_U@#hnn`;AX3E z!WUAGa3(GgQL0WmxjD3oG+ut6COOP3k~yw?&L31UF3ZfPU#{ z?Vdwu!tVMk61F(B-XBC%U8!;BFyiaUVtCl8i1(qr3a+lan67HvvA zUP9_KUWGr8eQx?dvx8WuO?$P?$rI^V(bhgdtQ`&?Qc*7=%U%d}MQ2|-R)0UCha8Ia z!_ZgaLqbzz9-8SFZug;$@I>%(Z(Tv2B{fE+HfUum1x(L82@i*zAj~|iZ%G#N4658u zUp7qu+;Zo011R1!Yzft==?|c>bYDip0_&>s9P1Vir>-g1b+kc6ZG`{VT3d{Kp|6~P zLkg$ul;+{#oqL>t;8t84OOz9EP1Cx$d#R?5=9A@PSKQox+1f z-wLk8K)lJR0~{w1^X{E>z^Wa@L!7v(nq$(=SnSO9oqk$LnjLL6TPZjxR%8VF?#VwPEbgw9G5}TD8@bw8+aIgg zdL^MP?4r(`PNJ!^yLdBkcWt{y_UO~J+?1F&4o3unBthZK-3ei@C-w@gX2RWC({NEJfL)S$h9>TtPtfzT;C^}=-KZ|fJ^t+pu&~! zf=%CW_V-JA0q27j;>nm{2cpuaDS3#yDduWmZ}ymm6sn#^pC7hW%7XLs^Vr`Yv|k+f zmStmTC+!EyZ<394da>UP!ND4|mxoWsMb$TR}WYl3_gid91U-Ym0zCLnt3oS23 zsso;T_(syp3E8@XJ)5jVOUYifN;EHI5l>J_Jh=6BAxtO0GE=~QhPCwDp%sUuQ5YFu z>xvnd@BFy!j0Y8eA)0l;CNz}Xga{xTnTExIuqIHEWH3=nQ9=yK`YW9jt3Q;*R&uQ3 z&sN>7{j^;!1$;ftMN}a1WCU8>((G{qSpuEE8sHAfSLbBKXf78Y;45mXxT$02hf(jL z=q))ZNg!RTc@TWz#pCgX0=fk1jq^x;*>*t}M6xG^-!iC$Jiz9jyHN~kKsdEG{Aq%{ zN*bNTJKX(=4~~Sl+?=}d)XEX>S>S4=V7lr^2g*~ZM7snUzrjumtqC9>S2FmqR4mcX z0V6f|5plFf()QFo#9i|B>e-8Wp{G*?-;h^a9zaKWB;4uSgnXa7UN4s(# zk$u%-$0wUg6g$p-;W5JRDYy9xan`6v3Zy0sP6{jQKhzy0w=VU!<=$Y2T-z)Ac&GA5 zB@m=0Xal*!%fw}jDsN6p;J3`rqc*(YoS!loRb>s*_lV=ozqOMr_UrarEn`-EtZmTM zDDB>R)f+9P$>NvIocK&ypz~youX&gn6mMS^6*cFD$y6z3{J2uu*fTgBc*9j3F(+Ju z@Mdxad9{Q@e5MkX`g8$ecJV=-QHziZV)%Cf8xK_)s+jXfVk95=CE|P6AAY`Q>(@C1 zR;zYRoK}~<<1EzQ8Hn+@FZfdtgJL>NT!V0gK3LiwnKh(&jc}(-?lr}A>>IPR`@;kr z4ROah86R+hE(E5yG*hQ6RE@^7!jWU`#Kgrbr^i8;QI?EoATAe)HL3rts9`K;K<1m! z%iH)wryZw(^yWujLW9o{t(lByr8!8@=Nc;1<6*;&{(0lTKmnpMEH$X@U#;MS5iTg; za}db;?y*Mquu!GT5y$i20il;t1a+$1;NEq`?BMK(N-9U#X2Cw*f?$Z|2*~Wq47%L- z?2y&}@PR*v*je}wF7ZJHoa8f5j1zPud7k~Wc>G>YB}Tb6gP2-x9bAvhQfH4We41}T z(+Nh}T@}Oo9*Smv@}VZ&GR$3X&TOnkD^oU4tOVl!kqW{J@EJ?z5f(NhbukQdl0xoF zv{Wr|M&}F*&uhCf(VJjXC5&Ma@d%l1)d)mrpU_MLNr{b?_t-ER3~QrW;D`Nh40c6u zBR5C0KuoCZrzzjJPia%W*ol=f-XPWr*N!2V zCDD^GDNBd3zz&EaslUU~;x45%QKxv?`+hP6w*gIK-+^X6iD!Cq*HDOzFg2J{@F<|g&Jfp*=2Lb+9rq_@3&d6=%|tfTV8gj5N~&}AY`TZFhOUlL+Zl+QfzVJy%H z%qS~sf^)Hp@UbjL%0c1Og#fdEiLHbGZkZdli+6ac>yLHIkeb=g@QdK2hxAiWlx!Vf zW-e&+S~H+-eJ!UVPQ9YY6-ITkQ^2tFyj_*Pi&X!+03rVHq82kRK8^!4$0d2LI@DN` z;D@%Z{4(Oxp%Hs4AyjRV-Vof7m{Fpg5Y;B-e)pO*hqyvB(%_)X<+Jfk^95mXOm^WM z-|bjmNBlcbyVN9|3)rAg%qoA$O=#2r9nXfQlM5bb;yZ+&^5wB*9RD(M)um7CiWC5@ z!?qfVNiM>0u;qYl>0Py&q=h#?QY*@VMd{FcUZ19+evTjb#m3o4SH+S>lg)-*4i$SE zb{is{aild%CUAX7n!$xRyx(OHQm5d1eRRm#$k2P>Fgizett8|(l4%=7N*hW*=k5Yj zVPn;-yI&>*G+K#P5BpaXE01~IX?VSX!?rQa#O8E2OWsJvpkzI^lP9@R)an>dlnS(g zQ(R7m;*S2cO`4Vu6Q*-Bx=ORbqEN#$at}tzy>=pUpxP-MBOqSbhd4jVIfiivb%-Sjzk>N!Rs-?^P9QWoB_L0_&(aLja|r+qdLm~ ztO72xYaJ3or|rI*5Vv9W#fSIHhgHjQ=w?KNr@ymSS4z^IWpKP_QvOAg*;i6C*O#D_ z=|#b7U26^sm`GYX2u}J+6S7Gy;M2_{JS?x=DqA{j_c6R399V`j0EoFwLC#Pwtpo5J zE_!Smih{Q)i9hwyqph5QRtm-3b?tQC zbtjABJ?Hs`XDwGy^@G+GE8N0iN%3@}CkJRX&cnuW_|}Lu;fo7QC-iZF3$qJaJ&kZA-{&fBY- z;nWAxcNJul@a^X+!6;#F_VV|LNmER8wVbHmq4l!PJH0&ODKhPlyAxo7(BxUnn9phC zqccHX7%3|OmLZE{(np4dvk}&equ}610(@tBjrnW{X^%W7Ncq4P7ka1wd*b+Q6xi4u zm|M|Wr84^-$K%M{_W0qOtnWGZa+IU7ZdfqJ_WXCKOH$+g1e$lR{-QWAWz_>>-h6NG zGRx)Zy4vl)Ph}*Ri)#>C=p^iiLztV+J}bx{gpU6E%ZKX4!oxdP*yf|!yp=N6!@k_V|~bcpAd3#V+e+h6#ZM16kexjjz-F> z@+|oO4_Rjw71yF}>EP}T!3h%FgS$h3kN}0dySrO(cXuh=o#5`SK?`@+u5`X)Z9ypZ-fHQ8ONbg$h+RIs@R z3pla#qK13x8_OJobYZDE4p?a(ktfr4WHrsYs}EtNB9Q%%5GQ5Y1HH$-*fpsAqc8m5 zMZvXjimz7lZ0qJ3xtuxZY(nJY!S_m$_vls#7gd(zYTEcVYpNp*HyU+acjdn>g|fYF zXt>wm$d99-HiFbo_iLrrkv}{5SY*clYfea?^h#kwk2~ zyrPP{r%KN^7gGfzzdi~ta;|th_OjbIY0MWaBz6b4KJJoVs4C&={u?3@SxO*jAoA1n zq##=Trajr(`0$fD!|%psUN;~;m9iqRl)xFm1*RW#Rk!lWQ^C-PhG0mQJXdz$zd@(O3fV z5(shG{A@K#jYmD^+@QV`p<}y z7XwL6n`aQ;0R$-6MlM}{_pm2i;^Ha4#uDiD@|@SMCvBOfrM}l zw=EBxSmU}bb$DodluV-{JU7HdrLW|fmFd`*eO4f zWV8o-gW};{`roXSgdaea&vq(^k)A=#P zPcf=@O-@6E##>Wfa7@r184ns`D)j@yyeXD&)Bi^9p?O$7dgto%`v@pd zy~+m%o6);=$+wciU&|ICm>A+zUt>Mg3WxSQJP7}t1RZK76Nfyu65H0D=YOuT0IeM^ z?dr+Ezw!zTqTJfnWTFHWe=G$|*L_=<=p4yT8#*__g_vMSDlcTwHAixZnzdrp7^W~` z*O-upCTb^ceq}((25BJDEgZBqrKDdn?lIas_pq8ge+mrMra66vs&EDBQtjLOvObt( zE@Cec;kPPP4UGTw_n7fvgX;~$UBctt_mAezT~zAe8%aKL(+LzT03Zx&;~@g+TiYP8 za1T=Lvt7YUU{F9D@)@!=rX}lQyMMNbUaq&6J2`NsnN(V#AX)^f3<@^m4eRo@omLq3Ksla=(w1hqD)(gQmTe5jarNOe( zrZ1E9&ZagaPc3fbZ`hk3*Y@CzOp=b%-?ywR>4h;IKX{TrCSp4FHxFsMPY|go#CI1m zp6ahD0D`<^ksM&jLBH7P4^nz!2Hv%}47t*JimD00RiH0sGti58J*iXu%#8IpWXehO zC-#0%wkG-M_MX(rCg#4uX5Pt-0%_i?RFprX`6f>J6mhi-iW}dmuc-H7_zDAy_NLXV ztZx+raVv)f=L92i0M#g2mHor$vj`GbHJVIGT} zaj+xF-W(qHa%Y1XE(#b3*q9{UT>}$>`s>{%k~keAD9p^NjO|=~n-Eh}XzoQksuy}K zL_)~{5JcB|Hb;&zfbWZM8>HLZN%(bZc~>Mo-9LC}7X5_2VBBRnyvDV`8fgQTH~y@f zi9W<6y&hy}vs#yLpjO;aX=_T+A@G59QdFjdTi75wy`4-ghCI z-W5G_<}kFNgg1|U@ZY$TR7+F0R(jE1+^88M)@Tp_@03wfPLpApCUj1WS3s1`t$;G;r#SQ#>#7`v-E{R-!^F#+Ee6b z338;pUNXiYM-q)l|JNXfpeeHYSm&W+eXig58-#htz4D8>kO{^H`1`rcfdpu$_`5Ds!uP#1wkntL^;jq~JWjjHGJS9_o z$l|t~^QzZ3RW4)OTmn85J?(2Y7c+2b4NJ40Z*Wt99}31@ys->9X}pWbMG);KjehT`GEtf5;n`{`o+9i+a(uE=t;qpsconHpIf4p-$_2qw{7nVb;{h zeZ3!89M?{;Pv{C;>y63ke9V)rSYBkiyYlK33x%111amI`w&WpY1x|Hz z+&o)Z0QWisnf1^O-}*fq{<4pzkE{uLUWjagQ=&4}eBawoj*b+UD|Stk^*w zXQN5kvH!{t9Hzd&LwXElMCX#`T4jxnSg3dJo}>E5Mt70tipBSM6v``r2C=CEVlQWVy4Kjg9h?PDSsHr`Zk15y zAvOVMKY4VSje?XSUeMJYr*$uV@Ke#44&n3+MII&5e}rlsWTHzGz>4_&Dfk8D=)cxg zx{Ety+lX!^PeRj+yVIO3(T-T~;~<*zHQ!f}&4ZwREvS6WW@pkfRj9b7TRtSNCOfwo zrL@)%V9SD*!T(8NQ~5oGyC!F<*x=iYqrB_@CsQ&{_2o|e(3k~{3U7vxfDrcWTa66ef*C~^azxyeo(21m!fv8J zS`fZ-hf9RYz?qmkvRHRy_+}TYo#kcDU6+8N3l7RWSc@t`UgCD=ND%E3e7$KT=fsQH zIQnd~Asr1q|LP9&8Y6QRku0gmZA&MO=xOETdT{0~*;GDuim@TRh!z9#eUH3{>@Cjp^8Q#xV_~#pz}f zeLitv^t_4E*uGJ1QNx%-3p)B2k|R@9*#O{U-n5uDYm4MESu9nn9F9@Ums$71Gk^L0 z5qk~}O`_nY3(oHo*3uq~HUvpQZ#OUnIpWuJ=B=y3AsN}O$%TV7jq|4ty%^_`?3I9) zVdKR4H*KD}6IIGar+?o#Plu+}!Sj%l=_uOaN~@42+HOwjU7>ctj;7S^Vd9XOK*C zKFq+uJmA&%>8FFJ#FZz=n>-$rp3zb1WwYhCV80cyWyD9`FyLl?x5fqHpT7{D(56i= zodusUP!qo{wvvso@*m!OdWY$Qx+as)TXyxghNh!4s$}u(Pv1wC$JHli_#z>&(vPA# z%ZmqPh2`Z^Hk<5b`bSu8dSV8)7acU2)!Xkc5IEfx{GGt-C86#chHCx%ihLc@h^GMy znCivyd!q^cUUZ-?9&aQ^&@pa%Gn6>@GF#BJeBTF&J<6lm>mq*YVLS9(5$|mW+hd!4 z6AUewd0Te<58Hc4=q(DY^b^Py^jfzx1J8UJg@L81z7yZJJ$2NRpAXlHK;~Vp&n)b2 zMLAV^yY-IHx9n4Ztj)1Qmn8;_;1ht6=VT@cJg+rP=Ddr3m%kahEyg5veUu>*wjOhWVk< zfSJJ8Hqy)K!*9czWTZA5F?n{jJ4(bmWPvz;)U7FsGH3s+_i=1BoU!-!yuBW1u7ie$ zNVpl#2umsi#s74MW=+=sKqE?r24|93q!g!;)vqe zh1^#u3v0KaDuqg}Udjx{J4gjS2WVn?G4zu|3%>rD7N`0m(+=U`Yl9)bQ);MmLDL~J zE2+T-;YdW~pTYg4M*w|2Q(uNG(dHE&BUa+#w-S0YVb39e)OaBbZLN+bHtT|h50zY; zEyO?vl`p{`+5iq%hn)}A5Xe!u%v3=WO}p8v`Ps-VIKIhgUMjPu@NU*`T^}Swf^w~Y z3cZeRcdTEEg3nflr%mFeG!O@|j?sfJp>ikdhVNM#sIBx$?N9@o0fAeOiP4r;4sDup5Zj!lBHO zzxhklHdA8z+S+5`fd3-7sD|~v)x{o1vM~6Ge!u#Qq1{@%eCrjT`OWL7n!_fzaJbrPKX6)QB<8&<2o?|H< z#|gcA6}6?DU2>TR14~s_zDrJW;ih;nG+znFct@6a*MveGy)l$mRWoR~H%u7ul<@+! zE~j59;;!GoMVzLKPY42CHL@O%3NqjZDm9}Sbm4Xql5#-un?9fsjN;jOxA4-&EfmDU zd8G#CAshm)qTltnd!E-J1-(#tLk>J%&wcGk^lm>vaol9wuEIR;AUA|r*X!&bW!!A9 zo+d*}qGLSFC^g#Vn*XU?Y$&1iJMTS7#CQCwL<~1`{I^*zHa3xnOn7v?vqy<4EuA*1 zBKAY)$&n)8g*$Ke`^QoyG9O9(Qu1vv-l>R8wHSuV4{9qGe+Q{&GLjHlL0QuXlS#v&fLw_w z<9ExSFqdw9vwafL&HbwB%Cj;?x33JMn`A)fY!SQ3G!~{XZ@vzA4DnB90TD_F%HE*= zAuQdfHG6{^e9(9ZT@%*QRHJu!m-~5NYkE=sv})QgGgT@!n&t>oUlzatv{a~KHn#g= zHy2Q!FwFh1H)n?e-LXFyDm7o}K{BDVDUo4*5dG`B#_V^CZg{o2_|WwyrFXnth&IPS zPyH1^t@ddgsGxxJ%$6yES(P_Qt=i;0m8C`(-Es)Ed-;0)GwHKCDzu**)N`Q1)wc%Q zaTyJZY9*Ds)TvMx@1U2k`oNCTKeVuHg4`U^QV_zheX+D$aQ7_7>Ngkk^6qZm0rsJ+ zoA|`69&5eUBsNuerxN1gYMb6P7#MFEeb%*Z<1&sTu{@W}gWl|!HRaxpxLjE^afb^) zK1VIg=%em|ut48|2?$@WQqHyOI1)!lWVnV}-~{sRNLz+li--SrMkYx&+c73>*6(#S zOuWLmlQ73nNDBCeL9bVZi(0~9R)fs01BN7ts+7Y+2+Ol{zuv%}KzSqLVDGhjYeTJl zs||pxExZ2`I$Y;>ZIv@j0j6>Y-kv2?5)JF^;1ESjS4NEDXum`6o}573Ff@GZ4nCA8rKJcdY9liMSfTW7=aB0O{PVd zaGf+~!nYItn_~uJdIf-qR|d^KuY1DJ0tL>V>6=DPQUJP}_Udxm!qkg-JMVfY*@Hh{ z5<$bALimJfq0k)3*wX$%8_aEB^O-AI;TO+zAbv>cy!cO!i{G`Zs06G|dL~i6Y=GsN5{3%e_xj64n&NU-}eVe63(1ZP_ zKASZ-$z+u@t(zPKLiHL>7!fi(Rlo}&(A0iYZ#aD+ifAMWi1djp(nGi#pcII@e=g+Z zx%VPQV)y??0E}zTaukgGMzu~gn|tOYGOPJjFrh&Jc3yACd}t`pFSh!WyKd=|d+#)Q z&OCpQs_M{3sZ{5dpkHb1PEP5H;?28P`%o5*FI+*PO%xx{Nm=< z{Vmk(i%u5`59zQg#oGpj%Wq&sf;2fDSqoYs=PL3kB;%w5cYT<>jNBbUAp$I}zQkYG z6U4iMDJzEFeu0g78W`Md(nN!NBQ#D}*wP58@-onn-8Rxt9i^c5|1dM#Dg<#aMvI>xoO% z2>xxszFf1CK!P!qNcwKti#?J(&`@@9Ao5jrQEv%8yI$IQMN@AhH@9b5Sv)TH z=$HFI4JRe2)dW_ghZY{XGJ(xQEcQ%T&kbnA`NdR8Z{^Umb!!4}GkDH!&O}UuPDmXC zH9qbC&Mg0hoWb9@J#^qDoLr{5B|sIEsv%$Et_}RYs=U8a6$oK_jI~MYa}(Ru7oz(! z2II~E_2`UYIPznZ5xh;)$13Bx(rd-$gqKz=K@9k=f}t2dp4@n9F9MwQ&rd@S33dza$Nux|qe@RoS;>LC&EL{eJqs*osU_Hx5Z$%sAr?bXXJnW{fxuzUy!3rHaF{|_DQwI ziJc`IO^0m8G89i|_Bj{P)ie}iw3rJ-4fb_%Ti+gZZxfJgb5XEt+?%wyq^G>cRc{l$ zxeD@BqXKC29Y35(uuLk70&n-5&!s9Q#WR921e~65vBev;-oaFl;4)~;;(aEAo)FBx ztp?Y_!-3z&of8c%ue7m!%y#lGhuf&A2y4 zM$j_zbQhV2Ie;i$k<2I1)d1PhcS$QR9$UHxn^D8r66zczrnP8+q0G#KC{e9yYHhJD zBIUdD=K!)DAEsUs_~+kza1XOoY9-$k?RRMtrHtYL zA2K{1^KS0B-=5hQsrqYpKV2moaTVNPcVqwpte?z0KHqWhbE!UmSA3I%5f~DfoAv;- zk0<%LxaT`%|GcZ5zsX)S^kRy*@m6m)m3a5`5R8|5K>nUG7j5dgSp#%=Qr~zlp9@FY z)hV!+5umwS--PRff0k#pdFX1Hgs~*Q$I7I=Jug1Kc#bU1Fqz+bBh#97E-B7rY2LJP z>T^ygK8G9f0UMw_tsTQg*h@C{*#r2NfRq&ZC<)Zr#3_vQbOLFO4So%3o=W>66g$qm z3*%|at_HS9S)ayZ$+e6xpGi@EI2s{w17ot}&d!2G1*RQk)plHVb}cn;=m5#G(QRA? zI@_+tQ0v*wCUbg&7_pYO#j03w&Nvk31~{HUhhfXzPBe7o?H)gWI+p$Q_R#eA7HRK7 z;`EfBkwCOyAmh$?E6hDP2CWL9+`}u+G#1oM9ytsH;BuVrQoUVwH$KC)mp}y8uA1%> zVE$&vkVY5hab*a96IMil$*PIb`TDTCbA$bYQaMiV@;N4g&HgFS13}c|IRP!N4^1Gp z>v^L7Xg5b7aM2dubraBPLEL$jl>?5A`JnUO3F4mp6SJwg-#nGBqb|p(5)AUD+4S)U zbk{LTx-<`cYmgWnD4Cje^3-Qiwtty~Y8)SAj7Zs#5Wu@ykP@oB<9u*+{v5kCdkXhwNJf%8Pz0(B+Kmch%lp?3Ne3eURClbR}CGhy$uxYn3I@!H$F zhMj=sH_y;$qaBEf%lt2L!!%n@SGH!|fv6<~I`+`rV;+g=TOOy>w&7!ISjp~`P~k_N zZj+M|?V*tc58_~-cfmikQr0A%O6Ksww==V<{nB5{H}-KT1zqp~SqK7sujs3SvCH-WQ;-Nl>4wSN}i zUe~2isJX;0>v1-1SA(;lkB)2I2ghh|P&e4wPG$&}qOQ9uD=a%9nz^W-a`#PBtod8f zizpsQc)cNF3Z}T7^K^YI)Hzf#q?=#Me zF>xP%4Ngl_Z-ttf!fk~K!P?Qy{V~D(x@18IW%NlV4+B?hRM(JL`W<^%4^IY)idG%I z!5NTl9PBAgzaQY|-mCG!?iiRKwdiBczJSJYBO}XYZLT|E1hE__st83+gcQ&#!9>PL zCRYptFgl)UAiur{`J-*F+XWkddrDSHlS>kU9e@jeCH~w^95_s>v~w7vcUpr?Y!z3% z7S+c+V6l1j5qv##X_=t`OJZDR`C|R_4NkVqFLy^*U?{h%mm#-JbOl-Rd_GK&og^cM zT00u5TV|tixuQ17P3%r05l=NlKOGxi?UJwOETImEZ<@hu>8hW&8p?vnJf5$_@2clH z28rr}@G8XJQg{IheZ5)HW{=;#o0u~UD8VU-Tgs+yC24S!I_$N9dm@d;*zIDFS=I8W z=(Hc)T_E&&{9(3nyud;9^suisZR@y%gc~&@=CTp&vTls$acF~95}5P;(*gz6ZZ$Gf zSsT#`vW&3%Pz=eENfS~01$yc9GbdvItOlj3m;((}e>iAbNLxTV4buRP3vsx5+jt$vK(y`+KK`O7? z`#sCo?TxD0qJBm0&^|B+os}%*%8)FQ0qIvd?^KUZeaLP&X0~GuHSMZOjisIrsWnHI zhe&n`tELpUjM;ueE!oyE5CMs(nE0+^Vb|TZ1SUeB5b@1Vuqoeq+Cz`HHu8JFJ4<&X zZM;J-+HQj=Qh`ngo2!k4rgXbQ^B+P>z(nE~LAZ-TIt8|fUS1Qv_@~UYB#xJ~T<0&S zco6MDAUI$I)Vhl^i|?i9oH>PlY^~CCu`Kr|St4Xct@p{}3I(n3S}4ea@Rs_S*6&~z zRt`(d7lFR?nN2+pfNAFw%-q1=_=$~{)|^lK1j-4!$`2<@7R)2Hs-J#=3=f3~5vKPP zf*GLsP;cFtNPXfSSR!eS8X)ocidDD1JU#u9Xr4=%>}=&g(`f5VtZFeA+kRh`4$OWW z-sfWW5-NO+JgV~6yBz*O_8v9{wGo$j`6hlW#;&P^np7fe{!EOK^l0U6RG>iDa7Ljw z1{h3>Dd12X)XE{BsH#c_A5oh9a}&Gu@dpY0Z%9D7`|R@ijP{u{^?&xy0u4e$w}C1+mSYpoy;U-&;d zux)WH_qfmLrh=DyjN@6AxT&f}TYfb*n;d-7g%1Q;$>uD=rH+ef;wR9_f1unEGQTyV znu8ErNpj>3EjT7Zm;5CLA3T1V8kr6q(p47E=nkf)8CVdzP^H2>e*QgWrx{pv4rlU5 zy5J%({W(V#pYM@3&7mrx=TzmmQQ1+`Yq`DNZtygzHNE}{Z?<0$>qW8g19lDeer>!s zu1LiuTO~Rs=K-E!ew+fcI;kd&CHk`7D80T=66d-^o)zB@cbFp!g|E(RR(mtT5xnEiAWj5G+jsU4TpD_YnUyVmo; zW!4a;@jkT%JFoa7Ha&MnZa!) zlKq1*BDIWO#Z)%e`jjv~EE^E5E2J<6qJBwA$_M_ibRkkHiZ9kC^|O#a^q&czoL|CHtaR&RKQu`_H~EM*mZ~&#?&B z{=GooBqscO&#tWlFJ)#GyI$ycAK-HGv`L%1x7|O}I9)IK$j5y93(xyxDO9X8wOii& z&r`o><1*^VgEi*sFJ2GYE=6ixx5iG7SlPmBHjOxE-9y<@awaLQ1L>P2ZeDpaAqtrwTady0r#1#*B^swvQF!T zLgB$R6%>GA;`QkT8oi%TpQ6j}7fFv@yb`tZ?9XwET$V7%HPYW<)Zb#}_)WN62(l^| z-f&qV33olnJ|)B;EKM-*vQyz5McYJl4EXcK=rg7NgulXQ@)B>ijR_NRj?8z+1 z(GfPyzLVpkV**ym>^gr_y79ocPV}cLZmM zr3#id5n8j?#rC!FqVyUF1|GEggU(?1xAMHEO?#->CbvD}_?Bd|EjUFo!Z-iuudWLw zTzZdhi#Dq6s5yz`&f!ELZbGrE2(vbh7`J~4M7VC=9mdC4d1@_C&+Rec{1Pc~&^a-D zI|Fkn?wz=GQLNhB^e&}c=vb)Uor|^eWM?tmezLS>aotYXF=oRVqVv>V&OD+P76rBG z@H%7YC{u1}cHl!pOlBm%_U)S7<6cBlDVdu68F~tPOm2S2*Np&a*orC#j+8RAA!EK) zv=&09LKP-HlDEMXXopR3cSa1rv zAasIiLg!gIKC$3gX(`>BGKS=<0dlS2L=JhG!)IZd=(M_{fFM3pt*bC0V=%DySjRoAf-v3fs*2>QvW z{LN#?SX7L#}!^a^}q+tUFp|nQvwB?)z;5EM;V5< z?{X=m&(S7`lQ}}p`^W*A4vQ_B&cBvY*~5p0)pT=y|0=C|uCTUMyO6)r5c$UK0Akc% z&)h~Xr31lsG*9)6TWkhL6Uk|}gN!*jLGVbKB@=dhlWe2MV8E)`QFLhPw=^96ji`|5 zc1|m&t|ZsA&Wq(m*zJ{!7H}dRN&8te^;Cyzsu|H>paf-zf!gEb5uu z>TIe485;=M86q;hlL4RCFB7Iu!hht`%dFApCK8T{|LUAcZA29gXYej!5OLHBJqBrA zm?LLr_e2rSEi#cTzjJ77RBQF%+3=bzqQX;ONZH&UiScFfyCF|%ldNf=i}`H9H+zik z{F~nv{T^`H^a98p=@;W-p;L)E7hxn=$H1mr;yQU^m;*SVZHHP71NIjX9VXzkA0{L| zcFP(iqZ948Z;C&K*L>_Do~7>aIDqy`Q?BAy?}eequaRZRGgQJQ7@~;HzLtYh%=I$c zUhwAa+u3%*BDX0+r;1y=H(Jk^G~M6n)v6uyHJg1hH=SScjvCl~WJ$fIDh|s+iN&Kr zs&x|0-ZW1x9JJ~6zbX%i@~w#sC>KsT3Iz@!|L}-*os!5I!b#;1bMQ>Cn21`18?D57@cplDfJb&bn+#_#PznnulY@3Yq&nQC1peB{E1AVq&Koo+oXcO5e z2z0bT3&_N8{bR{_P9tn4BR=xP4gy2G@hhT7uEs~W$Ks7hw=7-{)?==6?95vuAS24a z!HpfVgUtD?2;xPm8L46(EGBm5Le}9AGm~0$(BZi|Z))es44Ja@Z5Nu4U$pnynz@HZ z`sm)e<7C_ix6QJ!t?ICx=2yI#Oq4 zWwUP7G}MQwcwMQ2?`O8Nn=yiiU9_`-s!Wd?#^-mo%z1BU?sQo%eazE3?H3%1t^aki zxnGX1qRcs+SXY=|1wasvWod49wTap9*v#s>KH@s{8*WLJzVaCm!DEg z>#PcDZ>CTiZ4Tj*u*VH&bs}IZ_Za>E_Al&!7N_5ioi_Vl6^H+AU(!g+7A$VdQv?IE zKY(IKbGnbHV2U59a`TB#OUL`*+Tn14V(tK#@%JAc#irZs%^^9T6`vwc)jyW}u#}D< zRKZ&%-j~drSs|tu`mCD*(kXl&2Lppa(A!?f$-BbG!woq9l<&MS$#qMC9t?yyZ#ir7 z#0Ck(xpSYx2l#OKrWi6GOFA|CnlFN% zOZoe+W|XTNb(=qM-Lgvh@!zSHK7@LpfSoBt{RESu2-=e!Xj!!2#!T)OAQ-L zr6{R!5~aN=*!VHr=cU7c-rl2Uw7jiJ>@AURa0xWOKC8=|DaM@0v(Qk>676-#> z%FPU`VD6Qc}$N zZ0Yw~s`)pOEsY$~8-P6to}y!N-*8K7_jHXU%+9(bvbG3=&pdgiK{C0=m3`S(bivf^ z0D9Fce#l-iFPB9UZ(+M+^Nf_v_>1%YBrEzve*w$|H!F9Xr89wP999)h$WOxqh)9&& zW4I(^L3~*j-vt~txVt8Q<-632kk%NczmFC?^B%aS^Jh+n8Kf^%-Vb{!vMap3EQO&L z?P$byk64E@p`?2xS+0>RvDK)hHZm-XP;BtAh}|aAqHbBeRr1mu2^~%8ppn|SUE+2my^-i} zzgi%hsr|5h-s&Kgo1;%chl9eu&uBG=p1~WdzzfD}!><~&$;-ViFG!1nSmuX~apFo= zfQ<>YuivlieFL5Yo>vw9No30cHctEI3GM{tId)*I z^-zPs4Th@J0IB!0ftdazDpHuXK=Uv^HCF2ycMTW3sM25~fhX20eOImfyA=%vr<+bM zVQb&D90ae=rS@R`sAdfL-g~uoMMI%Tqh?KKZf;LCG#Z2`| zsJ6ERhC+-WIQ7D)V4RZ4!?KP;w2`+LgxX}yGvt-6Q?DKPt@ z=Wtkk4H*5@ihw1rc?d*BcE^5g*LoN#)me7;v3T)Fym;(GMxB?|w-d4P>%MVfgk{!8 zJRs6zRH_Wx#Y?PA+h-$=P6nfcAw2?@UD51_g~!hhju`}=A5kSZ@uRT_@pDiYOgIn|rQ zecID(PVn@zIPMjE2&S%fhL^x_husk-RQ^@Bi9XWL^fefW(YDB0G01@KAsD2-WtW}+*(MZYS|-Gg za=MwBR@~5S1ZXz7%*n(HS~_GRRy^!r=RP{oRr|tp3T>qPL&LScvJaj`(cRnprB@Rd zRp=`a9Tvp++&UV_{C%QJiMn@^j(Ax?tlkjp_n+uGGMux!BXaF7+l|J)Dags|^R5DX z%PlF?rv*s_d)=k^GDMURu%k#&V!JCX$FeP=A326M4%anJjPK>0h*i~cRd{ICto)#P zZA+?PfFi7Z>l{C1huS-5Khqf=_A}6^=ViC&_aaH6u2aL4Y)Nmo5p5nI5j>jPMDK^o z!ZL+($3=|_68{jx7oabgY?lq4H)K_Hub0Pj(dl!KIYmgUT8sHDr6FW@@iOE{l6Cw} zq1zitV>tGj+v&v9#Ve5V3F6Zy8F7)H5fv$`a(l;crsalWXkb&9aXST<2W zMDj*)V8Q8sSWh0t`vFLx=1foYR>Fp?TOg6tw4N=~KG&E|SMKF6d}-_D;hiK8HJ(6A z#i2^;$_fi>QwjM~FbF~*ufJZA*2a~iaQ==?#KC+Ik5F$JW|Q;<8kdX3c7D%ozQHgT zUw{n4Uw_JYI05K>>)IyWK+}8euf}%j z+=AAK+iLHi+yBzVCf=xeWmS+{`_~w?WJUe1H3HOSYv~JCFUd5_nuy<~%-9#Kls_0 z$McRZ?~XB7jp}&B=i{nvlo&n!)0FO6b+Xp3Pm17;!^`Sa(5{}cS!=GIS4YyW@zY91 zMiSfKU(5+V`^CXZi+byuBGGnfE@kNrlm0>o(;rY6Ad5@0DKgt}p8k+Wsxv_6F=S~b z0#&e~!Ao4c00|E<&>l0nIoPfBlN|lhnu40P0<-t5O4-^~4&2t3!&HmwKZlSqBdom; z%jzOL(s%6V$&~INET?9u4TlRuof$q}1ID*$^}fPW-V+t(5oZS`D5N3ej9Vr|yq7Ye zGjX<_?A}kA_5Ud-2SG;-YfiH-LT)i**WFYv7xkJ)Z$vh2QC(iEXcwUM|Arg#w-nAr z9Tg2_OW3~jGat#B>PU9C?wPQjl2NpIJ7-LGJu>kgBdN_u`8ug=zeTBJ2TuM^LqkKL zGERJ=_G_H@AK5>Djd8nmbM8f^3&q>Pdcskh=lKsp6OT4Asn2(U&#TRod21J1JQZ%> zq(w#FZOw+WD#iR6M$~+7k{ih9CuZh0JV%}X7!a?!>G=e_)Oasr13RZKx^7S{JhfWD zx8|vjeW=lzz(fC@^|&HzBN=}L11Qb7-gNU+@2sVK;n)Z$7D8-TRCPNKV9as=-4ER1 zvTJKJy830fc-?Jh8O^n$7iR~?J7+>YE?V=AmzNzE>tPe{l1pM3ME4|7?1Z!>APQ}bogD%g*6Q>4MsB>a zQN#=5U*@=BN*3lBwuW|iIzixVF+;SMsP^h|06YqF*rkL=#0@F6>_?Dm!ktckWkR0_ zD3sRj4{wTFyjl$K`!TSrtfxax_GjZ>k$@+V3&QYs(2gkQFE0d`Sw+tpADV`UGRk@9 z(m_PdC{FTY)q-)(ABe~#VmwZkp_R#oP3rwxpORs24d>wwa!vkDayG3W2;4@Hae7xF zX^tJcq)( zR}nb{Uo>sO=tq^WXHnWW(Z)6oar$NBPzQLxua%|WhqA9F+T21zd5*MiV(4kZu*$iC zjU7#T8^b`JRYlHY^SePh8e2+3xvWc!YW&7rCTA?99lyD~2Iv_ehMFvT73iu@NAEE= zlDZW&BQK4z=PyPOSg6*Li806;dn)_{jp8w(%v{KvdF`#yt=|z_0w!=e@$Nh+7BMz2 z2eALO)*Pxb;PbLZ(3fnT!CC6q9 zzL#+ZI9%ufBf5=j9;WBb^=*o-IcKU^g+r2?J(=ZTa2OeM_Wmn#sLS+(L3?6=2?oDjM5Ury#3eZ!J1BtdK%veX4 zi-<*|%9l`U@|5PThPK z<=vgZE^84rQJHZMZzbhM+y?j_$(2qlsc7({15T!G6~%&n|>La9Mx$-Y-9y?w{hGt zkeE@?_tb0GJS^!c^pGS6FkE;%^4Gj6GqA71PJ?tNk~r8|`~H4l{%jNZB&q%5qIjKo z# ztQSIop@S7hb0jREy*j?){P!zV&071`Pe%KGQJ+Uo=ar(DF$GONwcIt6&FF@!{-x~jStd!3PGC%fV9XibiKiFyB#$WIp4=_M)N5F zlI`R*%2f8cUI&u$m$u618i&N-tTUazB+^2vkp?ZEqr7pNSBRdt^LeIc^ej$?9p-7< zUdo^fQk5OwIOyxj*<(cU_cs~y&*`80DAQ(b&ylWJ-LVPy=idklNezdr#tNJZSL(D5 z<`yZTJqvKVc&?k1IDtrvyUv#1ezt{q8KTi{|1MS5G|Rq=toqb+>E{eP(ME`kpX#-{ zwBag^OIv`|^(w)8HkXsAkh3Kd*L-!A)BM{U12UoQQ*dBQu#kpkdZ*KE{6l%!wR7m zql8UuHjNU+E(4nKBz%{Da+QCKc#ZZJRcR)J)t;WBs{cqmN1znJgE5{U=r=ehQ!$VJ z<5j?8R39!mxx&W;IuymTn(vjsmdvfAE4`QAzUl_#yI>yF(pq=A_%ts~@Kb^7l zgkjN4svdUv-%jO2640^oL!-`$;O)lN*Ayce-+kd z+ca#$#oda#6?b=ccZzFqE5#*H+}+*X-Q6J&+#zi#?h+{ebKTGTe+yYT7tU=KlFS@) z% zK3*nPR<@@oj}A9lD)D=6hVN0+@6TXu)=j!n3)7%N(RqIlLM6uyTC~e}?7Ks~BC&aL zb&OdGoYGWlW(iS^{Bp_6LX%dpTIoe9y52&WW+Sovvl7)LQ;8h6Z0cau!yBHRe)`BJ zVBSEeRfNs%fLCrMt|-C=rn2O?*c787L%=eJc2F8vLPR$9V1-vRvR{) zUqZV)iS-wkysetDOn+F0vGdKjl|8t_e7OSe!=~1N9l^z$Mq|&S6jh-#PoGg|xO;r~ zj3sH*_Z*Ii^LUR!L?{7O3hRJaDv^KW&d9-RB#2#+*n*@5c#k!H7V*%CwOR=rKrkJ3^{_>l_2`3SZzbk4 zyXP1q;;%HwnAdzQ(x$!9fNS9B*=2NFTW8&_fuYj`ZrKKw<%AtP&7Q3Mk$r zT~`&>M9oaJ&1=NACme7S84|h&?+3ypjxD+{$j(!u6b>4f?v%*al1_lK$W8%*s56?1ZW;Jy*t|p2$B|d-JM2pps znE1zCuxs}69VU+kk&`BP^X6YK-Z$Moi4Vg1SLcb;Ez(%*suKjWI79(-QZi7O%+TJ1 zKr9iS(<-kkqF3qb3B^j^e!0Q&N`|uhY zQ>Z?_b_rW=fY+w|hFLshbOjB@*@dA3p{6 zFyTM_$N1)h%#uFI(W2s+RE!EU<%E1$9zU$P(=*_t?qBv@Pajx9B6bEW>u#IG?BFq? zdW5HfztLRUg=Wg$Y&}Pg&c>)&hiJAuem^y8LDnlr=Ibb`1=U~tlIpMnZHldO5?#vP!{+GL<_(67W5VF zR8aK>h!xT!q5w&k_OQ8m1QceWW=6vRmTYbwzo}s0GqC+E&X!75oZc5=iJK#|Y;dDC z<-#~cXP%Mf#*-8EaZl#=&h;}^T`3Jfc%m>AK~;JYs@}Q!$R#j`I^)hw@1fnKxXXqh zo*x64De-FuPMTa^)&3>^9;(Gf_xPQc#Q5oHi?f!P>hWQ7p;>=grmS1%M{%3`bxob? zeF`$K2=8=Xa*3h|IeBYwBr>wBHDmg{>Te%HtAZc=hY=4rJ@f{y{n5fA|Dz=$ml^){A z$>uBEJGMBBrLn+I<;dJJryn;}v0XF{h7R!B~I zAm84-`>pv7HIbzJhvtWQ8T|KI;*~tFx?)h^zTaJ(OM%p-_w9_`?_hJmJwLIB@b^=2 zL$UF_VC?Qqc*cT*iN_Z-bo76>Gd`_0dc74aXXd;?NX2Yg5g00Bh8ljQ6nQYj?H)(g$;02SuR#`dTvNhdX`iBP2elTrPP6ST~EybsUCvTiR~uZf&u2 zS#baGL^Q_|nfyds)&3edcmD)1AvWAZ-A0E`f6kwy3G6lcdO}(ESKP0QRRu5Zc4rZy zkSXq+{Bp6E13sJyBvS7qO77-Ce?#%D$pGoLm0bj}aFc)CYx5gm{=7f=H88*o;ZL#u z4;SRqIQ{n@f1dkG#8EH#+sFOp>Fz&x!M9NCO09thQSP)Ur}zH+0H1NMJii3Nzh;7e z?bN!|mH_P2gW@r}?-y%J#Jj#0zc+T6bQdn!`cu(;wxMnlxdv*%jY;`e%NYwiYepXm z*ouMx_&nF~I zF0JI)S8=jmv8yAA(;C~~mv0XkK&2h-Pu8>fz}i2v)Ud{jOnDMLeec@KM_ zC4oa@>BIbYzb8moAcKdovNX3ShXS4d8B(&Kl{ z{~M!?H}YH2*|mAUtPm@Ce^@G*f)tl29Z^^0Dklj3a|P_71^kx=2Iv&0Q2DyH$7)iIOWvky%vFn&t2Te)eZS?JGd zxTo=8VGfE|tu$z`{;Okhhq_6YBmPwOsg+s1Yq(cj9Nx``un&v=OR(&h!akOpars=U zKbB;%EO>B(uJn82t!_r*3|;$6cuw@zWKI-s;az#z0;#x3PIU5e`kqlb#@x!%?Qj0c z-_2>4@=N1z=D}?%nX1@;@lA*BGa|F^nTp2usPem;?7FLc*(+rgH^EIMga)N^W7G35 zg=`axZ~E~*qiyL@Bm@peZsGvM5R?*}Ac~G!{@7%VGyX^a(qK_eI~TK2zC{yFu1V=KmSJlRBgLP?2c{VA&$(GQihQu`8??&^zbunNQT+`>_Eee2r8_r zZG#=5-h9hZb_~S)qzc7@wU{I)9Mk{(rsKH#)PF3ou=fFz;=$L;)}5U3zWm%7$t=Hrd?{1oJ1@dDGFdd0c%`HfRWj(0G|kgM377 z36z`)HHH+SbmP;OQkwgz;_4~%XN9)ViIp7navMfo=3EgJ$sH=EaGhYwldE83t&J6k zJ-Wj6S=rT!$kH(~e^|1N`G!Iml$uok+lJk(+=J)hmEc1U5nnZoC2S0*cn=QU|890A z&JQHb;V4FZwTcY+<%;?q@Nn-a95wc(&nsc* ztD=N6E2_6x0@FpL@2PYUp>K_dDk%|dL68U~? zzz;Jgw$*JR7}IY?nhZSsW64MQk_Nwz5%%bY4d4gCXIX6sKXIfuW}EemzX_p2M;YG# z&9WGn-_WJ^XLK5`T}j2} z(BU9j9(aT0ymp~Yv>M%|kH6a_?jcl$Ly7ez*!@~gA3RmJ+5Is>z4~|C16-C#7*E#9 zmR8W!iboFE0TOCAX5`>0cw}luv2g1PWmr2WPw@n7mK%F5!vp9ZThb4&FZy`7owWNF zj~9u6zR;nWJLo)l203})#VTi4>8&G$U1KtP9QaC(_vu?k=pV)mViNY;?Z5`!6iHT; z6T1c7iE?yc&@x_72X(6rT}9iXZo$uy?hRTsL+yRBdfk~e@I)PxFE*GvLH1RVnc;Mx zY>BvQ)}C)Qw~SrlBzrZgy5x9Xgvb=9P6h2@8OIe&_3)cUZRPi-fUuByvAZ=mf6{Nh>weA{8Z9pZDHdWLtbdu#-@inOL4Bg=`u#`( z=g2e}a~8$V);69Pr`A?7ItiiF6u+C8^Q+o?E&@2cb~@Ipc74C7`_l;I7DX%TWKG*G zpnm>@r9lvWmun-OpMMJ+sa%UW{xBQM4EzfmsJIP;_;p_&Y(4eA=QRXomzQtdZCb{& z3HQ822QCODJQDyQGX>lm*x{sU1K*;#U|zFhLT))Wlzs)tNzE);Y}gp%=LMb+g4rwanINiP>?FVtj;G{=~gN51* ze>{s8_R?c$x@a}`#mkZ5s}mNT^D3+@r(LWYkDGPQO04q&8c=u@ubuAgKDaGxoAdAa zr#}Lnod!MjJbl-Xa`PxA034CWE>O{LxRMXUBZJCkgXXb=|5_n4$OE3FfcF~Hk{Y&m z1Sh?o^wnEa8-u{Mp7*~{mfITtLn?lBLVR~v0f#N7h2**Z)g-scefvOgdW+crmb1p-#61d-s9XcY$eF!}~r5{1(*Ad^)iW7??_X?~b2) z-giNHprfTd%DePNc)fbI*?!OZ-#CkT*9q85D|fw(6BXuZkeEzOV(QD&WCUw*iJmaw%D>AvXk@u|D8T@~U=h9?9wG#-D$bBK;s0ra*s^H)J z*3o7lSeXuRjr#PK0II0{yLbGNTJrLrYd@C4KUOxj<3*pQ8s@_Wo?EaFglyhI^ZnN; z?`my?j{_K_{;wY-6CNIMh$A|~`60*dmk@i|RB9g9XeNHo_kUNcK#3j$p?aJ2+?XvaQ=@)6rZg*NTn^|TZfE#71J^uwU zerZtL>ZDIFDL-A8L7@Ou&Z}l?BA&Vw+Tz;N=-MF)DIOV+-*=*t?w8C*$Vav?uc^hM zGQqN8?ifR^Ngh0M2@s+R&5Z^s^Nijzetg%9m1_-ZPnI$;-bqN~DO%=06xQ_ecW>5K zM=olmF3Fv=Qfg8bDbfi`Tkkzcvr+vU{OcP8z5kFG9eu4kOufEPey@t;j-AmZLS4e?Os*4d!q7*vCMj-q% zyx@$m#+zMBnO$l8v#Oz?GcDl)%NkgmJYXc}_=zb7N3X7k(su!2S0zH^L7>&SQT22a zyERthw#4X7LiTtyO-3+LDjQHddm1ELXm__OswNN{;J%3e&620fHJA0@M%Uf?p>PVV zCH5ia8cn9~uN=Qjjdg4za$6X7?@zPgy-*CaXGFqIuoAn_gdPV5&7Lcr8IR~kWC1N$ z!AW6D+NWP@4^|B@<&DsEA{wLXFqpY7IipJ=kzMhFZG;~=T1Pw$7gXL|v-B@|-QadS zxAIzN+qZJol8-Hn;9Rl#c7T1vLlxh9cL8xpbauRxzq2dK1hZTlBWCIH~y@CLU@TQ}M4# zzF6`XZ;`>9YQXFn>S zP~o5FhyzKBNJHv0`>M*qmMUCdN9+Tn7E9pH-m@)6<|PPQ3cpON@lp@K#*HPrNWl>6~WM~{n)g$kn-xfWH`up@Z7TxL{A%zetW#UKCF<*Z zXqLtDm1Fn;u(Rldao%SHh4q=B3R-{0zWHmGvFg)bW$BWl8;APMY|!M*?@U2&Y>HV z`BBzx6$?%FPgxPt|J@i_Px`Vi7)i{x&avG&_Rv;=eLo0m;N}q`e=?ibQ%pfWS_bN4 zUu}$lih)0Y>rS&~Th{zm-E_>X*FDN{q4%Mq90`sVM#4q`dT7cUUEpeYHQhAS?sa9c0R-QrhFHPR4wQNDCuz(I{faHnTY5`Vv?ixu*j0ei3w#- zE>+DR42t3nMvrW7R<4@-a@XI>S~mBMlh0|p<8hyN$OndtY5d%SR&MDuZ*6E9>-tS~ zI$2psXu4mXKxi5iRXVV@-FZlrEoB|M{LFfoOY?mKJ#lCF;`8IW!}N29`~gR7L6pHt430$1Q#{}C|X@l|#q?k*^lE6UCL+?=BUVt*J6OjZ6htgIjry~o%Y8~hae%mqOQ z=;qoyteW4t7753F^3Be2^HVgP*l>rjaTo58PDQ5XSag;mZy>&Rj4o^tf42aHJaF!D z^uPDml~to|a>>X;k^Wf6wmWs|DBE)G8q*SLNnxHjidJGRV`A+}?Y|qszSJ;!NCWTM7Z|KC7EhdAx=r+I$>vbO#SX?!wFg=_|G)o)DVl264?12k7{i}7H zPVAC<>A1`EA3UVPqoQ&nAM+k9jy^n$lR1XyLxRbk7=tZtlIWm;t}T-IjyASzHpTH1 zk*!$hW^q=tsZg#zJE^(UItup>w2u>$Y>1u&s&6y{^aaIfOJgRJLDY4R8MA^>%&WEl z*6e6?{@xzJ$Dr(EBsI3RP4mM$JMNzf=PhZgx)k))2i5D09m5ImF|6C(~Ix>?+ejc+KWm z^drz84Ce29nCYKN;{wP!=>hHw{zQ+b!fq4a4w`#QlY~4mZK@M_vYkdEtZxBZYAwqC zCA`9-BeVho|KLX8GRZ$ortEX+J57?RlHCHgO0h~3Y#fWURyO@+SD%`#*?(a15ght< zM(bFe%%Su#mm!5&W4e3);!Ji#4dUkdJyabe8up!iUABP8Y-S>od2uUsrFeD-7<3O; z46vLauya2xcV6?@%5BfF+2off@IUQ2N=6A(GO&jTid?d|lva?wDsCp;Rc4gCpN_&C zfLxtOOg`8KhV03t%4C(6g*xBzWl4YJ({=5@oku)M>POP*%O?1|xsmL}pT0eLbzdDG zE(EE$+Mx%Lz}TZfsBKy1{2EZH(oqsbBVE7UjK;!5+gTet;$9*SBU^&nf^i-6+P~xC z;r%#NkBzV~owBL1?cMk43Ae@v`8cDQLj*iK+aS5t|dCHYnO|X^x3c zdC|feLTHg!U#17=oXZ_qGzKhZA9bUj2WZ7=N@icIQ)Z_=f?m+Cw_7^}C}M?*Ga3tX zsXap2?Ok`-BRp5ubN(n!@WTrw%g&j{S0fZPa+s5u1U+Vru--S4nyWxHyUTO9t8f~S z3vwRVvKN+0x8@V_!#W}9@~XlBs%9WCJL!74qE3{h*Y5i=J6LMJ9&-@`rz^ z)u^M%$7-h#!!O*LoxyU2K6D{qOsk_l@lbvirpiK3${;uqI95`iKbWB^1Uc~IE!G$0 z4QrEnI1NB06K_MFXXUen2M_ADjfAsm{ z_TYsDr;Q!>plFjPK#SFPdBt&S7e34RagS7jkv&8A>d$z1yUjLd(Y1Xj3XcMQO{_$e z(gIl1u3|)m+=$quLnI%&TLtnIPMY5&*F2W)Dd#D&Aqkaw71?9coZeRfl3@=`F&~K9 zSW<&f*OEpK^hW_un%)|u3#(yBgVc}gE$y|k=0SjgzI{m2WO=igP}rKOk2{TDE`xC| zKJ5c%VQq*rPIjE*oSQ5YKr*AZ$N6`9M`h>L9EI7)UfAIPR~X?rYdHlu;;=g*E$&!) zKD-w!OO8^s*Yi7OFZ5E&r?dB{WdzgK?}9qXVoD-_rw3j$h2t=*2K6>MsebWy#h_zQ z%O;|qANBqAvWU&%+mgb0*h!b53!^T1DXx;~?-qQX`h^bZi2K5;m^0WVZOlH;KL*sl zhsLA2f!o)}P&taJH~q2%5<$aiRtaeE@aEGnE%{z>f7`0|q2?yyTfd;?t^xjt z?%G`T*%tH_q|MGKf-6c)A2^@J8e9s=U;p9%*w_d6WqDnUc75h_F^g^0YVQ{ zLk^u)^yc(Pdafg5J1gcU&q?rY%XrTcj{CY+R#U~yLGy?<`0u8h=VPly1aZBv2U%Xd zqhOG|lKx)T6DOU^M84yJ$r|K5pTVdcN7y=8qqp5jc(82zNb^PheN`1AwWwKbR)48y z@R<2yHP)a>DiGTvZK@6!55ApA*42(_Gv1+e-_(IP5#H~%Ru)L;Ojd%@eF`vZ5YBQ` z6$Dbe%n)?~cgf=lJ>Qi!Xvi1%EY4@v~{Y=M}Wk_5E4rTg*7O&%;-Y+Z9UM~SazS#sy{7ia z{(2Ej?pXR+2y?GrUhlzPO9f;BKb=jQ zVx9Hda-Ds@F#sOl0TJ=SA1ESr8&zi_o4_}>)(N-!Vin#kf9hqbA=$4N1TH*wr7M{2 zEPrND3=MtuLS1vs$p@7XQL$%FFv}3w6@J5^r6IA0rSawI?p1no@H`TYM#*XT`qS&M z&!jGe5{0t1)Z=3^Hhl5E>*(cQQxY*&({hfOz~_?WkuvICEHo=xMwrfLtESh%4fKd6 zq?Q6+$r)UwpIGR2u#s(^InmUz%^{oUZU-#=AtXTw>4Sy9P|_SSdHu`njNf9g$5vO7 z$fZ|R+Z5eTi*!4Z%i~+LWjH`QDcT~MXLhVB{+&Vxg1)I{8ZWTsbuf#Kqqy)t=xWlD zpTC^tPxd`+^_#R#gW<1dWb-B^#&vYV8&15&iDWXjoi15iqb!*o+t`$a5jaQo}EPb9#zdqTJ;Q-43CHk%{j)}UPPz^uEWN^S9O^o#LZlG;3rSU+zHE7rn- zOCVDp1c;&4RuiMEN)vne14X$tGhOWqdCMRX`Gtb8V8!#{=Om4*Pbqyf9R&!>*ud9v zdb9Otcr^1;nZIc;f_MKckE?I{nfGJ&GW&0v)E4Heb?&lL)TltWp=m2)P6+LN^vbSs zs*PWdF}X%KcE7G+tF44>*BeuEy#P#c^t*=^sSIVH6txF&$f;=oD>kxW(O*ewx~eh? zVCq(bev~f#6bv=}t^NrpeN|4b65#R$okW6AnR4%3R5N$Bp*bWdInX zW1T4<+eZ-1J>}#5OEa_8ZuEGRq+(_7G$hj!%}q!pl*PWy;I0PzHpIB5!6ugb@!f4O zy_4B=1KBKq-#k%xh*a!(bGUg0r8~Ag=~UFVl#7`f-UtB)+X?f@Djltx2-l7a2jeOq4I$cyZ}7!Ja+-8~w}OR;F?z^Sm<;TSC)r{b3#dZDLiAp~S{k!t5z`sv0I z4>IpV%5z$#%w?uC9*~uZAn&Lh?V8Ju&#;Rfe+8nZqGrj}_zlwiA2Q z;CQt=8m*Q6msk$cy-l~qU+gXvo*#!TY5i4F0xPpDJK4g~Gw02Co6kaLxdoWdi5(()FDq{#=7<2n!S-L zLJYdRO}PUv1h-V%@|$*z-T4!B z?+I%QNg+Ka*VK{rAF;G_dnyis^pnq6RwL`Yr#RSR-qvlXYrX~tG&iW!B8R=t<>yWOyG&e6HPw7~1PH9b!;%Eqw_48tH3}$qY?k%B?{hg-dUa zu;VjqHHNH;WQNo0xLmhCeTWI0Lk~ot%M-Ml-DW2 zPDZ0{7)v&(5#oDjl_r$zpOmo?Loq#mbFX81s4ku6G1Q~VfDM3%42zBc>X(b5p_ zfAWKR$=E`%a>mloYAe&#mu?vJoFEdv95^Ce(Q+_9<1)KkyltMAczWbI(*z5yHq8{# zE`DFn7VoBO^`*tEd{hR3z4IXfpVc*Ab98gC_Dw|iEE48gE|a*+l>H@YMRV;}WXlS( zUSLZ|+miwsZu(tetq^oL>OK`NBR9gQ>*DRBQ)$G7io`W{rplEnJ9b#8N(km#+%T<* z-s9TWr7aV^eRSAW(8W@}6rH(P%H8gU*=*$%!(vIbu>=MzwR@686er?eKmAOt!*9F^ zZ$}&R{3~|sA*1>FaykYdW0=K3*>0RTkUw~-2Q!r&q$Ah3io~ecHQf41fwH7^7+PS3 zhJAG&L?AWGU~;;W0^rv;6Bkk--vmi)2H=K`#7gEq%~&#~`rhWZRvXK*&>07kMc9_; zWy}a@F&S>a?W{K0gYUXxv-5Nh=IuH(?!>#*+f+IXw~J*^;E!7$`a(YL1=tjl17EkC z?#^CKl|MKO9(QLVUPXZW-5BW8JmBTr&j~Rc9|P(spMC;WUjjfq|Cn=X>dXyVJR|!bXJ?K|3WUL`r(zF;hw=ZF7VF}f zVih_5;1)>)R#Pqsx2M~Y@&AgD|00yTW7>~TT5;gjRQO+EA|6!1tLl50b3_b&A`@u_ zhF6ATjK)Kk>!>{M?nt|xOgZ4zd(T_7@jRPo9*O9KDR2Jcx zx<7B_KVA*n9}<@9V-SwZ#zy)`_fzB5TQ~ePM!yVfY<(TH-j8mM;;-0H{9qpWytL$O zskgWdjjuTFv4XAnY-M7T*;{@gS?h7ptnhX-+cIc;zbdT%Y+&{C2UNTvhaLv7@sr_~ zeI7FU1E_$dx2Y>cDRSkAdshXYPM)wV3!N$NV;{RP=q+rO2d+&xry~o$I7T=Od^gIj z5$n01aj{K7Z^v597VP%!euZpQD`e%y{sOBe9#4<(-!TYwYZ4KJs2+Z7Tsi6jD&xN# zt27?^QQOo@blKM~&zpH5(SNPg-t!Zm99#Gvc;w9b$eL{qR-cVEkfSlg7GyteGjGNwLOr7o#*PZJR+spl^=HE@WA_LU1~6rF|mII>Q-)Qtj>6Qcf5nA>n~ zvA$hh?X=E;+K2^U8YHhcv?^rB>O%_ebGEUA^R%Tx1oFL+B;D!xy-&A)Pzg_oZWhoW*KV+#j7Hbgmr)^3GpwLOt8a?Cu?N-;dpa|>*-AXok{ zx(thqOr8h)Vg>S2hx$x3hu6YkG7%Wm_-LxxdHOC>~9%XG@Lzd3;eCd#iU%U8fDVUzG;Tb#NsI);MRU zx&uDWDx!O#wjXD$sE@mBz|9Q9_3Fi4% zvqCFfG}5u>h60m7%>ThcbBGJ-HH3WYw|6C$MYFDJ+C)ob9@f_Pr!wH~$VqSDjkldi zPnbu~fn;@M#2SRNlabmJ{)|P4_#;=Vw?()TN4U&WjLGQsEv7_Nj}t@vZ#5F74|#HOZR(V;|EpB;v+obG+nKnMM)Rd-Mtp(0 zTSKMQY39@y(Z{*hd)gwJ1E+l~rgd!dTP%<>pXLaBs{`0pj}A>8p6A|wGq1=w`j-`q zR%UgBIAy7M3l59q{wjp~+OKdqtF>|00a}iLO5O;^8gszOW3i~+GJ%an#Np4ZXR|Gv zG-8~^lf>FTSgF~$9{On#buC;cruN3!Q%p@?XY{mO1+V{(6t6?|F#YO7Xlg*@ZBY4# zINg5RysNc1jc*`B6)&lAHt3@M2?av*6Ib}JN!jP%XJL)3@L89dA7-z=A4Po}`4vSt zI#N@$+G~6CvC`J?MVIB;vU7AzLW-qw$#U$OyR_M;bcOR&Nsh`VzJA8ia1*55kIatF zo=_25KhIolnCV_iCAMmwVFQ$G$~ihLi{18?E=Dzu@3j$Y*TeX!q%x!^3^Gzr|84p6 z?LZ%UKil!OEZBbxoxY!_O)x^`JdOU~u(L*}REbSAA>My>%a&8__}cWntH8v2 zbm8uP&qS@@^>0L#F4LA>6f!0b?IWR%B3t_B0^R)(N<{D@>T z-L}S-=O1i&ZE+0;n}padCCSMeQ?BPvKvkKMHvkNicjt6s2wIV3rmRp{!rY=6P;-cS zOKRN2&Z|N9%(er4Hh{eC)M>x2pA!0}PQI*aV8S5>%P%EvQg_L8uG%2y+Jr*J!`^%9 zjjWiHiaEVXXaJ) zztj9nhxrB!#=On$6<+s2O~yC8-)R@DzEk=Y3t9N4@-S|P9}ZnlbkOb5WXi6>M8Fvk zSllikqOxXm=kau9&Q_s??t=t)&Q)7#2>ed-db+x0t1N(mLqS9~=XIgi{B0q{D4CfT zJRI+XA6~TI$bsFyx!C`}#s_X5n%4O*W7{$7n@wn&^ji1@&29+1^}CBtQ4Rd8^uHIu8d077Jd{F4c25n2mDzq^ zy?nxR+>}zTQqc~r+9yAYevJ(Ba zAq#!%Ppl+!O#Wpa$u6>dC$5$u)S1DVm+}HQ?})K!Kh=rS&k#&G-y$NuW7pEY<4+pi zzl3{+{P%yTG08@efwNtM4+qX3wSQNuG2kZZiEvfAq)fPmY~(rUiIfka{s{x}sxn=D zf$+6@-o*rj|0T2jA!TLZEjR^JjU|dtq)5v%TKAV~O`$1o{yj-!`FCWbb*%i>I?(B* z5LfP38viuvZ(mzK(B1~qL^z~agph_dYqS@QKHjqX;^uO3P4rEnTFpvi9ADH8!S}-M zvaFvb##TqSqel~i@;RLH<@>{ny(5BZS9KsLj)oO%}h%_XT~a75VY`N zXfQpvhO)-O@*y$hqVl_VSP^O%HhC?xZ;s3*(ob(Sc5I<3W-q)(8ii_a@}9B|kJ8Sk zyRA+5H)Qt44l|u{J!=Wq6u1Hj-@phDpKVo~Pz5`-%vNevsh60qwAHXXtuG4^M;Lr>QJy>dgm=I^Pvs`{ z)(Za{F9RGFM`Ea2)$7swUsI^G`Zp8w-2`klqqqg*!s8R_9MDQww;bb8yA0zYLXRj3 zXVL7cDdgehXw)?k6Kn)|-gtw<+?S&F+t$vGT*1ZJA{R0$vK;g|icq1GKp*vs!Q04> zQGaKsy|w`huG&P}RG~!l13%;N{Po_B>MAb+jc5^uz>fett;q)}233u9Y{n4!2QIce zh9R@dLS1T2X=Z_Kh_{_&9RzL)M7Q?9Cu|J=tsJt~c(9-5d7+aReH>5lH1ELWGOP({)87H?JmT~=u!DIOaS6jPCLm_J`zu2}3GqAi7CkV<)eEY| zu%31mC&aq>_3JCK9_lCav8v{g?N;J;TN43tz8nOhW>gIA&4)RGC@pJam`ejCjjXHF zK&uB4wVP2Pwp56PD2mdSVoTgeu1Vmr$9F>+l(yaJ0|7rX(&7~XnuHC;Ovj&`R*UXu z__pI>Cy5ob76jj>Tq{>+f9g)mpQOr9l<5`EWFl1R+sUA>iEOI6R@wdP)MT`po4Q85 zZmPSn1kDs7|LMrGBr?xr=twoKvdwNA&=69HZ}QY}Fqp_?P%Nqnd;k)D5kvgDBQDL| zYL7c>*ow(RNoAQZ#2dazxf;Ma#`(Hkx}{N*xw8C*L$~w zo68VRb8MLq)b#eGwMT^&&C!IPVASJ|p_e(L>JU_No_gfq-;g0PEsSYctofLT|8*c1 zZ>eB-@M<}%+&IB~_K$M1)5j*(4KT9U;`zEysgp3N+l^_6bYoTfiOL>}UNJ_2#Ju>Q zI=RBKAZ-NMTqUioPAvNe0{Tk@&zDz^sZXH3Ae&Tjk8RRIBAAT z;AiJF%&%EO3dQdJA?8W-5)!h7s$%9Y4AsJIo$ZQ! z_Z}#)Y3^&ajzKd-w{@!BaA~-%y_9ClS>?84GZ@dQjpniK1Y18WF1+Zs#-U3LK3l+N zrQ`$(MBACV_F}*HTXKO1$a`Ndy5Lb^`vcia@9Qzjp(*o3$F?tmvFD@L@j73>`S|nr z#KiH8=?gUAamKXh_}GpyP4TXT+(b2^_t&6DgZxKbk$)e#ml@zTV|f=>u%q-KA}HH0 zw+z-Qdi69BZ`wp8B+BtD)%>p>fkPG+`HcVtnah(ZsnMP3N?IO3`G4&ReN^gqyihkQL2Z6nk!v*qyR}uR|kjejE^48P1kS7GMXq9bPudd(&9GroV z)oe_Qij4p$j2VZ5QQPI2fT}#odVfx_2XiW8pTCp(!AjH(MR=*(1W zpju3QKoNi1tYE5%=-qN}!Y`Lc7#7LQ&FUD&(9f^S^kI`t<^)FI<>MWIt8guib7Y2B zt>g#Kp#rsf9F1b^=*6l2;WHmJP74C+BejuavjeEFW|4c%)B?xE`(X(8zEihcz(`i> zf|K4d(*@S*TAzGBh4e?%I@$OWabM;y`>py+WE&IoS~5+7L<|`HcOGWfynFKFhw5JWbrSW$Pb~t3V5ai%N8yaH&urf8RWrk_&08b@y?a1Z#5U@JY!C}RK;MCuP0Bq>-$D{qkX%BOCA)SMcj=tz>Q=rP`y;F095AOc>iNA&GyYTXsR~#1wzUGiUtqpJu zpt0&pqjYR0!*^`w9ysAE7&JoCmxWwdS%}p(9f|9~?X`awK$fInu@C$O1^9dKOK&QTo!8%lE@H2i@wJ>SU3)32ET@E9dDdwv!!r|=0nX`}ao zznl6=MJIRWOOEBBq{gU2+|}bZ5^t;;0R0$xW3eJdo4m4iPLz6bLaD+|e#@n3y4yrT zeSdnS>IbdlLBXK0(Yhk2bT9E9?1zyp=7L^lVwuTW(fc0qF*|)OAtdH@_*YOj@2W~# z17`sj+!ri*y^yf5$D}(cQl}fk+Vg_oK8b>Hz{Oiqjm4DU2;nbLQ)Ha$T&E}ohAPk} zx*0?vC>k`X4Tg^PU_Qh}+zazpX`8n)s4LzECAT1KLq;@)5I1&ymJd6Ek&_2XW zWgSS*IqCW_sQ6-Xp>RU1o$|s<FEeknW|H#L}RMhV0ZhGcQ-~fb}n?4 zn)Hn-^F||8XLYQ2M0cj~+Ksaqh?jwz!k0b+d_~Ts0U?qRN=Z^TjukKFRl{{#x0UDo z3?YJ-j$&Z^vE;$q%D^H2nJ!Cyh>OOv`iBZaR}r#ef>68Uu5aeWoBDB1W?;hQt8*`U zpsledZ!Vi~KGEm4C*b0eHs+YW7M5x%Vem8T#VFCwVrQCyg&7#(DsL z=6-7mxy*}@FG9(o$%zg4lsD*Qr115c$oacZ2@PYx&Wt6w#w$lw6tdANJQAGZRj4d% zqb`LwHv8C|$Ap^iRZjvDzY0UUuW@xp%X=Fzz902GpkkT$lBYoM$;JlqGJ>Vw9 zT1;|3A&^!(*s15VPZ=3Svp#>3TU2ppCdm!gEYp2Q~Sn%ydHPX+9cQ7Z&n-`gmdzv28^^OhD;#VoF4%&E_$EJy+Wfo5`P zDH0@9EE@UAUg5tL;su2owbMT9eu(onoZcH1WC@0jJurvIC;QjpeSv*#U(grRMn~jO z3V#EJ&`kH%Apd>i9OGAqWQIAvj1E3#!}(h;7`ym-4#mEAPp^$nowo%WDk+g#Ly`Kp zn!ahNk1LV&4K@aBc>#5#!v>X|UcEhEK06i|Pd{BaV^vhRIXs)8WGUJ9?{n;wx$Y11 zr0dSHcWbwe3%j6#6Z#c%Y@e(R-2QIME-K9h-g&e2g;9^?TlOl(9~b^o)W3c|4Ke`~MBD*>-4-UnwsRu4#6Guv3gGl2R2(9PYr+4IYx z$QR!xx89jq#pt&6C_INA%G363VDnPzic0fXEzVm?I!+ypI%U<0>B5Q%+Dgc=MqXxN zWCd+zrSkORz}xw9|v;QrF_w0&Nii^6s`t@IK zQfb5It$l0g{)wdinxN~`smD|c1QzB0n3Ojqc(+Hiu|7#05zaE6^ zNe7omM;G#hU^~?}AI(t!_z@JS|J=Sqep6)iSl?ULe`*Pjw`j4sd6#zrI+wEiMA`>I{t8$H_ND5N;0u_3kNOQa8U=nkxEbe_`yVqspT|Jq` zG;$17n2bJK+uFZdjQCtKaci|Fkg8TKe5?XT)TwDu?+eQjLQ$VH5?RgKB5r~Gi&-YO zZrnZ@+x`Om76#`szMpxm2j)4!4U* zU`1CtugZlTcrfgX4-6MD6k)gLtG-yR^6kQ`QIDTP0`z#u>3SMhTd3>s$UewR8XkPL zKg}=Xf+=|X0LG3R|Ec7x&rI*}wN6?)wlI9fKi_OfE?2!)t*8qu2dYYb<@;ied`(e} z`uQ4@%DVD?%*toZFx^6>ST>djzYCoP1;HM)!qhxb%RkluD3YZo^9mvLF`})}d;BWs z{i>$(@>|XO`lW<}my1i>#({&1>Tt8`tRNiP8UErw;dY!H_7{s8MkQ!^w{~D(mLrBj z>P{0nFG^01y2ZC?{3qLJ(b|`t8*RB#Z7n+@Vzm0VSWSzBnbtS){{9a0azGUa&y;yO zwZG&#v!bx^!IMzaUhqyG#8aRHV1g9PYtGKacJg5WfVt#FA)|rL62iS^BmelvD-Rw~ zdvKu72q!a;3MYACdt6cu5aAax<)5@&mb;p-&UoZUD`lm}7Y!S>gm4oaAP-lfP< zejz?6o3~xqYZd*1ounSt63n%^-@0Y0vnyP5+tn{rrs z@k>cIE;PSme*xMK;t3DaRDDy9p)|wL%}?9M5pIuubw@^?!3$s=aPto6x!G`?Wt6vmsASb?a1N+OO^BaXV^b^dQwTf)J`+ zZu2=|Haqc(jgvV^K2crGcqLOFk1zZyCQ5X8yLNMu9rhL7-BGYhrG23iFT?!|v>8d|C;?{ zC#>%nzKEISrnY3NDNP2qH+F3Za&C-x&tAnmI?Ab1U_f3X!YpJ82fUfFo=PaeV*>nq z$h@O(v!+hSmiA-+G;|t{NS|nALp0kE^s6WU3uTY)zBGEjG$D ztUT9rsdYu`x0IbfkLaMtVJ-u>tXBk42Z0xwk>s=|mw}oD-TCCG-&GoBgAt=Jn6hMn zDfYM~SZR5Rdd!dq|G!MpBrY^jI}a_Z$FUQZDB39e6Gy=sl|f~dVzG7C9{FMoWR za>qZlK3Yl9iaPU|Lg@zyFbRfe?zRn!VX8N@hHPUFjN>PvvDfC#KQi zmi4ai^PihECon{s?(MFE--i^O9;u`YG|l&241A1r#q-kf9>BFM)h!js(d|?~L1B_= z{hQU+pmF|D^4Ok6x0g1^>vU&bU0vF(??P^c(9HIt1NCKoqw&!JVEHE=H#b(1-lp6T zfae=n`6gJrz%(bJuF@p_2u%==&8qBRv$Hpek7hnz!Ha)lB>@tqp<&n7>q_JPTS=bY za~#khewH;qpHrakc-Uy8POi3LGOL*g%~%@wJ?hdbN3~4#w{7g&kI6^9j=E(ld#B(8 zTHUCFNJF-*tA``q$Zgwu;k;7r+|L!lh$hLk*y#=XWcHWz9+Y~$wI^M z1jy+Z%(mp>+RC6T#MSBYKMSVkB`d3t5~$-0|G!59QGkbFOQzy}%Tv#RiucC;WJ!D5 zlPtrgI$DWb2G^21(X!c!{SK1DzbBj_R*X6W#ke(4GHK5%!iyuV;O%cU9oT_s2arvf zsAx+Fc%K`(^jQyrOI5OASir) z!ZkmjXH5V9m>A6|kO;5GPRJ|6fBhlvP{(67Ue1t@??b`D@pLIs+eiOHB^yz(XRDh& zqz}2&<7&nwjoj+;4O*s`A%lpHXG~y==CDBl5%Hqn4e|?TE|2_Enno ze5Nq#YHh(nfr4dMJ+IBa=uXs<&$}u3ypPBXWeouFT1n7XN2>MV^j|1O6wMhjG!%b7 zJR;t4{$`-+2RQcWYV)psNXoeS?DoN3W2h-afV!Pw4WSC*9al8b?*wJimh_S!?;!i{ zU|T8p=6miFb8C+nYwssWYoA@>NWJWYRItOttl^>MkC%wr*Fon0*o!R0;OJH9abW4X z8`7O90LHfPRi@SE7_ZxHIw@$qU}-w$hRO=5+>GA8w_+MH5EoYBIMo^+MjNmE;4Y*+ zX7{AVSix;Vw(TX#f17L%e*S>jxs2XFE#WoiD3s~G>}ARKDsNbb zbhnWe5g{0F6TWi#Y(f&n4iR$UvHTF~G-rkZ4`e z<7In8ZJ`VLLJd2u3n+x$twpo3CiQ&Ltm5oE(KRgD7kb@ocDLeb>$SE6!Q8Kd#IZ`E zLEZ`3Sp;Ot4J{`g0XZw)K^doj^T$+lhyepyu--Vcxl_{>FS_#viVP_w~0s+6v}CM<=)j zn226+BC$4#Yq=Zw49J{i&iGZZVdZ+80FMTXWW&J7VF`SYFK-3L?91v^pzad>!nSD2q%(9 zMWK(zj7z7=VORe!Gij%fp&PF%Jn+~1)*l@U(%dkZ0pANOZE853MB0~Xhu8k3;q+_H zYpq~;7j=fN@MY=Rfgk=er}y8fL#(ktkgaBvD9gr$AB2DaE0BZLg6V z9(R)gGcvU%RxK{uxm=xcVAI+{JMNt_TA~5eOOlmv5nJ#eQ?^FRHS8A#*d-F-Vm74bMC%16;Bqf{J4;&n6{+Id zF)s_Zp7?OU&QDse_8IIJF`91?I;Jm=f_3Ppfyd2s?+yOw&~iBLpyWyiU)k>i6Ev&X zcTrqHa;cTWI)u2}g#fzfFBym2U9Vp%1N^`M=J`tOFFN0lj;{d3vIYir_^T`^rK+pnEpK3~=kQ77j$I2zslXn!rJr#Y6-1xWplPQu3U*{j@{^NXgo&G9z1IHutru^3yU^ z5IGvXkQf54DSft>WIK8+;aDypx=*Ksms3H)VuIp`Hb!j(?h$Qm27_!PD#?VoTTP;K z(M~%^cO5O;Y^_4^XoK-(Ko%MzY&Kl%^zHvcz{^>`tfJ#=|xU+t&<}ZW`q12x6#pGPDCVSDG|0 zlHuxSY5O~~zJ@i`VxTe9j>yT&kF@9a`)Q~VVS1BKV+k+gp;52uhH+5m8)=MK|9kYG7*2jI;R$$Tac!*|hqAu&&_|C*cURlqUVzvi&598iB& zaiepG@-rwNAFKb0&DwZo)>yW3>m*Txd8=io#J~vYCu*g4?@mEKx7Dz;os3(whUG{n z**3}PkL?X=@-DwG#xfMycW(kzj`!ja8wu_%4S63WO}V1}t@bGib_a@_`UEA$=yKG^ zRy8U1O^4ZQ-JPSL`j*cVF?>JWQQavrwBLW*{XAF^j#k}2+H8`9myi*>SAR7yv-#n1 zee@^8&pwbfPdRYzt|AcD&ObZh zn%b*l4$kbNsQHH|5$tmO3qeSa9V<4te-j;-<8mLYp*_JXJp1dm>GNUP0PQ6Gg7`a8T5|S0HUQ2*aAm3@0ru2v3_nLLn=4 zvZ#M32^^iL)3o8F`%yJFzb2Vt!}y?ErK1W#*o^l3+}_; z_`RvALhTB2x5pa>%1_W9c?bQ7h}Lx*`p5no$P_nIPxi}w8EDGBZ9~%7hTZe&w6&n$ z@EVctc~E*v6opg->hl_2^VHSZ#g0}MPj-Nu*7Y6A?o&|EAJ(xh`1czH3*?{IPAsNx zMBcxvMVv5f6yneSLgq`jERFJ7Br^GU^Zco0Be36%e?nY09g&Nmwr1X(BJ^wxn4YGbOI> z0A^0`$$QAy4L4@O$cAk0kcu#mSH8hd2GMl9kX7rqd)uhXm{&C+DVtvRgh;Z!KHX^- zOoLrzBc`9c3hXxngT0Ti49&r1fjP`m7Hl3g;jtUv-H)cSCTo9^T4z|oJJij|3}1)%5ASDtWpAw8IyI>mjj#Oqpdo7& zfDZ#Bn}PcHGwf}|`97qL8)H6#rc9?zvxwMqQ8d_<-?3BQ7O3&9YTV4Re~`{3INTqw zbLUN#X~%70J)Y!$^=KYb*wo?+tpf2&5=#<>qY|dspnO+1xH0Bb=h|?nQyNRkf`kR# zh%PiB9Ci;mW&bk0Vr?O;+AvIPUWWj$z(_d`Y7=kMOeMY(Nnl!{+!QRe$cEo&@#S3e zu`t`~qcy+%Wu}bGC145cA^ccpR!6=-CiHJ7A&==OyB7bY zQ1Qm4ZdPQSZ-?%^_j~Nf#sLA=Wc5wfdT@9TMD7SCW>yNg>cM!zB-kMC6&FNl2+<2v z966q9PjOp_$C|jeiuJx4a=b~W>ZCQL~juMZoDw-1a!`m#i z8fHzUq?8|x_FL|LX&R@Smq?^DWRffutf2N z$WaMI_KQ;ZDUH_k*YjkHyybJ$E0d-ae*LcdLrv*}di9Lw1ihgc#NbYs7v4Vgq7?A- zWPhFAb30zA{Fjvy@@1SiO^m1v6JvYlpN{VMzt#8;vyDK2UujLAr}VoKog?As2e$2> zn{-fyotzJmwech+ zXV94k^PfdI>e6M_9oci7=Zd8Ox-kzjhDS|y))F3YQn*`n=Z8I3lFc&Ly|7}`=*{2e%C*RWg-=wx5339% zeR|FxG-~Fp*s{GgHH_B&Jqf|ZV1;0LyxG0(`^s~a`L8jj-|Y1lGDoUWz3=$>Gs`CD`nG80@;IZRr?aPs@K+?|1g_ z?rOirV_6wz4;^z#mD%r`-(isBSjWcm8P(&#AvEKnw+6013*AJ{Mn+iq+J!j0Je_i| zvB`CvbW_-a1lz=?O`(zG@c&Jqu5K8{4QI-W>}$iWONeYZ4M zg3OxCMCVb?UQfs1rcBwQCDt#h#9e}!$(a$URuV*M$r5cAUk_d;O_3tnK(1Imcj6>( zVUcLRa0p)p6N*kI+a-XDo<7K zXZQzC|I54{qNtFSzv3BgC)M4B>!b*&u9odvn0;G=hF^?*`&>3c?B|JYG~vZm=wi2Wwv92 zD*^T21_QA&N#7OJ;7wJ0Z;*c>n6i_c$VL-T#zB`b4q4h?Mgufm{~nD>-;8Sri~sx+ zk@uOiu29_23kP;f4Vj`eyeAhthrVhZ`aZ&|>4sA4^3j-i@ZAc#%ZOZ+eW_zS^Ldxm-S61Cr5x#c-~05l$a0P{L!21;JtZun|2kZH=Tb4 zekHTv!l-M~shJs}2J!cwO`A%C_&JCgzA*^iDs%ic*yv$7%^Y--sCc z{P@;IEeqs3e%I?CGBmlvM48Q+=q+4-H$&Npa5Yi;C~#lSSK+ zVrUxN=K^Ccr(T~Q-+>ACNV?VEjB|2&fvC-;Xv!!J5{E`BKFIRJJRZ#0TKCSI_?NR& zFCo-PmtlS*dQl$oppas@Wn;GHeV=j?&31b?pAZ`*WM6QZS4*WE=8Y85I%jht988(M zi!;!KR%Y6E%5mvpxG;_e-J)&F%n7pO@%B6n<|<8@Ng3Jlc>(<_h7apWzLb0cr?oH{ zV+FaD?dE@@wUXwHlD(xI~~mVK54NtO1ihIjz6@QMKySy zzZNZiDv#&%A0$O#GAqs?k^9=fLft>!;#@sCMY|yFC&vEN+$mLNH?Z`z|Baq@~e z`4{xRl%hXW5;2YFf`Xel*$PaEx$3pq>9TkRZ=>h7YtgNDIets3*NzGK|f)kZQz4m#wgP7F<-x;C}6W!GGYm6z;N$tc^TB|I7 zlHq()L1tmis-PaD$z0yG9TJ)Yb1th1n^F-ss%rEbCX3PcZfVwym1d;8k1g!yO*4<9 z=f~2RyNb@f!KQlhJ>tvP#Zj?$_Y3(bk`5J^eI;OLRGpAs%K^KqpTRYm*Ui}B<^M!o5P7fpkx>;j9Vm+~g&Fn*&kb@#@F3OyWtfLrQ_tg2x*(rCWFOf zcRf-Im${Jor?)Peuww({s)mNY2g7JFvpzoIJk?%WSC>78UQ;UF^U~2gYC9K~^fw%| zC`y~FqH;7UW!7w0oUz{^k1tBSpo1np|7ZNqHBUkl+jqznk;{l_gr*Fwd9=kylvwrcSvZ+0SaP_fePCpQ?{2rZBsI!9`@v^I zOxOh9ji%lL)@M+jEu(bQfU0r?wJueh$2!lk#l~^~!s$jg3wWdh7WeQbMvKi9&-#|d zR{C*vET9!VX~m*WW+@|QrEcpNVj-JELb5o4hyYNx;FL1*(Kve{Xy>uwg41^^!U{@(S@tf=K}>KL^`V3fgZqdh@FN+#X{0*ixZu(VCsoG`++uYs z761Ismabz1kY5qpF}ePtQdrLVM9knyQipGys*$HLCtze!4yYKf_S%W$C@u5_D`3=Q z=BEh!PPW~vcadxe973K`pz{}UQDG;Q92w9Ib2gi~icosKUurfQV?02!%r@s0*L$oP zJ3Pk+%}pvaOmVPR4!8{K<$^|WW_q6l!q2+y))hJee(uH{j$`GCNf>dnVh@{IpbKwM z{pDXZ@B*SI#Z^vUe}rvu2NXIQIx*_sRT0tbu)00GJ|Xz+U(z2Iq6sPL+IR56BR^k- z7}MXY=D0HGfiy~bdkr$PtL44O{CB3&>E$y+GpPPBh}rFH5KjfX?hEl9rAVvyILt7l zjk%7FQ2!)6NiVx!)ZN<8FMU+CLolb1_){-#xCFu`45L^_z&;|huJCxXm&^)iPap7! zUh!_KUH`rovlUDN=7tgF8p77Ywmu(mBjCnQcuUIkQh6PLGUywjz3vDFQ={m=u$ znjk70t3sZ%Ox@vYRX(gmJ{8fv76N>g7M=PL2MGy|=X2r2r@vuI0>=FHV&OF6G3$3O`#IkHMO92h`JR6G z?GTp-F3Ul!d^-HYHgfC)-ZkGSSWn<3QZ0Xu89^83C1XlSzJ{J^iV>rWP*aTeuCq+T zgPF6f_L@T30yH;WM7`c9o#`w(eMR=2$Dcr6RJ$033`I2D|C~|TY5khj3#Y7plnUHe zUhui4%~zM6KyMoi*tNX2J@HuEEs=JDsTmYlp9d$m9u(;v?k_QS+`b66slfWZDb_GhGUJHL1D>)8|6Otx8Ax{)*2}}3u?!c-r2(VBk$E&Cubve z^b01h3~R29agdf?9BOvQKl?D>eqQ-#(@c4?d!M`1ra4h}2S6xdD7T~fV?b5 z*}0O|FtGHlNtl~xB}c(QmBXD_ZH0ss(pTRRZqvN-$P#ChcR7rm*h_R!8s&AD-1`Flz z1D`osGZ&AT&zk@I1{BxkMqo<$SPeSorm-&3`p>9?`S6@DZMh%gVZ+;bvBlqJFo(an zIk{fQr+iD`BvTPf4RNC%SKe~Vb1$i8XCBG#{P3P|TwV_fQZ$nytbXRFtzZ9zz;{-N z!hw!{`#!FZC>!#yuh7GNJ&nLQf-5>g&Nzl_tj-%B^qXY2X2;7}2c)zhY3cg?jU{^b z_L<{<9^s!Ak(GA8K%l2*hT%THGH_)0WaGx47E1n2AL`WPn4rwG6(KI#66U07GjH*b zJ@#hc2ne__HDO_zk!!jl|M1an(#eWIALiHCk998W!IN5k%;Gq?ZQQq9Q7W6>)1GZ* zu``5p@;fS`x8T4q7Jc`dq0_3L^^UEtYJhQh`8RgdG|Bw`X;k=U9Tl+ZhEH<+Opy&F z$#U$$C2_fwy#_&Eqb8po=3VFbHh@!4i{ZF{vZ~wf>gImf==(br}wfPTPF|kXjF{0H&X)y>_|3l zphM0ju&aMhq$%=5lNtXb6>#ny`?$KGHY6J!PdP*t-~SIh%o0KU_p=yNhNq`lA18N( z7b7U?{?{l$#ntJIaxZ#*?c^oZ=VC!_7x|+NsA%~4hvM!zw$a@j?j zZ$Uh}OC?);-cg6|<8tB&!37kl3|hc*K;`c%gW9%?If?HLq;Q4=4bL&k$dK4v?- z(+`6Iv!wUSS(Q8IX6&c^<8A=ucmilf`X4>q5*1XuApMki0e*FN%_SsWIaZjK!=_vtc1Z z!iTtX@d>j=-l?DbiUWBuki~#A>-p?TzSI~YaLq>1T5z*ac;=h6_0)@NDE#5*LLC1x zuyXp2U!=BPQ`knY4w*fR=uf+hKfcuGG^fuDff~6my-B9Q$^o806cZeplCa-^JQlf+ ztK9C9E+f9bGLcFJvowV#ju2$7Dxt6tB|oT-wYN84(!w`pp=@Bl>yy>K;N8bkjyZ|U zUa#EzFa%HB`dK^Vk6Mau8T_XWTaW)rQiLu8S=E0;;jGOUGmq|8y1_zBHcg+E<-3i0 zKQ_2I(WG2UW>B3sZZqjq#j4P99(vC4V1dE3xEs8({I7(F6d!F6G8yB%xg<$KH!Kyq z1X=EaQsVX>1Amanw#4O$-_Tm9hkRRehHrP>Z-&cv^W`%@iE@cTctf-r{tkKN?Vlu8 zP<)yWIP;0`^;$}CUClC7mXI*u>9al;IK9LEq#7Np0qG7r>o{h59Oq^(?K*qGUwg{) zv7XP|Ty36BPfssSQWk`-Ay3r7>AnX6`-Y|Gop4uQ4NVXlbMH9cx@Dcdb)BF)%Z)l* zqij&$?fksvKcw$^qN`LWO{VWq;&pz7S*+~8b*-QPS%hv`{ChF&A?+dzBYN@Vt2)?5-=ujfb;eRtpzw+V@i$bm)>imvM`>z4f z9Mh2AA(>M?j(&3QNAYO>yo$gE>4rIJ6+8kueBtm`g{%gT+-qFzFgSvSdErq3GUBAG z`4I(#;D#QAzM?^pz=UiUKCFI}?#2tjJ|>mVTyAQ~t`bKv@$=&=!T5cjg>X8@Q{8Cy z{?-`Piy=Q_PQW4w0B~X#?Ud%y@GD$2!0w1&m;br6vDExfr{t9dkU3fHRZaqI^ei&kRPF7b~II9bca)-(>cfq4IUQ3H69x& zFWwVw&b}L`9%=;AjcrAm-USS0ry4ORf-#6oX%vLlg4KNQyJGHp&~zOQ<}K1}LorW` zHAzt4Xn~XyGB!+GK#{Cvcb@g`^H28PV{q92S%(wLIgp;g{0nGJckX)_=9KB``7 z`eYQ|XJz^{KZeU|68NYs8p0(R8bZ??O7zPe@0$4N?PlJhA>fJaXm5r7n#u%geO<$= zng!7^+Q-{BT-5ZOQb{<9b(_t`6z75}NfH?*P5w#{`8QmGwbEB5+BcQ%MtnsWlA__b z7y>ProKg6AJ zbk;c$Gz^mE@&GwgpdtkxGG~~$L4b(gANqG0enIH@t3m!!Ad~TPsFiqHW}fOxyj)_P z28-`Hcjfh>9IhH!wcpGWc{biB)w!*ksOpT-+nb6D3innUe^r?s!HSqK5p*u&KADC2 z@A-uf8n1v~*evo95#6xLx7u;A1j?m`wVb-ko=?~*&8^Dx8Ze5C zy&hSN++}H?9hJusus`K~?_-PO48O7`J=|p+h2+2;Ln}D+w^V@?S~0Zj?m>%YZj=YE zX6I9J9sWJ*0&ni?+2qN~Qu1Hc-u(p09C{H{cT8KBqSi{|8tW3OL+`X-p>wrrzx=l& zvx29cg83gCfckvpOfBaxUv-H;Ha({q9;L2vriVcS*LU*xdKAoczP*diDvsNQG=qdS zz=A8AHq;DRw}A?lU&==f_S@!joJQ)ABo+Z!aIOF_`caZMB}`saEty=C>=4?j(}y%< zP1z$Oi4OOt)O7y%;WJ6)=)-ug@dvxf^nc8KE{oplmEn|+vHvjV0=!cCX^7PiR%KYy zpXb4%e;GS=P%8X7akx0Wrdbktg~GmH_X>+`zIzFu;s(J4%c6$v@89~}kGmYeiy<6x z0`B+?AgwW9j0t95eRq#t&%f2>qofL1!CE0rcg0W`$Wf&Rkmc41Km0vy{`ENW(zrNG zoPX!k&5Nf7!5|%jQBx8q*-S zUeJ-BF*kOHG=dH(t5xjzB>W^^QKHhJ+mr zQeL^}RRpCI{W|(irSGFTqD2iLKeu43CUM>!S!(IGg|~7s!F#^F<$SgC-%!Sm$y`3l z^`G%%RcKd|8AGC(oQ1d{AA>Q_{fb#fuGS_3p6LA!NT|&gwY$z*iTt)_NB(O}Wl9C`N&h z5I@YnITI?P0p!T;+7+k8V==r~V9MHGGnjv4Zs$S})%*M8kjVu>!8s(7gj~56d|Mgs zeY>xIRxb&*ZvbMa&nfb@cuF08pOfb77SZELJT_uW3ssUM=uVL7S;d<&6-M&ffpa5w zeviZ2mhfPdk24jN2Y7hzku3%bzB-KWHyEW(at7S5wVStO_4;27UQf@0hZ%+I*IIR_ zN1*=nJ$vQ*pFd59gN5>X-5%x_I89BrU1y#q;YE_Yz;)kodaE{l_reN`$iLLb*XR9X z2ieNG@^J!}R#d0(x_ht3xqXll5)8({G9@336byezBL%xy8|CLAqdZ!{AKpGb2e|<+ z;V-VU6W9BoRWXVp(~M^KWowkqqfw}up&>H_(n{7%5G8s`Y$7nu*%Y`Ez2AdJ{T6B! zlV)w)lW|e4-!%osKI$%)IB((}ze2zvJM!%wRetO-)F5m;X>YuYB0aZ9Dcr@D&D!ME zqLITRw#=GE4tB->spm(q4)3!HzcH-|^HjOJ>OurdNV0a%K#9n&vXyBmDaJhtKO^$z z7c1;XbpYms2?`Yk%n5UqmRZUvF|N;DFM-J(O5=w=NzR!_EcRwJtms?kic-r-3(#RH zI|k#C=^K4IH8KI*51l(uH30rnHMMFU4Q&^)Y4vE%3U;!Y!hFZZ%y#_nBC5%X^NR3a zFLbBiUKRR;qFNPOyJ`wJs(6~pCBeReHvCt+Mk{)W~AoOoZ zcfXXIlUHeX=1p3bCdZuTfR}NH)3E6PQ@EP+fJ-B|dPjzpqK3oR@nZ1}5#zrr9ZHER zJ5LOcyb)x=p*gqFTd}Brhr7xs84h(4j@Dp+I@H#MeLmFK0Wo+e0B5vVUU>gb}MCeor#4YAQoPW4~E z;m2*I#K-k%_neg9$+*C$U|vhT?UH!qtn9n>C%Qbkjm;EER5tM$n)qg;NDY+rr)|*g zVXH&%+uh025nc)4T-*yJ56a%Zmf$6~bPRuudeqAX2^%@oXFS9E(FR^jHQy~Vt1f=U zdns~etY^EEZ=sFVhf0Y!;kjiN{<1=uUeXv_>~dsxnxm zbr-5=(58KaUF)w7Bd>gaYglbgUW~zsI)@JtKkRRO!U^7h?s;Er43nDX#^92IHox!p zZFk$%=@wLOiuQ1Do#KfvsZ<5Y-0&*nsN&dNu2j{0F<2edqlhBOu%u7)Vc<#GOU_w| zw9iqEtA}!;Vqxa`Nqvx@r);0cX^bLSx?6^`l+m}|3l7(Jy3k}7!;bUg>nMu2_>O_% zW4WPfgaJK(OBGQ&xc6rc4&uAB$4OuR)`;xgg`Q^ofo7l zS-`@2%%gB|`ivCv<@dXX7h0F3^&gZC%o20@cU@|CnX)i)KjG2RH0}CNV`jYp>73S= zS&JgiQ=2~V4wXxEOr>Mhpa)*EF`m~)cduyY!p1A#F{b%l_1M$;8%}cC?Qsxx3C@za z);67wotWF@}hx zA$l$tr(MwAj1B3q@WWLXp+kkPuCD0y%TmZR^`y73n}^4z&=KV2A0St*o-On6sV*hn z@c%do+`v%Bdu-TCn6R7P3#YBL)x>fFu+GC6Ea*{0<`3oPa9r1;@X&@631yVMu7?;I zF5p#%Z#S-Wpe@z^VgQ=+09<&OqIJ=Wy7P5mWp@?Uwn6)UH5l->CO5rEd+zjyX0z6B zg?R5ES&jvubzij8E(klfLEnGS1-wWb_}?HxWErP`uMQosTh*2&cS(m5k#xTOFU`;T zeZiN;aB_h{4%06NT;`sHeNQ;KqD~AABn9ILsV3Sm$#o&0>-T7etVoK!UzO6pZTCow zM>-dN#-HDgy37u=e5YqT)u%MDjEdcu#A1u0c<>0dYl&AN^{vi$QlkPhWm$ZQFp*Xg z@bZgcMPe2y4^W^BL++mFbdAwaYONqBQ9}XaBftElxCjdK7NK{;Q2k@1cr;-e6aDpO zmtl-f*)xJOzcA56H90x0Ix^3WzA4ZNF_o!%3@}e!y-^la40)6{(!YX@OS})+-HDzV z@U+bYFM)BEd*vrrNNr7t!FZ?Ke}=3;B0h+vM;fNgj^z~Zt1@4YwAyIe>V4+!-%zC| z*UVG?p($(_c29kSjHug(G?g1;6k^Rq@Msc3IKzjmJ#rN~f~9=r04(E+Z5V_#+1zYj z|KcW=FB0zuXafcIFs|2Kzy;nTW!{U$9pjTyODupaBbajr{71d5=#dp$?2y~z7Kb8Y zb%Ua$E}uNdUIN!tu85Yk?j%R9zAKhn1}gSu&Kvx__x1R3iC-zBfk00d#c)d;;uH)j ztn4>P!4;Na%}NAW=$ZOH0!R;rrY1*6ERaIp4V*4pwT^442HXg$kWDoN=Xgn2VI>(5 z4_(Ky3rm3>&#Wpey-cl zCNc?X>av5BI$vDB)0K_FG-79(PoL>J7R{~>-MX&|9DxLAfvjTt!M*muEho96+# z>K%HerP6Q$-)~+qB4QYA=pY^+6y*QAFSNJi}F)%ZK$q zv}7k$fkyD&h&_#-5RfyeYm$cI-s7vJTUB+iEiedl6*_!uRv$Ugs3^+Oks=Py@E&cb z6&{s3yQGHjkK!#2IpLUJa-^4h`CXkNqz0MDtl2e^aQAN8&$tPUFm(M(@W(YsU_Hdzc@~Rln7HSMP#FF ztQay{7$4}DF$`WjF>>3~xthGI0qIQlm#Zw>ZpWiQ732upTN6~dw>S!)Nu6n_JL8#x z>+JCT>T2O7;=sX$%!aoQ?*;N&*G#!rI<_(Uxsb6jZ*}u?6|3zFY$`RajOi27Gzdtt_z_DPS zX31FeY*WjARoMf6cK{Q51EUp03hOf2KXwXy==$WN;neMD)>Co|4(XvgW~ny!tujzK zEdhuz1!e}}|39A2I;;t>{r(^V0s6JCf4#poS<8}OL}YEK_PY7uueaBb zsyvEr*F zudh>8dI$$%GJ1O#)QpkXYYK0*Vq{kjyr6qSebQ$M3u(+5XXgAwf5=|dbtXgzPaNk& zGe35;Pf^jKQI2B*hKB@JIdG=1-3YNuh|*gIOGJ={>R`pKS;o<*?)?N$tF%EHcD0kP z&Qk{>>?y-r)6S_^9vuxsFYN9K5E5;*VKmxZS8Y&yORDPeKEV$orp8Z{8h6}Cc6bfM zyCC#XWpC{KR7>>OW*uG`$t(_L)iPI+;3T?ANV#WmJ7z^ zxwO{48vsSkML*r9gy_tacKzs~JHqa!YIy#3x+zuB604T~rKeX|qZ#~0Y$dPp@PRy~C8AA6k~$`>yxLt$V^+H~jy~F#p$o$auCD>gct#C=|FIA7mjB zk623lXZau`S)oyguk4-d@pn|TJD$qddqsx%zmtPE*FQgkU5_#XAq`U6772;NXo+@r z@S;S)+uS@$Lbmr1t9YqF==K71m^{IXGB43&Xn*Xuk{?NPg(eA@RB5nMuy{6tIM-TU zS!A0mnX$a^8@|GE{A8H%?I<|zNWEC&?HQ1mO37uEGx`EJz+)~BcmPh9Gel|&$UFK~ zMw!V~XW(OI^?#L+{X=5Y)#gq?3;CReWRB`+ok17fT?KAaQSTRdv7pAm=Fk3f!8wDp zwrE`omT_CTqLX!-W2?^g*c?l3rNc#D%+B~L1N|P^=*u5?`C$bi0IA+Jg7LO9WZn>* zT91X6Nr++@rgm&;)>CV$#R+EniB*lup}5Bh>l~S$$uQ$Krw}(Rin31f@1RBpE|x7SlIh(Ysv74~ z|EBV_k)qHNlh4N~?t{Ev2N=HB*_#s2Jx5Me)xyUO?&X?t=sJjxY$Lx_k{2{cq(V8SA=itr9mYu1(xGgwD^_~x0p1Xv8_hx z-6{0~xj8PYTZG-)J2sn^{onW0^|ef?3BbdyF;wumS!(M+^c^qO?RT+1^xQzQz$M!?$Y1wBIpoXPlwCy^VH78=PtrdU&#DKp)2l(_e%N0 zF~oCcIx#XbGBy@A*x1;(^ZPRp9srjNLN9pm%8|M8-dt~Qeh&1T4@N&$KtH~AK>SKn z$C4=>vcgTX3$nRfSZ=$ncoI^t{e~J`zD=sNIkDgVf+2S@6zUvDpiMIwj!BZb^?>kw zGj@5#%<+t-z4?&4h(~XIMsf_`4>?|jFJjkFcd4paO&)NXqGgOtdWG}Pko#iDAYB9U z#HJKjep-8hQ~1{tRdk9%6S9S918&7ymn2zAt7Ow3=45@n>F-$E5+}mep-Qm8T!4f6GU{H&?3` z<=Y;_fp%6aB^YN%I{n^&8WOWe8)s>*sL!|DaD_Ev+!llYVqAyMHm(scz}F}BU~Lb_YZ zn?Bdpe-x7^i|xwN{-_;)5pOD9LY=FNl>O+y{$$8~YLRP}Qo}Lcp}{`kV>w#R3yg07 z4GC);uJlvPRlJSRdY-wLy$d|~fG3?;9nf4D9nKc2Wt`$$#br6}wDemDo2Jn53Op$U zqM$4yRG=>Y+`2*E1RAR>0W%9F(SkuL?w?vv=0ZLvtKObfqDaO$h|kVb~yIeBQlm`$P!7JMcsUt z8g*)CyJG_m0UEcS+^~Ze*UfpSl|G7mO|ZY}-|tY}I^jhM)0C0#49DOhwvUT%g(>6- zlbRaU2bVNt-s8N|Z>@R7Jr2FXYvFHnlq&zS-#F$n)|0SEQKm`rCAPOdnD@KYPkGjQ z(ecH=RHd=Maj8OKvGYj*RLkea4d*RM{8u6+Y&kgw@3Ta7OE6cQ>&H_xwN?1?!FB#G z%+s%9Xtm4_G^>8=G^Ss)7*)fzX^QWlA4hxQryh^BEvQ+eX*$l5 zPG_t|JxW&s2jE;Xs?Usz^q9W>!V7oJ9iKdNf7_&!E)(g@?>DyJS!~o;6(toe$sIXH zL76;@=kqF5$Rm^U(&Hh9iI0ZndtBll?fIf9b0z99fMacI^IrL^su7PQN7r1gUl@b- zw|2AX2;zS=_1Nm$ZYSss?kv+VOiy?il^(MV4|%?^1W4t4F(}LXn<;|;$q=*nb$#&k z=n0W6b>OZpS*>s#TNRVWp0W45rSCtTYE&cgXErGk_WF0777O;5QkA0izLvWzBX_Ew za_)gIUH)=@X7}*$axP9H^cKn&bQIQh>^)+0-;Oc94BKmmx30}41i}=2?~f8n@1NbA z|MId>`NeMD93ld_34th-H(k& zJrHv+Xr1F|*!28#Kbu*WCo}E$H+aVdjGf5w4I<_C*)?`js=HykohakqJRtEW7IgZCi5L4xN+#mv>-9K$ZH$ zsJm~$%X8m|1JtJi?i;CRNU8nVv9BD4LJ0r!K`^`~-)Jv7L``!=@B00F#Zc1@OKRju zW`SGh1Zl0-q)3Sadv`A!9*6$1|_3(|mlO0p+>N zz_2%q*=w4l=PHKZDhVV{8|brq;aezhZ<#geKp=^)UqGt+YonQ4n8_l{JHm@<>_^s02mVB4 z_`qlqXh^a*p?i8Sg_&z)W@NW^sL3~WWkf~OhQr(&=bC?n|1i$lQ68|}m(>6~$DXGh z{;X=`S!Cdpekh??!IlE1ZU60-HDM&P>Enhu^*QYG5!m|h_2F{aAGlqe1kyCR&Z^0f zq-D_|`i`Xz2?leptaac{(Pc8Vk!@~Xr-?`a9DS?gNjfI!=!asBqGCT9G;x7E)-Dqo zB1ZBV;o>kx`qx2o#4+-n-z@z-P&J|N;inxT3BP?=S-FM+g*wMbNBigIkkK(FEzfOE zMp&D*=rbN*rQJS%dv=1<5BXYQL;2}S`vw9`Iy-(^a@&z=vMmuDJ~$v*F`44c0w}lY zO$n@iWsEz^iEd48$3Jgsxl4@x_F0MtXgS&!aNHlUAK$yF*m>II|b0R3G8i-Z<^8&!JQ02W!hFS^Rr$%) zV2k_9IX#nU&jfx87g;hOOr*APSeaxeN# zZN1DfM(tGYX}U44JSbUL#Cexjm5+be7NoO(ji9!=oL*-$A*Q@l@hilnfX}!>E zhB;J=Vs;E50NVB%k`FW{s=^~6sng8o0JIShv5xdVy4V}v!cB- z4ZLAuFX1a(q~x(z9pyv6R~gks_fuub&By>3!2R^32Zz^_0VwxWH9rcEPShw5!rt$z z-P)9a-+w`LStuLT-g7}cDDL7gCjrMM_W6`CE=#J$8`riyIjzEB)$0Y3LyF6mW)Fv3 zi!Q>$@t$Ku?RI3`o3m6Wf0bFt^B-zmJEo3&?OR@NM_F*&N~fn)PoHr0(JQTO1~=w4 zRr+x!n1dOFa>Z6AxZ&Xn!_q8H`RjV^l4FG_7TmWcx#=Co*9xFYnLr~wN19&B#@8JV zE8n4fe#bHh#iSblZ||d$@87EUBv*T00&s34n_CJYq=4$1*?cNoSPa=#Q=>Zz+!Tym zeMe~i271Zc<0GGb%(jleP zNbMZ8P*p${S`K_I$9Bv{>@#4E&lOPngA>ss4T-<(c>eYT@VO= zqMWXlAr-}It)rrKTp;CMKCqta=p9UzT?6m|F3E8DP^V(vhHHs=rhJTsqzIus2`vdB ziZu<{kC=+fK$@^_F!-n5Wbwx8q9wUizJ-iLi%c}5C$njX34!d}=3-n2R9s`jyf!{3 z>)9VmMQo`e8&tVAM?Ywo_MN548u!K-!G24(WiJrR<^R@VhG4OGeV-D2_CGT4QpOF~ zjUGoh8rxPGEt$_QB}%kf;$@d=OXiK>zIS(JB9h6>6l38sTVkRC@aXY_o8(R4kzY{Y zB=1q~v-Pw<47~oreo+}6xXs^zUI;{5JJ`4iz48l5KSOd9t z>UE<{E&d-H@w87%-$_Zf=a}PYUAy-RQ7VYJxbn|aaN|6L=&@u}RkVyAhLLT`oej(} z6R1*H|8X3!M^iL1Za;p-ve-0#HT<|%{MllGX-J)MtWkXX{3W~h$vH0_sQs6V=qMd~ zQ-mFHQv#h8eg@2Bl6c-YQ<*#DkTzs7arcc8i2%F_@g>3C6`Zjb3qJsai+uZ{=k9Kd ztw<(Ig$_2NjJ}Gg8F2l$FuK>@Fw3{{9c2Oa85c--ANCD}vSDyw~Wo^ib`P13e6t8FYveK!+P!-Xm?MjUvb-F(z^r8u^| z7JHgh-{!5@kcFfX+R+aDK>tN$Y=~(7)dGOHk>4Ntr0?~qd`u=prGDEQD7>>^NkFredL=b#x;0L$KY3#ZL`=r35iXdslWzt@V?d{-%@> z6z~BZDIRT82H*semVEDz>V>AARHI2}{~5qkQ|-&^7p zw2y+grVc(vaWZ~jn{rEvsGT;tj1wr;e{#ovys}=_v!(L5JhCkdxGv?L48LRteZw%? z9~dWX$m6eIRdNCa^88AMoR6$xQ61xPa6IUxF%3`CrU)qlMqa9{F~(?fZlTG*b1FAyji(ZY2v>cLL&}xLsHmmVj-Ow97v$l%-)f+< zkcz7*464K^S5QZf;s>JEwj+=eq&dpUQrnxMTA|YnRIU3XXzQXqFy(7umJ%8J0&Nd_ zhnvT6n$b&Y&Wew_5lh}RgZoEMeF3w}k5=tD!)2AIbRcjtboxSYaO$Rj4F>zlAFzAF_yFw z2Hs6d(2O;L3|_N}t+51-Sq(I*Hr4TT_NX_jkLXZ0o4d8J*c0I2Zg!+r&EMG&eq;%< z1{|z#QneJ8|5`ITT0KuMm@r}S_@N=nWjhY~+AYHvGa2T-UP-3sf^yU_;bF&ipkZk~ z0*|1@uCe%|X7r0O$h{u)D}J=tf9-Eiw#QZC^SS~)1hTM|UL6kfLD8jMBz?;Cwr3S5zSE&*qT7`o~-y2E->_%D#4CSt{@wel)eW@-% zX{G_p;WkrlMRmcArukPo$eHT?exi<9h=xo4F?)k_MoWFO$NbgW5BEk_4{=YN(Uai& z23`1jQH2@KCCqP2U5TpGePoe~1eatuhcS0RLX(Z~7D^VHGd-2L7`=+&3g>4uIMO4i zMXt5tqyFTW@&%EnAlr2TfOM?IrF!J6H8i(Ha-v85B7K<_U(QCY+@=o=KUFx;=?@1% zHVz8u{nx@)2I&J;pJb2M!)RcQdT6X2^_8}xWrF*yKd#-o)(yFWZtW<5sRDZHhf~*O ziX$(_lxlqjI=x&|qg3V&Wi?U^b|?N}bhT4sKQGm4>{I*F0o!(dM>dz*VGy7zPqx*_VgW9a?vmcZ&4 z8|XU8zON}uX?3Sm6{Ta#y!*bQE=w}!q{smUcrBFR_IK88$6;2c;AadZo{)b>P}}qK z)f3LLvD8Y((Vb`I#4OHHI&g`=7 zE^fOrJSfURpZOLjIz7?RG~Xy5CwXGKSL3l(li-n+*|kXaUY{>l>FG9ejt`UaN~HbB zT#~`IlK{u1Isj8*luR|qZwTZlta-(N|L?B)@8AU0mMCWXjl5FI>-Eshpw)`zgS~8MsSkwuS;ATPH$n7b3~b(5 zBoE~E6KfY@6a3_C`vkw%uinavLwh{ABj0yk-V!qwfzUQTvI)=k zL{LiHQ+vKTK`a4w9L41N@kPIoZMclrxX+)~p6=hRJzeqpPR1B1R%zE3qGA!v1R}6A z)*u(F(Moc!4c}~!b$$-9)8`vmYs0^YqyE9`eYDx#_1viKyfg}rbuC8f#hLlwWg`2= zCwO@G?~cdP0A)|S|4|N}@xf+LN+!lsMu+zf;!@X$l?Cu9ABErbKRw(CWuuJ!YQ;}^ z)vpe?zt_9P27Q7Sjvu7TQgxhT{;$sJje|cFXPWlHzcNI#PQ>Prq!MHeNZMA#btW&z z>RYqX<9tH)R+VPnBnDiZf5rkG+Iq&;4{G{Ojkc)xReUua5F}6FO&G`3Lb>=9_$?X}`~$mW6-j3N;urll zL%DUoyz#YBsuefFY~Pfl7?K}f0E}+ESB$vMHIZ+;Jy|#Ut}Cj?zO^oVy?gN#f*jX~ z@1lz$%*P6%jZJX5D4whwQ=pzc_pS_7$4_=&J9WETB(()&q#fkz9(cIFi{)%1{!^G|q)2;OspY6}!xrQ#y< z=8}e<^OZ2c^vu(MqwCMD>mgIOPhR^S3cZr)0Z$4G8s=C(u4_9F{_zgSdu~K|g3irW zp)AvBr_bV8?pV)iwsMZ?nA^_Ns1 zooYebTh-|T+x*uI#irB|XBMKqnwKI*;PB4zom zajJ-~D&uxb+bC&=6bIi`;Tf)HZa@^6FYkuEHo;B(J*tsekQe-7&Z;#J7N*v*PppTl z<49OFVL3frj}f5UE$Ox(5;?JsyoL_@ZNnlMPD8x6dfOs3i+sA?lh5k&ibZwBx?qr; z0v2qQq9oJGy$0C%Rf&}$DnmZcwZ%j6T8Ghjn`(m?(Z0FmX2ZY3R4GyY^X1j4RrllD z`GDrc`tN5(5|0t3$uPG{lUJ+d<#&((is4_k@28rovM3;iWZ^QVirhhy4_FBv`u*mS zNV>=-^xk+ zTFcfbNb?VHkV+oW`4H@yC9q-=ejYI8J^bLubniz`)TVYk!#M%;cwPGL&o}cLr+Q*-?dH50bqW5(pX$JU+XXxNfsP+v*odwEJb+@HtD6U8>OnXcbpf#8S49`Y&HeS3(kgV=Q7d)ae-Kogk{|rE~o1D4E0K76#>{Y9g&px?%?Ym#=x3eCn z{gmM;dWO`4H-^Ng?p=v$so5HI^9cBP7$1Dhl4_HYd0!U-pdJrS~% z#s&A6EEw2XiFHz%SQq(R8l|t-ndM1Z@((f&8__?r2jKJpv!KyO_4Jiuydm@bWCbyf zl3lp+mhx(CP{W0an{kFL_kZUV^V()ISo>|)r>O7lsQP&grIRxK`$TQ z$+dCvvR+4@jN9QdCd)}OK?EIak@&bYPIg2Z@P0MtYL$O&jNEK;qJtJ$Y59C;&pFZE zTyt${hu+IzqhU_fSYS&5*vQQj-r%VQI0)ip_RJgxP9DtefD7uaS0|h66jD`c7nue3 z26o)-S8J);VvNU5I~g64sLRhQ3pCxhu55mZBv|9Xg6oKS58UDYtcRjj^sN`)VV;@h z&H6HyD3%(Sq@exr@vc1$)YpuJnZ{EzUxHJejl+vmB_o}~0j zdn?NCnyByP7pvm*27LPq6q5v*h%e=v)YlVG$5pJ#_qG=fop6h7m0vbxfXKp38$9}4oq%PB3LcNu#!}if zw)7UY>iAtHO;7gLRA%)V`0`P0EOVqn+7_!f(JZr~f1Euv|=*Zp5tlh=)~5(jLD8tv3NeJCVfmmg)Whxrb+YLC>8Kd$0Ay_}{>0D{NqoKdNKn zFZ!btuYWVu=BuJerQWk?#Q%;3zI)+jAvarBl*mn~^*nKppY>m$Gr*#Xp&<;RP5g5t zJ_7;J;RxL+%An_6XPezu(MG)g zb!zhQ{Nqt8QVs7!p_^!CV4TfpZ7Qnfs7`-{DP|^=ZD*pQwGCEGd5a{bq9fsY2tZz1 z0LEEFz?8q7O&$igNoNn=I0uHTlo^&(rl!Ydr<^Afc#27B5@IR&k@mY$Stp{eVl}?| z=-tZVTt#1oP8j~ltk5rsFXMhy7!d%ghy30obGqow5=l{TSP}t@%{2&OhraY# zV`d<|iZNr}*1jyW^~?5ABjUy5U<0U*?wcdxJovLG_zRk>I;QW=;BiQtS@~p%2u6G} zx=#0EF?|&+PXXG%^(CsxS-ot`hyF|rpV*MwIPYegbfPj8G^~sfQ-ayX^Bt5=O~R`E zI(>>6fl7nrJKzkWgUp?2o!5(aSI6`zlUn3o)#9jAzDct(W5y@Um>3tVo9ujjzh3>} zr0qvmeeJAMAs1z^44>Ig?GKkkLOLlgMC_Kakst6 zq5@ufuHN3`67~Z2fb$rFm&o4?WG21Q%wIvJ1CS2+?Q*}WVm|1otFai z&K*pt_DxY^n4XSem1{`c5Pg^_0(ZB%{TW~7(MFIL#AULppYZ7{b0m(n3F24aC^<&h z2c}Bm$J+$i+T7RuJ^T~0YbaZQx@yHin2}+{PZOh1GVzj};(0<%#cC&xhZp>vY!zD* zj+KzEU;d>jNH3;~96eVPr@D{T$fzkxdJ#Vx_xZX&N&e@*zGT`s(mv(NO0$jV)P)Kt<(5##)r_L?Q+l13;3>pD|JIr89NHfS17e>g3^ zMJ>-W>nofL15R9TXEKiqSkp6>(Hbxg8EUcCBIG6hpst8OKZvN!I9<2<&U;~Xj->ke z{B=Axpg$-tTgrWT&a}h&K#!$n*Pi|$1#vYY!@3*4>?vw3l)T;=kw?dTmzMOc+zK2k zF!?gYoVY@rDQKejizzr-zCqGV!%G*6Jvd|4V$(_m4lv@b65+Jx#hTnx$E0sCqqUp_ri76B7joEE~?_SSI`m& zSH|6V3mD0mWJ-$d*@pd#R?0eF-*mIG&cCc#2+YuYIsHWW>2xJZqQNZpg+M~U;ybH3 z#UTjqCx<|eLCIEik4}@g-=m;vpR9(OsaKb%Q8b~%%)^>Bg#`ovdAoJ~uA!02l%$rB zWimpo02#u8K^XcX5`YD#1<8=Ugn&s@^7d<2teS@v+in-Ycr8rj>vlksZ($C)CO~IM+W;tMn^P4p_$DxnkAX7~K-ffc+3pj#LWcW%NgJLG!%d{ESZdU1Bo9 zY$~IxG8(O(mCY&QKku>S4d2J>;7!l6^=M6Q!$4}}oF3(+cX2SxWMIyoMdOI=l57pY zY{l!-h}qCdXkaQFZMuZClfR_#?S4Q)tDT2k{C2a1mXXA2NiC{Y?tqM11VZ~*O{`Wi z1F3_!fhoyr(x4!ZYC?|Y)o{Z<4|bMwD$6lu5vw`V<<3$|aL=rp+Ch6NM{Da8%!@^C zdAht!_!R$q#56#A>hTiFKZAlxC9Ch;9%i;F%H1SmAwu6y0J!neO)mhEdPI5zn{RA4 z=H~EembGeiROjlg;*`8K31w!UsxIX6ql+i4bz^dO*URI>q*9rWrsR`zN+PkR+)C;t zQmX50B;(*-t7fzOl(TF(=%x6`rnL6KOl0)-@O@weA(1qGxho?%=U98Ix&`7DyX;^3 zHtjDv7~R?qKJoa$`Erz>eXGsmgoJ|p!gHgXnU5-Zo}|qF=VRzo#~!Ehj}*BBB_E`T zK9`)dfS|Kf_kSh*D7}&3=yZt2sD)}}^#Hr*k%8 zq}t;t@BcRO2f*eO7{B=UAvg(!vuj(*=)Dgm?g*E-M2%JQ-ygTm?l9*-*`UWoXPa9| z+&ZtjB3fD=AufsQEx*&)<&%c#O?Tl4<4g?W+bjNqZQ`DM$V0$ZS+3{GwSTj zf9`X9Zh5C(m)HNq4+b(|C`$9`ctXA?BB&)u%%^{OfBGEhbn|<$8NugQ9QEHO(5qi5 zC2vUi-;^?s>l_HN-&fDO<%;7xV2Y->TP@L^w-$Y>jWcNcPjs`s;9sMro{QonVE(FmYy-AuUt0WOvj1b#7s14VsMJ_)l2+7LCmD0XP*^*=V z7PA?fK)}p1;e~=`SM?8&O2iu(CTdn0g9L6wx=J4Nly)xnNgv|NApRz z2fs4qth_YjuL8=|Cs;!&sa4w46vVr{hkjG2$7WQjmCtNiX-*aOTKHKcL@z10icB)u zNbzBza~`)uqg{kRj3hbPW}fGnfff4EKCxT*wMbe{L$N1DQHgd7hDnXCoYrRj!3tU)Igi9&)hJsK+*knIWAISKC}y98X^+~&O2cVF~k zD}qfw34H#nl!O)dW=07CP4X1IyluY6!Yy^=Q`V8zzkL0{2cv4W7ODX*Y@-YUhrWDw%E;$~>x2#+ZSURp~wOwh4; zD66#!T9&hZu&m>XX*>RwH2R^5Oogq8)y!|HD4-d=RyE<%9NK-Cu0)=gOhn!FW9*zv ziACARpn~Iu_|2`PguKU3nO0RkC2vn!w?vxP0`F(^rykyX>^u$Z*!Aiy>wUUwyX-?W zz1_W@?!Wuf&MxF21C6K4c9t=2e-LhlunEWbzTAXpvI~ReJ^Yr8{18L)^XZ)P+}h@? z(yCo{jb;3-L$j%tT;F=gHPK!^=Jd1M&8^X-xvT0oLRRKbDJcRK1+cYHE6td0f}|Z^ zHV*-Rg$DC4|Kjd$%N_{VVC{RWx51L?@yU%T-EVn;ogec{XaD$+(ta@@u-0jRUs5e8 zr2)BK;1DhHCjMCc0Ukm2cDrnDh5nAhA+pfM zkXNQtt?g?$S zb)K!{EI3Wduy0k`c$*TSQyB>iZl`{XY{ifOD>?LDz=ONmV2&@44&O6kkqpvJbHng^ zvCjUAb?2j%q{{nd1Q=L*WS~x<#V7>GtxhnaupEzMpCY0!KVoG@j!D3AR-d%D&fWo= zU6YgoZqwYMb9fz(Po^D^U_3qEx%|I+Vvj&VUw6%WpNZ>85%@?=MH@dU1M;%EnY?8C zATAdsj?fkVwnp*(%#rA%rxX=3ORYevP~R)ms?UE^S@`U}`qCdLCe$I2R;B6n{L+TB zP)Zt=tMFQOlF*-nEaI6T1c_SSycf63Ob!nVX?;}#xoq*V8D({t1{^jyL8^w-Lt&BH&W%lrZEVfh?4HZa?``a_ST`S{84Or)Hejyr^g0+S~`^{7N)*0+tZa=(~xFz8tBn>kkFsR_SCWWU0}vI z)&OVec!}c;z+NEgG$-Q`0l-_m)<tGY-vGfJmk|^xC0AJ2AMl;OHz5H`cWTAFA*gLyO#pf)_=00 zX``R232deMQLdR%Uq7ZVct|OshEz}5udOsdxoQpMDcLM>X_T)-SDADf^^Kp>%7aXB zx_2jK=e5~>NYy?~T?dMn^*(<_WD@;TjZ(~>kLFA^D>Lk)BFiUNW!oUJM|jP{Xexz2 z$D3&J8z=7+${K3rDRbAM6ru6ZLUqDX_nl&27Kumq}5-v{Jg3h602+bHl4xb8Dl9WYV}gmw#j`c<3>ry!<$UIQ}Ux zie-Zp5{D4q>ksyWKRcAyh6?>@u$G z4(|WcSD3fYN7LpJ^)(4jcN_5b-t;lc)yKX^+h^k5NKc`gcZb>B0!8aJSe>Ws42I`b zNBgqrhDydlP<0+59@buJchv41~WIx-uGb4+YdleBqT~7#%WotiP zJ4)FMA3D@T#Jr&XA+>k?xD}n9*_)3~>ia`p-7#HpHm`{o&oqXw>u!YuZkn{!Njmty zI6Xq;h4%7TxS^x;`aFD@)T^H%8PJb~?cKL(KmR-7hy^pCVexIniUdxkUdAY}m5>K| z^R_JbnmO1+id-+@A3#JOf)+%b5h)bQ|AkR62RoGnKx_|({6mTE{$|lVj~hq(j|`NZ zi3(c(38e77{3p)P;#5#Y5Y(g70=oLua0%D?uhkM`e{YbuPzYbuhqo!Fn|j;&yP>uHsmnQy6u#iWp<8Gl|w0OTo3LS>PXei?h6cH#X(zf`fOUf`v()MC!UJJgwH-cP&907dn9`(QiV@X%R6fMNHg&q~?ilBIUgy9VZs zTtsq^O0ZUh$V6!S50Z>z=&&5yTB*6CYUw4`-5HMl{nNYO^op76*b%MNarnCA;jr(VoiyWPr!g zHsN(f1=UOx3R3D7puR4??6OW_xX@J1cR+?yOumrV1U~o=ayL?}2Bb!DZg81nIYt4- z0c~0-WN2enBNjH4{JiF|zmQ6;AzZ1Mw=D8p8AoE}{V=RO@?ItuI~>=BEky%s zj>mxZtzo4F=67s$$~6z1>ebL4f7bwROz!IaPkSFH%Ln|Hi{!odC#iEJ^&PrfCMZ=L z_T_=kE)?@W&p2xITB5b8b;MbyjpfFvjm4|9YKx zK~#>9u_v|%L5eqNHD2Cml-Ez!v+vo1NKwV#!T2`CKT};VA~}wo$aq_S=sj)RxVpT; zSIVz5`Vw6H2+Mm)d3qQXoo&Jev&*rRj7je}xowP?T&LR0J*IzVaGPL`uh561knMO$ z5+}4!^-3W+$ESL4Ufj*K=M|`e?CBZk_aS9@;{1=lWrR)#>Sq4j%B`<7`YxS4*z3Q* z#CcT7jHZPdX~8SRX9q*aaCK~2&1B2>lq%}X3`}d$vN_KHOi&w#=|OcB46~~PNywnw z=7Ul>e0s8m_@d9JJTAXl1?fGFsr6Fo+lX=`7xLfjBTcj8(XUmgB(v@|ki{BaLGMG7 zR0TtlUZtDTv82W}(Qf-|;fnW0N5AToJY&7O07N#TsPu8z>3}B2>n)qpMPDRogcJwl z`&t}Y_S&CywpK7_f(tk`Z*T3b2&|7A=B7`@U{hhM>Pf_V2a%cPgn^r~^ifLB>l5*m zN6|%Bx?=jQ<*LeyWhZIw^W1#O6wD}pjTt>|n0d0GsHDR<#N?b>u$CTnH=vHciD2nm z`VEw#YF!rKQh!Y0&@52&Hn=x34^*s=>Vk88Ib$Whw;16L7D!i<3lFdno_(dR)*w88 z682+8je2J>?OXi)Nye#KYi5d^#;xIFdfcxb@*P8v%Nu$mvhkD=zDI}H zy1d~1%9 zZDbycoHajV9hhn4AzO`iPt-G;TlrM-HFCj1qFFjV-mu8pc|kXoIItmJ`~qx^Xh1$j zc{e&CVp{1K8CjW=K6*qLee`3Fxu|&HenKZbwvkd~TYHE;Qlhy36ARm$5|YU)%MMD$ zd25TUgdL!A%G+SBks>E^VR&)Qh*yU{xRj!qr`aUyU%=co}6-+lEK!Rt8fpsbBLhF2evyE^R7I>{Za z;(cKpAzwJyJBK) zI(fKw3A)acS$?=IYwxA@Ip(|sCFFhH{cC}ePwIJvSD^lSDqEpW_?!|!%T@7xA&P^j z;eI&?1CZr#N1#C`3LIOvK#__z@du14lN<B`y*wntF-ItW3^U!$VV5+V)@Z|NTudt!d zllDHG+W%RG)%5?SmjOQ|-_G6jhT{g!ONhO5{xZTt8K+bEUoq$XzmG6W^^Yw!9~*f8 zt-9K9Zo(h%P;8i*EYft(&>9{{{PVO{ad{+hs(dnc6%B|w==(sR%RD#vcyhtXqaZOR z)qsEVI5uu==|wgAc4EBHXRL9uO?jF3DAjV>)kdo2CV8cpB*bI$7$+uXcyv$?MH5DE zDVH2m0ZAX^Yq4FaCaob=YPiW2J;BGbhAw>{jG|nItj`yjaWMcpHmlCfB50S3ak&Wd zfE#td2Tp0aJ@uv7qg4FQqsuPrkD66-S&88Dfeg)t)|KTi&sA1;a^0TKR9O> za8b%?Xh**$A^#H*YaM%}ODvN*$$|VPl-GiYZ)7sJw;MDCgopAp`(oQp7&jxdL{a17 z4IB~1UqzN}qwnr=H6{BxsIrv0_D{*>MCcP-9V`I$8SK6p;v#!(YMdhu;diezxbp@2 z;mj3Jskd?s{(4+(V!&aowe!i3Txlwzm!b`*-di5Q!Vo~H5CJXUC`z3gcDpW6jdFE$T!x(mQGqhXi~o*4{eu^tC_5Xkkq8r>%reZpuNypW|X z(xy0nD|8Y&e~_ZCT=MSKkQ=EZU>H8^jc#A_3%0ZFnhJ>b(6=f@y^$)!p~YmitYU9& zB--PDtR410Wg$!i{YX(OP`j3@aQU=%JM<&`a5f1Pri*Hc*>ze#6nf3mFgKW_mym*C zPLDL1bi^I5a|!Uzu4LVZ8ZpK;9HJfeBC5v6p3FZs75(VZ|Dt&9Tb0?4ZvMiJ`OcoV zHv*{{B}HVcP-o~=-je*cE-DM}8NjZBFXY0Izt@_^i7V4SwAdlJ(3Mcvasg(LpvEWe z@JoiE^!Uv^D9h^SpDs0@tnL0Ns6NYK3z?^%$Ksg#Y*~D%fWyqDpMwImKh*ScB_@!m zmpyA)($7gW0n`HVUp1W<$#M#JUkh5AnRfEc2p=i6oj!ri{y&<|GANEN+S(zwySrv^ zcXxMpcL+fSm*5TohT!h*5InduxCRLB5ZvLL_ulXR=wGL6rmMQ@tUhb+{j6XxNiqIq zOO$^!>J$S&s8pezzkL&GK}Tw==uB(#6W2OR)$O+01;V-BDpp&!7N={U;-tuJirbKy z?@elGd8~Y?o-upbu6PDZMn^e&(u$0E)z_U|No!?#ZGwZm7@rHZv6=HvsTgdScXwmb z{w$yT^yqy6R?qp$dHa1OE8AKx-vEBi){F->n+f52Y6su`*jjw=#1Xa`;BsZ_<6a8h zIEd*zGkCGl$S%nocA8O99!PC7;XK@I%RM1KKgmjKQ*3}fEaLm%Bb`VGI8y)G(Jb2~ z>vURETS6VQKdch}^Dffi# zDED&UTUPU154gaVtiCRLz!wfF79hzCh;}|TsJct5T4HxvLu`$Xa~`^9JibF&T6V}w zu&8v)il&Nm*@~qse*iQj%vEdSS8-}t&w-WQrpeN#Mr$t-+Q7|4IS>d6jl{Q4Hvo7dXU8Xv;8bpE5ampbn6j5JK zHBv9rT(ApnH7(uZQzKT={&kG?p zMo?6bYoNJzYAbR5Re#wgPghJ-Q9!r^gA%M~Qf~gID6u6c=R@%NA5}T5J$Lf0S1FqF zYus~aClh$pk#)`MQuse!C5vtEYs=+4qp9zYa&vVhsh*2169UZ47CX1F!XdCyr9Kqu+>YX@G#bgniq^8Ojw(m~dKhI3ql3)6WbaYrNQVt02Rv z&{)0lOLps zzDFGPFKe(h#pUbN#(3Q#{cEyyt9NUx-~^R{7RYyt?HJ1c#fuAN5Rem=#ELv4K2N3U z?!UjsEcZlgzEr5r{Lbd*S{mm(gVb{VdGxuu2AY!hhv1@~cK7n23!{@fNa)C>m8QYfQIW?g zEWUbx0|Rd&e)lb;_ptXhe5u-m`LLg@7@T_}|9lf4R9t8L+{G@x3jR{~N^hcl zcS-PFGU$DM!(#@@&!tP^e5J}@RVy(M%lL2E zBfXLaPK|RO!q}&CpBT_=7SYRxK*)o-^|*q6rO!FfOALp!P@50CHAclY zaks$bRQV=_R#XpbBj4|D=6hsv?~9Y~$4a3}%S4u9P|AA;+qF)RQr+grw9mzd0@z6w z!a$^KmWC3>R(Lvqg7 z0-pKM9!)U3*|55ko}n2A>=Vi_8LO(J$6VpYzWj@_y-P@q=;h)OKF4dvj~Nbw4bsK* zynqibqD~Of7?vVfaYhvjh6yNI9W%3K%_E_v8o*mC7QFfmk6gavgHw`wE4lX>VyBtc=V@w1|T&${MibQa;A@tl@JIbu?CrJ5_PCXwc0QXf34~krA=g zNG&(9VNuj`Tp4};`f#~-ZlHa)X}6Cc#o(8K4IfA5j-wtk9H9UYV(QxeeBxud`ZI1_ zfw+$7L$mw>3h{cB~Ui#y2 zKnxOt<=5?p-muCy|L_GR5ni_UwnyK^NCF;Dh>zP^g8K=@R4+#vmZWj6Be6&73_6F zGX)XBK1d_p6Xs~*-FRMi*T9}dG#PGceiMPr`prz?w~SwLHk%6X)8^a=#oR+q>*}p8 z`B;!Vwl2l1*9o6Maw@VXsV4Ek2MKOi2j%oS_TGXiw&kD3C(tM$aYoO-J<&D;%@x;Q zBjdw7*}>e2mMTS^KZ}L?mQ|I$Tuzf7US=*>&*^kU7e$>6B&L|w{?JvNTd_Q>NpU2O zo|l<)7s=*hlP!D~XFf&-xFR&6>`KuCKiMzBB@%vDfrbCGtLFc$LJp>+*r^zYBgBnW z{kH-s6iCqF-gHPNc5!VO^(#DxkRj?{puBPh3mk=o+ei#4B@!8-w&+*<#&fN9);SlX zqLl*ko!Is1UnayCVT#6P`KBd}%YxJ>F3B^VXL@YA; z9n}_I5`BG7DD=`usm&@Q*^!^+URr_1MK4=`lPCYY?sw0U!?IeJ5g2zU$83c$6PLox zXVyz(Y6hk86gMV3*;K@mB-Y6C2d%Ni9DR9G`xZ z9UA1QFIOl!o&1Q>#&L%8)v$c&no3T5D^?v|4IJpnoNVt&>xngRaG`#iwAODL#dP#~ zsVkRlBE?pdyr|D#+pO1PB-Cya10mgZs+|uzgtwr)h}73I;eotJbImgrfY&2h3xcVWjUSWrj=n+6k6+T-+3LI99c@l~9UyAmHawWAqwa8~e#lQ$MFq2}uj`TlTJP zE@!|UNt8uohiPt2wyi1-*FtxO?Qnrq{saJ6UVdup&1(z7xLG6@y1Pge?o_(&Trhl7 z2BTk(JKeNTo4Zig>CuaHokii#LB>fH&_!;`jr_Kk>k32j!}g!PX7yTT^(f~#VN_eL zanc)AyYnwA&r+F&7a0?-?XypC>@D^x3mYkr`1XoxadMnY5oF!-QWREippN>K?aZLO zIT-IJnRF@4v1p^#{3-Z4azz@M7795|I6!E&QCPD9jzB2XEgUQ!v* zED>D{Pb>*65FJQBxn4pd;j8NvKE5G(ev!o)AuK_Q_jHI4Zlupr9rbTDum8$8`QQ27 zJ99f2GK1ir5a3vAIf9(%&Hi?#8k7&&_uZ&L)sVp-RhsALm*ZDt*8HpwAbP+pXI`mu z-c?+@W$1bnF`>2M#IQ-&#jsk%WyY%Pz53GgWm8J+g24PT5Rga_A$)xVbwh5g-oO<+6WJJk zE@_=!UP@+tRY`Ta28f8nB6#Hqb1fLVGjK4e(DvRd5n4EF1{$dh4xVXjy7j>0#_SQq zJ#wrurs_WbI)m@$2yYCVAVzcY!54(F@4ylK8xIR7v4|cfuYu^K9WGbF^l3h(58c#q zL6I@b?jCxjP90}l7p0jdy%s=zGXIxD>04H7aomY7`R*5c_c(I(=*qf zQV1CkACbkjKc6`oZ?WxaXNUVtJKrHRQ|>JH=XF*K{fmzJ`F^@}Tfy6%LpO4UE^QnT zaNEJDQncKA70-3y!!;R~)ox5DQ*&e1pC-Je5ksla zP{rxo|2A^y=bts3;F&(h?#^$nJ}g(l=!D+5D(qfGf~*xK)!GqzpSf^?75vL{4ON$} zNTVRbQd#}0*&`)w(`_TxJE z*-jc+{Whxbk7a;yaY36TC8mB2T49fGvZi(e^ttoq5BvCiRigEk<7ZFM=3 zs;s{Op9PDV8hTw-li}xT|GA5(HS8ujw!n5VUcI*QIG*q_`p*$Q8Sju-t>XBMS z_B}~SH4|1@=Q}-baK`)^HY!E)=24-gZ&oy3jTvHCfZq5E-E zkr631hrd)!57QnAVVanZN`Ao%LG+XIBf^&a?Z9>c(b*0z7`2~#_wL}fP(>hZ5r+h4xqtb)#}M1H>)y?E zeUinrPlfxEDMb*^nO;K?c56!P5VzsaHKy9Moi=mY7E7J>1)gu+gHQ?GI7JK-t>cPK zyB&NW_Ujc1gZMd60jEc0PZC?txy(`w@+p#YWXBE`HX*TYuPKakZfe|{@I=p;@;oMQ zhjH)eR?E2Q7atjC5MO<(D`U<|`mum6iNmY3e))!g6x7|Fp!cR^i`VW+FN9Xoa2Oaj z&3}Ez)oK%;-Fwo-I*;dfp<)Ozz^JGkeD6uRocy&w1D+&G{UrVGdZV6)3JN$v`Q>n8 zElT$`3z@o?)ZyN3!x7zp7}EZgFO1Bhjfbo&j(WD{#w@}^ALXIim+2o4fj^>MH#OV~ zbm9gB8P3$t+FFX8lC<$&Sx$Aqs_Zw8I%_hugQr&Z{x!JWEO%-G{NjG4r(h(C-7NxMQDG0w@Q~Pm+W;KdB&aW zSXTmUdQyCh8a{^LM$1G03Fyb0uDGaq|2D0_9)|<|uBSYaR`b4jMPzhtfHhxFu0f(~ z&CQI>Cki=Ls1Cy{)soD|3-O!h@zwb0--&B4c`SJXy{N6jCB8~UI=~Jd1_2SSRn`m^ z*zy@Z@0+eXdY5YH`>VF_e{4D}NpC(iUqE$On+tr)fv8;K7zQgU6cMWT0T(!4a_o0Uu0E;&VLMeV&c^o3VY`phmuL9%+P}+;Jo?02 zLik(cGx*a1y*b4?(n&&RThg?Hn#;$HhywMd3vIurXUEXgJB3IOIls|+{#vp>g-R>j zsn*tZ8{c_6_`z=?D~;|fxeL+mKTEY2GiTYjlU~Z230+;yH53oA4ru{r8nvHx_#B(8 zmx7&_x|$#;Qf1McpN)vhWQeylJ~1Q^0NgdDrR-;$fMO88*}7c`_gyD)@Y)MPSJ#%) z5ioM|OA46OHIU3Vetm51FuGsH>%wlRf!3L$9}YpJLmnXkk|KO$uJ%(4`UI^%(h~a_ zmb{ctn~85wY&+g%>s`+1&L7E=$3MWoUhaZ$AC@v(>z{bQ58i)O*Vm!J=BzkCK78yD z+V(YSFw-1OTtqiTZ8097j-T0VKlNNFCQ7@EzStT%V2%5YLMcqo!)=X2A3H62eTeb} zebt@Wv{jA9eamd<%a6#UjOIjZ|8K}1FHpcM9d{Wr4HA3r z2Eqdugq={@$$v=12HM>1AgjVJ^12c)xGciKvS0bO~-FQN*i@uM@rWrItldumY zZ%Jh-M5WL4*h-Ccp2}-3Ly;41M{$;*T%_;B&BM`63h;3KyUN2az><(TpD&tpOsmV+ zH1$J?M?=-`y{vy|I(E)`!AU)sDu{fNWE25NHnyZqtYhyz1P8w{?XV8BBv4|lvt)S6vVbc8#yS$D1?+U}Yq9kX4jh`$-Zcn~!9JQV7twH;e0yD$*bGHKt z)0r*84}SSY-TsF1e}?7`*%L2_NWTvYmvel`w|?j#uoRtDze+1i*g)kb%;%2c~3 zv&`mMPDP#qNVLj)9JOYvj(oaMhS%_qeMl}c(Z(sDhSJT6@19y>gqh~^LT&!%-v(GN zL3~Xs=busPHD%lU^Hrw7hp0_Rf`w*i{ePMuTW{{NYhZ4fci>vwfH)-y>H#PzJ%`ZS zslTG2Vr?xfS^j-#g(yN(PS|NokH~U_u1k}OkB%|YERGU_Wd6k@zxLy7+CM+Edm?~e zuST6!(A{pt==_L&yNu)p(`r_IOUIqIdt3R-?7wv3uO@z@_c$O%tx8~QSo0~haJG<@ zWYg+sH`;fQYx@`1!ta{0YR5Yse_~rar%8P0^|!ucd1E<*^V&A>A9^Y3{e-KNq?1^0 zH>&e$*)10$Z`jbyKwo7_G^u6U_E5k9Eny`kUH(LMjHH$pJWQRut3+2nXm*bbgtyq$ z=b5^Kl66i#Zl}Ge>Gr<;zd0;bVH7|*-iI7m@eQAI@tB-TUX^+=FPgzLC=*g^S?CI zI=>j7Lf_W?hxR9K;B!CTocYaLkYeW9PNqSr_RHuS zAv~90Uh?6XKinMgTtAWcx@G+?8=iT9GQdwo0owPv~Wr1PUbjoxdiM$scpnA$=0bK(Q71~p#`BfhNZwSSHXTT zOe+e;MoFU1jqwqsoW1v=5IXyis*xkBS5cuey*!$zv^TH`` z8tPKS?+&LKy}xXGuNTQJE?9=9YwAIeYdOqeeNwS3(xg1!;%SiBqH(Yha#PD?YX?Rn zeKejX&Y?OjpCQvrYer{x9KgjY#kRVa7TU*%acQKN-Q4T(FJ8{jm&G(HnywNiy4he2 z60V#8$?D7;PQK^A1&n~EG)H)DdA)N~GmnJ?!SVzkb2Z^`n2#qmqC+T2{3T^?E!-7IS(o~s<9CffB>TZ zez1g41FPq^&{-zh<^E4G#Wpclj6*+VN%gW5K6j1&AO==eYi!qoBuM8GtGy*d62~z zJ+(BQH^W96M(OB+COkO_TG7Z8sfAz_EknZV_WPik9bGDv=FR+(7?*Wq#e688`I#G| z$6rvEW8w3s9AMPijseLXp+5N;7T9O5vG4S;9`@HstX8E^Lft=jhUu=q`gjUz`EQlC zrOQxK6?I`SxNy$-xSQDdm9zhSkFl5qX}+1L$0aK`B_h6}!G$BjS|s`nV6?46Xld=E zqWj)qM5zd2*Ya~P^ZF0uxsc#ezJL=D`iyDqNB7+eXWos|^GZqDjFp)d{_slQE)-eb zjpNuA-sEe}fKPBhyc;hG3Zwb?H~mwE6Yg`hxlpcrrSkMkYu4U#X;**(h<-3MO&ILA z8cKfiDSdAzAk)Fo+^veeKyrr*lSQFnqNOt771fDeOT1&_uX#c}lhL%oXdKp8zc|{_ z_fU22#mo$AGzueR%-Ke_evymqW_+>0-uSYTFtt8Tz_vUeIQflIl$dG=B+uv_rkqux z1+SU6s$$jyUl|Wv_oU)4r7et4#yUV?D^ETh&NyHnF2FXxrERMsKoD8I$%sW=M>Hz< z7QxR1JA5X_ZJw^x=YQzMEDdBxgR9{x^galjL(nq)M72HHyDV8GlCSMgfgE>OU(i1z zc4aZBpCvNT(9DLwMwT#Sak3C02k9=_2=o`5dmNT8AJL=O!KDN^|mMN+n7CNNtp<_tQ8sxok!!-N4YHDv7aZkO9 zZ9xKm9Iy=}3~F%~#RawE!d!!?^4Uh5hR_r%Lgf|&Eb5rv`0yl1>4Lf`Z}C zbwc_sphX}L<9rEBN<ASvxluuIOMY2~4q7MEMy0;Ji6B@==e5JEw4+(`?c1tCjzC@M`E~2I-r9lv z`lLXyY3GY=itS%EbLGva-otRqS!Z1SlS`zGx!P_4#O1k*%xa5B-YQE?7fV40ptLbw z&8K6H0Q)e??xSMtqphGfL1DKC(f!vc2%*jJvJ+qU<)37V2el?$akoE*KE6l8RM-6HVw&3!WVs|PF@bB+{B84$#G}}EY^LA z3o)z()GvP5bS2^J)-@7R8KU;HHpDOI{U9`}lPIX^+m;{4x)0Na(j0eI3qTKA z*qxtTbfDhw?f{F{o|{Lij-zFLr6osLbF>XBPS+JfkKbRja*DB_E3yCgMeatVQst8SHX3&kPk@1(eiBcvbfiV(pSp32lG%%#+$}V<*hJ3LIGV&MUPFmG|0l}Z zfvO<-2T1mWXz@eWO}th`am~C|JBK6gBAP+8T~0rCIeUS&>UU3bLkPHG6q$FSfnsjp$!fh2T#5;`1l)>cVfxBYXCAArPhkbY{4YSt{7v$ zBpps1vTw%0nmZ)^LpfEv$~N6n5L;NoXxoqqT#L+t>^7bcE+F*KZ|2BO_(gHJ2o!J# z2|GxjZlDIPPs9~K`qYgMIjuF$c{!c3$Q*?O>R5_|?s6{%UOjy|u0XCA|HDc~4Trz% zOoSM3#K#ShWUbR+!{#3%zQ@ZIRb`QMPm-qpBk0=o=e2@{BaCDLpru+9Wko!uKXQ=J z=)ciJmJ>f`^8$mQYm6E~O36%B7$J11^{;PLz$un>=KP-AJM@?Tb(A4S_;aBa%RGDD z6JNkF(52v5zUk-37=K<_jn_u_!PgF}h|d2jLVxMLv72bOe2UTbSX2#?tP8q}<+Z5z zUqnO4f4pmuP~vzltBGu()9<)u`qG4@zNNCpXkhscd(1;p4D6~JnS}EGhKc0|abiXa zyO2bHH+I%-pGUYr6gYR6Pn6l@ipNj;hdEzbb;tK}&ENsm@hXDNQ?nlXGEVQ0Wn9N0 z?~q{eB$9|qQnAvR>fUDEpV2Q?ex|^NhaMY5v0)Rxvsj{F4e(50s5STtxUCK=vE1{M8Dg^ zZ`rr7IHU^KOlyv7Wrd+*B1Sv51PCLxw3L#xAJ&vn*VP;D+HS^fM;NBFtMke;&{bZ9T= zS%?TWFVoavD)bPJPh%X0J6u$FUwWfUYj7&D1_@mgdip5Mwj$w(73jua=;& zJ3Y>|*>xtkKZaoxhnbh}NJr;kq_w`$n;njoj47Zhuml_oihtp-gmbQ?bZy~Ind99h zz4?g-{%m(owMiHX8DmT}h$jLL2tChly~{+BcoJZiJd-1_8sVwSUPCifrEhxc{xyzz zT=&LrCE1)27D~A5lo@Hbuz0CHd6MC$Z_Z0)2~0@4rFjc&M+g6P>p`=0>4doNR+}~D zZCxYj`rK2N&1FVOhhKjjiVPx}4lodzky_-Ayt1JDbxR&`04(sOBbHxUQPWGRRwzLW zk;gYpK?9M6%0`J#SVpl=#D#-vkV3pz)(~%~^?s<4)0gFbunc`agJ8=BFT>M+P4;?m?RkYdz=ZZq%WKIWf1%%E0)Q zz@DjzQ#Q zCU+9}rxSuBXo){}3va8f&6i1!m)=_(e)F0>_OL$^v}HBy+caO&+2e&KN8dFj+j*ka zIb;N^p*zOsp*+?I*T>!icwDnNE8`L#2UwgCHnSD^r+~)yJ$v4C+A928bgKAS^jRt| zrvlCcDyag4!*ibABojvY&IM`}Eg8)=I`Lx+o>J{TV7S83@Ais!w!@xIMI6b+!FT^U zQW@7xo{snEt?Z=HYKV4T?mGuyYcl81%t?r~VPtz1Gf12(UtBNnvS!t7vlX^PPf7T7 z$;rzYRXzaScf_Uz3aecs(opQ*g?WbTzu5PqO)z?C(6JEbxf}=Zl<<6Zq@zhJHMnm{ zmO+6<>YUmSi0fvZ_7iBzvn!)!D7$uNI{l${UiFFg=f=<`^?1!8$r<@%MWcYU(|VaD z{Sd!;;NCk?eDao=sYJlnAM=%sJFwSMZv`rhFPLf^Hn74FBL9u(l2D-(mFt!>u4oZK zR5!_)d;=2m2g{2^%oUnUJz-^!x;1slp8~?CjKJpQMEV54$L5aEc>qA+oY5+mW~|D* zq(B>1VG-&*t9qic8!_6<;#`A@Dw9n0ewIj6atxSCR-4?AlfPo_WLCll^22zQDSA()&9E>nW5h0E*Sv$8!oOtV;P^QaHY>@U^r|8UshcP zh=xE5_SZ&fmi=8x@7oQPFMO4Ylf?;$B_-oKQvyNR7m?;S7CM_~@~rouYQGGbr!WX3 zpE;_FoG}Y(ouk;a!Zw97L>Cr}_)QW55j3IR;oIo7X04yXZ^mwd`C$gV)Kx&Y4El>? zIbJp&iWMq$sh9zAEjUYIKVx97J?Yt2d>q$BJ>$?T+H?yKh5XRhg7SoqN_#h1dV`)E zb!SdZfZo?{iYH|YSZoi%lrTVH85_Uq?fU1NCF4T8IXdO;Vf(gRH+t{t#76mpl|`1* z`X$FQxk7h`A`LsxS`$yBj<8sQoC4#wW>eEvdPkK_F&Sp5>rvGzb>aqaQ z!}h}mc+)3xT|>)02>77#Wixw}PzncTDC~QAx=dd-sXM_gjIE65Kpba+xd(~66k8mj zdw&>GdJ+o5Q$MZIS0Ve6i}&~PyV?tbjTQ(jFg#S_)aUP?jRH4aUG2ID-RS4(8hNr` zf%IdfoxdD*r?Plmyo3cm(s~QiMBe3LM-s-4?VrJ_fgrN$(G&=xbhFFnM#ovbM&^aY zGYWW?eHj=Ot+6|`PHG$4_ub^<;Y00sMO>;m@J?%=`@ee+rn1^PBT3(Q6AO#TEWxL| zCLZ)TZ&#c@>BRqcdEN9!_sKrEG0@ceiv0MG>6`g~zXy4YP0z)j*?(pcBpo6Oyff^s zp}MN@rE+p+aQwn~QoS&{66?Q|{&Jx6TmgQbg+BRXPc_aj2iFQuazuoKqfNLYHf7Gk z{{<**anwMp5&8g*c(6;kB-T7JIcLX($>8nOS#>zf$Gexe1+p}1|7+D47du~sVUW)* z)mSpX-Na~NTNtD;t}oLWbOS-V=o+8`XDtbk_dO^&Kx9^o!0W{wca z8sJRFr)(J`Hk*j8Fb`4*`hY(kaPjjKzXK)~B@}!4pI|q8)(O|_BJ^rk?Stw(*Tr5U zG>F59j`R4o>@+DvX=0G5_Sx%&pLV%QrhkKyN_}Zi|C$r&7-Y&ZK2Tyl<0$|8drB9) zI+I%~Z(`hk$Tx|0sU7z$YZ}cpIM+$jQssA+z zTa@wp`$`dHKwp-J-d)#F{>$AXdZj=B2i7>mH=+7Q;=-(DovAGP#Lh24IAT>sJye0n zLeLotuFHy_`H>{9S(%;E%sB^FPEArx&YRBRq&qd-SY^4KQM&7P3^(`wDNst&lu902 zl=@Y6O)YayeS}@B)zNQVb_H}f8Dy}s8IzDwAc9KjBp&N3iO{MQ~tT-JdR zuC~O?lEtmm{=2ELg5MxKeZwVX(F*;WMp!OD^Kr^`LdrDc5bG)XGIVf2L1Phb-)Eoe zJ33{>Z2+QEVfyvORLlWwJQQd-xA=Tf0~~$uQEaxYrT`K2qdEPNsK3 z1@S?CW=DvY2Jgr~)R>^ZfOe&FG&BuH+BBtnqaz6tAAW;m6+iL=^UpJOE;d7RT(d2M zieZNEbV;9Ig349DN$ly1bbo=g8#F@N@4Y=&EfB~I^joT4xD~x&!!`7K7B4ZQNspE} z*M0-IqSwnf+%N?qGGhOz38;=j`U^@!{&mr|9`4)bNiBSW`t(U&N?fZbe7^%N!1>AN zi*q53KjiQ2A~M}H#5?R3&fhVXaYx-~OXbP5b(A{0@zk7Th3F9b`?&>7YkA7)6MZBU zF<@>7KOO|uyk|?Ao#&O^UJuIxekKS#nK!@xg|1=8>z^wgl_{@a^)xUBcUN;(05il{ zYHE3YjvC}d6FU;KSkvr?trXJi-E`0uQA`e7XT!`Ou#;6)aSKwCyAv3M6WnCNUknI! zO6vt7*@xUTg^zTVZ`tP-nHz;%L6U#PQh_`s4a})r|7)R<^X#`TuF_X44-OoTlnTh zJq!n*G37As7Nw+CNs?>8duWtR`y`Fx=>#5=1Q>%PKX}Am5He;8?QPlTY0UVx2u!E! zIUcy_rgRkT2T9iJr3d#CenHxc@6p^*5y+HsF)FiSBsJ-*R6a>W8!gvglOHaDzfFnl zOXj8d$eMvU*&=`NSu_)?5D(|K*Xr7l|7lf&pS$OaGw~W$ARZS`bEi=r!q$Lq>lQl@ z!q!;gaKY-Y*jSQMPTVT!hEuc8M+?^{EukQC)ui@Qzl1c~sAB{5hZ~?hP1JHZGaEz9n=`+5FMtH`lW4k}SCSG0Qq5 zN#-xg(50`Hok1C&>CHx^J&;NL>xd=BD*2?Ng2NbJ(gunen_J6|AoXv9V(A5{(ed&K zzWBdy#V%SM7UpqW{RD>Cf{k0Xd{qa{vAkaz27ukqU-?fazvPvv(}RZG>PG?vlv!dF zl1rzb^2vghKX;;iBU+69CU!R3v%YRw^qSo#y$NVeiV^tXS$IN0P=L0zLvw-X<2tS4JZST_a(NDZ^Epp?B;kcbn*4Db-~P?Sk6HcXKYev}^;|$4 z(eJfwbuldjdEa=B=q1zKMt}I!0iE)?d1tUyR`N`4AN^(=-)&rcC*~i@RJ*pNp6@%! zCH-o$ObS81|7kQtZZBe%GPCn-dv>?ztF6)8?UQF#^ImVUO2_|O?DFY6HN=s=9ZwS( z5V-Gd3KZ8h33$UQ8U3Hd^B89S_9C*d1q2DAs#UTxwZ{jL>m!r>Ej3|m1K&D>h?EE4=}a1vmq_ zKl0_xF29~TrawN!(WKAh0<{*5rZt-Fve3l! z?Q3#J&Z!7MCFOGx>~UBO$;mgsw~pWqS&f;U`sjf@&!T(5D*7R1Df5GFW(Ra^9mR4A zrSQLO1vba7RnQeMK4NRgFJsYZ`~WH%9K`dD@(C1%eOGCx&johU1I2_eAT-e`-@F6= zZb^BV&g=M$gTb3vnXxe}JBm{94v8 zK61IC`S~)8-aiEp$m}jh$;(A&W)+nrR3&=zl9aIarx?lF?0vofp0q}|5uqal{!AB# z0=s%yX-Th|aX-Uv6-~(Xhn+b7wv6St^PVo=EI>lBC}^>|v)+P!j-+cCv{!&YD_RFs zu{sVKCyxJ8U(lZi{#=*N8KIhJ&r1yXF0f)M(E6gHE6RAOLwA3RgPC8k>E~{VOoF?p#u3n4Z}056?g1OqvDQ zaW|z|?wO$2kmS6Fh`6N5%kW>ON7#2SjuB&K^RGsQBrGDAX&&>##MO?r+If)~tfAvISJ`Gp5h`l%+t| zFNEX02p&bFldN-@l*lE(CYQUPJ@k2`U9x=Rc>6zf>5=#8TSt8D>M}LLTT>TiWRwE2 z5#sZ)w%0jmLuyv!08{1M{;oMA2sLX0DG>tCVme&cWJfJTRfO;|;sg(eb~MD&Nz@7} zakX||rA5ugOTZ`6W91t34$l#1Kr-oY_qJtE#s*3K(j{W`MXie86X7L`(Y5tCu0P8v zlu+TM)l&Z|Hu}9#5b>ufQn)&%YNXo7M3kSfa}y-dH8Q&P8NJ{nT4ZKJOmg+aPOb(^ zEi9RLGUehKn)b=FR=&6dbt2P?4!w9pV2_apYI^KK(yT-!&_9;G%D92ULN>%_IBGEA zgzLnOGbr}_OA@;arP3m+pe4hCI^J~dgFv>5_j}XvnW_~UY1-?*GLP#CQ#oR^mELdY z2R!paK?^Y41P2@5A}0xl5=$jgQ=_}{*(b!7kdVv47r~lU4-K4}TT%vdu)~ z$)E_CrQq>_0ft1CFL6A` z0JHLS9WxqSy6pw+9@62kZdULQn;P6Fo{bd*2+r7!*L{i*-2Mb(HFlMw6fN)R}|HYe+N_RrquyY?jN$`eu*{js3G4?Ucnzy zmrwn+_71C!vt>IVg-? zl%gUu?Hc(%G7Ax7!*=04esKPQ)xA28vZlaGGnmCZ=Kqe;J<2Kx^T>HGQjmnc-bd!O z)_}wRQ(@*M$az`4==XKla;3lbXYra%#T2E6X*i%c^ovWZE~;NSR@cDlOH_^{+;}Vo z!w7&&p%Y-uI@P61wME=OjdZDi@yKmPTiN>M2+J?9j)Uez{l- zSI(j?0==IR@w)r#@vfpfXgg_Zn%-ylaSOZpaZ;$uNkL4v0`i^rUE3Z(D+dQ=kH3YX zt0Xh#Z}2W>b=-_IyotU+u$nFdl(+G*r#ABEinfSxTT?Mq&y^`TWB9c&jaRCVXWkPU z6-|4|4J=@DbXN720cz;8A$ao=K+dNvdAuRrdC28-Z0jdKzB#mFsX*_W1lQdC)#A|6 zFYTezYVoU9Qw0^38|?-8MVBu+ATSkzztEuZAdWYrSLA7z%hFwo`I5S zMVbfy5X3mZ0x17<_5H%1L*XeFzqTVn6)a%L(I52adq$7E*hpt2B@S>PD^JebUJ!OEumS2T>igqMH6Cn#5d<8Dzwe0rJ=r7@JSy25quw zlpmvDv{I|dL}e_@+2+D81+R_WPn8dmvU#Su3iW3Kon+<`NM;r=0L3c931zZ*=j7TS zC9D~~%uMl3l|n|9O1IqdnHnUihrHBxY}~=?!GQjqbfT1$+m%D1l%D&m`MsX3x-B_T z(Hr91NeEyeDr$*mxcUWePgpO7@86lKJ9&(kjjon0-3Nuh(TO7J5D za!8qbO|ldM2%ZFn+sd4~bTYF$qk`K@jSJ8YqmPF9n-Cf<7u?*LcjNwj9eAvi=fQ=+ z!BNI0Jbd@#tmzG81u_QM+q{s#IR(DK2foXgl@xc`cwai|ciUTcIW z4@<9Ib>*TPz=vzeLR|a4MoNR65y^j$RLdtbyKf&7>8NTYTsF}^tXE!cLA2=c3^dX{ zP~c3;Qri|Vq0r%rBJ~ZdNjwBY?yMpY4G(_9^O&XOf+}`0GNJKAVb#nD`XRt}K^8uo zloyMkeW;rjOP6zWDkCg1`Rov@sr)QQ(j0Go$GrOuA9pxORN2mTx~{bBC|K1G>+P%= zJlcTV^C0QF2T^Iq`^~JVMjyP*9D+2;X;*HzBbxgczM|EuwY)+HvBl;6m$gu+8aobs z6mX#3A^scA@T{{D+cn*|f=sa56st#5${nxyy58(E9D zQ*8VBFCx{`meF8?_1^+17$P((lY+m_p1cFI3zvb?ITa&~6~e>O9v?Yb^XYuzB8b&n zc9$vURF?`fSY~(ZhT7zI(w!^+T$Q$snH)tCaVwyrJ7nqqB3pgNGX&f>)iUN!@z5J430QzIR3NGnnDIw1Tm7;x(zd;T?6la?r$OoQ zxWu9}E`7Dg@NZ!}pC!QG=STN74RrY{+YzNONs6bAQPxhSVX370unP0-HyCdiO3@To zM>zNGzX!u5ha_8)vlg@4A?#Br+wf zIc+<>mRp31JKb5^EByRqj4EkbH(#3v|L)iV-8so+Jjl9Us!DF#lYM=Y(h0ujkzLO<{+YR{0hZuKY! zTkO;DAMKJY=`#YIUb!O2_fEasig(Y--MNLfMh>r3?Z6R~!ks_lLF50Aske%1bK$y1 z3&kl`+#Q0uyGxad(&E+5#oGyB7Bb4G`?G_kO=K{xNc!TqNVkTGQ4X z%6~XDOzjgI_|9teFMK7194z(a2|D@;#kW1(bsd#}gngpb_x^#tU7);|6CL#mQBIFF zQqaO?_r~pcS#^D3o5;C<`?F=b0Z6CLL@qH9!j%rt`ae>;&t2&6ib}E1n~1~`Z4Rw0 z_w@P?NJ}}^i!9EheD)3lo>AvNeZ2MY2a*2!uJbyJPoMuHz2D762Up2^!w!v}`Y=|- zDjHVHZtE7R+Lq@dYqoCPAuI zTc&n}tFJH-ui$Zi@(YWsT$3uU$|jh?Ou5$*1l{3YX)KfHDMOCY9WVk2MuB;0#BP^C zWmJH`=sEj9_thceUVgT{7>N#$dD(1)b?-SxBsZ-Dqp^lQLl88C{v|{^3jlmq1ByXO zHA(3ymYGn>RQKR*tzR&9-FYa7Y!7-XV<$H=&}wBiRZToZYds(P*PH5_HDRtPZnd0c zCO|@)mEj5+Egocr&P@Yve?B=fV7RNw{hDwJoxYxJ6#N}X zs!8G%P}+x55Z9LWhXQWzj(bkg-BDjAHZ`V-ex#Bws$$Z&tmv#oj(L_p{{tY&z0pHV zOE?~f(S65{({iwoYN?8+r_bm`4{m`*Ei5$71CBNaxo0A852%c>{l+X?N|nPX)}mK0 z_$usZ(tg3PsJJ}PovFV=AM&oH2|ekYvOPyTvvw8zU|VN4%b|CwcPvW+#1tOZ28Q_q zUd@^6n9)35%f~2DB&)7{8PsE(JLDLU!tH1*PFcX0`>#I$~3!$=Yin^QbCcjPQ`H6q}ZR}QY|%;P^BY{DmRq@$e6p0AKe z#)B`$$xABZ^xVm}@IESk36I~^5FUarM7fQXLp2}Np9lc&DxAsk9HU-#@90UA2RC&= zF#-!-pSzZ(`{Qcu3|p;udj7^G&Mi_D*8UK(v2folVm&+%i+* zAyZH@E9+y5&^A(|q#o~lga(I4eSC$Y+gW+tg2_il^2sSFIW4x1y6U3fc18Ze<^mF8 znfT@|W$tK3hje{gO=8)L3Z=QIe^}0@ube)iWxks6mp3^L1fK9PQh(X#`Lk|To^{PI z?0a6Dck7vyU9tqDNm)Z8>GW}i@zAeE*mvaAvC_v3;SyCRf^RkO_MuYBmF_L?MetL( z%LLDum9d$=&%Mus!U-O7nIxI_Jsx--&#?Jo)h;)D?w5ToRMMB+<_2B%&Y4RMhH=my zybOC}FPVMyD>7!ov`=;>9II&Ld^y>b9M&p9RbHKQ*TF8&4Nz|WEduqb(ab_U*@$*g z%peXm6|PK+EyB^-%asf)KS=CUnUp7o=$Cb%U0cXpsNTl7@azn(%h%&uH5(h7Xagzj z{UmBHCb4~B6J1~l#i~603+C^kq*X&%nX~zc^dfxT?{??Hj!Se0w5Q4e&pf5*fti11 za`aWC3H^(EcJb`Ce2D4OZTYd8CUPkdI5}AMuikO6X-xq=f2s0|-~W6raJ~`ZTD*OR zpcgil+C5Vj(_fGT9fnYN9P!_Hq1ldA1SmNpiQ|rFa5(s6)AiYMQq)QHhT(-gYU$sA zy{40!a5#})zh9|>T#S;c_;F~%l03BXIMeNvRCNz)>kZGfHO!~WN{pyAmk4Gh(niRo zGQJr~0g*H64B1;ePD$Wh*KLttcj4fl#)Di#Y(vKBaT|*<<$@CmhGyYmWqWg~_+hoo z13~M^-|50njUSp0r_(2&dJSF2Y7LI6x1Jb8uHO#I%~cN|;r^nP*{YaTPm^Ub%Hmd& z7bAFw+wIxU#}>@%#cKjhwLrGh+HrVBgpSkQTD&}S{RJOw}c2zQviFh_iu`>ByUGlT) zEgv+E*@|d90m;}_H64*AL4w+XD}`yx)1q<DpN2+X*$>;>CSZv%K}~HyyYS zAOt?kpAqZ5FI{ZZs@9n!-n=@XRZRXDDvO>1yKY(%{bvOsKO^&{eaXFk{)?53en|AG zh&`U+9~(^t+Wu-ZpV;)BITiCHa^yGdD^3@oU2?M&f(no6%^nKwf@d7 z^WU&uGule%m=ZZ|MY28D1+G0JoeU{lT6~Vls~D|D_tnNo zZ88FHhw`^Ja*}LrX`Ef6t1x(#@D0cV$lefhxP1ZUBqU0y#`Pl`W9hdbA6(lyj_r4k z0TCT{9n%t$UE_by>J{+W0eH>yVo?QSPTZ!l_W4lPDW2SV`5BESkw1D9wFt~i zddeAhwjkPDdD?~y&WUT0Pf)nf$mf8dH z?AgZ#YL@JkQ-V7da~{@JxxAxb-Uw^>KB1=?nKPb@Dp!fz0+tY2_FtBaie6`@6`G?8 zS&BVTuu=8rH17pZnXG4pUSEJXOjP~Zue-I)0i`Qzq-_hoPp2n8WuCk(#I{RzY`PWB zYT9V3X7hQ5(1%64y3S?=19;ig>l$}}ohdwq+&≻sRb(BKRJVK2z9Cx-bmxgl#3QPY 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 Qe#4`wdPIfuqm}=E0VPdG&j0`b literal 0 HcmV?d00001 diff --git a/docs/images/showcase/10v-input.png b/docs/images/showcase/10v-input.png new file mode 100644 index 0000000000000000000000000000000000000000..edc327dccbf99a3ac818d6edf753a5a9f34fe2a3 GIT binary patch literal 520501 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

    uAyj=BBs85WCk?i8+q_uli%*_vY6hpP$K3C_l5^ zL2a$4l*Q!Uty7#8Ktql5Rcq#fKG6)4L^xc!Vz6h8ekqPT3YTdivb@>K*3I zZN=(M36q`2oS{%iO520n*Z$%xZ^jakyTKQLuNO;`vK~l2;ccSXwOd)2n>uCqSh~m`(C#*cG77Ms&YVelgpjmbLPWKG$779566ml%`@f@!if(;)#6J3U z7_Sc5jedJ_C|~LEP0^*;YL@{omH0>mkD!i_Z=cWS9|@npJdTPhg6STeJl?e8npZlk z>~)*6CE&~G0prNPYYP%k_%oQ(fm-T>N{QGI#Ys|j*Fla8)i72%Ixz6extA{yoFU2Q zQ{9N$Je&;O8FcUe{6`R_COe|iD!{RO<+i#)+f_{#7L7*_;5b^;-LEx_mZ6f#}5ZsDVcB2zW| zZd_@HxjIp+!X#p~5_|zTZ+;*B%yHa?Wy)qf$GkfZzP^#+?-$ws@i?SG=MhC8Qc?qA z9I+%Zm{Q9=3YwoDgl9zQ1$M4~7b|MfcKNPdB$UTWAVX{-r_eEr_KduN=J;>=LEqcc zvI~Vngw<_HEIi4&L47lvl+LA}g&Zv*GO~d0CLSm>!h0eug+`2!TL6H(c5D%>4+hLR z7QkOW#{(tow{Hj<@zn|2hl0iA^gWDgO)Xb0&ad;LGoR|LZWh@11^|in2?hK_ApUoR z0wP1DI_fP4IJW;joXLn8BR?e)-G6A!RXcOp**I0Pyi)1(*I20$@%K#jw3G#^L`1v9CPPprg}M z%1%HdZc6H1YJLVl>Ry#Vx@F>N&rG7Ub~JMj(9qN6+MIiSOm6Y5HaOJfKxc#X`oUrr zKje78(HSL)ooF38GaNPkNIx%wPz#yawJ7%VDlRR4$hY_$|F{rvNwvEAaHaT&rMOJ+ zczR{G22H~794-#Xe9pFmsrZ>rxRo2M^j)sn)_;flAh2vp=K@uP8V0m{D|s^6RwQSd zWvsJ|)G&7%uO>xPI)>qr_OzjPVy!^>a_ls1GUP#ER0CY)3U|1y#e>jaF?zfLxXska zpPCl81RgBUd6FgA1v5HD1XOXr>LVe~iLrbw{d3WB_q>6Mf|KlZuQ(GjhA*l^@=@6l zuN513vLretv!Uik$MfX&bG3fN-&|!$dplf{{aNAYY`ENtGo*qB6yGDbpIvgKC?dnR zd}!M+AhZrhzxCkLQ15aUt{SDtrZksiC0&@d*(#YbXIZDUOJ+Yascnpaq5D_!QEW>HtNx`| zDcs}FBNRK)8iyh^zbH7Ze!TQ1usNWbSt!I4kJnsZv&qcI&nL6kbtzk+YFkU%C14+`u@~tHTm2#P)1-|mN=kNYi*y9_s8)u zKCJ-KFrA%@jkErlLPw3@u`8O?r$}v>jF9!RZ0)S73dytILs|h2L67B%JkuE{Kxpo9 zNwFT>-vMh369FuP!;~jmyx>~8A#x$1`dVd=c*7Y^6pwT?6gFw<_OFUO-r%CAM3~GT za!ohDM9?7+JLXe5k83og>}RtOfz#I_N+$4cw2qaj4x=~^)jcUk5QWrOslGlZSSf;v zmA|bvpw^9IF#Bv}{Sq&)vMC}l#svSR;?|&Ac?2#m)chnTAvvjbi4&{e#o-t`Wf4a@ z`ZA&uVGJXWBdvMU#>qAD@prBTcaN##$d%3_g-ZMTN5oL^#@))AN%zI~pXNSnjnU&=KoBW?eOa}_?XvELJ}h|+G_Rj;620t>%r)?<`+reocdJ>X&wV9h@Xk5A953;zV2R z@jN(SCgiev<9=(6>%Pk$&~VS?V-nG$w>JR6!4Dw@X-&c-KJ15gzlb}-AUyNi*KZI| zz#ZHlSLZ9GoWHWVX}!U2nVZ&2z-gi163IVQZoq5(R?X+T)t-1=ioCy{n55R<0zq~U z0p*QBbc+23UuRO2v3J|H$CC{5MURt z0rG%~`JnutTE-ja_?jyxt_W5bk4OM7?9H`iVUTw(BwZx`yJ6sZhSn*d8{ZVnD@qb< ztbiZ_77*TsUk^|-;8PcB4I;{$;=K7&UDEyhE~B#+lmEyJ`H8n-&U24|C`16f*{cPv z1o&EY2i)=I(b*M-j^pxZIMCaJW`T^7venfLjuFMA*?iO{MR{0Vf9dmg|B)!I>g(jy zWZREK!Ed~W=t-dEn?rl2@&E*1hzj(x7WxDl&2a7jxxbdltVA-7y*US(mh_Iy;Y~TK zx=7VIUk`NfyfFpR8B<_#{;~ig^@s5DvJ4~V&^s{yT@yUEfV>&rmM>rA&@m9fijDuk zE=0y8f`{Sb5`Byf-haWg_wnIP2esvVegNh~nrTBW-##4c#bI;Kd=0^mxsQC#s_kNz1GKgGU+489o=V2Mq)S=U5> z1wPc}0()s`X|FIZmg8TZ1^P8}K@$s3Jm1q(h22_yWmkIrEI1r`O5mtLwOQV zD>iQD-{&74^G5hX>T!_Ltl_`w{*E9v<&P$p`b~CjSE!5MiYRJsgQo&Y?SyGO&l-XJ zhWLl@E$>K|RoGG}Is8H07Vd$Pf^4s$n5?pc*T#nARet>w_@WjZw7@EZJ_dh(hog^a}p2qee zudmqMGLt>3xj#pF^X|(|5P|)A5M^G=QX|M^mdkK+)ik$`GP0t=Dl@U&>R#Sf7#)u% z-i^P%e(Zjqb?V5WUgYWe6|xaCC^RJ?jvwG?1#zs@QP~_&@kv)?3lP|#{|tbK_vYBi z#W=E(L@?P$I0(lT7~T$kmls@2H}+ULbhd@)jWsM`4TBpwjTyP7TWcbn+ z$xvMje$rXsn5zPB{Bx4U*sNnpT`+e$xAnx*(7e^G|1%e)m?o2mSx^U}OY6qvjO)o! z#s42DR|h0jIocgToC|wDvD)3}r;r~6P0T)oT-GU)WhNJ<`&#ysK4qp<{X{?^skNzP zJVCsh;QErpXY$K{?qW-zL)2z_xfJ;+tqv z$8qxMmAJPcG20e3_O)z?v)eJfL#4T_Tx6uuQI@&viCD$d=N_Fpt^MLl<*AyhyU+Lq zM)OyLC3${?8IwJq2^C(aumsDn9zqMW@|l)ZBeF8|;PO`jfUbi>Lb8t+Nn!Nq-IpQ6 zf%*$XlkqIZv^X7w15^$Gwg4Jo@o1VH*QcMdRjlZYsR(p-uiqv6|f=_ zE>!CY3pHwgFVTymakyF&~!+pl;`AQ z(s8T&=Y7e4|o!!E=7-iSqQl`ym=ws|SP#Igv`) zP$yoFy8U6*+8TX+3lHd+vW_}9%+zwoxX;Lhr9U(LbcqeFKfI5aq&VfJg>WkPoK{7YTMIF#6}}!~G2dO_mxH zrHV|3I321KqzG@US;`Ayz4b)UpjJDm*wXfU0CX(OxEc}k?W4X#M}`)3?Q9pL*J|f_ zP41!=nObC%an$JMc#6zIhq0%( z%6GOFHgFinZsC56HO{TKdw;V=v&^;HC$s&YQki-n@ulBOh|lv}}l9zdAhg$xtcs#y4j1YM9E$Vsr)9tnolLYPc#R7P3~bFPFD8am9Y?N=uPKq3!io35WXH zs?}q;)*pq;Ja0HNe*l^~#hdCYibd6EC6AbG?_D`d!4IY6I6v%hy=h|BI#QgnnKR<3yc>eP$g%8zGG_O6L zWhn<=Yuyiz4y>WZDMrm6Lx+m@OKB1yY+)a@@YbJylk}hc}>0#)S?jBkN92!MZ5ClY;p-T|y7Le{{=>__kF6k%Sq^cH@ePWZlI7P6A5yH@j8NPKR|7d#ft8=3Va*c;vQ1eVCdh_8e=anWLDB)DW#T zXVYx$HM;w!FRTm=*%xO+>l*2cja&0=Z{iv4_mdzmAM0^BJi(e=#?mZoA#r5lC8T zTeur_wo9KI<>yVeWQaMhk%V32>u@T2Bow%?tc`4Q*iTjH^8cwpFlWk6@i%vC1%(F8 zp@-n~Pd(Q$EX0%n`bHz6o+YR!6+ZIvJGnNKx}7PE%k8w*Xk45c25!k*BGZm_0}=K6Y)A;f!s}mo&ny#R3cM*8F~uL!|vO3DK`As*XwVAvZ)+Zo2_g zl`T5SU$nWP``Xd-JX*&JnS?M>;H=^wZt@8C2ak}$z}s3(YRsgGobI@W-QAWuc>!Lb z!3(nEP0{U3|EP6xQp8ZWc_fyN2mgO>v<$h$%3tGBn;Bum9!QnV=%GNEuU}NI9b9a- zQ^Zg`z?Fqy9bbUr`NnR z+PAe-Wh|S8>EIj`{}^4gb6C!o<11*rcgr?s=>HS)2-m2PXLnG^WkbPl_hDK;(`LY0 zVLq)bA>U2b}#ZP#EmqFYd zk1Ma|$ixt6T|Z~r%`Y=3ZPjPFYBiR~QNKavdVU_7qy0!G>OGo{A|~ypDKvXkIzxmakhpI zg(JM>R$d^b?}IQ1HE;I3I!UoE2fM}lFLq7X6_J5bX z9~Y5`FkOh{i!E8}O;#>zpL`hUDsMYn=xin#6${sfe%~V3R9e)KJE>rf8u)uPUb5^wX z_K;&m5bV~G4q^8Bfj;3M`AwZfzZI8=ozivYPwX&7 zY8!-Z95{$cl~yg)I{e;4RwO1Cu92dyXVt=c(s_)bxb$(-18O_W8kHKDBs52uF>tTM zFeo=bzwayowDdABo`G>8u=Z1EPQ?U1cxnjA2kh4tpDxe(Z?B7INFS!f!tHfa1v)Sl zY4R2-(<}Y;74lk)gqFcqZvijCpw5rX*l(ZdZ?+N~6??;c`I14c(pLL(k!9P4B6aGC3!>Pnb1WQ_ zIzcK}_`|3uR!P(2MHSEq()1JclfLn( zq_b^G<*%i97#O%rNZ~5G-qaX@$T1sB|6E4eum7u@7`|gew5AHLi2ni(H({f9wXPoY zr!CQ+*49_e;fd@SNzVf_-p4MToxuP}XtF06*fDXx&Uw<4i1?jK6WF>l28ZY*L#snz#vnR|(o7`B{jvd}#3!uLk)9mJk z9~Km<0)UD%dl}yvYRc3|tw@!*^H@pT^KpJ43!Rxq<(jr`AzMb2fkMtx)uK?f^?}1$ zoK_DeTO#}y@itI}cOi{FrYB*R_35I(!Qr~l8%b(AECnM{NQ*nJ`Q`WBElSCi^-Xap zy>1h)u8s1(zztbmXGSoj4X598z5oJUJY!WVHb$=70W625c=pKaj|`wq+#L0Ld6Q9$ zihlB*ZrtUh1WI(6v3{>jPtlKBcV^@z{jL2tP0gw+l!Cd^hEt^2LGh8^S3SH47SXo0L|mE*i+t~ z47X9e^Urt=$qarTO?6W?JW0bgLm?I~CW=2eX6>J|bcsr9lo!NjTy*`pkq3tKQ>@y1 z=DAj}s2-pHK@PB?J`^u5lTy_-Uhc=#|Mc8$-@|7p0UG|D3Y0r`nEh=1BajQ6$J$W) zFgzB{@(4HDPrKkf6hphN+;6Zl*PcsaPMFRAtXm1f)bxb!x>B+5TkM9UX&>Qwx_@b58Kp3;Ek|C5{|f4$|EIWVy6;_TvI?6N*G0`I`tQ#37YjRu zPxd5Lh{=>Kft|059Q;BGTj#lP`(e@4;#DA$apA!SEXxZN*x3MGmrJtiO7J8&Q1C-r zTU)=`jg1Y|C}=+Ih|UH6yt5$J#b>8MyLnR1j%aYyxd&bg zZH;r0$YLS|x}-ME>s;kSXP&Biy|Nsq(8g$p6TSb47h<&ztg^IGmpsDMicLahp0>k? z^W_6sir#vf0;Gu3XW8yiNc79Y=^?)c^1u3~kz8eS{7XLG*FDEtdnT2V{506M+_pn? z7Y1-;9x=eP$sKPJJ}IHKZ4*#6SmpkdMOj-1;ZcY%gQs#7T5ku*6pD>EdeH#0yJr{? zK%bXbHcW3;S()6D(iniT=a}`&-a`Aqg7$$4RnXPSN*KN(_0Ef@^_$7u2*Pvfdr63a z48G!cfQR70z3*HEJbjw%L=w8S3o<<}J$K9l$L=u8E|+u6mD*J<`U{)dx+f!ncFCW~ z@-7ynjN=tI**rwMG!1osg=1K^;iyJx?SUaF5#&?B{AiIrsljfG;iYU71BU?TZ&6PEal?#pHBHY^SEH>s@tVgA*y_>mj{N7Xu#n<3oR2 z4dcaQ7hzIGo|i1Q6{?}g=K`T_A*D3gZdZV7um1wD|${5@9vqnZ{S^DxC3nD zD4;y$jm6tOOX(b)I`rpZ*q!Lg*rU1INDVNXnP=Mv+fuc8a_$Wd~b%x4Obm6l*?>Kh>15^2S0nP7Z0ybUeF=dsSDH~xjUXo!woC^|$x|vdR+}$gd#}iw9 z=CfK7INr)x;fj3~eX;uNV5%-z;&-_FH?`jWZ`%;k;;wb>gwAN9FJHw;@z#2qeE#6& z;4WYJsxq1zC3XnU{LDYVt2IJ7O z|5Gs0Gd)5jNRdo5r4Zpz2Qa{jO8iO7%{@p(3|I^YM6BWb@wDj8W#9lRgq9Ly$;HSi z_mZW%l)VXS-!pC58`Yor^xROmO;ju}665v%pFa+}E-sT{k)9+ovNJM1{47rMPT;2} zS4S>N+W|0lS*D9oFKCL=THgbe7`d!NM>Fs+^ASlf0a3BgNWiSFn+XrKg7oJM+G0NW zWn%b_$eyR!@|(LDefu91wLP_hYk}9&J;$_meGfAe7`gjoTlL<&AlAs2*s_ZC`%>vaR2% zHIG+1Ldz+~>Mq(95m@km?|e;?7D#^b(>YT3y+itv1W0OKkr3rgXTgD=$SFaOgYS&> zJ$49syQ)H|Ew_bTpUP+qh}wyX@aC6zi-(BjL@H!WzAtwG%4FLT9`um>}5yu35VX|`o@TQ zojq9`t05{dCXp;9V8X-4rwbFlE^T1GJzN@a(iS#LoN8Uv_+t3gZKW0H@|yPAT& zt61TL9PdH1L(eLWYKRB}XB9310RbKbMa1~He#|XRghfxHc~@+9nN6DSiLT$#AR3E7 z4VRk`pj@xrjOxSo=HD^ayJX4bJNzRZ($}1M?~`wxnWr-XXD?w1KpKQ$2qL40SqbR1 zMBn~aJK%Dag?(f35LIZgM0{c;x6v+TfTE*@;II|EA}!759ykrfc?g{%?Mcc{e2@0K zcrn8hX%NVtcf118pmlj>D(IIA(IY~DF+Kc?OB9Gw?u=gm(~_&~M5UHOvo=DY3?Bch z*i-MnNi2s=84OHgeA}=eKW-7=4SZRZD#SiBr@vPzgU-G|k=Cag2TJ6rqho`TXw{uF zVOH;wRSTwyBc?@d;IE~to6$0b+75dP*e=?t-s#;)B4XgLRRl7KH&^>qyQ`+t!g&+BxG9Tm2htlMFXnb?nlG~f~Qnp=3eu;1A-^F65XnABh9d!>IU z1Fl1Z@2TlvW*hMH7X;CIE1s)0fw$srCw#WUziU9yN|qdU)x*4vi}L>VB}C&*LTUhV zJB>xZ3d`-h2|me*B|dMYg3xeiar$Lxou+qB9g_;{JWTC;z9d9;LJD#|uzHTe@M=){ z0ezfsMS_I`(5jwuuQrKQ@vxeo^=Sg9I+{ngqc!0cFai@y>gHjSVV6x$$`65D>I4ZA z#*ThR0W@4=bVGchy%7u&PtXPb4>wSplw-TOv+tUkqT=-au)Ru)+0stLz2zsQ2x`kzwWjSf1HC58A_~K=ZwoiAi z;l6dNt&CVV#PEmgWa#W#seFa5ht+ES(Xd0DM`Z=5M_=kbk}lw|wKr4Al5LVER9s5? z!tc<&;adAkqrf!;apXFNOinbInB6}+(LYU;%d;1vfN^PnnaJFo!O~N|GwxO8lpS7` z4ie+vXiwT1fawpb;xG+9f~u^Wh?u(3J5DriD3)CaiqFAzjRuW$#ZsSvnZdKkMn-2 zSpfUq4k$FI>4A5z#UOkJwG0iWyU*NVvQj!MmI+YGU-(OCFMURJc==c$9}yEr}FW$M;dWV?Qh&R5%(LXSopnmZ_ZqK-vFHo}Goi3Z9+LkzP4E zr3&vO#?E^O2hWZ+!>mU#8Zbuh&UpH+G%*oW9IE&4q~Y`3D?Q^4Zp_hCV&O}`Ok{kJ z0c^X4j5icBKTS^}lTqG+CKKi5*qCw0^h7z+zLb>I$J4wV=bLFE!smJqZ@&sXhNsKQ zpaDtF-77un{9#)-iTtMLHdyWyT7?G5zK=w~BP4Ni<16t2iz-^;cDn8BsAV~wurNrZ z?F-+UD9;LL@L8MrNdnRU+T&xuxiVR4$Ni>LEmh2S)G8GyAG2&n@~8hJ0LAwc1xE1x zsQ#=N5s}I6+t595As&y7K>v1gz^B+G zjSxNSIcjvB;RAH2#tIhCaSz)I`VJi6vN@cv{v1vyGQE8Pv1j*moO>q`Fcm#lefc9w8q?*|wi0KlsDnmk^X^L~J^e zA&Nx%r@#C=KQnV;l017q?d{57d~?4uQG&)5*%dS}z?|L%RFme1KUARQu1x$6_=NlJ$DY8n;5^=drilKkmqk{rV$prh9KGA4nWwx&{5izf0z{NTt`AqbwLxRVf&85T!rCSJU6z4UWh^r~ys09+x) zLN{o99Qwn3qTEXU5CogmIfxAB8RG&lyrw;kt<=BIrUuTzlIpFVzk z>(qFSlOA|Zv@`P)$@{uJl5Bt4jcQ*pF>$|_m(Qd;&wXC)EV{;|NfkWY0zeM=PXctb zHSvT&{#VS}nzhHuC4==>FqHm?Fp|)E6y*lT1o7HT#oxBYb=!wc0Qeu`jKopZ5^MB1 z#itK|B?|TPrsErdyZbJ8GC{UrhIMG}*AsY*_}56A@_Bem#Buwa*_NO0NNV+8wGO^5(h1sS~zkp@De-l>V)Fts`fFi*vWB1mVF53*#>-9z3`2K_DPu3GLYWV5MKus zf2mJzO>7>fekkOdnDxqA2EVVBKoK> zX$9K5@J=-uIgl*qDuQuAl!i)bHIc zOt9WdXRAEcvM$X=kA&XaK59A^&GS1+>Ne@s&wcR0#0vY#SJCCKB3E&FDQ@sxXHaZb zZU(lL3>q&UB_GDRft(j;nF&FeRYZH~QjW`2t4%dQMpQ}h45PRa(^Z(uY@E+bnIo+9U`(n{^_iSxV|9Vg=;N+U!pTK}t=ehf#8tq8B z&VAjzEEXKGuX^q(<<|{9xta*-_SZ>o18;690Cv~B5zpql>KR=cFhV>8E zhjQdw+M@>R4E0|)4BiI@aHOzDfYU$S7)T@FG%EuH?L1b|atZ8%mMN`$X=1+nX2{|i z&+RW`wOAlQy7Qyt+RP@Eq1IbJZmT~rM$uHYt}D&srRMvK5uCq?8DIN?vm7J>Fr181 z9Z?)%BovQ_602;$guL#KNGhYPY^8}T@#M(d+#}deeP+XhYWf6iu$~9vL<@rf?VTQY zc^_(71(w)%EH-0yQ^^%}DLWG+O^W#qzg`dpxTojwzbRlyE1&@l$N%?vN5hx1=zwe!?o5-LBYWf*^~Gv=2WgQ z1)Qc${&4!sJhUAy4w*dO%<^nOWzqdnF-FV|+2LIoFqf@}2k=zsq9;e}3u3hc^aKv6 zX&dE9)?k|=OBB@)Mc>9vLoSAvy!VeqMZUhKl<>r|gA})Z(JpW~o0t4ipULy)_b^Ss z%O3tB|3sqD2V@U?@&ZfpK85RSm~c#m085rrZ|XK2&Ft1=^3t^NRIqz}$`b$Gn_NEl zP0~L&c)Zkc1{HW(5O8_~;5W?r>*qT!WQhZSE#@)F^TlQmVxG=PV`gryUS&^K-5hO| zJZs(fM>ZmHaofvhX`uxk>kK-pV#A;FLAY{sUd0BB-H@|@q`0`?#Wtn+i{s6m5*(^Y z^Nr!W&bViv>v%@YKD>v2*&VDKw|Q4Y@E2=w#3||E_;bwKHU!#kG-?Blw&}? zzL&jbTDz@xG(o^PPAID7(6K00q(^}~zOFJCs5>POQ%clK)s&W&+G7#YrNiY1;a}l| z-SEFm5#QYN=vlOAIjbD!qO#feecL!bO5pMUx>X)Q$o@pf^`2H*>R)up@6#a6*9Igo z?coY}!Qw#ZEZDK0Y4US`&OrqOMA`X2b6d(~OHgaN{w(0UI2 zPI}1-4J*mPgVPEyUC_hm6_R>x-%feyz7zb&{qhghth~%hTJS_<~Ur0FQw!KUm~OiL>Et$YYXw zDB*<9A`d`duiv>Vi*Yd)E2Qu(yKd&Eq@-YY815%A{i=@3_Cw~2%O~tv4ECPIJ=Z48 zRw9;6){s@Jd!ru5@`VP7h8h-C(@vgzh`dm&D%4=YTsV!Apy%Pyo}_i%K~Hkf)wms} z?H+77L_0QdIYD=~!m+uz8l!K{lXllFTYogmR{5GM*z^8Ka?AZW-I=w8xXj?eOPPp5 zr|kOPtDDVDBi511O(7Hib5}7p#jL6sOU>S)V}=F@Gj6}@Jv;wUuNjhB-;B0>Rh**N zR6dcmK%f?`2{-U-`|7W9rbd5BzV2)Sw5fX|<1T^}44kI8D32ltCBcnX12=xSP=_Qr zE9<(wdLXMKGR-`e%)Ah?_0dTrZHz(3E3MD4*sx@kSz+mu(C(Rq?9mlVo-G!J2{R5y zf9faSv_qrkZibr!3591{CF5G(olWbfs&Qwxr_gkY4Vo_a>`E+G6t8|Q1sY9J0^2c( z^RhP^!-{Sye569+kKXgbHPRHv<`3aCMKnho*I%EtRV^^c)l!L|%!BI0h&zT#vN2swmsz}gz3$8V#TxW|6?hvpbD$s z{pm?Tosg_}*J)?zXV~G_Qge9{=PHeQAC>&q3e zOY_6bRs_o~7A!k0^*B~_Ql3lV@Tq6~ zlh-2kF!FyLJKpuLM3C%{0;l!8cMr;3HuxNaL0C-&@$D zBgXWC3d+jCVDN8kotvo{%t?H6@OZWN{L{0YuFpAY->orz)Z}SPh=Nm*6=nW(&a1BF zb$Ybq?Y*E9&lw*-2@f9EmEQIEoz*@lmY)+>0=r@R8|i*(C)4I&7j$gC-68*YR_*%c zQG{^}q)0E>oRkV{vi;G7xd&^VLbK5%q`vW$9P~%jvGf+aZid(qY4m z){J)xyB_ihYmsM1hctM;Y=1VdCfvF)o8E=5|I?aF*?Tg&!3eyIpEEJV5Z;Zi_4Ae# zgjU%+*Sw45@(b{8_A4>M7>6e9eJd)TQ=B2DkrTf4$yGfPp4kxcruNkdx`#u zA7KBrRLDvWEG~x&UDOq`-jqw|8%||)I`lXDaEdPVr0JCV2fYK63sxXVM|dd30fi*W z+kF~;_RVqiEKdh4axnsJJ1u0zb1nd-M^Y7EnnV@h9BKJ}q*v^c&iiVu{_Q}KxkO!W zA)v>!^-Alz6xz4pz!aS^+aAwKbx*y7c|?H!!~wqxQGwng24jFosRp@HPdwJ!1z?fL1TPCwKS~P_Y zY*EW%8WwlK(lV%ZT>w`xLh?IKM{e+Km`o0LSr29=i(JhGsxD3-jaz|gf~W@<1EYx~ zJq8rcGh~G$)noP(Wx(tBd5C#O^v{-r=hq{YNSu_J)FNG(rpj1DjCKTG9V_f%bLe&* z&bBT2Y-rbKiyopuMJo1*{a@ui(dK%DV$1C^gKp$$W!j0R9l1&C*Xi?7gx)rh zQK=#k7WuKbEnhCecID~a?;jFy=vT`nms#MGk%jsDp3<)#*`WOH3cTf;;8_B86EO(! zTgssye(>+#8Bu=!xxJ1Cf&qY*QDdjPXjgSZF2;`;pPs)M-t@5F58(s^bmh>;ak}OmQn(?Q#0W-> za9@uEJueoiAZfMZOhOT&P`B)x!MSTLG()t`)Bh1zPM@Oe7(s30)No9UisN@6-L=}6 z0zmGyli{PJ-+9$7LqN6M&N zRbjFV?@8Ew{fztGDy1Fh>`T)N8n*N5d$3r32jH$)bYl{yD6goyfoOXrQkjqpArgU# zX~^z`6v?X;^bG-_3yF^?HAH8b3lsm?B+}gjQ`Udtti)vJW^YQ5u|gcWxoF$aQxft zT93wUbkn3X%-aBy2N;HJ5{o&-L`Jq=aHQ-T{5*->3k^ML?&d1PUF6to-Cu%bNXr}4 z!PE(MG!zA0*YGy9e91u<)LL0E#$|oRmB9~GyZ1;BBQdnM@M^k`W*dA#xg`d6?Qp`-%Fn>SunBchrh!Q!UZK&6rH-&e)4<`OPLy1oxc_caCB%4aQc*Fjv zzSF&uCXa>?OrqvnPy4ok42BZ36;?tvJwn-V;`WPUiotqsUMvb>(vf`a(T;7)lvOIR9wbN~OJ!1e zQP*&eYAde!V1+GCezx8_%Eb|~zg_8lxQdb{=rvvA@&t}cBN>9Z@`yYE6(2ZER==13 z#^6Tvl*PSfFK5{FgA|%wfFe1It9J>DPf4!#S$70U)#h-HJ;K#bD|k&PrJ%?K#DF~6 z%s5$6k2WK_Kf?EBh%{q9LvB|WC15kLF9EFaqb**{$BBjNkyEE%i_e^8IKQjX6ytp^ zrB0gq_F^{dvlUY`70RA+rEBE5?q+o$F<5~A*&FVVgz}5AmDnY{{1DAMg@;AEpobH6Um`fb5(|(c&47fTP5;dsxAsQ<- zl>U&5br!JU)tM$nQOyB#uC;C}vB8y&GXx_A5>ZACm!^G_9^u|vc^YI9#DC<|M7^o4 zY`1CtJ||0u{adjP2)ocCg zeJ_wKHZ7z^8|e7CaFC`4^gOJBQ0=Z3Wg=XSE$8f&nFmQl?7VR=(d}p(^YxU)m2Fhi zHcDa%;6@E8=R=R@N=6&viNuGoO6%Fa^{MSSFQ`%lRoc(;M7`CKZTtE<2&hSa4Jv2j z5K{nIJWMVz0*NqSWA#LN>1RM$q<4{=a&$RZ_f$HR0)ygZ$1_W>$}wK0WT zAmFIhKqN!U2Eu4!?aXZs-yC7E5y9YS3lo1v{iX;sTDBUK(9OB!58YjwbkZ}q& zYkDwoOce~ce+j1jw_CSpmp2y?JqpV+UUN-WOYJ^_p0Iubz;{?#Nuqn4^H(yOa$>)y z`8)xU)PoV;I%Fke=ZxO;I7Bq*~%Qd^H4z1}LX!Ex-{a7K<%r586x`86^+V+xp+3k{`yc~F>im%a(!bn^8Q)?T-pj@_L83K0m^&p4!gU zFJJ{!ybmW1yz{H{LZEshMZfR-x1VVYi%1Q@EWc^T)2+r0VCk8?*o99#%F-W>B^y>41lCq3-SS({kmP$s=i=GOnh z0_wDZ?Y^+ ziK7~3fr)lW*T=$BeLfh4Q~Tt5BxPu2$dyVviRd2a*agknvRP(TEKY>4nU<3y^s?fG zdkwB_RyDuRAK5-sI_W+5UhHJY2pozzK(6uM|~)u#Gss`&6SI zQlhpLX)y$dSa|&4VZ5S)nDe$D$O>xd)G-OM0)bkuOCo6L~s)w^XhhwL53W_PAB{A9dp$dE2s9Lv7L%|y1c2D3o8 zyfh)ZR%o6dzerKR@WS)YN~O{ZI|Wx&7vTAeB)_J6s(SOMD0eXekR@K&kE8TU+_Fzr z*+f_q9oB=nIF^qStxv-x4l(-~2Cr2gpr+>R&o@2rnm{z1kdq5I2D57CvxxGtLZ0Nv zh5VJ=F{mAAGzRv@bGX1i6Cms4e{)p(b&Baz#R}R-vccgNPe~)@Ls(i`+G$(C`-Lc; z`H22lY{^KhKX=|#+r3=1UJ4!yWbz4K64^p@yPFJ~l%KObp5-Z)u=<>pDLD2H=dYPQ z^kMbstRyR%^Ghu$&GSMbCkzqPVomB%>l{L`q7~4?aWJV1Enp_R=f3I_f7=>b6p5t& zP)?Xc31B-tRlZPnO%>7c97bsNG1^5oEoKS*_w;G{J?DF>7;QIFWEabHSFDIwK6l~viQrW0Ci8WP6x*qAdS51Z@keXAofDyd>L_iyFFF4Zo|4D2?nV?zpkJE zI=3uS?Z5$(xH=D8^S@(HTrP$&3PG5Hq49cVcPRt#WWisXZJ@ZBJ{KuAYyifqQ#EcP z#DM$=NKO;#faR;i0*opgt2gJzO+`U-CvYwmCDF@EJzL8?jv)PRnU02#6Gl+~TMB?8T~NRLHWjQWiGCzk4+@hno`R zl$}P!zrZ%Tl^XaCb@y0z!N>4)Vk|wmq)G>(?|tzA0zIq{yHQn1RTa z*5WmBvuX6KVfV#_5#kF{b^j)>MAwF0&4+j;4Y&X7rdVZLa5=1H{S73}`R$ysgs6Yb z@_0M9ysL2f)&An!A@>m+otcASZn_YU**0OE!^oIoWSrMcHfn#F%&kq+oM_7~Y33et z*rrSR%8p0@w%&&1x{vPd5sUQ0Or)Xe!H76>)n8EvW7?R<^#)&;H^9km(s+w@1x0tI z@?sjV>_zWTf+_}(hvF7^fx)CmnAn#knI#7lF(iy*#DL&KBJAE;ZS0SogH$x58QU}z z@N{=najY++9N#z{629k+<)J{EKz#-_#|wC`=Hz@VQX<(KAO zZvlM>YLur@wo1)^vxmxxD3?fNR?qWaG-_{FDgCkRi^Z5R-o!gnG#+)3{I~e-x;1P* zTP0&09d^B~;})#Dr|3Ho4y7Qi&~uH7T##GY)5v_&wrC9Cuw&Sz!*H>tJoh$pdX)S= z0TZJs_^fDRsG|ga{t5@)8&VETX`qGYHu+84`&Go4APw;K{J4A=J$G*&H25?Gfy|tw zH*}_qyT2}@m(Rd3G)ep#?Q`K3Jv(4{Ktp7U9uqGN1#QkoQ1u^~Sqb$5 zm&%FXjb>B%XL^-=4dg0-X*5Y>U0;A*8P`@$9`O5w4S)7EXF@nKQR{faDMabua- z#jx`yFN$6`)O&a|qqmb`?QswPP>*3_HoSkWG)tbTV}(s?ph)G*IgWtoG_=c;8q_pv zA|`Lmghk#`PL!K2jdEBI=EsknkFY`JC#oDiIp6-;swB%8&d42T8SnQl#yD+8n891T z4C2G7z&JYyj@1>+X<#{x`dYv+*hzbhvaNCUW{*4=oCl8jJ0^Eh8h5$a-oBL?YCA zWaH@aTK3|_3J5@SCI={#SYdm9_l7R}9|ZOn$&1+n`LFiIUa)MK)ORWPG+$@CsaEDd z=32&ykKx@GFU+ig-AW=lmQjawPm#Jem-<$4F<-rFFFuVaYJlg>-$(N`M82CK@YHU! zca^mHckQ7Lh+C(5>iQj$rId>Kq;VGw5<&m3R-i?vr?r`^Bf=zpMvrlMQ=srvSNd)Q zu?kqjXTTUyrA$WRxlw(ng;HRA@Uh=1)f?paFF*|HO*%bPCFS-Zw5O&~JH0&?@M*k` zR#ue42A{=l3}sZFW{yI$w6t`o$xV#)XhoPp0=^~iNEyhI$5ntTD@Q8-$L{rhGKjIh zTa{rzn@x;K)X63^+pznwwNw;Zn(BrYBlX_;5w0plq}suNbVf^o@)JNMLv}dv7HGg{ zJwxd~LOgSb&Q4SKU+1-yE3d#-^IoxRXFCq3$8%j!pH<7=Y8Mu6fAO+-788qRmuC6x zO_sz|T-*z2d=lV=^A^Zd}xt zx+8?{>f(5!Xnf{3`EWD1o`{UNx$Cx6-(_5A8MLP-U7~UM48A_(0(c@5XL1F-q4R4C zKyC)Mwxf+9xCjl{W=>IY+c}jIX(yMK4#F*ZO1w^Ym|cQY@}#4{_3S202Jx(oIP%nF+*_bGNCHVEvi~XgrSFm`xtLCD4 z({Fko$ei;*LNc0>ha995BM=k6KAgywPvz%vW(GOq%M?1wD^gij8Vn!%}#4UW4Sx%(}vMl@zn>b`^A7+{9p~sXA?s zwALr$m-hFc`rU*u_BvLZuSTw~4P-O`vNXhGYrJ&)yo1JQp(l~EW28XG|NC9vj82bN z>v0*a107`_ok_?J_?pk6*)7ckTP3*bq1DQuBaHZ;iXBdX`{Sn>}N!>2_z`&OStaHFcubsl^KI~SLd%SaBUew^A=VBWgja@x?| z(Ykt_fzTam+6&s7r@0eow{A94?9Q!`Eq&t6-~0Y|P<3B<w zQ!#b*z(U!QRVwR4Euo{-#&DnASXJg7>W9tSZ%Rwu5}zlXF2#)h^g@W(cV)^70#GeU z%45%b}-!Oj$B0iB62&W1UY zRZjC8Hf_Qug8kFRD3hCXcX*`X-ZMQORyr=ru2`DQ8H)y-1fKY^1Iml#gE$IJ|A`Hu zw6b3khY3ZfwHJ6ooFkjU8e@E$sp&tBHGKH5Pt;v)#Qt`kcm=e(w7t0B{ZS?7%tB|O zUPw?>f|$X=SDZ+wRMwoj(XRu5hHLxnhiekd)Tv-ryro*{+e>1Q&GKA04 z%Y3O0(66$V9ov56p0CNlUoi6_q>`Q5H*(xa-s!&N-w}2w(|7Md`X_~+mqf)pSP5Y` zgoPE@kwhWfBd=r+#nD8kDp`j=6`GoL^-($mpNYLfXlEgZE4V>otzfo7S_=R%NF8=QN=#omTP zu`yc2j4=h4RR1;|bPC5wtVC2Ie5$y{WHvz7l0UNa$c#&4NO{CrAb?ZDOQ z&vdUJ1yM+`lL_8bOO6zCtyfClYGss`EwHNK^4c*NF49C}ew~VE6rd+_TZB(~P`Z(x zUiY_bB%E{Dv7G+Yu_u=1@PzSwb@;vqm11bUJY8K`!v*lfYQCV{$T0ZbcF0$PkG?4~(xImDewbbtM~fIP$DMB@p^-}K)$ z>i<*uiIVgif22hACH6Ba-EB0@26v3V-N2-s%Dzr8B;8xJLu9}fnqh~aDfgS;nQrhM zV`TGpJD3aP_UI;@I0R$QV@x)~ACVu+#I#jFEX@A;v?R+D5)$~rZp;DZt?eQS?D|+o zr6jb3{&2NlzS4H&svW>vI4eH|t9-fO zg%r;?#>!qi0S@1K!z}L&yQFu^j>q52M)Uy#KsfonR-%jsNG}4*D~F4zPK&z_Fz@G-4S= z0ksPD!%}6r^V0(ky$WZzRp-ZhQ7JE)a+caLo(1lviN!9NwT0KNWcF!npnSKbDq{hu z&}wQMc;A4zk{goarCDpk{&(-<|H24j9(OKxHnAXok3yLF%Il||voH_v3 z#kFwQzYmF_&ecIs1HmeDnAi1%$I;s0&^XaA;b#fZM?2aF@8h`(?;~vALM$FP5D9B6 zhH0*SWYfpt7|VF-GWN_KaJ z);C5)hVEhhd_v;%p2_bBwj?yJ(KkeegJ$IG`{L#TnG^ML=eW4QfrbPis z)M@FAqx`z7g-XSphX!qLjz+fP`2OYXK)F1wJ+yk(Dix4~3UwRE?H~v_FtXebun+az zp3KnR9KAC_%Q)GY`bnbC?+_b&ygf%l6Girbw(WcgSiq19n?2-t(~0tP-of7MW*@#> zQI#p=#i9D#H9&F!Eo_t=eSsb7$1^jaDKp|^Q->yu8CPYIOrL#c1jnq2OL!E z=^6|p|H^{Pb1(`7ZZP2BmyaUn`^cfs&)fS?G9vYEZ_m9mfsb6k-05cv@K*}|%haHK zUMSPG7{X;gPeFRR!tON=tc_q^^MY5UE(ufkqEN1DTaEhJZD%f@6!^IPtI@TM)jXL` z?7xSu6=pRMFN&(l;^@Q!uj|`~$BX5%rInH`SxO$}>%r65hGj>>sQJr^2maP|M{S!rnZn}!&`bDKvR2Q>9dTn9foTwUt_DzXr_p7Jl(Y)Q_3 zwg&&f#j*6Zu*X2Lzi)-j7;&u|5|uU}-rAPmVVXqPPekb4;V zrZoPSs@O3hq{s;LNE`FRpm5m(A{8?r$`>CB4hb>co0lYgVf}F|LFkG5$F2{urv`kZE?npW3#b}-VzCdf;fmU~qwkrKx0kjA5 zxaYVaq=Cby!@S52;f_hNSU_X4Q*CfvF*Yr4DteOq`su$$s;j+H9Kb@cf?fvo?%IBI z!oxkCO*1a%-PpalC4Z?r*E$sSI|@)B`je#ptXo-YiJb z7tp~yWF_OZY#rWhln0Nf<`saOBB?}hD(%La09qdR`fZ8Kp?Vz&2} zRDZ*??q>9HvRPM5Ss_eGzZ4{-u8|e<|+Mt9W z4bmyyotsodQo5xCq`N~xxh{I_p3q>k z+zmVqi*kCm=%8hRf_G!K?oyc4?LK6oxm9PmB`pN}?!uWai)P~~Qz&y9dn$68T8jzy zGoLT6FsiSZ%t}(8l@b~?j<4lYgIi;dax}nE8+1`5M_dy2 zef_4q6^^vgFLTLAC#2*dIB5`X1dvIumMvQ;^EE4JHvx`WHi_eHF2CzO0hnJat&K^$|gE1G7=nao`YemsRt#k2y!9Q`!#a4Rv z)!dxRHcN-jKZcDh>+Voy$`uR-F8+H>$By;_P>+5*;&{HMs$-+^4P3`!0}Zye9l=&( z#8$t7LB{pw8HI3`_53Xz9`P-P=j4U-g282)dg@Iw%oEY=7H(e?EaVxpqQi#=EW z+bdoMGHX_5_A`|(Cv=87?Cui|ByuAk%+^X88~+}@29sck!p%X4$9w2X&9k5u5fL%E z4D70iY?9&LcfXn7t)?g@(5iusM8i=h!|-(sGlk#R8sKnP&JasHALKQkEFuI-kU(HM z$pmsjdff&bK>u6G7OgVr$D1G5wD`}4g&^F}IF1H>jKej5|3Hm0`|Na_$xPD=U{~yz zgZToW&kgN@&SXKgw?A6zk<|w{{N zH}&_M9wX@}$2fGaKxu#E(?o}E08BO7zilY_0WI@1Ms;aTi|S(LztQvY5_Y3S*yMIT zrczhrS2$PnILimc&LR^Uy+Q~M4nc7_5)nhMDs z`=uuX6_t9NrbBpk3Hh1|hq4mF840h{t_oW5Cjst@_FLIJ zr9F)AJ$EG%UQW_)>T&l4e|6mRzA#A z)YDk>mqxi(E2Mk2DFD&NNU=6A%;NCKS6KLez+nhRX4|B z`7cw&B$;s>1O8?|Hd6b~P?j2@dpB~D!+R<*zQ0x)c(u}eh6S+r$uAo9GexMbt6mGU6MN0COtWvkD+!t^SVH>^r~Avr7PIi zNU_|@qG+j#6neHU{`DTJ^u|{YP@%9KH_k=ehT}K|XKQW!X!@rB;5e}fL08W*ut<|* z&zn$`VE%~PxG(m_Gq*kJ)P{QN7KJQ=hh-N4Xn$E|kAexM z(&*2ACy)y%a>)5n<7Bn7zQ%AQB$uieoFAN-nBKm{1X}tsZTpM}HQU7>oi}Ir)CRar zk&f^gY`9En@5XZE%7EO?ZMs0Uu;af7dvT(Tw(tGdr^)2(W{ZZP)zX34U>e4F?R2#i zT?Zb_d?^Rf!*%b+EO%j_05?EFybgBi&QElr*RJMAhGJ-VfVV?I*I$(qg`HHLRUUQY99(=Fy!X0zx~n zaTY(sDTtD%o%>;nS(4?yKcqLJ9X@m8!kx=b37j*K=Fa_%B2#_yMO_`u*~CRpmA&$o z%3JS@!S0}kzYhnRY?(_d6`kGbjzpk^T;I}oXWhSLbpQKfsGQc(A7zjD3X`+T7(6MZ z{o~x(*2xxz!jQatd|w;+gEN-Z8RMBbl~nML-yBWVZH5Olf zFs_t;^i(eQE}N$VxE$Zhqi+0kg?EMVEJ}?UZ;c0kqJ9D@E-=H-QZHScuC`K_HqI5h z-NFy-PDcrWavbsw_2~P`h@1xo24)%eVzr&p5tPql`wA@k%bl9>9L!cHkk~CY5?(3W zED@k+WS3MgdvtT14wphAmLxQnKBmg=q_18$t`ANZ_UH0$KP(iKEn*k8&EY|pZEB@& zj$^Yo!R*SGFAX{)tm05~exk;Z{9zMSMUGH7*;aNdLk#0 z$LMQEIqa7NaKL}|G{_{BN>b%M{x#0RmpwBp=gQL_Jh~CNA`x$&gq-w`hQVq6POZ9Lcs6tV=MDCm1yqWRFWn{p z;GWqDu;R+FV--$QU|R>1JoeQ(g#{9hG=>(OlI#=67$F{U##pOa|3 zIB*bv0OGC|f4vvAHltx=ly+?NoMIhFc`FvTCT(rajK`+;BMsv5OXFW=-ltqN;rnAM z&^m-oVABr(s`LAsGl$Iq!hs}CcyJH4T9r11hvKt^G?0vG9LG5OsAY&kL8+Q#j2abh zK2z~s9CZu$4ftGktNtfFaG<86vl?0h^GNsFC8^lLSrpE#dp}U4sonrMG}Z%r#QT30 zs3X3|1SAljKJOvZ%9YBu-!RDzA0BL{(tM9Qe`^*T@cuM_T@o*+ym{yX05<~8_X~pG z314;AFtbj4H%?`QR11h!w310ESR+{5>C?DR2*6ypWYX#ey+ItRs(5NhnqsJ(os&)G zD&{Z>>xO!3StE6r7Im6J0`Tf@c@0ZtvTHK8c3&jvqI!2!Lc+UEL-(TZlZKM%*h)Mu zRDDZ)dKv!c(~!+|rSr<%jzjAdE8$IdE5vhOuit?)s%GD3C9YKohirPc@cle*J*Ziw zWg2mUg(Mou-)bgxnyoa?8@{ieN?wv^G?}6bK%RtL|HRUgReDa@a@mdI zyaw@7bHK}5qARFUsss!A_R7ShK-AGf9@FxG*TSmHco*e#>b69oJ!##b8_K3b-eJQ- z3?SR!4etCX0`}?t+i=T07BJHJOz>YGEk%IW*bta8LD!Rw1nr443xp@ggeNd-`hjxV zuqU#0doGu^t-T#0WAF!K^C)z+MGWZEVVTs5qWvLH_myq^6keadyey5wq0*_DRt~cR z=1Ne-QytFNub}~sRY{$wu8VaWuNT#>a4~~$o>KTYldqExu5@zUq0E~`w_S_ELP({& zjf6A0Bb0vT%@y-aA^z~=fYalq70O4%V*;6`TTgO*X_Icaq|yS*zGH29BtYWMTlmHO zlh@`8uf8uKzmu_~im}H`v62wK%PljlY)VKvesQD=79uLDL_U^;Vu7&|H4Q|xIoFYj z=aOegx6U7s$^GEbu^LB#mX&HHxzd-@YJQl^-~2gTbO(s$?% z<@dzQH5|%vS86KxNb-K=|I}^%7j-@tKh-rdG9|n^o^_iDqB!6=)2Q*{Z*bcKoa0IN zR1vpBk{sEz9}F>cRAxgd<}(GjryKo&iHQUN<$~<{bb${V&SdSn&kY@HAch@8nKm7a znX9z}OH+Gq3cr*6bq}V{)BTzIZhmeguUlAa+EiZ?*MQT+G*wa7;`_kh5_wT+=vQ2k0-`3Fdo_NmDH!3HNQahT(+ zk|yv7wDGL^VOf$2%iI63uPsNpVBP^+8t7Eu84qHGS!-rRaa(a!m<)y#X=XVb9YVy# ze|Jw@>UAK_kO`Z33UYOo4aZld*P~uhw>{&F?!CyZ5AMioiqqFyGT$ zJvw+FX0lVj!j56T{}!ylbz~L(lRd(Dr){LsL5|B#XN>gn*x;qK$b(RcPSXzBT%8F% zzngh$_WJVjam9s-@rYP&d-f^?a8BDzM17_%W^(mMxI9IFg+4vLUElToP`Gy@YTkxh zG{2oRz1-$x8t2{8ZTpzP@83UtlnO(Fuq}+oGWB~UfGT%tZgxH_V z8&UUCD4m=1@T-!GU5#|$gevw6D+ZUKMtwD9=5me_f!a2mkxF@qW^MOH{rIEw=3}>| zxP9FGZHhv37AFzNosK3AdipL=Q6CYK{uh;OPshW#-m!AS34wG?I$GM*(~ZJ9ubYD5 zGie|p1+mU8{*T@b6n1GkK3gduJvZxWu5@*Qtn~BTrS9`4JXy4qlV^N&Y>+v8TuofF zW_zz|+=G$d=URyfB6xd(&ZEo1=3KMXBk*M_f=GWtpD*d>w+!U(p?DG-xrHCwHoZhp z_tZOiD0wE)2vr+pi!JG(o`x?Vfdfw z;i3LQ11e7)6@|GywI@fi(DE9)D_qK9|8s|JZI(m3*0sqZFJFx>AgJwsP2s<;)pF(| z3F7xm6HHZ|uD-%B9MX&R*R9Xg-55q-KAJbEompTcYxJMCE>IHBUTAit)@tglYB9|R}+*!n`Jb7{LN)%c!M1_pYm~BNLo4mb%}adDkx_)e0lh| zAAs`dfL2I|qah}xb(CCTnQ%?{2( zAIGX+cnt@Xw#X!~$((xnu1n!CYbe$-3^wh&w<`;|86{q3G|)f}KONI(a8BYPZz&)}ixBbF zEc=|XUZ>KFm8W?|xX>@>G`-(Y;0iNRO^)@Qjri%_DR;e2wNR}r^ct@4x+s5OQu2t6 zXi6|`i~Q4U_mQ>7y(VEU09}9C`swaz#C^2gD*L92oND9@I-2!RcmS_cK)$1VOLqPR@F9F+UHzr*WJ6d6W9oKqA-HX|KtK3TX2^L;aPXJpXU=ZdQzDJ z3mSC+v;d#Hc?q2k%^EBUYyWag#A4aBU(QsP=QnhwAPQ}TCzrd8m>s)w+1`au)o%%M z@w8I!)|nE|_h1xHIj|zCmykR+LNJe7B0lBH z8(b8q@_Ajelg+n8+@X^#AGj^P2Xf$%Dosut2IUUx7#h%FXWIxjOM}8K{Bi3BzPIwD z(iVHL4C>T-5hk$eKIcLG%kF2o1~z5ITzS&xu&K|AZ^G zimSvr&qW8PN3_bYRdo+eYX>zEh9;NS+MjK*fd13>X_Y7NKVY|se)8#J(_t4u!e{*g z&IuGU&Za9o0fD*g$ivlb;N3Bh24tu0Rul6%?(6?ZzlL=^f-J`!cJx-Z+5C%nepc&j z(CN-C2lqaIK4PWS=v4R#s0=DBQj@JQquF_pMWbTVQZ8ta*bjbD#(`p$9)Urs z8qO^PVz%5#^EM{B-Z`7$6*V+zxb~DPG%pFW8hbuXRc@~YUXT$u6g%3)nqBy{9R}pD z&X9GVrPmJ9HV;WGD;L`LO_!>mmo%8>Yu7)1s1L^-V#@n@E&A~!xGCL85~|vuH9`2e zx79fiCt`VvLgUo0R`kZ<5FC)u(D+mf-j!Xv(A(ilBW+r?A@^eB@p9?qL|1$&$`czT z(b{Y5uxzmkPBRNu6OZny)teiGg{aoMpW*$BIGJQ|OJ_P>49@dxQmbX!DC|bE!)Lz8 z99ILkSpZme^ZMpc9v`ms!DLY#P?dar#3d!O6qcF~*@;sCcP1z-Oajy-Bc=NK{sFhw zulnv$6^Mqxfn#HcEyE^x^4j`-E0M#55sU(;9pF&OLT)d1DSaQkWTB^7wy_t^>pjuj zg$%B*AdWUNfp%VwAXo7r0_*X%3z)D$Mifq@`l5koq*fs21Bj`xT)JNfjB*5Kf zrI=hV8?Eq{SyNrfr&9i}V;1v*iF=Pf`3>uL7lW>dqaMEL=t)nl6x#GH*wr_jcufk; zWfm@75;^G@6&rg+-puZD+U{N}6dB7T?0OYA0ikI0Sa~@^I5B8&i$=A*hMb-zx_PMP zJWAWKj%Fi^J$gYSMf6$6OV79j8mEy#&7^=uH$&lZg12^&Px935%RL7;e4Gji-4RQ^ z{y^rddP9Ud`1k+y@<-y5r%GkQ$G!(98zcT_Km6KPl*CEX4Lx0dhIM&$RnJ)W#Iq1{ zm=0{um5aeV_uq5Vy?#LcxMhCC&i8i$tM`xFWh&i1)lhJT1VasK176#Cu3jR_;??|R zd_qDB?3m~C?aOrFP~BgoatT6^!8eWO-VFx!SBbNh5AN;)Fn>?=o7~%-r$vJy+RvSBBHRf2Yfk4{uqj5PDUq^RRbtesrXD zp?9!S$ih_Y2=Z`N81+h|vV~&RZtp5p8HxdP3g1#i*?nb4TQ?B|#-t=XaZI2fTTaNI z>s+)85>JSg!@}6={0XS;SC?u##b)SicH}u~28!Xp8+u5TTaCq7SYF7w2H8u+&Kv5>$_LJLcGaEG2AJ~*K`Qq8M{ZI(A zJ!6h$tF=Ef%$l}KhLcG|q&3;!I{%;+5~#$}wA1433ER3dB%Sbpq`#($Wza}#8ByE` z(NLHmHs$j1oT?N)_wJ5SvMe7Op?g(5g}n{pUh`3MRlRoj2<+NpJefUw2D1k;?e+3~ z#_2uWg+ZJ<9%vjw8i0=<0?u96OSEaQ|iya+Y^ zLBD-Z;jf@D3%52+&Is|lIdlJhu;><)A~^oJb_T<+@@=8{w2dppF6o!r%x@qLP~1Lp zKeC1&_|;fI

    fq25Hj`q#S-^l`I=~MYKPfL3)TvRiP6Nq9&d5eWIQ+vM~3Z$~nw= zJy|CQq59x+Ow6%~=Nl&Xvgn2hRtuTY zRYNKDU8u*G?THSdi>xVkno}}FRnz5%=HKm}{CgJ!K*(b>7uiLc*L{2UY3F8SkK zFcyt$#V#VRT^BH=3K%Rkw@|Nuap=R{1*W3kB@eITc{kvJfVAw9cQ~v%J&M{0=&~xO zkz#WC?PF|Hc}lQ=9OpvLrc;OU{CX1GHHHIU%`-XjH2B4%&m69PU_-TE^tUwH#C*zD z0lKPa<-q9OcfSn2-&Ch9A-cDuZ~qevwd5=sexZJsfQX;Jz2eca{`2Sj!xjjOQ(adC z&L^{7!G&xY{C_9)3HH~=nCUhB{I1U@(cYM@yIP9n^1}jO9_xPx0Rsy%TI%Io&B}l0fQLVlm78EB+3ffF z)!BgQNC#`OSq#4MYSEKc^ZXTj*JLl%pJ=77S3IZuY(#DO9%u&)F+b(n%!q*gT)*_LrU_PT-!uA%F6rv=zF!fIioIz2^!hulU?7WS zzhZJYgI!#jj+T)A#G**O$MF^>WA1W>D|b!w#^bI8YjFxo8nie2N;O9(bvt9ebq-Gh z;yyS%rsFSJi&;qaLo@;xO)ZZMQWAR6d=_5xXsybqyd$t-eQB08>cYt27L=%2Hd#G- zMv@moD*ZreCz9?s6HBKJHjzX#EHV8Mc_iRhTx`0o z2w~%RHxSNU4ZdN6&FEdL-*@U2@Vc~bvS>#F0rQM=hM(#aKYIl1ie97Ao-x~d7D zZW4N81bICgCs^XL0`+e77x@gtvbqMJQQ2-9&d#Ryl}~J-^O8zMcQ)trv-72QFtWaP z$n4{7vR!lM@k%@H+Ke@i^WG&bXsn7E2M-k$6wxJp+>6eQ;M&!W3%cT9fxhMhY3l?p z>|)l-l|o-aEj2c;iX=2{bl(r_X~oV~U~(EnNk`7NwDwRR^MB|adVy=npxWR{ng&3< zbB&p?^m-_R$-M7@x6}Ud{-Cs6rp1*W@8nV}8;B49;LV*8mMjpF10-)Ab>nl&I$~ zxzY-7`CtL+48d#NPg(*d?vVVH7bJc1B>oKk{{G7e{A+J-$MUajyC_S~{#=rY1`Zu% zfKe8~-Fvq2V%3>fOmXEu?(BjFyNb%dx?Yb%K0e!K6yrwbs*A%>6c8_f1Q$(;MlE

    5trW@0sr_K}kqv;45{iA0JIB8e%9lJVH5309g*!ol17qc3Bi-#v>68AIN&Z&+M zTDy(j4-PZABZqO;=ju6`4=GnkxM|HQXw}2vGY2CtY)S(xH(|aw$3=p2D{+O&m{gEa zrP%LibGUOqJCtQ_IwR*P!7A7Cj*g7j{Z4oBvN&6xclEZ~utS3(a2xtnfOz^SO3QPi ziocW3Z!QQ`{!oMoJ8z0&{zGp2RXSnMjNva`(!5#mx6Uj$ENXbD#MOT7A5>4z&3Rgb=ES8@EI!H&sih;y@%;J07~ht% z<&1W0pGGi(JQ}6iG z0SiCB(`KwcDa2!d+N!Y^w97rNJf^%;Sgh1QOW_^QBe?U*y*e4e&4=0@PTD=|LOj29 zxxWyoRy=3awx#dEZvz^^#7>(7ooA^6S=Kjw^y(Q%0?zyX%rlNz?!6Uxs<|&_-Mq-C z7Rkc^;PSgn0&9lmnHrz(qaSs;g2>JE5&HHk11>NH0OE02pW7Wez_x_jn<@oc6z9pJ z=Me=5hccGi#m{P5&F5&dSFm8gkdl>+bqhE7+&ijI)19jC^8!r0$1I5qurBsgIz@b$ zJ8IL>-+#Sd-2ME#x=ijFr3kW>PMYEIZWN}7ja|v`B%<2MC%UJdC!aRa2BhesTT~aH}?_Hky8@uhfFTeX~Ix2hY^%fUTu349#WmjNHYB!mrbRvs4?F)!20K04@Y|P1f_o2Dqns1@W5^nR zE_)*yShQ<8qG>{_P5S2JW7lYCXbk>-d9TrI_&PH)bL3%=*Z#5XwMwxvAwGx?t&M3B z)6o%1fDXG_M+i49b>1N31N*iH~AWcW}0Z~_LwoF3OS&@1z+GL4t_OiW<70k?M z3lX?UlgN_ChlARs5pV^4oQ`UTSISeQa3Sm)k1=bZUrl{CYzJ}N?2WTX)u-+FH9iY1 zO;AYsD_}%bIHSXePL^c(z&cQPPe;kdqXdJK4#}wFYW}U~2G_i}ds>QQ_CHAe+2N5+${sa(Q8uzohz)YXa zeNZXZ+W7dzGkoV5b2Ez0tZr~Gt#{s`3ixkqgrJ_HL!@1RIkpRcSuPeY07B6(MX>7k#OA!iEzl{uuAAT2Sj4P1F4%Uf zi^xhQn+aO7_NUSTs9mrOc$j|5EwuD!z?S~X2fzt~>8Te(umH2K+;pEVZ9B+}G&OJ( znhkZl0k!TfC+_xqM(hz7xxGECM7%cWVg|EPSQsLnq6nM0nzN1Kuiy?b*o#L+5eb3M zjrro`RA4H%?@@y}Wm zle#=PL)!RmF!ocX^u%s-Wdd8;`yAQ)6fy8a36~gZZVI^7xr0?)&+|@!}cTO7byq4Y11L-YwYY%BFzEN&Y`10IVhQ1zt4C|f|D-pqZ;0#br6^I2SOJSYCyN-WG z*N6SV8kN>bQ^=KrrtSU=F$>|OtCvt8A-%I7W=O-u)&gC^lCN4hHsQ7u5NF@ECAI6u zkJJ}6qlpV`(Ca2JhzMz~2*Q@4zFfpaEw);5u@2x2LW59QHL=gzG4b1!jpoQ%`GYAL zn1914$8uaA+#!R$;@g=DmFs#*usF6S=>L!$CR=#>5SYa0v-)b${Xtbox0pza%Ps^7 zx6i20ZR~hO(uk|q|4&c42)XT2^SA43oa)H!?E#H)!+bv@_^@qj`v>byhaw}={_2)@ z&bLx7EEKN9T2#x#Z#auM}DZBZ2J( z-TZl~g*lQTboy>UOhCaO1JULIjq@4rMZwh9i{!=rlWvf2i`~FC_x7T2h0l2%N4_(~ zEl?q~{#A8)evsK5H%A8=4f7sg(cmj)#joTeQS{AFb(nrKL65pr+*@Dq>+WsU&G|rw z&K*NtjfPi3H)>g$q(v8ES;7>FBT(ep;$_ek+l!EkPNhd5=6t){BuwRN&yBUXH@dpN z!aBC5ahOyd{V6bt>8^NtoFY?yY&MVt;*9VKq!NLF&na}#wstM1ls0oshHZCJpYZ0b z;+={oZzfw*dA06b4c1mT#PiZ{so6UZ zXlNgKvfJ{=Z=iZ2II15u+E^ZSB^(?g`hxQSqE%WGK3HEQBWis3cb;CHW}vL%of{zB z7~P=_Je^1uX;%}0EhZ=^=-p0*iNn?<>3qF+U_D9UGcBvidE?sdddrQ4)~GO-QG(?J?JTHqh>OU9u#Gy8jH z_DHRw*6gYpJr0{~XLaWS_;tWHH+a{*z?xuPaUyqlcyn2q9{tgDGpW~@BgS)+*Tb;u zCZ4P;!JJk;7Di_CKBVuW0a&@)psJ;hS&HnwXzK%My)eWxiIe}QSwmRkbGzlr2_ual z)viU`fv&H>(9WkCw&(8!If8{V$u*fN0*t;zso9@S3*)QV3 zSQOx5>Pck4<}m5=mnGyBcp79!2`3i`FV?PE{`(acFzXzKDdywRBU-U(g_){WcI2^BufQH63 zBJj7(JmZG$8_@h@zGIF!NfNB3kU7-JJgHI~7!hq1%O~Z*EgG6^8)+#pDX)DoHK|&K90~HCcnPZ)b|1{R`^vrO4*M=9ZeV12K2d)1T zsKw;AT@G*HrQM&UZdbgui$L|8@`65m+%ae(`!4nMLboatba#m7-vTmso(hd6nPcSa z)~Oy9CmZx3md87P+(#GveCDmPrYE2GJzq|E@)yXH4Spl7L8kxqbQXMr*8*~HG~JcC z2(>!R`+OU%%Q@+8CyxS#Jf-AgxQ+0-lIe^%>1d$6v+P8}9;nql@s~R>`Cal}*RrlU zcxz+??^B!%*|Vq@06aCc<57sX5JaAYM12gS?~sygJm46yC0cpFKu=8&m>hCeBc zW5_~C(|+z*ysc3yXrs^lk#=wOb6TxUw1*)=Q8$0#Q7Hl%K(^ErCT<(Scx6Mq!EZmM zMi9{YLnLSQ3R9%rw6b?VmoQ&ByRFh}SiH3N`3f-|2tpy`d}Gx0e8FUKGHXWvPVZKD zdt4oytB{`QS!)Y~b_v#k#N5_jK)0;(XRhNRnh`z%c7vM7wb=&RSG<+W1xDU*slv6e z+z!@ks706+8RYgcptHZWtj$u5>0A9+z{<>-I=xc;H_m%NT?SiM%c0s!0B-%y93A}k zi<{k|q5YBYwcVI$s}&aAM?W@C@9**u&o>`Of2VPVKhFVz+`9o)|6j`!OuUig=g)5h zT=$o0)#l&u@lBg!Bedao&D3Lmh#&*Nctx0ZDk?apLqEgxWBJ^lI}Fh?jXtm+K!l^a z@S8U<7ew5mAQd-~&DY1@2SvhsTN3=%nX8b2fQsr8;EnWv z`OWqwi!rc35ptM{cgYe)F&I@L1yV7yvunM5-xDZlO;Pa;STl?k8mb8(9$X*7h*0wd z5aa+{E`^^ni&RRsc>*^htv3s9bzr8j&i*Ec)B3K2ie9%EJHU5of&I^FoFhCwjWpTs z`4uUd?5!LW?eR?$xD7OTQ{`iObv8ey1PR&5Kbdp7Hd3mKipjY+8)#;FcotKgxEp9O zrT8a&4Mhc4BTUmVOuH94UsoMdKLYB^bT&1FfnN1ov0$L1;e%N4dUn9ExvX51urmF(!0VO_8YTyqOEP6@4nqb&|hodUcC#==p#Tnz4UKlJp2a8 zm&2vaT|l9(GZaR`j|B^NJ~czT7AKezX&Cr5F=Oc}+LNv}wa?2G5QZaWoHhj_VrXPS z`V&|wl`_RoPhX4236gZA!P2Knx+|Zku8Oz^8S%JwF z=5OHDky5^~g^H@8GXx3>^6nGQSUZvi$Zn} z0Zx7d7?3;wa#NZ%SC9Yg6_OEfn&NnQd1b#YO(d!Ohr=TDZTBX~EcoHq45cTYV z_SEyg$)M4$vEFdmr&r2+)%;Ks@dEId45;;eAGr6fFZOuzv`b%*o!tM60@r-xP+>yp z@zP{5=-6ZJUk>|Dr}3E1ougC$B?Q1}3YXc~;D1Il? zd$6$6tK?yLd3!76$P#+-nqyoZEQFn`b^CzGpjFg*N8_&D&b3I!4k4RGt5`u2g5u>B zxjs@Y;N#0VfPfEjvtFmE9Aj9)dtFdA_-SUW$C22{=1&pj&r>)<7~DG08#Q}jkI}f% zS#X3py#1;{#)nqD7&|E`E@YhEmHt5RTyfw2O?POzw;q4m1(W(B9^P{eZalu9R%@Ll#RjL!EupElPm_Sj0 zp|zE7ked9CP!*HxPXxy)W<~F^z~N#RT|BFapc^P?&^b+IC8eZ-PW`LZ4o9-mxNPW% zvNy_lwbLOpJ@mO*@g7vNaR^@8yXx+`{$iDiRnrw zx@QbMh>Cg%Vnv_Kv-3q>B^uf<*tHWFiLy>L5`CK7Ns?+-gYa*Zu`# z%A)J%Ovp*_`G3_)s#qXeWq2}=g&zSRdh*1uw&_S3fER0UGdoT3xcB1gQ!bb2~!&?L7Og=td+uYZ= zbE2JHT)h7KMlzh_L$WAvo4AoH*4wJ<{BelD6?WO=;Ihy_n$}yzb&TzSzbYDK>;HE!>UCHPKwPK8k4`Rq1vFV{k8= z4T3!tWqLK-VS+FD-{gVHqE*u|m|Uwf#BH~TLMrI`F0K!kKl&!+)I4uKbv0jkjuiL!yx#32{J#6pT~BRhEfl3+vfO^=;U3z3k_>45AYSIJ68*DP##Yt zH67+vb8G3{VahjirN_Bbw)|tc$G;O7%p24R1PL{GL0-uvfz+@!`gC2vy@S$#=PJKmxz3-!D7}z(d?3zUh-WpN^T&lsB@8Ds(&Q8`O zQy|_%$&;m$r6miB85)Qx%t}aMR#R~Y(@_jgrssrwigYrFqWa;fjX_;D@I9^o9c;yW znBmc`$LDa%adfjPahhrTc9_rcc*SL|qC<^vM66Scjidn4gWla;H2>QOd@`f~La%f@ zV!2Vc#y=`L;~#utjPvRE_?6{u_sXDUfa=f1FSS!KlhU~JxtgJR>2u*1DrOiB=^peR z_T&l~hWyr86|G7lUKgu% zqFCX^3B|x)?!P~h*F9#9k9`Ys4gc!%Ir*n!iGI9>jX|4G@U2OC`-VcU{7#thA{ZGE zR}eeHx7~N&Rz?TwxtiO41F=1^;yjY;8E9A>_~1aJmcXsQhoqRzTBUQf=If8Phr6XDIi_H zYP`WnwI%zSoQb90UX+_oFt%EOtv>+KS6-rKe@3ZP3)SD}e%o=dr`!kw2j`sME_g||j<&0W zHG|8)8JqtWGIf-5zr%MLACn8)ddJ9hd9s}-warvJJ!Qux`JPn7`w|Iq_Ym^rN`}`m zjmoCi{C*+F`*)CZ~zhHPk3^6)(#fTC^mh) z`{NdJz~AWdTsW9>1B^pb;FgHNJ>1(1#iEg3?k0Z<1nwh`He>Ke3;eF@2n}w#D4qj; ztFML7?9uvEi@xMnno{cCp2K|U9m~y<%#|e))R|91TlK5|U22ga};B)715!%16?top5fiS^Eiqb5?N$*5Y@ z+%!Q8cB)L9ze^4=TCtIED<*K)X@=7S`tB^M90Q|_h;O{J$M>o}9gcvwd&)~8#)Wis zSC0==6Rc}3hy>p>kCF)lme?egf(GUS!pR^ZmJ?PLZJ_n}4NOx`BqNo5dWRbacubMv zNYEKw?cdtyGvcG-3a%Ua*){I6FFtM!ryEQZ_?Ef%DubDbyz4!%{~L_wEky}QATXua zk+r9aH>#1<-N#|*^~Ma`|+3%QmtQcvn8?62D+^}w5rEP8`|-KsO*S|XGSNj0VZa2xSQmBNjSz*#y&uJDZ zB!OfMd=#^A!8QX6O_&8QGx*wkE2GlF=Y{H_e+EA3cD;06vFD#+M5wK+LeAJTO2ro4 z@zR#p6DV+)J#YoKKjbXBhp;pJ(lb45g6#->-+MTHH@h94;wy6DOq&GqGN+@rGp@j> z{^I7$)3gxJ@eqdJjlS}0ir_iOjp$ed5gDGK?+aOwF^=yY+pm zRJ;&>G__oaac?w*#prL)+jvZY1I7UGE)G&fcF)+6noG}^Yqng7e- zP80I@;rAVx3gnq1AR$FhT!M*!Js1&StwJa3jh0$t2-uy4SUZ*Hg5-8p7)El8Weu%*?mAgg{b}@o{Y(mqUynMxwb9b(`ZE zv^H5vx6BI-KBNm;we6|Nyt+f)oaS$CQAuAG4x9wg&DPfPL8ZLaVrM(x`CX2)Tw+dW zyV2e~xXVe$OYezWLgxis=^tQ>Lqzy~w!*=0UZ=eY?{e8Rho`+sqYVGx+Q(R$=0fEv zG6R*AkjSvAJ-psZpC~&1m*p!7xc8lvPU^PBse-?M3n!!(RyxiSsObPKtcuNoo>iulaw#z?ld+jz15@XmGyUNK0gLXaC1D*p`1 zTc>xBu47aC%@Jg`tI;6-)Ngh7Hgr77dm6i(SF0N`u`J53juxAp0yc({@deQi14~QU z_OzN(1pG(ZR@xBg&~4L(_zN+INYPTZW>(Tuh4zJ}9RE$9w8ex|3y}zW;8iD|i>kzi zU3nc4ZLnMQMSyKZ)^!$~4j$s068T&(hx8j>kQd_KoS91Y8s0<+>q_qnIukt|@^yrO zYYR-0Sl8z>NY|gUZ04`Q9TXeS1BD8LKVugG*F6e|uo3ZJI8_;lxX^dWsn_vG+E zTdmIb9ZZ*Oa>$3lWat36Ar(!r$w5F3I=@>`Du}vSWB-PVHgE!YPvUqu@`}ynkbu`# z=)?UzG7~cxE`gk1zLzgwc2rVrq3wWlG!*dF$h!pG$_(aCPbaL3^qPp;1E2h_j+U7E z`042pK!XvhsYLByHj(2&;kR^LAEE2hBE$;?RbsUxIEqpN$>QPpF6Kf7-|%aGH-gZ~ zAyVJR6L_B+2LdMR!O1~-?dHb*YVcK?{lxaK z;v?z|^4vPR)1&1%ImaS&|Kk>#{)r5NFo`ch0_f;pw>PNJp;z*`5Cmt1H?=Z|F2361#hjY5BKDqZat467wAh0z~vPU&Q&N3k09HrI8ASzi4RPc_2% z298pn*GHaOO^rN@b7fPBI1`vfS5`YAhM}Xv5)p0{StNcju2EZQylDI`Wn=80QE>4C zY%F&NvE}RZcE&T9PPWk#7yZUXsw_|7H6y%|qqa~ef|zshY|{+0OjsP&4Zee+p+2Ws z`4HTzSD$aw$EcrHVqDTmO?cZ*-Zb;M*pHnv(xGDuxR@s;Ff`L3U$A)mW{JS*jmeO_ zvVB%Rz@ZR)2_xq1l@|*Ej???Aqn13C9Oee^3G963oHhW>vtK_3dy^_Q4EO+3rC1FW zP?X;fYE@%KbG-Ueh2_zf-~K7ozsjBqgQ+&f!A8e*_szi9X*cwIQ23#MMq>19WX%;2 zo1nHoo#7lx-db_&72z=58b*13@PR&JnCjyNQ~=|E$-fKYwc=Rff`5p}HK@JgCF&sr z0DdJ)KkPx=MjAKlhx-xaFk%(l#Zk%mpDyCq5sa8l&aTADoEMi^ zw@&d{ezRqG{rH*b?5+MQlr_69|Sdg zd3rdK1geKn7h%v0uX8&}lMyPMKngIxAR+k?o3FeA6jAPXo2W;4EHKCSH%>o@ZkU+% z*H5rP8&JaCosXwPKO$^zs@<^s7a;RoZ(J=!a!aBeAW370r)&Z^VIA~AfT>7knC!Xy zOEXxll9+DZV`nN7oynZ8@c7xUChmYc7O$Mo5l|~8P3jMu44L(m9mHd4-05=9?zONo zWYQai!8Xboxn$PDz*h^x+EuD()ApDR`Z#xHo=vvGgZ_2vQ+=u6=qV)tG**NRlTGJP z2!AbS&`F*m znV74Ijzhiete7M=HIS}sZKEHbH3OAsTs`gg{+&senL<0)n&r|j-sst=kp=_Ue+|Rq zzaJ2W8z*uz!ofK%!{_04y3vGb6F`*4%!ZZ^l6mtKGhT+L^L-VnccM~QYy?d=bjPj! zP>@OahMgVb@7B=^pmG6ga3^39I&3e$zrXEgbHChA`kYp(-{OjBL;yEeW7}DhD)=wg zSLB?O4+HelS*I9M?HFbPBoK;^{Qs!EQPV4qg{;51PVL=~;dS@vH*Go&Xi=4VU%qNl zblK?9DLq-g&+quld8Ptp-KLfDf)G;$b=Ch*fi*kpRX;Woney5CYO3^+@?*uX6Mgbn zdY?QMM1HSxW*;){7~FUPRV3!056RBmXc^^Ugqx{YZ;Su`JfXvz<44FWuce>N*6^Q{ zy@!Q2=cK1iC3iCaRf*=&0G9x(0hkYn-JEWqlm&oZOF%>fI!F`=0^+roUp{?rZ_{L_ ztSjrVlo&PcjpWkgFdmMAt=^juTUu-?x9P`&c{i~;QRcbD`msh_{Qo2BE1;^1wzUyd zLb^LdxEya?(X;(_rCZ3F?0;Kg9C@N_gZVt`PCQQ z_FrXcRc^05$arwU7bz=#@Frj5ymLJIK~iJ7jEY-gD^898kxr^guEMFBH0@gUeDI#g zQ@}Tl+hTi2&>;)y%3Dh}B8Dl|C@giENm}gD|NemRP{tOjO4mNK?>Hq#@96(^{lUa= z@i?0r`cgEJ)jXVXUmlN|$%;ZepsYKZO z{mYZ{EM}D9I?y)JFWkrMfRvW^x9TP|8JNaT)IZ zA`%Ym+Ly`cr{;*Rv1e{`v90hy>Ff^@>Mp32JX4DlL%QDXL}Us<^CM#^)EeCgaGAA( z!OPPj1uEi+jCB8$bAPsM4z+b8uvnjLqDcSdC%GW{DCF&ns6FUYVc`HabB zoV`uwLhU#%S;9-YJK4xq-NQ9~6T@Tq+{|{7F<(!J8G^f9z3Uz7 z;bL8XeYHzbqW5r9L#0Sb%&bnH`x(U*L!rtyi>l8OsgeiwWJm4UM1@dll!A0yKTM?4 zRWBZYj+5*7Sf2l3T(-ZtF&N<7U#JeliKmw_9ndPr2NnSAz4ZUxUT-E!RPnZkzgd$G z|7OQy()=JcnjwNx2{JQ;2yjDQT@Z16e(*=pg&!%GA)2}YgXMIKfJ@`~bUyw@&j=7j zZ?Jj3Q#jcRb9^*aGz0wqZ-GpFjY^7~l@`Iv%WL!r4Z)i94*u0!R@O+BeXK9M`cF$u zg-~n?ir$!EWt1@THB1i=^BRAD2L=Mepfb=lND?Da*iNFZB}oSS#reAS9`yp1EDm$8 z8eh(pX)c2==SGw344%4sZ~U!|=Lb3ccI~fF-XBTdOb>YX%hA&O?;!uW4m`3owy3NP z`gk;ni{_A*`64x+OjVAghK1E>$p0R(Din2$5A$}g{<_<;S5X5CtknFB+ORKPMn%Ka z{A!}2xMxPtm`t^&EYaoUi>qp4LSk7&e6Wlt6p^UXyd)LBLb3>}ZUa+k&zd~HHvL1f z@3k#!`kWViV?D3SoMkKW*KHl~)B-!GRNz+c_?KnVX5$3}5Fuwr8UWHZ)z9M9s_of; zpwA1NF1lw_K*Q7-ZNOSU`7pB>?LD^T2PwriyB!(pJ;}4`zg64_y6b$_>x@eIzCoC) zTg#rpdzS~SwNEg_T=szu{D2(<6%C7yRsH-K2G$1_1OIv!@f~?Qmbh*xmiTAg6(gD` zwP~9UI9NFJSaOQ;t$#u5bTiRcrdzEF-KqLag8RjiDJ7;S+9GtOr%~L@f3577f@atq zb_BkV+onCxz_~`3_ zlz{UMLufe8%fQO^|I)iz-qF(sJMii+sLh6x{C6(+BTc@|dwU`e4wq4F9ohBvoGxFv z1QF)=>Pjr}gGnTZ1z{|+Hle$Vrw>qT5PW9_L*~u(!2~p-YKyM?jefQ@Pk}W+hA$s~ z_O=5D^U_+;=4H0bAJm&SxI)cBucBJy&!5J>51@;Y93iwXfR2uMrX~VMrOfub5AgaP zj-qfkO>VFrgt%>Q6WCi$_kIouZ3DTypco)4n9LI;_f_yietWo5d&U1j_a(V@n{adKM77UL8IssBpTR?osm>VP?mLK?Rm1RMFYA*B|3tU{$ZHNR1?M>X7P94 zY{-dXk%ENz&DEKf-d3tXDX=okU)X7#5B!!*_1{e`kW;98;I~>&CTK}Cr%Q+o{~LB^ zgW<0SjAS99M4w&&{|VvL##fP)MmmOr@o#3!w?ywQQ;yen6lG3NudaH^vqK_3$&rql z{o!-IlzwgVee6Wkb5YWiv7LOpnfRZ%T+;BC8iE; zkNb5lRAPRM$dBE6wYC9p0k=PYdBf`=XT|I3WYIhKN{)vpEBwN@QVgka`Oz)dm`wl? z>Ju)Qe00h64m?!M=Z4?WS+W6WMvA)vn@@CrdK~w}dge%OcYD;Jjn-_ssHY6tQ>K4| ziOr-ia{>FC;VOn&CK!ciN_VBTjnnoVKUKgJRaAE$gOoHJID1k`taavU))W`IWt?=f zW6H&G#~#jFzKph*$YlIcOolS2Ml63G7&dX^HL;g9P=JKX96I|3*DV|>kFSRhNOF|> ziumpA{Huz__`pqspRDIzfR~YX^BM~X=Fv%U{Yy({rrq_ku^yh(rv2R80aA*NLCS1e z;8Oso%XSsGxk^pk{^9qp>P}C2_UnlBC5sH$Ditot2f_?55LuEEkS?fz9;raJ(9rLr z`GOjjvfkGuWTj(PAqquwu3f_SahxASOh`@FOjJl8OU}6}jD(gJ)NO8dji{i0QlByu zZq}m51K+6OrxQfWYBf4z0HYOfpw3s2sM~GXl7?_O_0GnN?BFsxz6xV0w!+K2jJQ`p zfj`jTB6iM%dh>K9^Z4p`@LL{+$&(I% z&&zc`r$~ZCNz$TNe@BAgrOAWXODp$@^X;j@{RImDcwzE9ESNIeASx(x^DmS&Pi{;W zpD7FR{yXU*l2g_KJNORP?dc+Tjw1Dr`W%-OZZJA63&FP3vN^O)o9@(+AlpM3B9Rn@ z{s0lW{ub&3`wQtmG@pkFk_&e~zZi%8J9<~%ToL%q_2Cxf9%#|c@2&)ZDIGpw`IQeK zA|bWce=Lws7vjQktjEN`@dLW8)zQoz=lg56AA_kOz;>ddm`@lk9lxGkY-vJ>xWhGK=&EI7$}l)Q9@GUS%lN zm8-qC;(z~RC^S^PoZMhjx;K{UjRFvOC>qm}^0@_y0a4(n54^MU5kDz^Ab;_b2qiD% z9v=i9i51;m#1df#$n*UD`hNLzqQ<}q#K*U~dt4pPg-wMem9{&QaryGzoW6*>;#1gh zdOPap+UR+;TH=|0H{7IixEyeO8pFe~xw-Rx+98}`pLf+wPiQ(H?WvLJ&9JkGbz!Fc z<34rj0w>buwPFrS+X#jyXM~S8to7bK?mNICx65uGB1tG-5DJpCNbbR7)_QhQbm$8S z*-7RhuC+Uj+!Y7IqPg@qN?%-AQVpK(op>~8WnKh-Vgi=HP}MwUmt2o}3$-qs&dmg2 z6>$qSPtM2<-1C*e#Ri%u@}eJs?+%UA7y5(`Bng<3d5M3`&I&Wx-oDCq_a>lT6`iV+ zzq`RE432l$T^+kQD?F}aP)(B@B!n4QouIH=EM)|&-4T5jW9A=DyD*h`s^*PYtuDm>QwWJT_8IGJ+b_^@zT5fB67alSX{bJY@ndp%nScj2w$;LDO{`%LENvTkc6cO{?&o2Eo)u!8(6X>vJHDHV@ zquHrn-`?sE{8}!vpS5+nK5ipub{)2?w49W@x_{Pqx9XRXyXS<8|DmHaohm^icPR+0 z>Q69eFKMacgn%;U1=NEeDEsWGJ8iXNB+Ud%jmn(u@p@I5c&SEUF5TeC15^%ySKQB; zeWf6ieyhbu_6Dx4t!?}6L07~C!507^FzWFq2f1@CSYY zfOPvs^t?ThD*3`Z4zGBbFr$^izI;mcHpC&AL}BbC6r7PLsz@dT%j4Tk<%{MFk0^`9 zf|Q|Z-u66-65Kr!uZ_MwE8_B0Dd{mh!z>B~@ zM^BrRWz(oO^#O=Jcf@?9NfHhvfuS!j?Oanxq zdd+{bTssAVib^Yi!-dA3DIK=xzv8P(SCNt#U{cF%K?nrMGY6vrPcWX?%qqlj`KrQx z^l?#d-+hVG7Q&x}x@welFfNBQtTPB`=Qo7UY-JV zLL92Vm^sA;Dk-sNn&Z~G9yPJ&%c~zWXjV41))QqlGkYe)!=b-o0;C#$4Y60p1bmvP zN_~H1I(p{Qg5lh+t7=tSGpX6&o_?1%Nmfv`Fe+wE<7xhGaNz1Rk}_R(!Tv z5TR&ZVc#`>(fvaSdq9K7vSOwf->w!t?weBUOJcm9aL`-nE&an`4k!6}x|Mq5PCdEW z5qe})$GO{s@LQb<;?kf9pEel4Z$(n$y!)Fi0$hzyP5k8jt}oj2ihXYVpf^u()-%gK zjy(R0c@if?R76DY-nY;D>v)!g&s>#hnqyI7u13{+%(GA$wc~TV=;=tVeWmN3`G0Zr zWyw5%yY0QreYUppLG{L@Zmkh+5eo??Vcn@&E_0lIRJBBH8SIsJXjEvddZe2KcQ4 z4huNhNP!?ozFr7ouHEtiCXx|in>G9kW9NOASUoa9B(v~q^J}Az%*lNHl1G5Dnf`*q9I{&BY*+IAEUVmUdz{jl zjfUL?U$Z~J!rp_baTU42k9&|v;wR~r{o^0bSFbk_(P6W{LLoCI^PBtQKb68j+c<`F=`N(+UtAUa z9~~P|t?JGovBh+fE7b zNLGq}D!D%;CkxW`tMYrJnTVQV5N1IfgkJkL+b=c;dT>`FIkdU$Eyt%UlHW46Y8F3dhyU4ZDM&@CFYU!9h0?{m}NVkV2^UE9_o$2BqWD`I(DVC=|*G_ng-_Ov z$X&7a^Ln`dU|hP-r1f*#!r0M#-k)T?`bD4$rcJI(Y=IJ@L`-0=E4L`LiDK6*PX zRRqWfG3fL58-l+6Xja$MEIyj?i_@LZcJm)qVl^>HXY!bj4qiA=Yec2!Li)WbEzPHn z?fysc)?Wcrjwdc=80skprS-8PNdmX@e-8C8Za_8z3+L*7Q0{b6vy3KMQEl;3|3BJj zK^WJo10#|elSQG)-8CS~tO!4ImFFAyQBfA|?!JRX1I~W+GBw8BuO+0W69oZg3Cso^ zfQ?``&(Y{|f*HC9LS#Xp;A{V~OpSv%@jn!l|CI87p2jgyT6w|NKfNgNi0RH%X}}F9 z0ih$gr8z zVdDyvg@Ntz@o^g*kRKCr0U6<`UkG6AgxV8~QF^v{)5_Pf+HMR|LP8P%t3j*I8S$@N zYUd7=CX!qaw$c6Ubp$Ezm)p?M`61A=qkP<(6e(ZVFjxG%-BoV9b>iWavAGX3~i5;5uKFz6>I)9LJaTkD6xAk9}6{S8r4CAZ&e59J^z zAFp3n?mHUS1`?U6Md&|!D)H1HX@-3)*wQb{u{Nc9D;vb;$%jNV@#*&Q2QE(nc=%T=X-lhjvz(t9f7ppG%qG4}na|2+*we)_sglNlEs4AG{ri5avfd}=cU9qo z=k9Hl_-wDo{=KF;;f++)g_$QfJilQuUMNj$owRw=k@COeaBuXj-R%Bf{CaO07y)(Y z+T!24x$%Vmz=$=(oX!Z+g)FyVa80XR?wkLiKaMFZg~zF)9SMidD?=nCO?|u@Ucl^sm0n)ep;1&G6c_&VAaT}!Q|E%%q3h`?n` z?9h#(AA4x4woVX=gQj6(((eWvPU8>(PwunIZ!lT8r419R;Agn*L3|IGP5wgqG5D4@A% zj#AK@9FA$6Y!^q`Kdh|6<40>Z>(C#S8G;7IE@3*2`E{mWc?*uLjY~<6)VDo;K{vz5 zYJw!>7-2ZL+PpqYxM%w`JEeX{e{rcUj5h9HmQ<)Ae@z)h5?eH$?5=Efl01!#t{JnH zvcNMv{z6&|F&#%W=OFSwzrl@)iW*`C8$xKxZGhpdO16c#1Of3d%hg}pVx~h6jr1He zWi!f}!G9(-LRX(dT+mt}Yt%lfY| zHbsWql_1|p4%BTM;^P4bwl6>&%cO|}>|gRh*15y=m5+e?EwTqO2Q1_aPxLR~L_9vY z?gU9PJ_W`6RqFwgW}BGY1=sYB+)@4SPanL!fEhTw(zK87UEcX3Io0Cz%d6iXG;ivL z<8|lPc!y!9ZT|e7p~n_l79{X*B20Bnkw-H$!mruk8QYMWr;7MhS!)tJM;@7)-01g@ z(syF-dO!9}4YJ18@At~|A1o#@V~mDcPZ$zPEuQ$1A0RLPsTL3L$PFhmY0U0Idm{dY zD)h~c|7AN)TB42mB;awg98v3VmSaqueTMMSj&JAeJKBH^C#^+@+d{n!mcYZYfQs7g z2a{(N!&Vtj52{@3Hw#j?lco)xpY--$jj;$5PW>22I$YkT-r6`;u$Y#H6Zp547#O9? z&;FRHc&g2>97Lu2-vh?C?>8_?8M1v09EHyF)5>R+ie-ua$II(jhu`Bq5ZJ6%w9~6% z&PR(Rzx8{Q?%u$m)@b;9<>OPKP6*K_E4}`l3!qjj+vWAzMysI-|D)>{$Cxhb=li@0 zCUmPbqDpjMJP(MQZaqRqBFJpcM=16e>)!kgC+pc$Es6T5sj|@I&`&@E^gOiR>M%jM ziPw~Zghr>BZCij4%>`@>7}R0wPB;BZO3>U2`JL~#F)WuGgPM4p%5VcYpJDnRGIv-N zIS)bqCiBw`YKn7p8shC5(3rs#%K3bSg5S*4_w=z?C;M=y;aW`6I7xx;TXtcrlk1WUtim6mI-Po3RllUL z>&G62s!TX=NY!r@1hO5TVlo8$N?C(2i0yIYbOp)|<|EzM4ERKu4QEyQG}KsSTGj;@ zKG;`m^m;MDU!KU`PF6EfSxA_qik?5YAd^hS3lE;HAo@`j9mWvsEm_QwCK}Z$m%VRO z8-06mgPS7I@bw^)NX#NN8rm=3FFsHmJVz^iJ=n?j{y4O+TUP(f~6Fy(Qe zO$oFbKmF*9!Wdqn72@B%2zlDYK-T)Ww4b#by)qa*JE;C#sZkLHN{2$l;3{}V=!%6tb$|X~Nwfz^TBakvD zEY|CtcOnK`L@J`$l6_-pxk0GHSZ!fLe2|gU;XXXY4elSD{~o&Wo}=_E8LCBzEtYBh zW^a@Jw@*V54dJ&h7BRpKBt3U24fu{U9@-Y`HYcmqS@XU(lH8h#1u_aZ0XNJaLz=IO z8-zost4eh@tt_Dht7CCVv|sb8&aT`4E6}sN>GDtJSpfXZHGt5b0KbAK2f#~P1cTv~ z9%L*yO2FF8gD?an^9=a>PT|4#7rYY%{G2S9*54_QV=nLu!C?rlw$PwZ&X0AzJts)zg#^>crLN7M86hAd3U(oF ze9g9{G8{_o`v@Mg!C!1xKFxPakmpQLo|=+|crgxOd|(4?65PPA1OukrF*QnSNp?MD z9bzMmmV7ERCD5Md{l(jc-_L&ye3D>LMHT%{!duB?tktyYUoj8hF{C^_hP<-d)H@nV zDPQ9j$H4SX;6+VVDZsfPaSTYay1-`ek*%MAEX7pRS_yLlz;EmunI&fb+c@|w)UJ};qgaif- zpGDZ4D6V$T4q8I<+r+m}$IbiKoW&sXb?`j*1m`R~X& z6w~gMd?1J*v5J6%h4)3xf{QFRC={72)~osIN{qyQXU(i{r}+AN>KnXtf^hw#Fl>3= zMJTD8-6|~&QukRJ7k~SNB;MunTU}XL-}(kIg1kadq$H&oi0yx<6SpvsO!dq=Q15pN z#yAqEOy=a>&|Qfxd3`1MW_+Vjt`UXlR)jqiN7(sSU)eNSE)1cTkP;l-o!D$imU{Xu}gxLK!<`L>0@CgA;f4*vg_wHoA z?&l|~uXUzOjgYe`$m2xb_;Zec?mdXWu^3GJ_=a9JKM+{KrYmG4iN|nPjaSzkEg9@{ zUgl4YWlPc9?*|eM5erSb_{9W}KGfsR)lXQ^>LZY z^Rc>Tm<9$98g7u?s2aG0C?#nZ+f5$S2Hu+@1xC3%t1~_x$I@^(SlE$V2z!kU<~#A9 zFA*`pqTRtZ+`9C?~|mf*IAl3yLy@rU5DgtIg_bOb?ruz4J5>1t-K z6qtYH8o>X+?@A)*)?ejxK2Dqe)s2*A<#*74TIT>1FnBD>X&z@*$9tvx+q2oPz=S1b z3zXMDyi@w)0kv?k@{Q9GpKZ|7_6d8V%L8s-3{{jel2z-6|37pAqRdW-U&nh#g{9{N z?^haD1qcl>{-a@BYJc*7@a~eD`H$F3?fQy%y*K@7X`IW}5V+~GkCrRDf;mTDuq#kL zJR{VeENU-doFBQ;Q8lMH*f{)A2!e}}IO>x7zm>vhRGEbU@+u)vzikHqcaL5+_R^zPn|B}< zQ~iC8@D$@t6qlk4ujdI;OK9Ry{nGL>+P7*eZ*A9zwcEjlq2pXyBLDjj1d2w?B25}N zQA|>=0dJLO0>Lksco(R&e>u(+ob%$4UG0#c>nbYH}=m4e0uVFp{GJYpYNcvZp$+m;I^6UAT)(8VPMNkG#|)dPcqgJP9GoF)Uwc=X zop03HEW4vpra|ivIGbh<_YNpXW7lYdDb8fWD}(_zhyM?IYg$?wd8PR@5Ux~p{f_xt z>b&H(zuW-NlFBDGq*WKmswb3%fG7xL++Xv}39#ukumQLL_#r*N75|og?|99qS&2`! z)Z&z50?NSMt#8)5*EZ5OX0M}bZFEMBNG(wvg1|Upq1F-QQ}MSRpe8Fj{4-hWa`S#K z@p%+C8jRDM$k(uc9>(~GW{-2}VNleV@!h9nKiM;5#DPawoNn1y`Qd$MW^qXK*Zw{zr*Cmp7Ke@CW0%6B3PI~a||sRS&^|J#nXCtaJ3VJDQXW_pNi z>|#EW|0iGkOQWA2KJxvcXCbSAfB>WR2Q-IWaR={_a4<_=Te}$t;T|N_M>9bMswJy{ zh#($EPX*j$d?rj3C`C@!Yn@LpFAo|2{w~ROyubRm64J$dV6g7iB@PCWRf|nsY#%ih(HBH&Qii35Bj*fHm8Mj6ct6y#y;pHT(isykenA%rmsQ{i6m>|J&g|xStF2zb&SYjHtOpSyh`w5igIakT==tg5c)vuI{%zQQSf^@c z(650jnv6i+i2<yW{!H^JwX1MK;pceanQU-|# zJZyYgy1eMFH~N!neT)v2U{-K{YG5^ytMR%eEXD63Ko+TcIEo1?@k4$%pfyrj6&E5P z96MDhvLGi*xrY58%eE7*)^!G#R$+$xKk>od$JB?5o~_)~8b#Ojs!l$IQ2~Gukna8J zYp$-~`OX6h7_nP}8gW0!=F<867^B>9&>Q5jWNK99)2fE6G(J;vm)dSBw$ysSQ0ujN z1`{aZ+~HBajIMQ!7D%Z!y4;LGnD@tN+~&_1%r$CIfpdg!m%fOS;k&o(l<0tR)!U0O z)zZ;n@F4IO76W5-$TL9f3;A1KV(@QZcoTZ#^b&Aa)VJ!E6^N5LU63j06k%axNd>|! zNx1b6rUs8sPDZmxtIZc^cV|G4%psk`_l8O`e&U^+H!FJ(7L_i^U#TRd*>c0qg9fzY z69ZacC+O$zP>^w#z@$(7UW-J8K90ppa!+byCg^OY>4?^Hp)sK0ii|=c`U`C&3q0X; ziNDY*+ctezF)le!h1=j$#`21{QChAT^DJ-}d`Tg!_(5@4Nx<&>bI&W$)}$(98KHJ# zs+>{sN_s-EO_3DWUmSQwGC=$mx&2U7}`GXp|>e3IUksE-Jiv>t6vrM7-3p z3~BTPfkWnVN-Bi5)2|V#*qny*M=WsiZMg=XBl< z$$&4oDxgLCxS65nUR%}%>{6(t;y?FAQynj+y}-me54DgSmGt__)2z%s#Ss@BKNnD!T!^H{owaKTsbU2 zQlW_;E;?~i2^lg0sQKdn6^HZp6qummbx8cs8^u6lyFTEjaj~1BFiC%Wae<@k zb&_RU0#~=}#FvgPi7@EC;lHQn;|vfX%rc>~^?&{{1xECVdkOSk(X)fMBKVJY%Mu(x z@A6+a3Bg~X^dAcC|V_l*^(2Q0}a}R^8$vA{b_$tFv}- z(By;F)H-v(>Av>PgY*_|&n-GjVzd^*?o&7(0SZLjgKDO0lU_76R;S|hY9K|#Ks#J^ zavl6>b*uZ7>b^aa%??KSJ%sGD2ad^LYvXx*z*|No4E+2_Q~DlU12#@C(j8>tX!~Sv zdKXC$T}OH|bVscR6tb+n(X~N<_Of0WxUnXWRf^b&!*gUB^06CtM@Pc;?D$=((&>mo zn{b#*KzwzlGVcb4Os#$;d;x(Qaab-K-z3ZWW|69k>A%h<8il5=x*Tyf!G->LJ)h5R zh)ASDCawx?P`gC)MiZIh**NY_dG==eBiRvD)2n@eS^mW zZSxUxK4#s#TA={{6N*MA@Yb`LH}`AP``xL$XBkDkObFn(xLxD_X>X6DbXAE%Mf&mU zruTDp;%ft=8Z@0rh8PA%T4A5_M)5qKqehjZ_lk6oT9FdI{R_!y#$KiU6$o=|wHec< zflHBbLWX^t1%}WDhT?u?ernq^Y&LBeZD0S#)O${S1a}{m%oU29pXC0yNThOP0^NNE zJ=PoTB3gC!1ggwBUve0K+vpDKv~d>kREo)4m!3+0&*-|BG9`HhWK8E-h_pKS?_OW+ zMU=|@QQ9i4wP0?&Q~Omygd6#3GNQzIKh{Bz>WF6%z1*`EuamaB@9r z;C720j3pMa+I6-C^Aj^$3ff&|=6rKX<&R3X89hCx{8Spwpj_mA`0Wa_Y81f_wdiZX zkhC*WN;ik)=7=BCy56%G99Ay1`E%j~g}2SS4G|dD^#5BwoldoL_#w7Dgr5_zXZ%>K&s8r$l}uoO_w@A4{_&g# zWC?cr*488c)g!-t-I_O^S@RWFFqiv$KlKD2!k>X$N8F;BhU1DEya1eV~W`+B1|SK!8U2%a@m~Qk8O+#Dw4w4=?_9F)`2E zVFE;_&l+*hc&45pgPs!ry#X#y>#_h0sNMmoI^a)>0B{iF4|sLmvCP7~FuaWR;_<~X z+OZg5jNjj948vc;w+=6py?F7+c5=T$h7RNT1oQIcmmZYkD>3gcLFky6upt{~?ImDQ zr#<3@l|mj~{UgJeonoTX-94$aj(@gqthoAt>TNyf-FWd`2CK83Ryj=no zexfg5y;>Xp%W5N?+>VolYv);b zHm5rrmtf~P!!;D!INpIQK+5JJ+9X;HP{6>+^@~D_i>1e+)AE^}SJ`i|Cz742VfNxz!jU6heR9h`dKw$V|p$~XTlOJlQ5g9yt<>o)4K)_Z) z>wvH-+a_#sRH)J>ETI$QaYv%qBe&8-UCsR+SfcBO1h+r9UPV$mF-za$0nYDoL&54~ zu1F>^RKwvotXbRjFVB~t?`!n9KmtcuI|v{--rSG^mxLwyk5B8Pl*9`)ct^`kc-59t;TbG9pnelpp$U-TBt?bjvr$JDfxd(5{ZhbC+$P zbISHNy9_TQGqTcOzJ9${a{?Xl?7g}+ntjjs=t^O!^HezVTuMJnx;KJz zI6T`dqeh?0WgC6ZEl43-D^?BhZ8M0L(TDky!lTm?zqTMK4g~hn{=$4d1^KIp0AVJ7 zh31V$-90|Lrp)k$&xauKFM+C?OS{z6L~1Qzno`}*dD5)n${_6&ef50M4q-B3Q#4Ab z_Rasnste{TAKN7EwjIe^_zz~w?L-9YTV4hCGT|w20 zM#}5o2+6q8Pvvm0P6K+^g$A01k|fxO!xsnqB?&CEnu!~ONj>;osO)qOoFg;TlyTg( zcGU6Zi2*9VyO~>x=}9xTKPrBxn99KJ#O7M~laM))t`a0XXfOQ+2mNZN=e7nOhsSpe zFQ0)amOHC+9FxvHuIH9r^p@B75gZe&r=L`Qb?FNN2gpboPmlG!KV&=XKfcu4o9JDf z2!hQPbjcyRyml^i&bF!9qGcWC$L?0hh=^^f?zd@1M@yWx+vwF+i~a# z4=jY~D&R3qP+XEpF)HQW+fhk}tn)H;$L9EeabYR<1`Iypx|G22Tzu4?^YJOpyG;E!`dqbaBo9({Byifd zUlwVWx$ki#^Ev#pSO9E$=rg*`t@li z2c5qE$0tUiKFls>sM{w)&eTrZ%e_z5v|tg9-yQ-!0m1!c3z>Gin9OhADH5LY_C}Kza&Fx~plg0}`m8Qr`Pc!{nKeII zv{$IjAKX8V596QQS=D-8k?zeA5^|mZf$&O83OqXDZ6CxOaNE+gY~YxJHGf@_7*wfY zyBgo9F7;IvHulW%BsZ9?CKMd;3qN89{_|Ktt6F zWTTC;o6Fx7?fTqu#vxtA!IU;H-r%bXP;o9*NG{h%&ZR9`bZV=2?&DO*m_jBe@_v6E z(;;@CzGR1USz>3{q7em*%+aQ+Pt2=mN6TV+uTf6!N>>ryygBP7Pc>_ZeLwRylhy8& zELXdlw*=T8%A+@vonUbsEOzY8P}DT5*%%yVMod0i6s9e&9F;<@WRq)c5i@Dzgf8Z) zoF@hK&-Ql|8MRAcWcDV6|Hc)B`1q7QEIWWb4`w7&lV*yogY~-p>T{N40+!WM zIhD(NwMiTIBladhg`BQ>j$LVroch{lE%#eU8a@mQqs#1l8O;6JLyBU)Hq=?!dV=z0 zUo#NO^Xke~Wd96Q;IMqVuYd9n|62Q?TrH-I5H_&*pl?lAa(A}i+B?nfHpd<$L}ALeTo@(F~kNcQN+-E*L8JocNR^E7O0{-wI<*cI7{O%PnP!z za1-#y8d%Kk;b17cLmh4Q_vPILp2^vF?zU|}g%Pk6duSYl!60MH2l z-cr8#xufqcr{$0ylw7h$JlVNqxv?v96{?ni%@B=7K^L7j90H@O|9_J8z=(U+Q@qee zgZG&4qoXPg<|;bNLk7bpfMbP`Ii!nR5{;6??N$dWg>am1CY(E>gyt zM{$9)Yr*+W7nZjt#iuFo}Qzt_y4WnlT{LB~9{~?jvZ%W@`C9r7e`dyHIKu>YKWbJ+19#gru zfcTpewNh)>a})vhRG)l}%WA1vczy`Z`fdnL#ThX6QOap8_4R6rT=oXKLh!z(eSWcZ zvg}$-X_mU+J>78DTN&hrfT_q5nQxi)g-WQ%);r#mz-1*fN-Pc+iGGT6cQL!#(s%6+ zVV`oiMqhoKKV+7=@f+dO1#aqI-DT}QGQDkTQqHldhm%QpM4g27aLDX-VofDZv+0<% zk=(QH4C@TeGtl9Pq=PS?vJh z3%xNBLkWP;BlQzAOqOKKYw%PFEs7SCamVJ`8T*0l6>##7k2aZRnjMHh&{wys z_G*Sm8Dv^k?NztDn{oQjHj#qrquN0YrED72ABb_^@=#nt=_=L#9xgFg83=DVVE$uq zW$a)N2n!Bw2e7C0<~{-N6+$%{PVSnu+dAgYVUdV_hlqqn@7w<0Syrxkrl_<4#fVh! zP>je>LzvYe7bi^I9fO(JUzV130W%KH2rK}nNo_^E1*75&8^9A8rtHyO@D+GjqF(LH z?Cs-oEHYj_RQkb00lGOkeu(vHvgQSoxy^zV#02n9317WQXyqAdfZSI00U2l0XGWZV z_=)LXH3;eKxBV7QvZJqBeqWWU^2)_eK1noAy>rjEtmS>9QnOWekUFn&GnXN6r@~`7 zxi+-TmH>?pN%E7g)813i;4M}N(42qyk3lKnaDwaH|A>dRby*DYVq3$_4#%5i2b@zG zfVdEdfac`C@h<_7=yG2uO`wH4Yuib6>6CC!U z6wikf_#=c1D%Lijm}YeXVQXYJL~q}|P2K_>C2Q?)jd&6>37`XIL6)u#7rMq?y?JJc zX>q!$d?kD?W&pI;3OvC&B{0p*(_Mi1C?LI=#L{Ym=+6zKg=QP9c@B&2^S+pq+Y7TT z2P3&Z-HCAwqm5OzVuap-a*du(q!mAg+q%PuY(Th=Z{4gc6r`Wb)OZS99xO+In7Gxu zwq?ERV?EN!_{jc#)soU5i_|JJpPK5WZ1-U@mQg7E(FApMVMHG}hBn*=36(*^0-USe zLUCmOk&GcCN_c^3w>}`Z7aog6kP#W6KaZZA*+8Y;@rk6xkjqhx5%+oHyMKT@%1d;r zKQlHr_NLMl3#Rb7ejqbz*Zyn)dm(5cSZxP#IiHPC^+7`JfOaX8Kv;5U4Ke&#EJa_L z;p0n>9#kPVe;y|_CY5Wo!W`9n@)^#<1dUf{0qsGAaYzB>l3L2gDf)HV*@b|rRH4}07%qO)4Zto`kP%@-55}P2EOgXMop)c!W zRDNT$V4IIktF-i6OL{?h%Ss0^&9+|2wc}H9w-I9!I+y|2E#K|AAh25)Eiq!z;`LmD zFoWtA;JxkL+B}gTT^)BIZ}QZ;d`r~pFnwAMdKw5t=x>m4JC9;$GQ>~T_IV9+GIJLD zE&e=nRzE%5W?1xu=c-gQrt(A9cxxP>)H++Ja>bUIdo@`ZISfH1(D+*4)T3y*Km2-^ z$#uV2Jz#Yj-STL|%Annujt3gF+V$_AUWIf0S-FCX8uc=Ei=pJ$y7*iN%*xv4WNAR{ z=-wQ~BF1lM?YTY}LuAk`(>-g47fItJYkxQa?wx~o*RBu1)21%Kq)0kvVD+ zfk&~<_kyoC*Vkz#G2OdoD}7RY-3*S~=@aQSZ{L2pnOSV0L`mQxq>08jdt<3BmFl!N z>gjsNBZxrJh5B2PWawD!N*t`=xx`6*(wu zQ?Y7_n4#f24-yVyJEQw-T95n>Ci1mvY!kVPM1ynHpp$fx{rLQr!nGT87pz)%IzX=d z_cW20dLAH+Iq>lTex*$EJB+{HNWt_cPJ3fAz&QI@9zGHcQWz`3rQ&IyoyF)S_d-N3 zjzuzEp2h5-N7zG)@Dkj=BO@h!W@!UpL z`MBuwSMJ-fj%uzhX`#kB{QDo`dwOr6@JlycA7B5$x8h7nSu-a#BuI*ns6?7`RY59)V+08AN_?#0eWg2S5(u@1jcp#{AIbc-$XQK`7Rc=4iFH4O#{kb!{ADQJ_@KYB|j;_`RAVmxs8 z7GX#{FO>;Wh@OIAnpY;eJDc&bXjT&gP!1lN6EOhtk%FI0Bt zm3jbVSi61zasUXAtWJl^*PBTttFx1QE`yYOoI)q9TGzd!ScTB??~(;WpU7EzsTc!k z7=hyg(9p@pCD=;er+6to#Zb$ACkX%=(f|q{uBg}o?_3&7dIy-@?qK~RyRs~TZikDi z86;~;Y}vYAofiywPYGPY>(x|Nnq2f*6BO1u(q+PNiM~IBOt-#jggaFwkJ%hV=jXJA z(THG(6ryfSHKj_D=---Z5&b`+&N3{@sO{Pqh%|zfv@}C^BOst4AT>0|AYB5|q0-Vw zN-EtjbR#)*cXxL;-{yIr?|qMhAN~|(?t5Q*U2B~S_N^9wD;9wA8GaaiHvvN1xj_s3 z5CGQf86_mxZAQ^(-o<_7CZkH|MamH{zn^7jc!Ngn`e!iKDwzWp&%TA(&W4D-;K2fO zROT{K=Kw;aHEVP*ZYyox)+k#sKpg($0!-npvEOot##<8Rs$1|VhEY$$BmF&zHsAdn z^%w;Kai8!&A?-HN3jP|=zp&g(jkHr5$sVZV+|lP}^_!mE5_w_LoXJYG+T}=FNy12m zbRS@jd9%rF0b(VtURAuBPJhnsvS+nqpn4LEPzYUr;twWT#ih=)e#lBzFj-K>_E6G)aWVlb$<>MG`vX#zGQR;N@WT; zFz2+7=Be8Pa;M3~o`@SPQ7Fo@gJbxNc~2{lSnMMz9tf{TX7v>}cSXIJkdUDjZz}8~ zOW}6d8@NOrv%_K;Q9wR&zF455uK}#0dPx8X_!O+--|kTGkIv@n+80WC^uGcwiC>Sx zeVUyDD*4?uc1D`U&vX)rpMHoeX$#UhiFY;Q@FSS^lwx3GoUS!3zYUDz@~79T9&maG zQn)_HQGDtTP`qZ%(nCZX$HUHSTj$LuN@Eu7U|?>Q?a^)J#XhOmqO*#*q*~NHC>J-E zGnR1AFI%0T-QfE4lw8b(E?#RdagyDv2RmCir!2KDxW)b3{mYhX=KuI?9zgmZ0DAGF zMqdm%@k7MTb~(Q4TsRgaxKa&+Vn7}aJb6T7R!nsFJWq@uQR@yn0o^loo=FZn+j7%L zI8S3SNqGNQj9VAmM{(z==6H$JI>A;0SHTeK_s7ux32^0n-5v79Kii0?+0}jS;e2|9 z`LF{u$Wiv&_@$21Um*+&b$rXM?>QOaaC0;YQ7zOWq4vDPthAox{U1UNJD&^|>6;c= zwS()h7T^VACP1g_PyU%odunSAR#WUGfaWR@o9sPUw>zHtP~o?NSg~#S@^#+)5ttt= zJ`@3TA}Jbgr^HxE9W!AWU`bEGPbqg-q6w0lbNG5gd>Y|PIvj<#G^>*uqNPY zPBa*2)fW%dsitY{mK5`RJI=d%aMFnfb*{nK~^KY~mocY72`*P;PLx~Zt$qnCtMfOb?xrjmOq{brf3$$-2HNq=weo0Jy z-SKb+Oa_K8v?xl*SYT!4SJ**dA-|?I8YQSEOCulu)Ml>E!S9yCif*m z8}r*FiDGUs_VozDGjCIG(+FlaxR)GQXKPDxnT&v(VXdt77lhpEsghD!LNr#i!7 zrepc2X!y(v@9iH<`OWNoNunWoyF%bSDO0zeP;TYjMD0gyl&eZ!0q}Rt$HFdGq_+*% z#3Z~{>!9Mw+y&!y&mxKeiUVv6?9(ux3m>x?b$4cHr8)KkJMMT0VeT8I#$OoLwgfGg z*r~%kd8fj{>t}G=6CA@kMO-VRrhd{)vZt-ng5|eb-p$_bk`}nyTRyrf+(J}Qqa%mG zh+-HEG%F^{lJ80<2qLF_lP3P+s?(cFjhP}*oFE(D10(52pwI^KGsvWCwyd1X6PA=#*q>8*dud7o?o4x zWt<^Pc=*Sse5~eQCv)hu>Yuspx1yKWZ6V)Y$G|g%NX!qn9{oC6UMm>wFUL6g26JFg z_591(R?*TX`lyz;@0-Lb#}oI8FT0L<-Hb&uu8W4fN2{i4m7+ioyp!-P-kP@}x?bn@ zN&k?cY$bgyc;0#*d%>$1Y_mFwW^+Z5jjqRuZNyB#{+O=|_%H^Sv*JYUsRCmQ`nM+{ zxZlHWP7yX5jpn>vF{}j36OR~340Fh0fA-TZKl#6Y7QOZ;__8KKys3#n`)^%(71*mZ zt#BA>8UP`pX&w?orFrx@esTj;OxofBzhw(Rlxi#{)jIf&hENNLxAOHR3V1suuG7eb zCgxVGKF9bv>*Za$if(`1L8SbM$~X!9;Kx5;S~e|Z6V&VQ;G=?Aq7IqvX-YBO(nlJPY2(Ju1hsE50_u;d0J ztm^CCV**eC(`zwalc@NMoAKG*g;?3%3@XqEo@u-TJsj8%ir!l9wC&Z76tHqW1@GRr zG=$>e$s~HE{r;6hoBjYs`{A`(fzyQ`-hnecu55WP4ZnbHlnOQ{$=f3H%>J3Fk{+Y2+#iI6sUb@LteYdi zzRPg8D>}cpZ?={S6lkQ{_NNF0J(@o;_61kCPwn%^5h5lENX;I0J6{UU(xRvDA!=(l z%ZF()rju11z@XQMRy6(Rk?Z;PdVD4d!a%E>k!8MI_UfkUyIv_6->G7RoUFkn`85+$ z7cs9<*UOXLYnEU1iiHfl6WTWX17kT|y?1q~ZrAa|EL-n9t81OMgTMbnI=QO3Usv66 z6m~v%Dqwf8Q<)$BXMvJ(jJMw)aPXuQ&rm*kCDc z(R3`m@O8OO+v>sAW%c^OUZdTEJplZ?6Vmeor%;c*eA+{lB#7rvtTt^BW1sXJ(bB9AX zC<*rb$k+wZOiO`Y{(P`7GZ+-PQu%|fL=C28zg6usGu$=F2)>-0@XA+;FWXt&Xd{%7 zu~F=x@j;AVHh#o;n5#+EVNxSDjg_wkN?5is7Hq-#B47Iy`#;##)Lb&~p>0gU@yJD- zJJ4+IjZD67&Pi5~N#wb-ZuK$ClEDQWmS{rB1e(zxI%UrZShW2X{!m^%E4Eye=?rdu zSB^1UI1z#pSH9(sMHUD*M8KPUL}TQ{YwZ0=kg?u0Gf_lb9O4?4{sq*|>X}Lm;voxz z@W_9Zq4xemR+?XbUy}#~jZoSI;S->T1M}WPei^WL+Z9{By`6LSJviA^SvLYSBuO|- zs=QYy2Gjf_r{duDEThRzb~Mbt)`rHF(V73Cwd$Ykx6`EQ?d+Am0&vUIVi?N2J+)en zBs;b^GR~>;#dquel96NH|8;po>C+N)jsWsQ061bFL$ z4FGt%l-KI*E_BM@6@AXUa*XIOgp(C#ydg3k{nCD7olP%QGp~@}Wh-%ozoDUlL6sf5 zfnow3b9?kt%7{_2yuCfo|9Q)tQB~SQpWsu3P&e!61VyAQvqfnBW*zMP}h%U%llE1V_p z-|l6rj{b}_>MsIgOy+bqQ=4@{5mFyV`H${9VI;4qZO~b6Y#~ zJR`J!9F?Lrlx+s$%)cO7qudB-Z6Dz9Hg+qu$f?f6CryBf$Y_C%CZ(+fxiB_)r}3j{ z2DS!L;mb??!g|>0KPPOTmqK;oIg-yA=g)i^w!w{#-(!d0TDP&U#`q#`a09Fo^ZA2i z+DBwnb58Htt^I7yjeS8FKs*Hp=J9jP(!E)nW_Dg$9<1#ETmS97O>LS}5i1CyzF ze~xJd)xvfi)hBIwtIzgd8Qu#AHWUYE9t{O9)4$hz^8xMC^&TkAEzOAtaPY^KI&&xw zc{U@Z(ry}-8uV(p-&`myn0o~@0%6UlI|hiYQ>@><-rrp}<=z$FgKN?l94z>cRytp1 z$!uVshkdx2-pF1awWvrFFZnz|cLEAf0NOP8KlORR4;*Ol&D^>)Im8{aU9;32Y;aS5 zAW(V@#3I5{V0X={`NhG+LyHT^rx)|#4dC~P5RZAGWhl6c)tqRW5pN)kj4dHxTt_Hd z_XpLid3KpmH4QEIE2ph&u@1uw4~^);{7{%Ou?AZA8?OLPz+-01chRHL$} zQF`d}d#UBSS|jxBDxJ1K2IaEs{F_q=Yp24`MiCQ2X^QuE7f##I^~2-!eh#;G{S7`- zN_v~ykEO%eF3z6NzX0?z)^+VN(;v?;U%6v_EN)%{{bBjb zfC$?(uHN5|Pd7iWZrNbzH7ci5-W5h6*~A`P92w?igG9lK-rJ^#xO55j#fa4Y zi$075F^NCMyHgW)`;C3YW-6I~?8jN($>z2GbnXL4Xm-nqFEdrPz>fid(Ff!DT&gyR zLqI1Vvz;n4Q_GOqP(0hQ98ClI_(FS#cE@j(TBi?dJ-@$z+tT{scX7!t_f1bu_Qz3B z@#sdFDX2l?XER9L7Te5w>eP_#4jiuaYBz01(kuGO;zLq(Yk2{yF*H+qVW!pzTi<;U z1Dm2G$;5oTFh`U8VSrgQ8)McD90>c{XP7q{*j}JbVhXZgJ^=G|gmFyBZ0PG?KHuC1 z_YNRw?QYu@B?!{ac;vKRK#vC8eb$gS+2IDU+&XR?v{92=KJ^+S$0dueAct?$;JGEL4I+i&D^JjN{>gix@GqZrRt2(K1HQhcHL~pt`Z`1M+$&g z!wCM?kd2AKzmh2Vy0rv=5|Mkn#N_mFd^yA;rK(?6B{jNj&de#X{!KwS>Wkn)0g7O+ zVEO$&DtRmO!8+7!eqmwKG{lEDi>VoK<6T!@VuZwOBd z1NDlk!~V}t#UF3BMNyq`H5F)ECoIj$?AGh8n`cABEu_@A@T08VH%7Q6_7kf5`FV3cIT>4i#BS~8PFeQOnz|*}yNF8b~ z^GbP-T?lMo@gXsYT(y+zUJn$kRJmA%_|?HIC$^ordP0zl`|K;`a`ci+(6P<8DVE)R z;$!d8Fdk?#aMkU1}vT2p{Zh9yfX^^?{GtzH2H04pqenbsCxi_x;%9H<{w~y z0Ofw;`eq#u?!#?h1F;13-J?r;@ZN6lz$ozp`(r_D3eo18(}cs7P7{ajjYCnq?RlgF z7r7o_MQmGzSeoo&StU>#wx1-k|7J%T?i+E32|HhmkhPvMo&TH!z^C)n=#j@&c_`Gj z48~O|Zz5tsMd-*@$+fj6Q&9j>+eY1Oa7omKYsSMkW6y-4OfmgfxMib40@^oM${^!d;5uFDjwQ@d$!G?Z+J_5^k~7uz=G8r`fxo)D2x#blJ<@jNU=c1_--F%ss!Sl`7_D5EOpCJZDZDQIOo26T+A#ET zZd;=CE!?GYfHYbAnZPpd-)kBN;U!awKvf#wsjSTXry*Aq)+sGHhK0?~wzU*Szx3P7 zZRbW&vWYZs`yAQD`c2i1EOkcw&>+4T5lqiql&2lrvBsT2pc$OkJc`_iv~3 zTX8dFJpTu-QCD%{lL?P-frj@-hQ`y+UyCkrvJQS~AO_yooNsr&gNAgJ+$WJt*Ir(a zIZVHo6(p7;1`WFl&?B6a6NlxJrim^V={)Z;V*cZI@S?2qiIfQ|36+atrG(0 zcNgeH3?|9b!qCPCf-+FFS4xy!4!-}GIGq3XOj8OcnHH?klY|_aZ?Dfa7eUeVzb0># zfht?WUNRxjH20m$q_3n|Hu~3i|BLpI@YmO~M<<(HI*omeAz{kErNUv`OhhVDg3V*D zYA+zRT%;MlwvGkv2%r8rnGU53cwPWEHpUHBMt2kD<=b;6HSfx)=h2bCbp|bE+2jizo4R`Vm-|I zaQB2aLEcfbP_N!ELoT+oxht9_X!qVKa{qtoT`vgHtk zT1jsq+RyDjbu{GDbxglY-wVn4y#KDnU}vscdDx)x^ELj?m)sI-Qla~W*&X{Xg*^c^Ut94@K{fR(t(VE$(fVawaP8Dfi|)klM}%;OC_SYy`{g^8fND z?R9=>RNLW$kIDBCkIwKqR5y;@7L7?YKafs79@yqp>Lvk29tWb%jA*ELWB>9G2^23A z{^m3S)4{Ux8JNV=v@5OoZEuL;`E1%{*Fl>Izx`U`&QXzWE!Ky80h?wYRQTsk7>!)a z2d&4}Tkn~I?dc4Z(x?7@p4*G-&P^KT#JJ9)V}lbXrX|8w9h^cwl7m4Jl0w~}wK6A@ zvFl5SIR!WVV!05*@+!FIK60T#o(s%yj*xs^a-1vw5vg-N@a= z(a<)+cku?nl2$9YUOP6uP^xP!K;~dlI>~+>mSd_}Pp~&v*H)9+M;H>G^^r=P_N6F3 z3FB>o1_h%J`T2(&7N4)_o)kfl`QGQ{?Xihbi}5MlSZa^yx+UFMV;WqmAX}s1 zrBMK(R?n2^MSLr;Qu9u||Y88amI#SumFL!IVy--j@WKlFiol`x2dO=wP ztUO82-;}DU`rz3d95|Vz{f_pm(hZDXd{v26#&qsmx>JDm5SG+&IqE1u4shIit`Lp{ zZ2N*OyoK8StRk+@Gt&^YQr2$JRCcOL!sXJnMoO~#nrr;Ac7 z6RKQt%qV1?*iLzSW}G_ONUjdS!Me1qwE2h&>!G(OSWDOYk8&Ky!GfJyW-|BFXkM+gOH0tR zdG4q_!jWtX^HYTgn3sm0a*DayD*ilEvnKse?}6HNH$& zK60zEPr(+IdbD01Y@u<&LE>cAsvxsb7asVSLSlD5sTtFz%18JVI%6cvPk^7VNbIox zeq^jZs^PtY*xBJNnB{AFXj2khj$t$T#2tw&3DXy$H#)~dncpJ4XBxr}$mcvba?i+vjvaM23bn30i(mr?w_uU|YM70~-J7dN2o`ErVp-4E`~><&O-L;0 z2L~1zvMhzDH$NUDYasULI`fVJOo#IIYtY*uyA)&daP>3b^}>Bh?eY#S z_5fe#PHOMw60GIcQ3YpMz2_!(?b7v-K>v{~kmb%s` zI8Zt`6e|3i8M+EHB$8K^wG5ukSu!YNdHRBI5a#PW;}M8Cc^?6H)AQ?z$f`|&x&)+t zqF1EeVot^zKVLn`yp>@g*d1XiV;bL%Wz$43I*V$(FX(RXO@N02sDgiuTHew+#hrf! zI$^5Aa{I;QxBKA?^7Ju(Lw7dbzA%M^`>bgB2Tuc;g#`hU^X(N+=Ix>Xmf%q8Z8rn(MPS+is$K| zPjzIh9CxIGMd@V5KPi%+5~Hu;Uv)oxn!2*D`aIh0zIxn#KTFP)_0Z)A$JAe4&lKIK@e(epO2sXcS;jpP8m`XLb76`=FeGAD{QA=-4J?*H@Q}3VC+r=I@f|LgX$0 z)Z<{;$@>wKz447NXhnteQ5Px!k_ETrNB}P^XunU58fFtnJATfe!nwY z8|zT$e7FQAh}3HqR_>(%gNiAMIboOZ*S>jpBnEm4DWWZctjg)(doEhc1cYtDqZXhb zOs#+x#6{NNZCCx8(=#KeMS&0unmvM2WVP^~v`qk-eJMbFVw4C&j zi4s8E>tIrtFGyJMg?nH@1QWzUJ7urQ0irQWUTexZghXD4(O)ZAvx&|A)_X3O)Gj<@ z;jj9X<%%Wh;7H78|uZ6LeOZh3IdkW5d}a94uf zusMZ!>*f(+Q{&hY1-D#%j;=`dt__ev8bisJh5MrE!@4N~!1!lv3q9x--xP@`HJ9Ey zhK$o8Zk=xK_$-MmAc+8H1zYk#ts7@*(~cH4y;&lA3$T_ulcgF5=kpuWm280T6U?2X zlGYuozl1MC#sDf;+AKnbcy-fLt z83TB#d;*l!!@hD)zlqK*m9bz($3I}5ZK5BG8LE*l`W#IGDQHw?c8ez>B_%}+GKC1E zFv>wo|3K{Zi&n0<830q`X;BM&C4OjuVE;amB9p_H;}tP_tMd90yFiHd^H_b$)tW!9 z-vhi*QYOv!f5gL))jM0L1IcBVH1b(_YqF`UlqLt{;F2FisCkn=ZY@CYQeY^AicA(8 zr$M>|1xLKp?iKHEB@p#}06WktSYe`D>ak1*U`MEHwDe-M48Nxt)gnI%QqZoV-A2hR zK_qtdLVsDfFMv9gi82>ZFSxBI0_yFtV=n{7vlY<9(MVn2@W}r6yHDP3U1>Oewre_{ zFTjM)_VbdW{+U3Iof zV?#HOBDn}OT1E#H>sX#df-eD0LcHzhLEy0IN#a7G zS4@1vFN6Oa36x@dYU_&b2&eLv*#T{PGW=a+t6h=y{(QEJOBtpJFO8QhS`*YRS@H=4 zR?`*6zGMKM3i2myVDHG&zYh|v`G#U*`adA2^!95#`ODLlHp{?|Su(USkbH|$tdgZ* zxYnbYYxST0?6mCpe8z2lAQ{b6*|mXZ!Hd%8w}1s!H(XFD)yH(cusnh2X%hg@(JUCk z|H=F_>;X{Xtx-Ldt5jce05`SBU7$KSrZENv)S-lF?K6TAv~std-1kC-cqzh8_#mUEvUKD5|VriB?8IP zS8V)UR2hKv!TODHKt~UMIJ5T?nMqIFBOo&1hA}Apl{g@5nczqZXH=1f4q1#9O6Cl$ z78`X#D{y@u8{wkRD#r4G#Sjs6v@q4T%ScezvL0O>^KU;tM)TKMys zwhl>Dwx#=BC3hQ^wa|J6OXn7`@a`a2(SrAm@mm*n zP6QSD^rNGhP8I9r?B86B(C&p!m7J1-mr~k#_{hlfnx`W_hqq0@*3*mVHc}V+#A?L% z!SUYIER|o3MK(nRK6zY~fg)cn0(7Fvb4?l>+T-Vgmr=yYrpE>|%1<8NtKcVqex|_% z{n)s-HIP>B5HueRZlk|s@* zyE#M))ktZWe+9~;s@6eOmg&bvQP8Q*vUvq~aLG7GMwQ4^5Hk9 z%M6wTaiz_CQ($zShCzW=1x}R~#RCw~#oi>%G3g3`k@^n6izrR1o3|s1k$S4aEJQg& z4sc1ZJsUg$;R7H|6;JMOZx*(fw=s^7gGBe2@jNUmxhBfY@T5b?TV&@!fKeqYl$^!F z67=zY1mUR|Nd$;(C{^1Tb((?86=-Z#0uHfiu3!VAldVy7u(&lH_Jd;mhhHPp1d}>k z=+j+4^i|0k9Qx@DX}_V48!(4cz)u`t$hI{df{%??OhTtrrm0%cgbq^ zLm6F1A`g0zvJWn4C&-InTx9Y+Y@P)v@QE1(STZ~sf2m0f6-CEZ2&sV2(tlq14(yFS z7E?|+Y*0e5Gc*j8WN>!r~hkeryD+XC^81e|(O#3%sqR4VoD zPnst2fi@;9eKM=Hr|k$= z*3U+mnp@pz;x#upUc5j}!Zo@Fh0<01cWr&}RzoY-7Kx^>cCqV5)bGz+@9_7lWq$nS z_r?8u{5XS=$Lgif&QX6#%9^HBPHQ^f|4JIi>f;Exl~SIN|U`QJW3MHtXIzKt<|AQgEAY^pEnSZ(gyH-?TO+-8HM zfZ0Y~%Erlw>rr#w3UE>yi(pA>`g$=;;LsPq4wpgeUw-kt=RMhjX$yD?Zh*-M>2zzP zDNQC!WAP_0t%QFl*10Ppgf!=<(ZwW7I+YWvtimuS#DD0Xw;UFh=eFQ{#a6LLyEz5usfurg?f8BpK^CRXbrrz zjwMM#x#~r*U@9MV!&mEA9H$umTHYZDx1Z49O*zXpJVw1j>*3eP*aT;BB^qi`Z5V;n z#>L{;tIJU_thf{veYT*Tuh8Jvxu28i zghQ3Hh%zlxKH1|D6aVZ>60GKT1fn?hHlOv|t&tqek{%Ik^5ROP_bH)?LKsr*L^ez5 zVmQQdtBeo%r0cW67N!@X(}}FQ&+_#v3D5~y+RMzo)mUa_AOp>)DOCzRNju@uRjAEu z#_UE=JA-BkO(=!f9)@x;Dv$yi>m#19LU%W+-)y~eU^Uy z1x*=GtgpuakR%u8pWWr>!=312o<9LB`#w_z8dDPEwuiiJSSZTVw6~bluNY5h^D~hB8xj_8N%u%WcNLpAR=-Hn~?S&iO+=7SSkq+{f(Rs`j_ zqHtx9-O5znl=hW0OHMs}cS+5gX}d=`vup@I60F!jo#*8ZExuS-S$tpi<%n{`uqP9L zY5`Nbl|ekEDJiSMkmJjr#S8^Ks>UN@YHh!xE?GZoBp6;P@Z~u+J8S z&rVO|gTlVn}&JNmTWo#{$uVOP7<_Dd3L)jg?Fdu`l6h9MnB9%2ag z1isx6qX?gyFtQaqy}Ve zjhbc|{%-2uGgWSpCO2M~tyxOv?&0AuQzb444$!#!^LYmSLeja0+d8t|D!3uotsCBe zT9%uen{)v~4Q3)9SMF3x-Wwi6KQ4Z-wgAQAZ8^hP5gdlnZ52ajkgg+TXvRuI_{T3Rjg{u4VRWmX3q$Sv6#^Bip}Q+o z+)jj7p`X^o9&KLwf3R^RRpP?6FC)JHj&#HANqY5lV@3EuA9kEj)F`(Xg0ks(8QHIU z3+2g$Gk{N`AZX4Tg-Y&h=fNC>)qTHeapn%P~ z^B1=n?n?;t?Si?;{O7%4W75#2nmU!YT~gDTvR?lSrIe7wBI92;LZnK|CoFxV_B;wu zK_LX5js7EiA^}VOWHGljJ5f;Ziyp%!nUSj|Y@MJfV0XbjW>=BFIhKY3m}U^Phj$b?wUdt&Ta4#VF9L)_*#5O_Cvz_XH(LTT9xG%9|mJ=Eq#jeixE&fOq zf&eKfm3#`$^v?;mZe7dE$lgHmf3CU(Sd(U+|E{FG=5OqK18 z2VAA|s7t`JFgV+t?iVc7%HXuRmSX^aKp0g8560o|i$4nnhH?Crz3+7XC5-a)_)P@L zS349qu7;47h0BbRE4oI?)(O!8BFp1~%2gcM`RRat?P;?~Ru}|9D&+3tQ26;b=Y_?c z@TkkXaw=XRav%nN%r9OI<`UAHutKS=*$OZow<2A!PW zzP~#gq;bNfJzC~NcYzMl`L3jOWBS+8u=vV2`YTVk+JrW*O*LBb%_K zN7^S&nW6r5=RRO@wg)+m|*pj*Eef2X#5?6I0ca+rf3$oRTTZ5XFU@28jlFx{h599CPa^iS>4 z2eK7v%C#XKeBLMqi@}WtjNBu{XATLDsfQ2BhE8=L3A{qS<2OCFX9_2An_Oun8~%2s zkEMgU=U?vVIjFUsJFiR2Idje`p_~6v^yqWij4GdB`+=^W^qfL){BtBqP~tvBUCW>q zC!QwjzpFX2!uU!CW1kqmHDmq1*Aq$%dItUg2lvGp0nq8vmB#zfadS^{LT0X(-?KgO z0=#YSyc=8Q3}rwm))JT7`A$<#B)z7Aq#u?PXJj5F&S50bm}-5J1U%Z0{TROI%^U(F zDHvzcOI*4M+^ZU^NDPa9=pV|ni{+Ee;cUHvS9>$n)B-HP zrlbGu8WX&}`-;I~8B8Ij2l-FBM8XNY;=&u>8ORUSc&?&pjpYQTpkRwTJ6F+};XZvK z_CB7Qar_G${8jVAML8lYDCVybWI=?iFfx<=2r}JTr^w{*v8ihf z+ESi@Ls649jOA)tr0DyK7;qCcRE(Cp(L!y+YW$tYHEbph{)wnvfE=GB_VHj|0qp** z)eqm(NOX1ih1Kp@=bII?6Eq5hB9CjC0^6Ic!^w(*`;yS@@w}j(zzUL5^qXKwGodxm zm7w%1v(}`{^Y#j;JzSu4uL-RY$SySwyJ*sW^5AYM1?E*vgYB^b70ZNA)eBQH?=?lb zcTvd1Xk7yJ_Zy64LBhk1Q64cX@V88A_$5P1t?LU}74yqhPjejeBH1Y$cPUe5YHnWK zS6Qg_zP{~FF&g)_!#Hws81M)QVNxm6(1|KtSr0Vb7Arz{j%ul2wpF7-asZ1z_EO&6 zEe3Q#IRNtfJ zSIXD5Rd=%=Ays{3Mf#0nthQ42rsqy^aE*T(j`~tsv8FC3&W3LN33D@J|I=56;}aVJ zTIjgVr=swAf&V)3^q={amJ4REt% zX{bh6vcACT6>78POGf3Vx6e+TQ17i)-revXsXt-VScLuw)H0z9tv~TfhH4y+>9+aRx}tJ-|D!%B4i##v zfA0Q`hZOz;K|BiL{?X_nS=!?6OI6xXL;LNLmIg zp!vrSG!k3gS;=mE;`-}zUs|XK5E&N}3TJVy4%4)_kOcsRQ?SB(`87TX$hz%&5(SDN zLjTb9Gks&}E^u-iP=P-|y3;(x&btFBz`;}_beEr|pV0{wPx)N)m4_q;CHsY=W3t5)$;VcyraujZ>1-{puGz6v17R=N!^ zq{iv}=#~OQV1tcmuyfLz#-}7>aR}RI)LQ-Z5lT zh8rCzaD#no_xt1LPwgs1YQLN@YmF89yCqunlb)Z>PCxV_l3DU5a3|mgORnUuz6+rs zVOirf77uy#oUmG!!dUG|goQW3ZH)40Z`E7QPbW>%&f-LhD`k#lOanNy8!wiF7eAeDjRLP>wCM~PIIeZ4EAoqhwHK`M zvErvdKuR1ZnNq#MJzJ-gj`MSu565tpLCcSNeusVD80$$Nw9(v}kZs`EC!arS0+fAF zcH-l(x3@c;t)BzV6pQaRAjHxo6_jR{RKt+Sf+D-l_C2M6B5=lRI87^O=pnX&wKtn( z!hz~_fC~)Py@Cy%%g>-inkK;<;KFRMVp*5-2$);ng3V+iLhfu=f>*)&*#Z<_{6mO% zkG<9hO$i2@hA%m2=vvvr*=m1rO2yIJFj+;Rr+1QjQ93IbX(?=xP~MsrCRk%QCamIc zmYND%7B2X*ORnd7_|-P|__dxhn}qeNp~A>t4JTy+c_=hHi}W$E8p(S@%9h@(XOM>b zj4DLs2)_hxwTc@Or2$W>kEep8exCR|BadR|ZJf{c+hOHN6WL-l%joo|(J$7+gc{N9 zDKVTRC#}-X>rk%EJR-!|33DOvBJGLN098Q2mTEs;V8 z`xjsQnAcKvC)?u`NrDb<(Ri?+zuGmVA;o6j3AQY)+pRHLD8Kz^u5vq1=CW*6Y&n>9 zdNR5WL47pM9k-mPw##mpzY+T27E#=r0o%5(eg%5y+WjZxYS}c38H&MEm#CDA9&bZgZsvJc`Uf*wBag_WtmAcrIq*5n z%fDJyo&TN|ty(v%)U_rFPl}NP8kU@Rwg-A&3-ap&ku-^MPFR27bm`hI;UP?>z^~ElKR4eKWj!}&b$VTGNYT`&RJ>;77;EoXOP0|MZOza? zOAxUCi~t3eV&7`<5~nAT`)b#ycM}u1pB4v*Qd$owT|1Ge>^T0b08Kh1K)(O|KTSFm zc`4&~{{_2(C!g%#_*gPzsvJZoBWXvu!yZ!itAzSh{En(QdSqb4XW5t#uPXUG2ph8n zQEa7*-0M*tuz_#boFxHQoUi25yN(|c*_+-3wG#7r3WElKZZx;cBYfZ&_XEAo%iBeb zP2k0YQ4)&u9gkk2gj5LWkHYNKV|6@ZO5pARIS~@b&Jz<0em{dR3;!OukCxJ-t@J-c zL3u2oU$^IU)wTZn@o0pQ;F|Fb2qpria@0SB3;n>eSxy$YOnS?=n;-a& zx9#%GdUZXNKwYNQ69lJVK|F(ON7pdyFrdbJvUqYg7hl{GBklLm`<;{+e#?ZD_HWODH`Jp^Avh=Jrn1faQ|XGw-^Y# z_rtaPbW^dX4jA8lu+G-No1x(8t99B7tk8v*=@y0&7v+*w8uuj<11hLg6G$%f`GOe) z`~zrKF9EQHaBse`KhleY^!nQMRJ74LTqq{$7r{CqKJkycC(q!lz2Q_((40-Be(AAu zP%C9Px44H0VL6}e5Q8fhpr-D90qn1Q=(zr9k4l(5#+^uV=taNX_pAzNr5DkRebsMYZ z>2|MzOB24-vW;frBi@AGT^_p>DysB3bXv8{Hl2PIhTkdiW_#=J)8`a(tRM}iId-N4 zf5-y~SensD*iE1F+RQPrJknjOFn9Zc8Zfo{h(tAQltfXaEGq239@4L$c8VGom;JHx zi+Ra}C;@2&bgwr7P{5bnE;Z0#+=4)$<%MB`2tA-;;Q;j=P@cU;%~x;JxU1E2_toe&BbTeAq_^UG2be#eXF zT~G#_PpBXY_X;ed7F#^uL}s!i82}1_>2M*}T_P_r*;F~_fMB!sf$Ta~3ei3}74jp0 znYc-HC1*5_(P!mduYOqubV`Q60@Fyf82|83g@s{3s;V6JP)7+^wh@OrrmMP}8k>Jw z6VxrVFNVV8^wL8!P0n6-&QcFiJ4Z5wv_N@oV^_FIPhWGfu~-J1v}QDP{6C_;GOEg@ z{oA0VQ5rTW(jXwY5u{VPy9A`WLy>MIC8fK&8AY z6@y}#7Y{e~ga`JoPwY4P{BX>tC_aDBdNSVZy^wAt5;MP>y_vm%#pAR_v&-drvi2*Q zI%4U3KIb*Nb+4mwV$QFbG?n}XLD#K%LY2>rmo+5`ngJ?mk*uF29d1sf?$|vW*gSq` zh~U3-c?-&(dK(tCU9U%-%tOSVW9>qL*3TChyq2$ySFg-_Q2&}a08m(D4pQ3QTF$;B z=JUW$;;&?;`X||cLur1iI;@=_ce)^$)nESaAp1G%QLVwv-93a^hv!YcZPBJ_V^mW zFnm|)Z)GSs+_C9OzhBmj&t+dMlw^HO^ki?uUW@svDK^jJJWn5A1NTtVWqGJl$G0j7 zdxY#jLi@S=&Dr=RH+arCkvKF$QGG|36iuRD>Gs{P>vUW)tPMmX*28avg!5`Rn-1F@ zJo&N!Zgn1h$QiDZewrlhcJ_T_RQ!zNd|{$nGi&roa=N8rECchOxOPPM7B)@I4OW@C z_di4&iWtm6*zPhZjaS6 z-x=QPbAqm9P^-}SAtXy5gt!(c(GDc>lm4}p{XA?Dvau=acLX9f;lemZAh0w35^J0E zm!1{qe6uxY{WHWc0N{?s(rI*c`*`|Hfb*fcZqW6<#{m;bXL7wg(1`y-Ge-`EYz}L8 z)D>uec~2HhH^%qp)I3h6n|22Qk6|C8jV*1C{$NeNFMFGIBqO~$zie3UBD_z=WN3&l zKY=c5SUIOm?`NvbL}X}kf7Yg7iCZ%Vggu0_`Q7hrbFu}l6A~WR4E*0mRL@>cjO7(W zW%tTWgJy6v`6Qi{C*OW#ycCQNgVUs%%PnJ~%3^f#UcKQ9vu*Y;?of2R-&z)8Naoaa zM!oe^JeW-^B+N1*Yud*ccJ|r1Lg(1gE#+(M*gW-Mo#b*0OMyq{VKwn9{OW zw#JghP^N{H^z$fam&Bs9dFX)mm?*>pIFcHOGnosJfj}u(Sy_!=FW-dN2Zqn>J>&lj z{;qV}u{faf{?cFX`{I?seqt<*a>47?kMn6cNHId9IvUk4*ao24C-;BSY19j_59@`4 zUZ&e`4o$b%gN9MwAnV(6AxV;4^!(xG@0Vnpmie9`i7E#rq~Gmz7iQ?HnO?`M`db=I zA+P6;t6aMNZsZr-7ubeUbIiL_oQ=IKKS7EOfs!nTvlv9=)*IR%RA+py?^1mTcvj95 z^@mGp%dN#VAktIN9vfwof2wJQpPkD>?lz=UoVInzGe*r>aH`wto5JREg}IgFZ8o({ zPY;hjIVroOb_?sY=LA?)5*@rE`|~{m`|9DAsmZG^1`FRGvr)QvF|)G$r57GHNw;97 z5&iX#HHX5*JYkWwA8b*t^|?T9{8P~pJwL@p+`_<<3|woG>V$TL)799yivMIlzUuWZ z4?bJ$fap@MFcx|6fyaFL{Ut{u8rYDWk|5w`VH|UOjB5Eys<)0Cx*#jMQKNE|@etP9 z+l{j4uF@b)@Yi&@Z#r5;`;ZEqa=inwkfrC#MsB%Lzd)y*h1C?5t!*KeGqAc%OzUJaOQYXyNd2(1EWMcE9>)sA+5rA30l zog+cZ0?;Z~dLBR=GU6r_w*T5V9q4INKCDEdEF{kx=GLE?HrGEIzrAKYkis zh?yQcDrIqwPMUIo;WT|9;Yi4M>M$Oqb503g?y2QxljkG8kck!fz(A9)k-dI|z&qMh zt%hS-;5lnV;aX}B71sPwR7IAWno%_v@Q1I(*E#djvsgWsqhz5mMC%8icijtARACUE zC!b|1zs9VFbbYcZ1V%OZu>=nDH$%xhezvU<9pR4;LLfK`|9Dp%^??7$6S+bwZI63P z(imD*nc97!D*Wd=da^R!#=Uay1ZP2CAh(z);UUT&!09 zjN5*_Ba&RmXmgi6FkE##8PAISbdy;Rf(X1$>%mlc8rcycs#XmP4q|-GiX+ z*<{&^<1MAJ?}#c(d^iYuo0k?j(^<;?oR`nVRuXRS8Y z?;4-Iy1{KXhaXiar2B-e&?^9o)t`3 zd?EEm?ZOa3be$ID4y;1%)sowW8IAWuQ2zhq0#j*FPGftvM-aoL1&2oD{WU66jMB2f zp<yfDJ@ni4`bw|9i>X1>6pOJJJfe7d%+NX#>^m|@m) z#xGZFh9k81Z#?XgD4*BWQU#S%=bMj_lqNENrK%l7rZX7?J5z<az}QZ+kQho zJE+aAm0ykGwtrJ)J~sb^{zc^n(A$JrlHu3yiq(zIrc-5X$WBWKn)ETcPgx(R|F09EwBvbS{lND{+e% zH#(ODd`jNI!5TVhK8l(KDMql@bc1HFCwKY#L9Mw(RG+7k}?23A)op`K$I z8s$Qg$y+L?FmI?@I#7CafhSHUuKNsBWPHA(t#KQ(3@3C@5a9S@IYfsfaUV@45$;oDaW;E5oA zi0vvfD;OgFe)I*$d7LnBq8Z(t*4B$Itd&~%%z$xAS2VN@c%iV~RHVQQBt*5fJ&7-? z`?>h^NwwL8V48L7CqD?k7qRzX0y*Y4o4>RnD6vu3pD0wO!lC>Zj3g-1Xg~#NQhmsU z%B4&vwz$~f_tgP}k<7cL>FU@Uwz_uw{OSQ(^9@9nr~OG+!~idh(1=|BU5@I3O&-Qk zzC71Bw#BiLHAFTxewbdq1hGYdSdzq8`N-yHHE7`CAPOQ`AJGu$Laj3wk?hYO4%SPz z*=J>2Ye`&LAfG^BROYNwIg0!$o`^o|#$HI@E!WVYtw^*Y$B#ROskzYIJXG{cn>+iH z>p<0spbY!C^`9s4%-VK;(U4?b+h;(-+Lra=6`%$JSwWkj9jGhN%0@=UVJZ@bd=P)p z{$;<2UBEJJ=fF#{_*M1b)Byc6F4TFuIv;d+>Rzr^Jx7JRlSAg`1)jk1p;j2-nRgN(-K9Xtvh z4V?%a7M9$Hm=dyJGI4nUc%{U5@cE-q;`xJHL{4`oRkYEYN~kk0>~;^4BbwJW=hOEY z>ei-{KKi=Dhg*5z>o6I*hL^>L7IE8eU^iTD`SCx_=ShKs;70|IFUvu#DSymJ?^%T` zt>oGHkQx2z4Eph`H9ruz{2(}ROoI(k>BGN?4~D3?8ssmvH+ZSvt}Bgn8i~D4bA%i# zLhWZaLk{c-Hp_#<_3xe(9{$>SrMd20u#~s>vyDH*0 zdrflq`tEE|oPgwEC=xhLP)Wu~#Udc>Vz2mvvL34~5mk!ruxeqGa*KIRr7kzONMf#? z#TZVPjm%z`$GHC=DqAVoSQaBGPf7P$?to{|twAkqI(unlK8*>mMbQAK{WAUg8+E-Ppf5)$}q#-U96K+v>xd^U!Z`;D~juI+?WWy#+S?XCHwmbTk^ zU+kZgueisy!T+ET6cF2s0Ro55mkFQr#FE5FMfP_~LcXcO2^hW8A>sWK6US~o$>kz= z^|cPs84fGZ^-M#O23tN*TsF&BsHOlyyIhvtAz{?magXlTnJVgAcogf*J3oWI&9}1` zd`pZRfT^JFjCvS?*>Oi8nD0GW1JO#-(H@qWt<@vbBck1}8myx4EmVyA@Il+x;)(w5 zuS1((tyl@EH@H53h%w1Y;Vkn{g5&&>%;DcU#ZV$Hh&CM zQW3|cIVylnbs!On7iQh~(bUFc<;r#-jT6K9omFTl9z$dy12T0>5AqK{N~IM=jNORv zS$S45M#~(y;vRzF*pO`ZLcf(Ckzr1xVZ9NZLdkVbry;b+sRUsu1X;Fu+7ERGnHE zV1{=QbS}d*u~VtYp`OHsX}{|hreQ(d36T>RhUbSxH&#U~Xx5=LJh;fh_rF)-ZHskn zSRafa?(#$#*TOy6PMe<|7n@y{Qg4s;ef)APWgS2_SLWC=KBXq+PdZb{x2drhk))@l z_w=h`_{HA2C!Ljy<@~&(yY3XVf#Li62l1h^J>9{jxJ;v-7zp3;)W4OlDUzAW)!4Y5 z?f3gVf9iYha-Vd;zZzA38U7!Zg(UbzljvPr^7ZNQW$U8>xVLXjG;)?SKDfbI#-T_{ zNC@;^?3eG|9vW`Z3LMRpTS7O65{spFudY^WrtIC;V^wuU$_=_5K0il5ZTtEhab+#4 zRlUN2z$(v~{*4!JcNHTo?F&QE))4oD7B=+Mf7#Y7(#gjQqQrS8M2`FGT&DMmInwxi zX4pDjt7zVE{V(C74!E5Ui2a)`;+Xauc&Z~Q#hgT6P-I5aE}~;Tw2$0m`jcODKWGNK0-Hno>o%~SSXfpahD*XkSwjE^{75^yb=Os9m0e9CG6Q?s^kgc7k*tk zgi)^2c@3~i&vg|er9 z$T$kCzR%Un6K05WIYqL%TZ9L!okvqdNJFZo0uJY-A1%kmj{At8+TV!{mD&m#71?do)yv9Uq99~11KJ)F0MKdT||^V%A6=ituhs%VnA6?I`F0$Ny)>;itZpj#0jlmX*G{dbI~DO>6qodRTdYW_~Had#GB1 z!Bp~we=kk!D!uf>e-kO(vXtHC?{BwB9NkCGi|wH`Jqb5hw>BU=y8#gZ{z5iM*^G;0AjL9&r#TSsUOw()l>1wiV2BQ#eu zCC@^Uj}82!_Rg8M>>1N>aD%5)ATfXKu$^;d@We_kOYuNUKB0Aa&xJ}sas@KU=k>EJ zM5+iEz0!jXh!swp$lUWNiylK8uoBO<$y&z?z=jUo)!RlNxCiu-9LEZN z;+ly6vhJ5DX`=qsCqU67?`}C$p6lP+F96b8xxM!Ad1|dorKF^#g=6WISJtouw3fk9 z?~-9CHt>U6Dt<9v3J;q`jZ8+TJNynFd1)WqG_oN8-MzoX2#gjxHrXnxWqb~`Tl*C* zPZ?mitZEPU{J&!E5*>61Zy@5KwHd+mv0AQ70*8CtKP%Fg1`z|&;r$4i%$_Kce=oC$ z>DA8J2Wb*#w%p*C&8EoI&z)X2k=`Yu3#4G9i>y;Vd8;F(L+9#}zl0@OOu5BzBVEXh z{hhBy<}$iEywH*WV_e_BFPg0j${~aCEqXPVwLecd4wQIS1xvd?(v~tD znWTw;%Zu`TjUoY#GRq1>qgQ*OPG<1E689-Z>c4S~>fV0m@Vs#Vu9N4o{0j@-r&YWPdr!W=yfh=04?|hAIj!cO z7{Z9YzN9L_v5|vYR3F8PS@)Nf3ISt=TN~JJu8RiQ)B3Suu|5I6 zphPoHb0H=HTjduS%u7{|#W~)^m+f3S95pX`9s+C|la6cV?pZDc2KIj(pAlSA4&tu5 zsr+1PuV`0D`V|;V@f|buJVsqI^T>{!Qug zWb*!o{=j1T7dDV;Ah39>iDT0#(a5BBr+y&&f<-Atp<1HVa0UVnGF6Ia@j3QB0=p!& z%wl&qNR|Pz#pBK9n5kzVmxoCh-;KtC0_VRo8gW4b}T0hdR=5N zEj~!hNxe^PTHP6N`sGZ=ptF~Y=jf;SXcV-5zVEL`wq$xCPNqj13laXnGs})-f&m}L zEaQPhW&t@SMz0S+b=K4g`WVy{ZhP6y}pf<@N)HE}LyHG#Y>1xCk)p96hgW zy?QoyP>&aYPBNaoIS`97W<1pO4Wo7>+uYMAs>SZt5mvX$q1u9!jzX2GeA7b@QO+3z z>ZJF}yfjneJaKEv`w1Z97>$^-YzFc>Ye>7fcapuP3ro>Db?0h!M{h1Y(IL<(w#O&w zGbb6XsBIop-9N3jW?B-0CL9@kQka*^?%85$Ni$63syu^A6wmO@ZuRa0mz3R&EELx- z((D2Z`^ruPTM?EsxQFJ8u~T%*S-%pF6U#4^C%WO!#(DuyBG8s-@?chEFeksNJ4 zmaJ;Qz@&k4?kpur7?08H?aHc#$aM5I(u-QPppt z5k;N<{)~h?P5*Z;z{?xUdOmAE69^~t^Srhp!@RD$5SYf}Z_PhPhcWcqRg*O|g~Faa zRa#!RW3-C@%rb)(5X(HVV*CSl@r0rL3-xA^0Rdqy7tR_QC2{T3=zG1@a0mn|Y8Ub1J(IbC5llOc=DvFdq+Q!a4&YmWE;8 zz1FP{W__v+76j5M$J}&E1-~$s0%wPyaW4&eh@lo~O4^1H9*4F*9QvbfJl%wv?5r%k zfrLvI_Y2}CZVMU`m!I{tOXO5k@T49`mo44F(`Qs-%|bj7BKfotxh$$>zl?| zLcQljA@ZzT*jC=3ZQBa74rS)CjtXn#04ywT5ln8rf?Im45=3Qw)K3GoT^2=fQ|k8& zlXlKU>lzeu5Qe!94>wx1?|*{Tno7@hM_Dp1!%g|hN%O}NKrNRJ>DhPMVKmN(PGFOd zYW?t3%<mUo<7p9J`han7=R6I^N+6v=? zYHI7OEmoGd6hAx5cqLdmp}F9Bl5ukrfaJQ7^-2#n)_5M%PGWjoE>o6ibX$m@EPpGh6mr2$w-Ip+CAOMGDx>cL8%wbU9)mXh}(yGI}PK}KGg3;R=Fz|z|cZB{Q zOe*lKz5O}jkwMz#$88M0lehk1#9yt2QMMHI zWeDxVhkx8v-;hB9r_@n9;z)rq{oP4Fa{_3y=eu50d`(Tgd*bI;edlwAZmdNk?J@7{@*(8?|TH8xcYGf{T@%c0O`9ViRiq;^W=hB zml4(P%^ON*-l6-6wj9;AxHYzoX|p0wcnsm1?Mtan6e^5%?w(XySC_9TJmN)4?}g(H zu>{WejMq^DI=7}y2>=Es7{BO!MCRaK690)b(s{08XOx+eQgu=S@`bJ&J^t{mcJp%_ zM*2JiL~$A-l*whe5uteHD%r@Po-5HNrM*DzxSf~&xd}ozVZew>=6+$p<$j^{+VZgF zo}(QiP{}xq&3ok(Dphq9%L{i%PSu*51*_f1p8wy?5RSZUgi_1_#`dX!HF>txoB!p@ zmyzI_1MIuxAhZA2CgRr@tvZ{h%xAmSNz~4%y!dy0^e#n;z4)%NEl6X!Y9Y&y|6 zfA)?oj5H3Ps#O@}Gs%JpbkY6Lv!qzL&|5gYqg$@h7|jUiewO8s(&doa^)M&Qm3liZ zNd)(2tCQiqX$&q?y&YlZ6Gd3mvj1?%WTSX&eD~P>Et(lp=8Gp!c^7W$!CJ-anvfyG z{7?>R0WELC#b;YQ5m&abmJw;{D0^&_IW02646+$kVm16A6_s6kUq4sBzb;!4FXj3?yi$MQ$vc6$QV%1PxrAW^LuA5XGi)Y~u;Dal zz}Nd*!@D}$t;I@eR;t%}JJe}E-6Kf0=Ce`2)j7RSyBDdnvB)6IbcDSWtygC~lyauU zFAi2(nVREu^5WufE?0_Pu?7{WRRev10%7QUDpMd+^jD&CGjG)xOX5~?s*7BS7WY6B z6!COpPz>gEU+(?r^_ggMD5X=D|K37Ux895$*khldSrtb}L{qOMcKDTA1V$MIh$<+2 z=L?XS8Ox5-m%jCQIGw;4*1`3X+XH)X#jqPct%FN??XNlhAKD`)1u92@Yn2MDdP+UB z%p+bwzq|@be7Nf;l_KDcN)`$~qY`>rCT|V{>t3@h5(dPj%4F7s-U`_u7^d7fiYc`=IpMrpZGWOR@G9umVq8hrJ*2ada8XW9bIH%E;y%?DQw|bUa$` zDzxI%YG}PeyTWoc{BXQZsa3knTZNG5754(g@L+?e-|gkilM`!F#;ieS1U`xR9Bv3M zv*@5kHSv@yyB>SAw30+;J+j)(QYj;t3et9t-#vTk=nnDZKRPg1HVaW{{^`QIYqv2F z#Hr(rW=Z<_ThJ?N{ke;MAXIw&^3^NbOKsdl)_T)&2eU)-)@=3NCPlzkm@>{T%hnu{& z9GW>FvuwLiXVCY6yc2&#Dh%jor|U*@p<}_`)4)p^;X~R+j|np+vB|90T3p_I-)lVQ zIFk9j7qdbU%I2_VE;9ud9=oxjYDl`8ah`)`d4JL~NM2;4wSCP9_)<{e0Lh<$yRD`` zCMk$?W#W;yM>NP>!lLcn-khd$5guY*{Y{l|5|vo$a%|9$)qH#LV?UV2#by`V##V{kRYU#xA|Lf#3r? z$E(H5*1LU8*OO4wKT9g_ENlwn50b=LY3h@?JCXlqQGXYV%N#Dh%Ku@icUEk42yA}* zoEB|poD0}u!$>}Lvn;gE!p!}GTCrJ4OXwe!$(re1%EXt2QD^o!Wy^)&j};1#{5t!L z#oW`^(s$}y(jD|mgceoz6UxJ56_rt$OR3`vS>28MX=O054wJcNsAmElG&KuMTC(vR z(Udv-Oz4rdy6G%!_gO5u9QK`@y9CQAamAL+lpDy#m+}@OfYq;WW^w^!WqJpo>(AdF zK7T^8`{(KH#eO%?Ep_&$dHE0n`14#kE0~xfna8m&MkUWi|H%pcRxlYfDUtKE|FF-s0`8lDQl1qhG6_~R*oHy_7ZtZZEO$P(X8aET z6}bB1>kHS!VL`Lu-Hf?ETia{tJpc8wC#vzi-JCn^M%+2q_ySY+` z`u)FL?{=)W^P#{kBI#&0)tfgN8exRCCqPHDg8bsg84p@IzVsFKUD#m^RBd-PTlMbe zyN-~7=wsm62C9I9Nhc0am=IYJ8;;F8?O3QfW>d+ZK0Zb=p!Vv)cR2+icO=`%q+`D! z{EfQ|GHWJGcpXig}|E>I=vZ)@~~# z=E607y5Yp`#CGiW6uv?(H73p;kN1~qHG2W#C#t%P!d%3nWO`$7XcGvkydR&Ux-5U% zn?J8*y1&^q3UZ-y3>?P_3*;`10yco zLn_S5IKHyysOkQt{K-=O*Rt@i?G$hU{zPlNT(Et$REzqdX^PwpmK4K;I_La&iZT1n z(%{wj_9m5Z8$mjB}w<_g>gP1g-K`q_kb|n3M5$Xu{$ir3+C{jl@3ne zD$)lY#b76NI1=FG+BYcoH-_C9{BI?(d{cpGXSXGuUYF!+C?S5702nNCNc}+e+3t)h z)a?v^(Zd{WhqDP~oqnRHXy(*$j$XCkizwQzy8`Dw*JfeSrM4DNmplnYfH3`5$Uf@m zXYT$Drmw(vpsy+O)(Z84tc1@acRu~)WMY1zf>5wPgQy}yVD_viGPPab5MJivaT`iC zB1usmS8hVdb3C+6NqCIh9Z34PIbGgc$(UTbjGg4^!FT#`@*eN6vYBw3R#&??H|fZR z`qV`upkZAqWlZ(|E`g$_j5u2|K0K@UR7cr1jBc06kI^{s zSIloc3s}v^%VrP16VUmHY!%DP${;_rtZ`Sm3WHA_h(fwv<^F0-iC*?7r%=V(zUipO|^3-YTcv)`W^;Ta7k zDo^_p++Rq=GcBhFK$qsar5FKJmx6M+;Es25u^oiG-XAnUyKrbQ$QAvi8dfmHS6eWq z&mVmr-lr7iBlfc2e_%j-g999<8MoBg9Lrx70#rSp^V3DCHxs=ADFPikx~$4CUSq#E zise;v$?w10}n6};M0j?Id@;WmKvenP*Zbbe1!g=6XPaoICNH6XQahJ>PDitR1IYYl( zw>ac2*@DBaNc}dXqzjExYq)$LAJbSi1HgPrdx5Rbw`vKaiXlODBc5)>w%Q=Qb<@ZjQuF7E@Ssvk+c)x911Y z>3mofy=XTS?x8;DgLnxmzAID6e47KWm>D4$nnxMO$Z6X5lA%fCQN~}bKnM^*%c9NC z1H*g*%oIBGb6V+?K889zEmy{pDCJ5)GjPsHp2H3y#1p(Onh(vG7I1+#N3)-_kS?Ei z+zV2EZRwvz4iVd8afFXrUFsz$_96oTr3pN%Z1XdDEFdh0M8eyA>+RaW50nKr-s1j4 z2jgHfrHMo7=G`5tzkKR)r{m{O{joype~Gh*hep$+t@vFvZhyYTuip?ycG@`r=u*Vw zx%6c-idol6AL>?AJ9wp4iuw}p8drWTTle#0NIa0tYk}XL#mr1f+*KD64*MrAZSIRv z3IR;!ea3v#;YIH!7bE;0B7uWkVy3{Zk48P;I_`Sa} zBt^VLpu@b%sML}qJ%*b=Csu9AZE#)lySO6YS4+?$wBuUA^8xk^V+DkJ;!ihX2+FrM zpSi5OSo@uXh(^fC8mrxGN9B*|^K)vql~(7t|~Gcsdj!ZEXq?Zcl75%Od0RX`nL(|nAz)>sz+Zwwdi_{ z)UsSIx^sfbj{9P7;={o8J1$8Q!5tzQOYxf+$*S)G$-x2IDGYa~L^3xP-T%$yK4ha< zAbY;OOE!m$oLnHD&vfa-T?=4FlPNrYe?M&L^gq9$QolhXUb^db?_e8Bfd))sRCj}{ zUdL?={yWT6&yCNbv?jzLS22mxdLsK{$_aVu;aGGD+P`yuJnw8inh#+j|gHxkZpTYhy&%q34gGV80iho17*D9x%bgr%B7)w$Jm?~{IVA;S-4C{BLUd{fFsQ80SG?;{d0}d9G{44P$3IUnN{aZOIM&VUa2fCXswT5 z3D{b|b7j&-PWzZfakrx5|D8&bjFz^LX@9pAkG?eEAJA#Vj0zvs;A%_Wv6ds31oF--`0hKRU!8+*%A_E#6UBsSv_t8+`YRy>`{pO~YUGrAr$`R&97}##<-gpGUxWe^+M0 zmsi(Pb;}>O|3T~RnTcrYWs8+P05w^5Lx5z6tX0nSeey03v0(U~MBQcy)ZsUXy2Ya> zojF)qjM`bP*a2KqL)o^AcS&1=iBeVm>}l9}@+w5W(V3iguIV;Gb#VyKm}Kao?pQ#% z(Wof;T>k6vUA=29m4>Jje`v3A7h`U3pNnUpN0YyE_W2iI8oC5J#)Z4e_?smvpVEnlIN&Y zr1fLly&WW%daNN8-F#w9-CKmL)5VAZw0d|mRVYW=ZRGArQ7nS6OzG|Jhn#Gs@dh(b zCNa-awc4X{;fInJlzIv*;iBDaViRgZzX|N!YEpO~i*8u0ZbApV8vR%nU8RL!b_?#X z_YK9Tty*6I+LLr5^5=fYBD(kD(zG3SaWXs$twdI|@kBP=hD`5PLiWm79rxeu4@@S7 zU!eyLikJ6mL9f(m9B2<(E*H66*Z932T+x8UjVF#-w?wK`AshLiVHbVgwKt{idLt>4 zN-FM(izW#7SGoQ@0@#n=a}>e8fE-9ort0)4N-=Z~9z%rh-@hAd(UNz797z`QNn-8U z%30z{gYoU}> zgX42BV-kl*jjm!A5)bbp!Czev;K7pmfWntltHnb-UZv2FDriJV8hWVQ}eoJP)g1ga$RaE-J z)TV=B$5^Hlu)6Og-lL6@0R?XsOXdV4uqc}4Kmhl=S}Doo-JShw%Y!}cy357em91D3 z9Lw;rmNr1-9IM@;19*eQa<0jh?XS}aJZ|u>5}S_aksmazqR%@{rO<%X*E61O12&W7 z)4>dptF6u#SNpZPws+IE%XTkbyug1P5zAA`{~oZ2C8&0G4#-_OXU{iORLjRh5WoK1 z_H8l3zD3U|%U@BHP%yW;XFJgNx}LTQPBB6D_SGh7XBt@!0?w{j8=A!4(=U4!5)?PH z*1RaX6sKDre76?Hu{XhTg^YLM7_wiRAn8k7fego*OM|UjjhiRY@KbY(sr<@sxtyj))8AvD&ARDHIY(~WK4 zCNdrr=eaZ#P4ZAxTx~>4QK9JO)e}5ixu@Q#}sb- zb10T17GYE{PimZLd7$PPxIdAacYeGlaTDiS2l~q^mr*0_S;Ku2frq1@kNJIR{C}s=#)11`_xnp&fZ0Uh?lY)bk!~)=L7sMQxEPSb+Oog>>8-nP`3(`n zV02A(<*zc3rcUUK?}rZ>@?`i(JPMNHNN|*WlyrA>D4NJ_LKjO0ge3*Ztm$G?v)3IlN^4Ot)?_Htgi>wl|US9&pfia_XQiJay zkp!t$F~N2mR}nXDQ!BEk;rQ={o}v#vR7j zJd{g7bmzo>(fPUJC0}z6LbcNH&u)5!625mM7gNf`y$177A z%7uu>3(is$=5rdJ@2bju7O20+S5Dr8fPmvx<3X0=%m`m}p;Vx`DU+u>bGc9-NQNS| zJ{&`>KHC(Y&o=u+KR6-tZx|&*w2WES=UyAY@%_FXYmdd@pyI90nL4}Qh9>A>azV>><0!*LkYvKF|pxl-MF=( zQti=AHHG!1Yp+n%cZzCKx9oz61% zsAc$HW6s}UFVVb2pn7NGc$3sycyzKv{acb%3wO9qcPf~Wo82!BU@iA*XM>yx?@hw3 z+$(cFzWqEZ2BM%*yzb9AK|BV2NUNsL`R>%*?0cZa#%0v-6$!yjcMZYC=kwqKVF^3y zCBlE%J1?!~SvyUjo011d#CVZ1O10UPXt74MT!xVZ+_SHt5Sl0fkV}j$8tO3|vzVnW zRpq%x?yx=j!Vd-Kr09V*O}e+g6rC+QdI#g5L?Qkk6*Gj!H_%dMx_*e-_oI)BIAozKL?QCi}vxmmy&Qr4P!gV zIg{D7zv+8V7AEn+ADgNTR*Q?4aU_j#2>*naI}K<9eHpdY(9>amCo~@U7~5c{VuInX zPTG}9W=O8d#1d8xT!uAYR^9s~usJJHd*hU}zkAgOi$<^r?uHXhEuLcL@Uxt?_jcKB zC?a^Sy|N{n3|8J=@3kJez4j-v9&f(VJUt!OsekPOo4aP3MJ!W3l3WM-{a@|G8`;EA z%eh+3jo;DK7^0!XUyIZ$pFY`{Xv8|-SqKE}V#}?aVr53Z75=^lsn$o5-f)snlkDD$ zyhU2&&rVx!kb%3S>Rf7ru(IJHf`r?rHlVdwN=j0)qhH8>sl@%Dr7Ud8l5%My^WNv0L9fyWf5-h{lJ2v79CQaORZRAKjhk zFpMBz)9qY1A~#L2CacJBBoGfp#{(*3$dq$j`A9oLX4~dW+m%K;&9H;8BUMp0SlZf70Xq z7JUkv$)X-uBL!X7q5@bxpBUZUU&7RCJh%~1i31xPcN2Z6pDY2=&~!HIh3au9<6oVL zObTzlRMLBElGLqP_KZIQS-O%j3}1n^n58z2+a9+Rogi4laYujlY#2+S%=Z&eAD!MK zusnw@lHSMd^85z@uzR!`1s%8pI6boU!&fd>A+UYu_k)L61;D@F_E#MkUGM8CBX2gT zU-}40i6B6g{d_;PMeRH*)e zgUB7lT~PEnPfE#gez6X7z)Cev_M~e}(cCfN_m}75rK+^$GWC+0bo<=|lZ3hDT0&g7 zj^&g2R7iqoGE=0SOzn1{gdjoRT)2 z9Adl5&yjt3FacTZZ3zL#X%dR||6}SbprUHmw~r#AfP{iH2&i;R4xv&i(k&$*Akr~( zC?MU^E#1w~gEW%T-OUU!bn`vD?>XoDyOwLIutpg6-p?J^^}Be@Qtifq9llMQb!?rl z>kKr|?-+|f=dQYrLX;g7sJ_Q1@@*FpH}H)I=nOjaGC6G5Z$V@_95rc@C}~RiOR$kC zHr%3}k2&UTq_*~~wZ9@+DnxZh{+#!-n%EVR$x~U*N=bCC>Qb@{+AXduk1_mqGuMEU z4qzY}$a*FxO(qSJ9Sg4T{95Bgq514ZfB{)oC)wY$Co+IMcu;-HzD3Ypz@U%$TQYSN zkKbJ!B{Y*Nn{&m<4=vb1pJ=4u%qr^`C`9=k@6c0rjF=Ap%~jPFbNl*~A71M)O z@Tcr4c~x1)WH{{k=}2@rn^tuu2UnZk+<%9rueG1w){(iIASuM6jPQ-#qC9_Usm($RO7KgduZ=*X>$ql5Fvm0C2iUPNHh;U zjy1DsECoETh5ym>AjUHM_Feo>ZLf}?dnjp|PiN&;WzYl6!jEP5fFQy!uY3Pu*K-N} z0)L^|Z)&l`!aSNBk}Y>J6UBGgr@pt-f=|Y_f%zs7^BKsQ|Hv8b8#jHW9 z4yB-*#>Y{>XAe3B(l{INHD{QO=YZsl2eZe`-nuFq&m-o#h^(+w((^P)i7{WU;$%#H zpZ(}k4&A$-eNJE?At+SV!;--ylj$?FTdnRy2rV8mE+kS&%7vHyEyjwG-6qFn4#plW z<{CrUmKQrn{YHIUz+M>LTu~7a^^{Amk2g}0aFUs_=0yx5go+=LBC;#SB&uEd1Bxps za1172XdCshzh;y)i{EznV7a>vMmj*G@HQ6aGRl0LrE=rCa7P`+t5S(BcYzkmuG6Ar z)%b&d-VFzsKC-t10s=r(yP%N6#<%k$E1N^{Dw_|FXZzRtQt^2}nni07O$&Zctn?6x zXFX|!asQhFEE=K$?*rsSnLA{TLJ)iUA&2dqH2YfHUh3}?=1yhnF@d~4jf}r(sinA_ zOYGth8{Xr+vS@W_Z81Yf|NaWC%51#FZ@Ci$lopeow#aC}-F`65j|aiqmv&>8s?@QT zH+OU<#_J@Lfv1-%OXN~Prr65Hd(g`62Nn=con-;BW+bqVge?#gJNa;^34O%x$) zdu8d|P6OR0uMeG|M^sq$E)Px}Nu^3`MOdUd*HZ=Ag!JLb&q}A-&8d!t82iy|7cGU|5dq zl}dHdhspAqvzSDSfuJs%pX%@Vo*v8%9znVbkW@KXS1KuiNwH0cJQpkG<&0sQU->56 z#9GJK`#Zy3;R4LlsbF>Ss>ws6)&Qerk(1k`KN!TBXYBEgFC^s3N6<-zv#7IQp3Hkr zG+EIA3q=@UMlBAA@h<^FP|VS+gO;K2-YXpUbW6$}Cnc`Vx+I1~l#mJR@!;9aRC&3n z;dnQUaC|%Q><ZGKy?&MT;tZ_#|wXvQmCxDRo&LzN+S$@$d>py-&nbvH@^Vb&!6JoiQyL}?V-=2o zswKnv_p?h|?KDWiP)wzWR8F6Yno+gzb=&fDGTs4+LVswmH7k0B!4WevC0@q-!N78T zdl0=T*oAh8dESJWxE#I$su_hcrRVyta=V_y7SXDDpL$~0Hip0`Qm<*Ql8uXt5dHiF zMBgNTkjW8f|N4#??d|(+-#@8tUM^6?(%)a=5!+X(p41@}{8FJawR;!74<~@a(ssQ+ z#D#y>r>#vwiVD!s1if!~EsSq%B9~_e^jgQrV$a-=IT~taj~e1#RZ|#l(}ypwsTW^5 zZ*N?3k0+G{kIL{6R+3~!n5oiz6OT&Z%{Oe0tw3$_2ccTh)Ju;z+)D=la&*vh;Zvp$ z`|@PUtk3pXQx>(ym8>TXbYq6}Jab4kaL&v=HycVBByha(wKE|ZX?81T!QJAVvU@agN1KeKH(5kF_V`P^H2oFtpwV|9YZ0b? zIz3@;p@#c-uokn6n6_d$j*CFkGl6NHL)S}!mF8mcVlf`8er@~B88Yxd2+RZ&`3E4f zvKVh*f9@LO6k3at30L;D( z%dqn{gR8F6^ZJ?s&H8+V2~YqZhSETu;E!uZL`HfkW=Ji^Xl-d>I*b(SP!y;awH*wc zC_b5J2BA<5PKabjdsC1%B3V0>rC4E0JmWsSy>a>lBK)_KEMGO(zZqTcw?p&umoXkE z$wr^B&;Z<|ziLjIfNBpaAQ4q>0v3g{IA&Id2({9D;j!~>OMeG)T zhG;U)|8pSCaa-+ZCkg({u6crsiL#!C*&G553&-uq;QI1F9ywL}7yv$lGd=m-g@m9R zX*fuQ=Bjbspm;sEP#}Gl1YW`DsTV^uaG)K;C|uR7S!E>>u8~O>P#G>|$r-x&Qatv> zx{1X;&ceB1g4O_-$+%z0nTQC)-m`ynhs-bc-R9fA!^e|(Q5urtEMB~JzR&%bg@!xu zm0%+hk0p>cbRPiKq@|5JlH-87!ut}YhW&W{G+%F;9Qh4FHI z^e%iuVFhYh=Y;p~{jEhj7NYs=Uj1gY7aA?YT%$w&oCq(cFH~0c=e8=TyEPx8=WL%< zirc8$rxW7%3W23%Frl^kX=I};!s7NA+FPXOGTL{d)jB&^ZEfLbdX-E;PA4cR7zzI} z_(-KhO)^!Gs_*3xy-3civudy?i~My0R>3wQtq6;Q_V#-!4~?A(yz2MVbdCw;RQXZD zNCFWy<_ndw369ZBEE>b_AWdyoS3PS%|Hm|9#3`jwMM7n>U>tA=EQsaHysJDv!c9{* zVn5d&6xS;hzEwoL@oM`l7^A@|T~s-T`>oY67lm|eQ$Fg=dlcXAi{lB@p|(WgdY%tc zh_Ut`2kR(6xMVJTShE_Fi0YybOM@cDZlRW*@1kERxJ?r8#Ho+k zvJJvsHYn|vmi5t7F}Z51HvczJ{xt#0_%zWf!Pq944Dy6VHtyR)#qWe-ta_m&z;dl+ z)wEqJFIKD@a(#8K51f~Ehs&K|h9I>k`?A({_08>18nBg+GiB(W0Chj?kQGSGrQhNN z0*Ixnrj>Br5wfhB4I2Cf9icSpMJ?c`M+?vmjEeb0Ud?EaydHE#L-@&OJs=5JB^dAo zetGrmPru%5h;2xYz;}dHiq!hN8=nq62LDqVMM+~Y5HJ$bcS;Pcdmi!PQJ>?3mgd># z9Q)14jTw|;FvX3{GmFLa_KgcS`Bl+>@8rw_Y{v>$VVItAFvUBGHR59 z)|AE9Kr$#DR4$Bag^af}A`rFLVKDA*FZGl%)S_lQTQ*UFZR`2?I!kPZc~)Syo4%Ba zK^I*-u}!y9-vX0fDcyobH;Drj1@jYtYeX-f4IH%_i*a)USE^Iqmk4Sih(uTsSNBkj z)!8&@b^ExfZB@+F66i z05z@$N)oghe#&ody?r?#rF`Tb~{5uERE4?Ae2u{usSD zSy@iOMAA5)?Ge8Sv8pEw@|Z&{Gbq57WPw;$H1c>T?Rw+lvSOy3985Is^j)8nvTx_8 z1}SE^M7j8tHLw`*#x9%W)frs`rk}9^IX?aDF>S^{S%1DcRbk!d?gE9Q#nE4c@>!uT z6Z`#Fdo{)`cYH+@F6 zIz#L9n28`x`Q)q+wIiXjtD%KvfPSiv*u}sm`aNbb4K^-M)K985XIUyr7|-WM{y;S4 zsLCfs8D<@$drQ(inazi@8^oUr=6tm(R+%%emRkL}*JtXwy2_(;*nRqW==0$g6ZNP0 zgGrOU?r>V6QRT^bgB4Hj2Y*%x%l|N~cg2WL7mx6aT=T)5d-z5gH;cPYKyi}(bu|Ay z;!}j^%aQ;2HM!A-b7Y4x96&tOL-+Gz3^Wj7NTe)w@uB+OeY8X8Vt%zE?W|ruKteN^ zo2mVBwE##Djdcv)sj}Wjx1>wz8BUb`+Chg94Q>7M+gpb>MF}<+s+|DbzZFyiwo0hxgCP?(bxGwr}(yl}|QyY;Dmm0&J{qZ6ZGEtUBcqjCuwJbiNJO z2~4HlPgNp*MMHpM#%WK5V*-jA6AVfA@=J||6cRn1IY<03@QZy^t<2uh>PcrBCWg8<=W#-%10OKy1tRIAEqu^LwtpkROb3>buBdk&&^|B- z1s)FKRN2JP$qJKY5KY|M0Kk!L%Q|&w8f69o)(dF2CoG7IG*~azIbGjvl9lQHLc|zO zb295FmcVD>1(p@)n%wmo7%H|7A~LnDFG2LeT{zesJYm!LCjUaVWs7)hC|^zZZyoR4 zg>}PT4+Vb(V%Xe&QSHI+{usr0ah2v)xmx-@7^+@7HQq>UteXn0)N&ScW@>|7X9@@X z{L{iWQE|6bs@lxk$H#|-!mVVc`49b+bkwE{x5~F* z|6^K7m3fJ2S}_&}f6=hn%I7ne*wd06(SkF~HZ{#5fmjPx&+%Qw48&eNntiBR6XiYqlXeyzF_0lA;yuZ)k};t_>LIvo;&IW(?^LSFqBE6HWv0_N=fJD1 z_f9mZK`z3X#D$m9apLspG)IWJv|A?oR)dT^>SQcB>bS0=8 zNky>5imwShEoK9krNh!c=P5a*8OZ?eJHa<62=P0|q6ONFzd!GIxGEmG27O4*Yg4Xm zZJ*}*Zz*~IP1Hd4?Y)8ao8DqNVP9k<`yrpzl#e34l++M#<$a6AeAqD~Xg7$cUAI19 zt4{j_^xi@zJF`I?#=XByhO-TU0v+aju+j&zNU+immc!^HxQ1DQ2lL|iItcur^~Z~o zC#n!uKnmyI5kj$?ooG3PmBL8%xib{>43%FdAN73{{pRU#6>$-~rdC1Jy%476P=%E6dFhd`nneRrot-R`(VfVdfJbsgI|xs zV5(4~j1C}U=x-Y|a9YtTXZHeW@I?KI-fiVFU2V}hm?2#Ye-2itWyjltp1;wESbyKV zo^Ve6W)kOFSp}QRogkd-SJ>ec#s6H)RbX~~JC8Bzd2(wJDAXp4=P_~kJl)OTnCitx z)SpjMm37vqyw=q=QLLY{(2Q9A+I5`FR-&(pP0crc1Df!~Y%NRiL$kPI9igRJM_=2X zuTaa$x`(n_!oi^I;IiZW>V)&8|AzI7*^x2v4%x~YVKOb#l*!Ar3ZV=+iwgf7;MLC_ zA(U6e;}=jp438&6RJQxCBDFX?tZnian$4?(g{jqh@a59|I2)Orx*pZ@ADMRTRAaZ} z4U+94sl`8vl1+m)qm36R|DzsCH=R%mLARoe<=ZP-j|yGmJqmhQ#``Yh^%xynhrwF3)AvPib#DPrEMymZ=kVd*624(LOcG%S{)(b!L*JiM8-n zpi(idAn=*cVsPrxe7)O8y6!*n4#R=dl_}LDFcz!H2friq zhw6uGz@Gilbba1arUzC;o#0+f2b-a;c%B?Xh&nx(8RaS#s5Q(!9U=q7wsv}aF@MwH zCjU&?q(debv2k>eE=gRjSgV9ls8vtdI`531>MlCX-DP-&Uw}GD&{Hg#d)V#9@b7f` zFiF5;rtMAgEFfy@K8MfrPD$-zQ*?*xqZJhh*?xcd^ol0-72tItc#OGP8x z7EAv!^cA1cYvs&(NbCk--$({Ty{qCFjwucGW|c~9%~Q7nrcwj3XD?nz!SWHhpu zZP`BY0LpLB#e!aDh_IS~IkSmge#>=o(y}t=ppQh=>r616Y8I^zrJSCh5q=*2>lz zlaM2g(e|%~5*7zj(qvi=9tT7r$m7=?F;W_P@)&9sjkD4iawpk`2NO213NX>&oe7d$jru*NcYYc3G>{kJtrye=drR)R(3^m z3?I+g_4GIjTwW)f@{IP`O>{Hx8>&CKJ%X;?&llj+yPOsfyYou=#0Gc+;TqIH@P&c4 zUv`6+{KG{@zDM#;H&0$ak{8IZFxpKC7Huo>jQ;h1Oz4-tf8daC8Vx;r2|rQ;8If|; zY*z3F(&I6$7TE3Pr|{H+YbO*4N_`Io`a4*zEs z*&gY9%_TYRPwg z@8d6ewTV0^+T|^vr1XT=*DPy)Y2C`JAT-!E-j+if0mg%VE$Ox0dJIJe`&N7GhF8iZ_pc(^D6dW9| z-K6o@yK{Z9E2f+Rm_zzYneP}ia?_%=!3k{dzV+_*w>Fqad0t*a!1kFOWOjFg8He@R zkP*--t^O4A^oS}@paxqAaUc*^ELUOr{O9vcq~bfV&=X|PYY2zS!!lnL?(sAv?o+O0 z*0ZC??RUQ{Vh2G3oT}&N)-N^9? zxgtCBnE9y#L5v86Qi}NQ#uoi{KZ!H(3K%N8qIcR3skS*rMw5gBEuN?4OfX0h6o8T0 z#aa0Dz*<|1zpg%67Fl2OaGVlFjj$)3wj8`03uP}!Q&csBqn|ql=`OVAhMEljnbjA@ zOJ~mOFf<=mqqtf-W4icow14#zB^2WvLz*frl~_g@_W7_(zOZBX^}QMk3WVds3bvbt zO_7x)4`ZEbDaVjAjSIt+p8jf%uaIw??9cUHW+i$-gq`u3G6;}_%qNJiPBa^zXc`q@ zOUDe(tA=T?)pnkr)1uO|>#pF5l=>mTCr??lSwF~8h#V-eTS*Sq=+ImuZXi>CUtjp}~hOtZ$$pzDVS4)uNvRkr=i=9qzv&CgZN5=SE0# z)1~Gt=xcWT^1_mrZIx^G-o)wj%;}~fBjzB2G$?E>oW+9~KyW8m26|k*an~Fqd!}ok zcKp|KT`FvP;wP+KnRRhOIdJ%`6vJwq(K8FdeZeQt(k=V+(B|@-ZnX}tQIo$rRK#Ju zPK@@XsO3U74kFRZnp7WB4^`yt^2!73npiwFzR%uVhM37jwKbm121x@g5+MJ)pR5DN z1LsaF>NabDzn@#dM(=Hblk*;HM6ywd$UA2qdkwQ0p*$Jkrr$_gxhK)xl;FO{_Di5;lJI zVf|Qhyc(rzH^fiWh;Kv;QDNMzQAPS!PZ16jYh{HVaykxlOHV?YLn8e)ke-^)@gwS- zbCq6Lr>`_8B%8sCCSPVkRLbiNSPa7gRKiLmJFewC&HNe4RMgc}9S^H|xPQGbYt1Je zfQ`m&3_W+BXR%xB4V=fpru&#CD0YE57 zbrXJ2e{K?I}Tfxd=Y2(uD zyy#afp3m0*D|dVzgFQ`a??AkwwKL~i7X{Q5^Sn9A?_3d zKkS-r;-u{kGDa8Tkl-*?pMvu**QB|IT}xW+c#;=vnRSal$o;>Gki|iHwhUIr>0&bI z7bM4jLxX5kguVsyYO&C7%M>c|8q#G1!ZoXYL#nN$Q%zj;M+1qA%}Ib&{&u0V&aR_S zFGr;gldy3qVcf=QdsWtI3hCF;nrJB|38WnKM+QD_D%!rk5+Mwmnh@ru=KtVM+z4w~ zd-#tk!73 zfKR?EGp&4zuz<~y#Istxn>!kHE0BA4cLqxAd{Xm~vZALJ0Up8oA%uVmFn2i6YW?m6pSpVbC2P zX4Qi1v)`L1r&7)0D$zbJJgM zeYl~RXDb;*+={~i_XoDEr?L-Se`_Cqgjt4A3a)k<-6LdiBbizJY|JL#?2RFRr?rmVgvayjE5$xs0DzN~ zW0?)|Hh#67XfQ&x^Za(rT-1=9V-}%xau-b-xJi5lkSZG1(BImjAK_PI5aFR-deB=d7h0^P4X!iCM7G4{D zSjSF#az4=Y#n9NrBy{@s7LFHFO)jE)stGm8RYL{J5XRZ?;N&k(JoB|sQ=ZV4JP? zWSsL9mL{v^ZjVt+^r0B4%nqc(jVgNt?KrE{mdRW-a!F(QPywevszTW7W2)k4i(w7_ z!4t%}gvp0E&O7xES5)s1V^^k^$J16Dvlp6CteTOFSs1#FSkYyODJ&ZJfih}3RGES+ zX|hD=yDaY@oa{N)~N#{6A}lvd?4dsfH;2`Hi7KVvf*tEZkQB zdlJs#Xo>c_#~X9GF@l~RB;NFSzvxTo7*YlPiTh@rXR;@^9VZyO`&TAc`-XLZI9|H> z$B!OyZu}4}zWUGUdd370~zfk82jte z(ah0``6_v*0v`y}A2bzf2CdhLg=|m5G%Yz4^Pf#QZu#(qi3Z-qz;2s>cv<^dc|$a? z$?n|CG$9ZS$u?rrn}~U*D*X0=W~@zm!X0fzv-@0^)?byY$o;U9Zh=SF&Us^tZPgl# z4rs^uRC3^S`yJG~X2IXj8qmL+Az<)g=1QF)!cyYQTt}~Dl$0fj8ofF9q*PxjZyojw zd3I0j#%ol4Z!v@WjZ{*+WbW%xb6k#C=OYkJZ*4R?8mmW{DZ`?XO90oadIZ85#;Yv7 z^_Kkx9PaTU}7iV|J zyytF5=g*wYA!^r3-tJhI( zCO=wlOnURhSMA-=Twl5hzPNR6cM*M1P&Xx_)!0<%IwI6K({#W4lkWMa452>?dz>*r zr1HS1SM2E-S;!L#l%wAN#nH5wdFWi=Ttn>N#`3wf-CyZRosZ%t#Z8x=hnLxgJoU%z z277!|dv9%Br50kI)o2;GC;?ZfL{3-WAMJr?L5t*ndNALtc@FXM;@R75x|Qx|oL|qX zw9)RMp}muMtun5wMQH?N4Jn>CM(j5PeLJ(Rdy75+rn7=B0QW$GdahOy!ViT(>IBNE!Kw&Ms48y#0QWblnS?qCWj05TkgS+?G#J1U)5fnbL$5(aCy@?_o(BD-876vId~3Z zq2@jP?w|oZmF#X4R%6PxFU6LwN%uvw-%n1PAr+k|-f<$S)b%>oWzK(#f;(uZ=LpF! z7hhbpprOG2EqR^Fp(h%}Xv1c^+8dypBfmT^a(=Y44|D5qW!o6pq3Q+;N6-DHyJcqsFT@7tzDH^Z8{MrrM z-$%sMt~EX#a}fko;@;~ulfksOGsIwD-Z8)C?m>v4ECGsQS^HaI{Nx~>B-9z*i!h$>N2yn^S@^m~uknYw*y586C z2`gitW9Xy4p#Q2&-+T5rkLQ_YPF_O{maY6zr6HNcayJ7doZF5m(r?jwFoyNqaP#s! zYIPtaQP%Y1CoF5p_~jmqpueY~*)@o{8~wS*#wfnXhHwd8bY5H&}^ zaj?lxlj(_+$G8xrkc@CRZVoIDdOqyx+BedIi1=+Jd1ZdgIeTLzoR5v z9amSVVzz9`{i%p|r#`zyoOKxpA9G)Ta1>J9#GT8V7D)k! zT*?lr@NYQ_d1ojUP!%*8_D$(2WZ6EytZ^Y5yKJ~yq?H(}KUuM^H{))_)3CoqQHMsz zo-oi6_8{m<__W^Pen2#y!kAT~*H4r$&9rlhd;>fOH-T-8At5WMY1q9)B@0{E?TEPZ zJ5{0Mox-c?Erzb+SygI?4e2s7{nXV{dp#$8o~hzA_glOq0nJ9N$A5P~#qV#OwLqD? zIrKh;mF%{=ulYFfg>a_X;3&-O?{OJBqfRjoUW$%`69Hl^RXBwTk9k2AA4haj)SMiuAH9NcWa;Gd)0JL!j3p* zPl^)xmrtMwfct*!8KG{4u5B%wYE|u78$oeC!rh`FLcE-ddgUDsgfi%Ty3y8H3Q6 zs%Fl#ZrqyP+k*G?$8&3&aY6f@Lu+M>H9@So zu`AHo#h*R$oQI=lSAM^8{iWJtjOTvzh{64}6j^LVfyC4^!|C)bO~&jt{bcEeKK%k{ z4N9gBi>Zpb`ATtc=MOW>ss7=ggXTV+Pvi#z$j7AaM5uR=)H{D1)K@6$wxFPn+-~sJ;!tbuPO0#g2Ij&uSWlRr1m&P$&J# zar>ywI4&spKNtcUq0jd_JAws;yJl$^6?Av7 zAUb_RURM@?906_%&xPw3r%qEUFY83yIhuR6lykR9NK4H6qYcOr9o^Z{?g4y3qsN&U z^O=`_UH)f2XiNIZAfGS<5ZhE(^58Z}*qGZ6=(>3f&~KgFA*f@mlEpA+0cv+Xk@N5< zR-y6>VR16n$Cu6DJQ^G9qyM>C-UI^gzNPYD>*7A01{ zYMg>{?lf}Mfl<7`<-Dg;=BPKI_g!k#_Z#KW)-3PT@4RG!0@^&$)%kHphnPou?19Oz zRrEfP7G?ai$Z?M92Se&sfI)lU65_|fUU!c-Yi#K0Zd0q%`s`q?EJ$SOe5}=$WD3nd)`vvkJBX%p)*raRUM}Ig& zj{W)E9JqsTa~w|G+@S=oDes#S*Oj1+ zU9tW(VbhM~;G7dkML5kK1^Ey$1;#3xw1roZiaVSMoZI;0(l8NS2|*4eoeAm012eKJ zON;~f+RScm6}8b8XoSVJ-fvkehOs?DbhnQ@X{D}U?aVe847w~NODtT6kualY{(bEf zLK6+S%mh0e?A;kS)zOxAj?A>{3MW7~$U1L-WFEZGG8QpY_3xh}*um0R$W!t&ZT#5y z$PYz6++U#R;~L>89Yk!!0S4WPufw)U;1uTX zN*!29FM?ic^5yK~hRLMJX@0B8;N?#FG_}61t`w1ardx6-JdPJ%ImbP8e$3)`t=&3w z6){pAVAYiJ!+bK*-0iPJN1w(-UV`rjy}zb9zV=e^4Zio4yC%IIU(G%vxmCZDZbu5H zYB<-QI}NNJ=8w>RO!b^o$m<-{DPGWtHNPrIE%zl zmTk{sR$>h&3p;!Bi@rNxp5Odcz2DRVn0rK2(n$sxY>}j9fJuZ?Vc$QrlQtx#H|f#! zh11F)Y>wFh%~v!EO0&}yk-?~%eK*}xPV4YU_+?EMJfw)_((#lt#rfFT?QC>B+oFj2 z%lEJs4Pju#Y_V)~xvo{Xg9`ndn0@~0@_e@?Pm{kx+^q5+!l3dd%;(Sz;vn-nb zS#a8g=opI8tr?P>3{PCCBGzSetU(2+svTVnjk2gMjFMn20wVNyme|aWV#CFCT-OO8 zsVyCt4wt$8Q5wlrecTAt%D@vrHOjP4f$J(ke+T8= zT*E5INYS{!zWZ`VNbbj~{HZ#!%=!hqZ?mO)m_CJ1@pZjF6H_+he6%KC(Wg;pnHF7z-}-C)D3?}C zsX6&{inW3$t3^E7bNE%)Yn{l!z?pFAwq3!%4Q||Eg+Yd&mf-yZA zd6A|L%RCsa8snRX?mv_9^J7Vm#jX8FUQW((PgLu@);_8cG`3#+lV@7qjFaYcSy?Ay?&(NfYwRdiW;J*w#ujT`J3}?G~&y0BG zaZBka@eF6MX!L*Ma4eSV2f$yP%zMkf2$KeqCwAQi|M-eE{rxD@)6MlN?fPVhh_9n> zlVu)Wi$e!^zaMgVLnuWTqc9x>5cJa_zYNJ{7Wp*Bf)?-|M(@79MJA&brAy8KJkmL@ zoUBkd>7e+Vv(9sB+pj$fD+h@pV=X1tvV?H2bi#-~=%&tRMt8!zAulO!L|GL23+5Fl z&yan)JOISh@1!l@F)`M(Hi`iDGzHO21Q+;J zuELMY;Q30_qxUNIc-X4xm-dhS>;~{bObo}pt6*O*$l)1qCG&;JdVp8ekN?n2MKrTW z^v{dwIAcJSto=xdW`3n;+;cPsBI%d#vnNc6&IfDltzbISDFl_6LuR+7A;Mxk33hciX24ObR4NlT6cQ-;b+*#2i8^Ud1{7q6ZaWPL*Qp? z5aFTXpO&@cXf?~n9jE3~60_IlbLGT8U&yzbr1k;kH$xns9_e2!bgV-{M=vkPDIZgf zc&&UHF31!{zqo~jQdl4N#=rfT{Q-RWs)JE%>Ub4Ry5CyHwdqw!FdTDBWt*wE!H7V- z75_E0T#U)Pf*}3j#eG(X>B|P;Amy2Ro;44*6v+NAK8q6{!571VN^DsZ&ioR}ZOzwq z`54-s7sh(=s=*6F36INOz{4b(Y3>=gpjEPe8m;Ta-{9r-<}!fB8*+L0C~6!#mRU)K zejjaBwJG};D(Rg#t{^RiKgT&e zuU{{_=&3Lro#+&|WobB{^puq%I}HV&#HWP@ zllRho?Fn=L$Z>(%s{wl&EZpC->1g@Tr3VvKo65Xmc3Viz&a36b67K$u#2-@?6D= z7vQ<+2h8$*Kr4UCa~}|!U+>1Zq}!#iIJYHc9tGe&Q3eA(Bdffueo&fJnu1Al$= zRka3MQ9A|pnfE`#m>4Q3Wj@#A#~KlqJv?RTT5dg=@9aU>3ujh_xjFahR1#pY5$R~w zJlH9%xO2MdNb}^;s#}?MP){B1?KK3oGABBt?peT7bvOHOm*HVE!po$hH3(R5#~fIWrl3xf4gMW?FDu zgOgxEO8+3Nwh_O>m6`N!fdlF8s76;5X0zJ%r>Z+1EqP&T>yU4y8i&RLE8KUveczsu zzcE+gy=UznjBzYkgV((Ls0ZPtNz$qwblEaqe`h=8K=~qVc0euFX^icHmiq;*k9)=x z?NoOxo4VYI`+6u<%4r69^$Agl4z9FqZj+w)V(AXOiq!s;tqWWChdJDw>#R}Jn}8J_ zWYcJwf__i8<-{6CU-p%sWTt4Je5+#-b&p%#ItwM-7O!&GSnJ%)^1hi z%FWLR0Wd$p!k>yplV*9*yUXswL=Gu3EZ0Jf;+btz#TJ1D%iZs%c@8yXRt4nhE?lC# zJF$sj=2Sy)W>C4yuP0j8y<_xz^X9Iu^EU6Zdw0(rD;2L#wq_}M)Qj6P!m7l9-7E9* z&bQl67fvH+dv8GO#!V%#UB}!d_A|WIssV**oQxQ=`rRR$=;Pk7y5^$6d4mHK@LI8O zr?JLiO1_qSPz%8n-|X?-%bR>3feqrV1~FuezE_xLlaPcqHN2FUHDf* ze5>$kvIe$I%M_qFGu$7^tI6_kx11yAH8bd)D4QsEr-1EF{ixOJ?+hQ;6$joz2_T0d zTw#^8{JhqyS+m?<|F>ejf)sL$?$%^3h({^RsC^C0&l9DKj%&x@55L-~+Oyp1uFr)| zU7F2_x>Dt1;U29tHPwH-1rBMnEHXXkO>T8)I#U!@@Sr}e>&X4@Pq~F|bq7F7YIruq zyCIS$_Epc3Do{s@5@DQ?XBfIf6_Z$&kklZ4PlUU);YU(|pPYiv-x)dS z!Zoj`nS;|P_NY*%GE`$pikL@jc{P}C?yx)r%zHcPTm1le(2D-~^Y+TCsYlOC7ZN2W z8eRX%MFeA_(NE(@ij!pXr?)q!{&2SJ>2jD@v75&Y)ouFf*};0&F=Ebjx1&;?fu4(t zi?@A-1B@6!w-S1~Ur@qk5yheY}v5V3tN z(%naZQVd1a2knv8_S9ix)e#<0%6fIbNQ~*qQfbvyqQrc9Lh&#(k{1qBr?DHK+e=`9 zTv%Rw6P}k$P)Z5WU}VC&`#X$9Jze#uaozLme6z!E6q?IStzd0Rf`lX6tXNSRJpRRf7kc4ECl?*RVUgjbhDZ z9QSOhlrGK@iU3n&Z<+`YoE+KmVF3Hz>>CAPMW~d_M5tBsVBpug%mPJjc*Kb445LG? z8t9lZCJ6s@D();uex1&9XcI4MWe!snO|iw2H(Gw5a?0emcL_z(FDKG1tySo zO2w}6uZF&*rpdo@vXQrq7Qls8>?fK`CT`mua06)enKM9Q05Q+EnZ_P+oUexWQoN*J z+*!pd=Jxv{n(@{D+j+7rMwkY(~S^7}}IZ#Z~8@ro5+ScDHs#CL_JIF!19w>*#jtHH@~m zlmY-Zgn3k@A)&`inzgf_tlbDgWz~dkHBFinmsbOpI-x+wy)^x!LLpz}f%4`pX+Qhi(eNTvC!!MQ0WTX|xR|3j z%(|^wsuD^8V?E#Eqq+K2HL)@hU}U(j&(>+QwaV`SBegk9za~l2YW=Wy2GqkW7-1mT z%AmI^B5r?Bf>O+(x3bZ^)>fmczi{X44}T@skGdY3uZnD?+p>q@rLpg+b@Q_!#jPoQ zT6{nMk5;GAbRpI3A zt;Bh@Y_l<5O#(!u#Ut~P&r8~uBr86=CIh})=>k5gTOyKjd}ih|H_l_7d+8p3L6#L= z%GO+BUiO=|81S|kf(+LT+xEa*<$@>7F>HRoGO+;OT9m_f{u*!r#hp!>d{HT(mV)!n za04L8t?^}LWrfsLn&3Fx*3T&UQhW)RRq|~8G?ELiLUcZqr$&p+FhalNDj}_xa7uzf z{=?ALm}jiwPPi(PfGwv)xyZopalN)DRdFjo`cjAFL;Itc9orPk#Ov^WAesa%?VfeF zOEkBdt>PuxMiL&8TN;nG**$4o?lODF9k}F|>Yw8!Ed}nW;pN8NV~9P+3$u~@AMHJ{ zwBT2yR5z8e0T)gLFNJ8EYG0}P4kv=HG#a7-EscUW@my~2WpMimrh4F4+$))35MITr z*pm7v>Ok#?O&yOr9U?I=%B8-eVART;dWW9}fM1$wzzIV!#pEC{B!Eo;ZkHGfNPXdj zf{LuT3b@~!zP96$wc}LtyjGnG@Kf?*)n#`5{Ooy{KNX3QVcrUDu^fx2R_sY=pej~D zL#)mpCq@T40|dGc7f2q!N|Nc00hg?)#z_$n zp>;$Un6WMOq)!W$PwZE|_`|8dtL^T6IeOn`$ogA6cNA95Ax<98XEkgkM zDPe!kd0Zz>(1hI%%#_6`Jfpv!^PuzkYJQgcs zJ=jn4U{tQl6x|-xpQt^s8n}Fh6tv$Upf;Zzy1leP6fmjh?vzn&$eypdt`ms$cEb^+ zzMzxv0iQ+O$RgM6xYu{A=(mR;K#@vkye4qXVTPW z&3!o-T9ii>*~by~B)&JWa$TfkSx`AevQP!0GA_yd6eV5%|Cl=Ks3@bh-J5_YDJjw- z(h}04q)InPNq2WkNrOm93rKg@03t9$cX!9oIlvHS&v(A}J?9UXuBD45%zmDI$94U# z)On}L>P-bfLc!{UwogU#M@O)uZdh#jURf)Rxh;hMk*rqr`!@f_@fV4BA>$s)Hl?)F zm2z-=^~tY&wZv}@f;iBrxLFMg6dbHA`rS2vv5r7NX*4oYR$P_VDt5C+q)jCyQza$K zd$C$5hKW9Kg7OI&l7+fruZ0@zvwi7~0}jXtnTiSotMW)LErXCZE^l`dP8pQ)%$iK= zWC1rhL`X9SdzgFjB1()-Fk?PnmLxiUv?AhYj<+GZ`A}j4XSE_hsCF3n_e9Uhh{8ZH z?^(1=0lzpQg*Y6SjsoTO^GsZnm$%7-b@e}=f126l5d)ZasfN)3_R=`I;^-FFB0%%tj?dZ7T58tWjZR=D z<6^N2c6r8c|`V2m>yJa_5djc`(t4R7TyfT-F&YXgG+G7KjCAA`r!|Bw?538Mg^&?=t5ZFV(u zgKTHjI8E*entzPEpx09R!Pg7lMX=E?$*d=+A%Bin6vVG~g9qqzKD&j-+ zt$g1wd~raU0D6OaW5jjsWUW&&=de>^HhuL$XQq&l$S z?5m$C|7DuVS^Qdhp4hsT*0|b`_)LeWDv~K<>x87TaMW(@9}#S({?2@p9oMs_;Qi3s z@Q>qWZJ*{T5)2dccgv_-!yFCy?-V!#6jfx~3t5}E6<=kfxTK8RB<)Q2GN0I`Q`2x~ z?UnC7xdikM_ovp{_Lw`vCWW3$ZH#>$-$r$D{Nrx|G!$Um5$!B z)~Gr6UCpL&U%Biq=mc)L;Igb!HXn6|oUs40w4NlwL#B9)!lA@nLt(fUtwUN7X7tXes zEnc8>WFJPQE!sv-p`RCa4t9b;njOqN6eCaMNWKtxOw{`jV~6lz)2N8!`$v5E9G5j`m%vc>cXy= zgp86t>cRoM3PQ|jA2&=2n!|A+lAf1Ym%Nx1=>~Z%c=E>B#E$N1iQ!mK%c=Z{l|SxP zl?;~Z^(BX2)r6GE#cP{6y?$LjN~g1l=jYlTBQhu`)>qYI-f<}u#EYa<;Zn&DyZF!z zGUd#CV&#btt>ul;Pvdckh6}Ofu1Xo>*bOf`xD0Z>(4NE;hC8z5D?PS%kfXGke*x@2 zv4xvK9|C^BaAgkYK(^|S9+9oi+h3p#`Y0oF+C}@fdzul@Av_T9QfcJ&rrxhhTT3q{ z{#sx0MAST5CY@uxS+5ja377LTMpq^=8C8_bbPsh?UWk&UaN9x)<2~Yk1`oq{PCzkfnu#Nzau<>*UJ|W@f|2SyJWgI7Q zm+a##vEt27l|#E6RBoPFCD!r3TC;g!g_FZgw*Spk0)sRArN&0*3ZqV(e9flX^>eUT zG`IqjGQ@y@{a~7_!C<-&`}uzlIZX=?p^-`ZUfh>=K8X7L_tiLX)U0&d$ans&;!1vJ z83wX;98Zh^S|CH_65|GWb`)Nb=yPn}a(eDCSAxf}4;W1zz9_ zkPJ!wPdRAG8d2pROz2Vw!t(e**IC~=AjeD~Ft;C|Skaz@<@p#J$W~T8vS@_T3iUw> z@Q0jIlMBqjG2#f?Goy`GbPJL!w+az^Whn*s+qu-|lA0#-m6z5_aiPJOflS8OOe$~e z4&#Kp@=urHzbBu+qe4mBLX1#*5THYyu-aOE#)>cX|~mj!o# z7qg7E>drpH)o&11bcNQ7F4y>6OE)f-rCsCUJXY=r)*0s(B)J4;Att{6P>mu%$qzFg zYoX5>luN)_klV`cCRgDV7E~EbwjOGB{d+C*U!vO^_xk38Jp!)_HRi~#a3 zSUrv~cTO3AJ5qf#MzU4$VIEE=f!KhBy=#dO<#%hCATL?5}g z(D8(F88%+z><-A2gaEDZ_@0sM=MifGXZW9>#XxcnObTLF-M2ZiDMC*t7wrN}ZyP!b z!ZJV{=*KjhIKh`p+HN^C1lC#=SyC@QX6XJe4Cc`Xw~tCn_`(-kv@*$849VQKQJ^=~ z+CT#K!f>U;Q{V*_gi`!JeM<%iOIS_P9ZWXqdiYnCXECCz^#%=aY9j+o-=FVJ4_6p^ z0tx+#>(f=tQ|}g@^n;U^YRq~7Ydi7v{awo&HD=+KPyjXsEfwuAqozM$?_B4HNuq15 z!BD0FZdz?MkCNhNcZrD__792}0x(-+HE?77tr`>}zVwlev2>cn`N&Ex-lf>$FPzT= zbnigU`32SHI2)~)QqTQnper|#H?P`}dNjZ|gS z0Kv-ijC-9x`iz@jUR)cQYhefP_u(-i|0hqzw%+8S*V?D3o};_o0=kRgDn`s#ymtJeEdPR*e;wn#9>GxHgj{jQ45Y625%}clg3H zm{70e|DIyON^-!3O|{z%47@+RqY%rp$cH+v|FO-o_(QZ8UQ7wKRGvuOTJ07{NX`(X z)V!n_91c}q5XS1*Jz{avI_737dkj9@>X zFWuDo>A4rzw&5!<-qo4LyJs<$?+4bHQ?0#k&ziJ1R`CPpl^ySHA5I-2GZJQ@^1mXo zB*MXxv#{^DU6(QSe?t_}4iGW|$S+!1g$?gS5PgGHfWu|Do39vi(B0il!gD2bN-5Z-n3jAX>>*@G z6Zp2SnQ$+IWO$!g>Hrdn0E>UFg}MjUm8<8!#w9P7<^>*#+E(v(;9hl|qH=M`;#u=yF|M!#y11bD=3yg_pEKfZ0ua1N z$!BUfSthS!%5>{@(mkEbYb?f5T@cM8n_f%A{hKRlql%ZWm(#yYj<Gm|LpBX?s6(`bl2n^1z9*+OP!vD}1U#0O8>L zn3nU>$a}e@iR4**ijYBVqKVA7g*ucQS}$K=<`dz}Z;q(0VKyFC6YsSP6#E(T3m>ep zX64v0C<0Ea@8rDJ<6@dkPnmGwB0a-i#51<4MUP?kR((Q!tPZcWJMPgBV$$!>c}7xt z8I5)BtS?n#;j$UJ*S_HP5Cm{ld4HmH@G>fU96#AS z>O5vVJP{RnTkeHNw^Fv0TseGs>8>miHX%yffUOcKCSFQEZ6A`LCqTiltN^Evh%0xU zl!dUD2v~jDnH=iTuu*^JIgyQ|QwzKPy<($f3s^-tUWA zQoDlJ17f4Q6P6p7yuoNAF}8(k=b~$=c79|*ENlTT75%cUadBdMgx`5TqVGwP_T@_5 z`D+RQ+weL$W3INCPEFdJk1=F-Jonl-2lx|%Hprj?m9KE`-XfcpAMkHIIj?&qH8yu! z5NMstBFQJ2Hf$TgZjU6eYWn9j zIk$rY*^ewH5jmx87PYjDKu*~Xg*SS^qA5|KSdE3@lX9N!5fbTmS)0@5i`^GQb9hSD z52?mBKF$)9jP|F9+a3lJ|92(19fD6MZ7`6iN7MRRkF4|i+0N0%P^Q`3Z|c%A9dsu3 z;)sCY|Ni3z&g+up#^2{)_B>Xk&IY*L%wgKfB+9}*lqq)?PvnKBpG;RKjVoLUieP{o zoXR)eLE!senXKtOl!-3QwFU`cUebku&ugTl1+oh3u#!8 z_$Zt!tcNS+wD+@DSLVP|4`~ut3#Rl_u?*A(f7p6K%FR3el zx2hM!64iYA2Y;D}5J}p*nzM4vSTt%*&TagNP|%H1K1KSsMSD-}ag}6cF1Aa@@TxpuXFyzy72#-hgKmvSq`J5l2lqu#ik=vXx zpu%bN<=r&D3WIxo{cCYpn(iC7LyVv@uj=Xkm?V3JJpUE6)ibl?w%v!64R9(M=Hn*? zUR`~^;@;4I`K}NJ-LN89hsxhbsN}|LYB2cYa7^|=;um_|&>*SA- zJ7x7zt%PGR)8U-U*s(vxDQZd5s^OfT-X5M+VNlIiK-%r3e9pmA&2Yml0VgX3g_lFI zE$L(Fv!`!l58I|H8u#iJ?Sr$|HL6`U;L5wWo)!tr?ko#FC#u&q<}85OBUZP!Rk50{ zc48uv!TuCPhE_4_mB0T05jJ|us6G*`N5*6EP0-!J(JMSaDR}sG6-2{_DgM5$mYEfL z1Y*astR)O(v8XleiF*>8=M#4QxG(lS7b>SY<{(m1RMelqi&4>CWrquC$+ zU!gpz!IlNcZ~#I&0svKBm(w>uDIH}f6|HlLn2Pt6|8si>$}<@O6j|#BwV1({J9J}r z+VR3*L)&Vh5epDI+baxV_A4EqdY6)(60>GWPnsTGksYiQxsL+jGlOzE0_GjaqIElx zQ{!{!tJ{!~$>({_Oyqr2!@AdPGp_(51#G69_8ZVMe7MQeC@iDF7ZD%90fFJ%IKm+V zI*m)?le)ALgc4&-mnQQFpmsKxohZpy`5!0@hO2^Iu>4TgoW`)?lQt^Aw=hb+68#J@ z#k;TF8S%rPbhD5^RHZOEzmkrFGfJR6%F)ZMhdZ*K^ zI!o!mGK04BkDdpAEm8vf@@oTB)t_B`^HEy1yDF8WD{h+a$M{(Lm|a(^auA=#naum` z1Pi|f7PVEQPtE+w9(EBn8gC{SZ}LnZgI&)>xmk3F4*JJ3uSr+p{eY*OLO8DY0lLXyv*?uh(srCr++6 z_wxYVtvu;PP=LAFA6hn619?cM#K~*!7P}v}C6Y9)-L=}jvE!!ImI$ysQH@b$Qb(ad zkA(pDz4_`?uEz0kQ*$$N!IRx_U)#xMaiRKUZ^x78<;TI4Lq+nG*9U&+KYsl(Gwgm5 zZ;`6ID{m(Af& za%0P(AhFvp!L_SZry4$D5CU-RvTKZYE<&`~7r?*b?o-{EJsKddP*<83Y?G0DSDyjnyJ|7Y{cFQr9OLhE0DoC#alS z8dM0^?|#{YI*goQ6tk0=S7?_?S#E~E$y&uFT$OLC_zW2uNO9TA(P3Ihr`4F^E@83y z0#ok{>AdG!@-^iQmKLX|E9cPff~}i=a4I9{QSUNHGCW=(k)d3Sz*V>EP~#ITV!Mis zQ8<%(^@5Q0US0N+l0CbT?;#W9ayN$K{gsx9(c_cs=P->+H{l|Z2GZz~E!eK~)KRdj z*A(&qA~$`@WP8_n9p8N0{_AMkX{>eIg!yt`px~z(Xg4y!oASa5uChB-^K?)U$^1YN z%!*y-U5u`eoN&SC4_itM?n!)xHr%X5EI7CELB}J*hs|L2Jgu=>cMuFO>v+P(7*798 zT$%dca2-*6OS5jc%`Nav57SApfY@pp&Tm1GIJ=Y6jkn*)`u2D)5N2T7^wt?_>gGE3 zrq@{GcD@Q7r`8G}4PB;Y?D@Y(@$~rIl~Y}L%guZ6z-vpiZy$tLtMXxrP zW^@7=pVdkH8%=x^j;F=z@&}KtE{MV;24tRaujpGI09gRg;gDepZJlSnrd-$*MnwhH zo~P6@cUDB76Inx#*L!|{8SgP0D;I{(yOCF#jH6?Cv0JhVr(HF$?ltn)yuFaPsbmR! zNiUZ=<{JJ~y{(W{8F9p_p3bY4H|GKhb7h7Y_xswnLWiwxdE z$?t~SdVS4l1G(Hlu`36F6goq{4w}!c#Yjgk9^1Vl9up9x6^|d!NN;0Oe?*fgVY+m5 z4zqd9Xj>b)+gOsTdi<=VpdPc28_Uv*fw#HPiho+qaNfdX@Z+0%`PIbFA%~sl$h@?t z8In4o|(@N|MHK6Y z{LvJe;9UVS+Rp?sSc}asWjV{}hL0mGW!HE~cDN@BbTXihS>xSB?Tlbi5*k0)-(crS zp}E=dX||W&MYp=dM=h-neo{bp8Tn2Z^&==a7q|zrBo?Uwv{Ari_g@rw(+@u7*Ic$9BKXf^?GA^0r&_$} zYi$=ptp!{RfDXU0lFz{i_9t`|MR?6W$oO=+N`T;rG}LYKOc<9e57Sr|@wu+w_he0} z5Z){?3Opw4F+4&@AFV^&-A*DiL6Cu`(JVqbkp{pA@f8Teh5yXnCigW_vMqkN|Nat_ zP@&*6q18Nu@8|jYT*GI7fe6Eo?{9dO#&mi6eAU;-c=86R{(AX-uG@hk7tZHvS19sT z<}3XC1vqda#GX*f%lVdjr%&Yq*st+B@FG8EcX?_uHtOl3IUo9Si_@tYTk&_+zYMoX z(M_lj-OnV+`;Exey!_wS)VA=h87;?3@u@nVpw=)TNbfD{U`>o0}R;r9XIay5jNyPh#>m2AX zN?BBuJ_-4BP6g&$ZKT|m1+mr}E?=`9^$cZ#m@F5O_^Jo5#1|dH9+{M1J&?1~OmH0H z;mBg163ZtyJ*QcNb4`%9*FBNs7|zXo1bBLiel~evQ@#j^Jv$i#t`LHPBrd2HX?Zof;&$!$7f!pKx6Tjaj55PUI zP(A0LhI4;5deLqtFYHDd1C2#QZg`-o>vBVf6LNz|^BY<2ZGgo6cw?C8>@trQGeJVS zgIiNxqzgv`)1@PUV?x(r?`PxFN3`Bs&jhi*+M%jqPs66Y3FWE zmITRm##*X`KVy&z`wK%)(^oEjU;pY_8m~B^uh^o6;==}caJ&?@dx31QVkMZ;$$ao^ zKN5steL+&$>psLlswMCSmao$%IQMSg1hvnRX2hsTQrSXq$yX1d1mC;_+ItQr?Q`BN zm-{6AI(osWi?ck|#3@R(malPhD!j!Yf+L^acWi_57|c*^k;0G`+Usqq%1fdA+@G&aZFi(N1@Q z1RETsYU3GndM0en;HpOhhdf`AV}uKoZ3})TgA=7+DR~Ih7JrNjf0_3zJ`Z=!cyWLL zh5~>ve%=UBQA8YM)#bE30GTRl?oNRIBV?sEV`D=nJa*Ap0~F!+-&syhm#edbb$Owwb4Q)jIKpavLkvg-+ZYyOBH-2Za%Z7D}!7U8+E|T zkV677^^0#_?k7u~euIX+&>FY(ockbuRs%45H z!QeBf`+=MJOqPL$&WOAzf~~IC#=*eB!gq%Q$tRvBktmKfKc|vYgt}DvlE}@QY2#u$lBVt*K^&n?x#ApX8?r z^hbSkH`JYQWG32+pXJxc>-p3K^N8w2U^R=_6)pxcBX2#O4z5rbezO_1Owq?O7Z-*= z&^Edh;C3e!@BingeZT7QfYQ@Lw`y;70BdCUd3&^GUSBH5;$*f+1mXh3Gd4YzwrzBN zykj6E^16H+M$8muS;k_MdEKS}!xa^cktHEJ`|(EO;FHE6=EO~}61+UXImf%zs9@i- z=EXbac(q+C{ssi{6dp7gj3s7ZkQFd;9C+`gfCmBCZTxF{h;DZ~=RQ{8DO9LdL==0K zJ)BHcH<$_*u$`mL9{Q&O2jW02Fy)7|X~HU*754XIYYr3*dfHSZes6+Pq;mc+?dJRJ z5PC(uUIa|$v|-1{YPVJ-H*+4LH27@eLX%+O0OOYW*0%EhQQL8$Np>jtH%LbR|6c5* z@hDFF&^JG`aYztAS>?efVU_VrirFTz;Uj^I<_>p4zizi5#Tv9b4z`Ar_*zGMO}>6U z*1G;Wj!{*Kcj()X&8?X1U;M^ltb0(8v$#paoJSnn*`f8w+S#Md2vU?_;g~s3}{RCJtD~8y#TkY z{b2-E2QURDx;00YFHWdVo+qA#){qON`~%3`-stCZrSI}f{8+MP+Ggb!zJ$_Pb{|hT z8SiC`5*B9daLl+^jh4SoIjZmK#P2N=*%o=EX8l|@L&@$jFSVz=hToTU{?v-{lzn(L zZ=T$ZlTu<>WzP@oP<4+IjU-hc3GX-^Aq{Hijj6Yly<6WXUd1kDq4wgKbi|HClcvZF zW{lzQaaF{3GTacxA0Q@%7!6ydce6@Zm}+8Bv01J@mPqR4gUg=A^7ZoDY7xb`QkEU{ z)F@*a7m~?@aShr^HMUair!x(2bCIN@FWPd?e6uCFOD7#ROMUtm_Q!G!^>%x{&^9k{ zv;|Je9YJp;=ZZhG%+%KF%##0DWYc^=px&8y*>7&1uzHwarJqFx>sco@))v0I<_R5tsn2B$ZaJ z)e82XmD8KrnD=X;6fUJR)Hms`R_)Gr`PTYV(4EW5N~BwXY*#p4@T`Z}hBEv}g^-c< zVvKlyQ*&yp=Y>`ImH#gH$kjqcBf6%$rnT?s$yxw#x?ScQN28R+Espd%^EG}58K9E~ zrssV=<*N(;bIWTK^y^vgS>2}3_*WRuglCX{EpYY;`HcMd1*=|*RZrd5Tdz3>ZygB# z1>OHdHP}q*=6mt1);9eF6|>B>6C-%j+RnIaoSH+p-QL<1xjs+uLYBnoBw4al>Xc{D z%mw3`nHn$V)w>g~)!Gts3rYmOt!4PRs=x_7AL=*zh($2>pG)LX<`2uVW}>~>y2+e= zp!EbB?Fb;j|KGnhA^@XTBW zg?P(-TPr;bCAvp)%8kw#A%{IoUFo#J=14H?KGN6;9=bVV1t{}Sup?-Djf)L)2^;>O za1jfkT)raT_kclQH32hd4?oxMuf?=KpZfF&-&WSpg|8xnzsXy6S$v)Etp9*l?ZPg< z=YD<$9aGj&OoDo`jqYi7$QSfF>?k&9GsgyxO{O8u;x&h!&X=*cQ7REB64fTq1ko=LxpN>b8>6ZjW@iNjF5f z90LSm@^UKriG2jIT19#h{;UOFsAOWKmupcyZZB(2T3LnKFuv;UTZhvwJX9VNeH3N% zAECuMHZ&{>=J7n0%Gd8YCK;lH&(%2PQh#TSk6e#=4S8{1#UUiKr?)E0Q7A{Ji% zv|$`Pd`A=#^;iO$Q_L;ZlpbkFW_@k257WJz%%5?kD?4*#VH41Udq4_a*Bm7A&Ib71 zJL&!n@wJ&`Go|n)Gv=7Rez}e%oqFC1(eS;&)GaO>=rQCM;I_^l&MleGkJzm;vDM1O z_V)3iAmV^aKpTzl&LWF;UwxHwX*DS5z>{Aoc)fwX&cuUco2d+i5I>ljOjVp#`-4rP z`_hxGEJq{qJ~(mb`_lngo#6ty6QY)7n%++)nm!e|rm5XT135Mhz}CZlc81G3(*tB( z4s{urVV45?>%d}SqYB(ElEWFB{(zN5#Z1CV`pOGTy033xl7QqG=~!Rqdj)3`n`ue=Q*WThJW&2G&ybK>+a6t#L~-+S>rr< zpi$w1#i&w@@wdseC!s%OxMJRLs>PaYasV~%4I#j!7r| zBz_RoMHDuOH$4eV!GMbQ+osv$&6i@5Y}T4VO2X)>J8bfQWMB%ymMdE~Cv5UB*zLEo zrM{O}6E~PZ7 zXcURa*PtB)HX;`9I$JnR7k38kje{0^3xUe5SwPV#wUYY+;3bkW?k!L zyJ~T4WUXb|ln;Sox4}@!Fr~dQL35R4!L8#d&15vUGIwetj^EhnTbj{017Fx9yoR1? zlIPQCQ5 z3C`O0(tU5+rtoOlFZwg1;Hv%L@?>A@Z2QHf@4tRd-zXIH0i0bc&8nH1P^U-&ZqLFv zv~fsT_#(0vXk4(5}d4KOQ+=hTM3D^ z7dePjMQOF)7pbA<1uuE+?o|ojJ>blhhfT0(F`wu`-0*;>sBLcA5tUqsiZbsEBc2>L zhP5rTZ3!dn#d7=;MbkgynMcg=iJjs~Sd4#U%ZQW z1oZO5hOmk&*t-!I4j}af62sr%*~(Wf;3tT<@1uUUyKE-OUgH;*J8DilSFbkM zw5haV=!Vd(mPBS%jx0&#hR=jv$Q#7gwT34Mb^SLnP-Z#FQFjBml5*NUCqOaAQoiO) z*}>O7K?Lo7v+gC+NW67yQ}0R;@=S%$U|0f`VjAwVN55jik~t(W#lC-Xs#O!TFB-_K zB9zL(rpF|oaJgs+DH}F!od=$d+2(f>ES2B$r_mc~%2l&gD*NAX#;!)=WHH>}4sT1x z`{Bk7203T{Tdpq-KiRpM`N6#TheDT>Zt`cb9xI%n1zmh|igeeaV~Jukt6!5-BlWkC zW9op%Xu7BHz7nIv8sDI2)Fv02YidmFu?qK7>;-M*7lj$$)K)2`chq{>Jese}(JF{9 z+-0^Jk|U)^Oc_W`HOvuT)`U$8t;I8C+S<$K@$Z1f(mK(|)E3_B5%4i3%cI#IPh6El z1Cv-AS#Sr{Y7=w1Gq_(AdD8AkPq9}+XG#R421K$eWq8c)j%Z6 z3e#dScOW1n1B+@t@1K)F%x; zyEl7x_0+cI{6)*{F;nlJ>!K%{WXqi}CIJ_6GeLp3PcZk-n7*u9ltmaJFj`BFGsY`4 zsbP&3YLbK7MXIJSe!BM84RhWNXNrTO@j$vz_=f;ps41tg6j1ajRv0{1Db%=+nuG%@ zIC#(`w3X{as&S4KcI$r*l$~VgGz~SnKUFU8cqb9|wa0W+u}F@5=d+;<@Gg|h)SyD@ zAz-YiQF$TI_e+>O9K>iOvg(Yi>(m%L2L4m^v4&fZ(s|V0DV&IPET_i1xOv~R<@)ld z6Vjk@xSn{ij5Ox8zvhj4$>$&GdmCDXe<1uif;?EyM>pUoRzxyXDJQVE!tQDxewL^U z^$o_}9{CR8d)8UjtTq#D_v=QEftJZFWUQ?^^L=+a_@KionvdHLgwfvoe`wN&4jVl= zNS_&l{xAP|U=4b+F7>ip6H9X`MIs?0*W?hA8m`{fCRf9+(Wd1tflqX7B3Q>WzH|14##XAGt**YAA;m*FyS!)UZzqwBPQT=mtYbfLr6yPEg8 zG7HzcZUC!OtlNO;c{?9~Kbi18GWp#P9zEgpv*Jr&!1aVY1yh22=`z(k>|8Hy@=1c_ zN0l9+%$k);0GzXm`h{w6*}eJES`YnM2e-iM-+e}6E&$(MZj37Y@?_18O)Tcgo*`M;82mWO6B3s#= z=Hvwm_XHNrFk|V2t>bx@&9wS5O-^}lYK%MuJD6&qA{GrrRjmr6leiW1LxEVls8q6$ z^JnsyYkR=5P2kyZ<&cJ2jp|kem`i10tLX%0;6^0Jg_WKvvG^sbznYk^e0jAb{;nhP zL9m^B9*iS^(YXu%xiJoH7SdR`?)2=9`#e&HPRc2liTXQcCpOkp`Q3pKWrk zd9`Ge;CJM6w#4sTbZgMj{jP}6zHlw`!Cn8fLI5}P({l9kghFhzDv155IEdzx-$u9^n{zNVVdhB7p|Kp3VeaMS`lOz(<$b02%xm^hBcW($L z#jvxPQOM$iSN<9vMzG;*)oiu-b6~$$u3*l+gz zlZB^T-&$LXjWwrGFs4<0hWY^Ix~e7YCS%Q1G*^<7f@k}Zc}nAkgq-Q!tLyZyH89|1TFSeuCuIv=i9l*-{-?&_|GK8q2Fi>N6jJ(X|1gc z_gi%@)s4)dUpv0HM6e5)R+RMSRM*EM%pjGo^|Y(CY&;8BZeAd`RhGBVUsB#KmHv#tozoB6WnXJvHCP`F zIUnD?>@}NwaZuoRJID9v!F`mBJ%{?{VpIaKWOK4grf!ObJa0QUIX9PpDBEmmiD=FdLEeaZJD5#f=w2UfzE(a$ z9yC4G^WvUPYPEm1zcpTjBb@pb+oe4FV+_qN5nI+mwUQOa`qjJ)U)1RZzx&`9)+w#` zxYb*02~KTL6R>LY_LoZN8tN>1pRin=!-H;+=SjyC07+K`0FDg~n?%R7FL?HThw;dv zg5>%cRh>VWJA+CT!DIQoa=j-L*y8JRyWU+mEO^OWEIqWkI+2f!1bUON^U%oG#!7g* z=|W`Sy40C*{R>7Sva=QwrIAOm^qEH6N+ZsajvJ?>sNp2aA_BrpXtizD>MP7{x@b$` zL>Upx35us*@EdHzcT|o@TwlyBZ!9P_bX3q44F2d(sr$W;#UuIiKL`pfSH>3g{gCxW zKc8UY@v_H!A!zR94>0E*dAs33+KQz)^4mWd;zm7|{SsPO8sFyydq-Rbdj6&L znLZ3WKXEin_>Y5!Uq-vyjDE|AI{hvPJhx+_=$&=JrT$dY`uL{`ZU{DJL8pl zx66sc=fexLhxkcUI248B#2lE60sLIWx;`Lk`8(Mhbx^RMuH0ndyOs6mC&k~Ms$O!w zm~rc{>7y@P%EWIy)iCg1-<9vgq>y2*Me^nYR{95WQqmZNSymz54DuoU2Z~(Hv&wbv z{H5hioN-cBzy6&Y`{!;QmDJC7mtAz+Gy#gDQI*!w${p6%>lrbg;qx&wW?VkP1ce(9s_$7*Yp}b>^F8Kkj8Jd zY=U{Fyz4$>#$7py%%(UP)N|sCE#7#~(1QH=xVWtsBGdsFKb**? z|9sNFM5}fwhgh$p2n^&=QSFuMz1b?;BX2Yt2|mZOMuIRUY6dNCAfXbOYA{3xhCe?Y zkk7fZ!J^TQLDf)dqa$-^@ICu4HkB}Vk*a$}X*Uc3`!qW|&6UQywOA)y@6i|&SGv!J zrj#GKVBvVa-+|ioH{n6S@P;ar?b@3bAEAAVp?e1{B4`K!jTh@neh+`~-ipcGxZ%r@ z%yE!+I%4{_C74@KJo?#@R&A4j|WBeQM9z8#wB<)t85MF0N+kEDM7{E33P z13DMb2p~#z-heoD)MD!YN@XItX(%hN{Vp#Q+c!l^FJ`54n2OO&JP@c!^*6@M-406X?7I%z8ze!W zI#jAGB{-VL11x^8j57|u7mS*SKM>#&zC7jhVs~Vn5gfypciD@8j~D(n>b9)#=&XBV z7pdD;p+FsU|Caq#iazO25CfNOySRxr_WDKMpQWNQqNQ>5;H5I_Sts}4XYN=c*X0im zT1C)udbgs32~iqaGT_799?eZkkPXXqO5y+|1tl<+{|8qsl9}~y`(0uk zS^VkK7SGmWCwDl*t5>h=PLN5c{vHP!p-;g^1R@f|^H_C;Zj#a|++A{_?4OJLW5KfN zd^(62zjTAIF$msn8E(y;J$dF~jp6F9b#Dq{Pb=(Z`)s2>ITHLZ*iY8GW6>XD5{iPB z0VoEr*ZR_)@+>$#r!bpOp3$b!dEZ7J7^+cdduZ3VhPmV=8H8DbT?bQmg{h@dAVv#n zrYvDz#j79j5-k=-?G9M!KhlAx|5pLkw6Fw{alV!>0;f*3y678N)jXx%39poss*iWBG~?cR(7omtMIYwN2Cf z)#LKbk(dYh3NDK7SUp$z+EEfjJe+>2eKb)3G3r(heP3)J-sjd|Jr=3&o}gin+H911 zW|XR!)Bl2Fe$nqYgeKU`XVm3s+~A`cSm>AFT?b}e!4#D@KJ~ik9q88KWRG#M@_HZu zL=m!HNeAU`-i37kQ%ft`AOog1dhk)fb^Tw8683V0^mIx%-Lk%(TJ*T6U84?}be=B&P*F zK6VlGqHJ_nk51SoX4U??#xVNUy@!jd{>(&hyoU3|yJFiO1Xu-M#JlC@Z$RWVx(>w< zAh>?G?Cu`qY~wAsw80trEfKz9HkFTO(~u+VasqO2n!(}ms;R@mv_aP^8 z+K&D2FoN1K9zB?@S=#99-k9g0$@!p zonWH)naPEB8y)-)t9OR(JC`*v-eg)`dwNb2EdT`m23XvE~tuIM}{Gt?4xT7CYuUW~IO zhb&Q3|FLWNu~cnv4c(7_ipIq!A#u$=ZCcrU-W$E_B`dO6rC%la*rXS+n(+QrS}PZ= zrC_G@AVGKHm7&;Qg}Ux^K1y4e6-{6A9|u*^h$B!CtAC&L37s&uAa38S?I^J>;xFDi ziLowIz5cON4P#trwYR0EXd{0C$fm8PIeiW+lH&C5_`J)|fa0lpWrIYj%43>Ca2QVA zYyD}ua`_qnN~#m0YdbBorQjpgmTv1K2g5hZwO|>fKQW0IS06gswuw1_`UFc-J!96p zCSP#hbGDkUk1;=3$dw&J2KnWyG&mGCa|u9&<^ISGTSlA+k26y4H1=Jzkxy`k5imu9 zzWb?W*H5~qfD7wvUXvp+i; z?%$GSk8E#BF|8O~!e07(uLHD${uyd_AwVno6(p@r)fl2h9a`_Ew}+=}2sLA{Q*RN~e715cR^$^JyRDn`FC6EZCunk!# z`D>$J4Q*|@Zq zae8Z=d$41?ik%1b_JY>Id0TJJ$@WVX#Rzp#uA0-1jk%b5_`4cHn z4gWKeFP22yTi^bsg+nl#)b-IY*`VY|eZaV_$^T*MEr6m5+xBr(Iz>7bkPs0N1j$`M zX+#N;Zs~5=B^9JgxR__xDVpIH1$W?6=vsX`W4{qY7VPH_}*J_~}lwPmFL;r=b@yaEde z%e+zy)~zutBr*mAVXxqb6V=)zmvyd4w67jRBtY`G-w<1%H~A+uM*onk)DP!lQ(LNYjr|EGJ zZobt)uTaodXMJYvdUz9J+pLPARlVu;r&L#y!fzy%alGEakY2)f>5YWo@$vEYR5|0- z>0WTU)d~wV!i}s$nEvIm3e@>0G>uDoz4wr;%0K4wgE4O;A!mRVD|psMQU0ppKo-)ni$QQ$)zhTOo7f zmQvAkb6T#uSI<4qb|QgZ>D37zw*fWZA#ch`;GHOvJ|nFcRKrd1Uwr+W)Tcs~2S9c_ z<4mb8JHh?gtl>ls%ms3HfuI&Sv0EQw%<$szzNA+El2)*tF&c74{@2BHHo?bnRowe%27F;PLTjFG?J@iNJ1NWf z4^^qBt)cx0)TK(cx5$m>cD>5n7b?$OyTzGgQMYGp$)aSySbZV}BZ3z7AoaCXh_l&{ zaB0~=jpYhGqVJvrI8kB({jLx2KOmyaM^XXf5eY=3(8}+4o;DSl!0mr7{*dWh90OX& zTzuu*b9#~S{N(_Q^&lLeixoabJLxsMQ^+XBzX8sgK(hil=Z(=wy+)Vj(jdaDvEaUD zH>tB`n;)9>hZl%`((F?hYC{Tbkttgb`~qxo`pH<)+zPV)EIValuy9_xB{Q zqlzi1;3uj>+(Ke*$(0|&^9rI==;QdbcBJb};LEgZv~?!N1GA_lWB1~uQ)FXTREzL> zaxBNo#RAKAwjsNSzIq!c+rYw<({wB5$`jTnnWFwM;VO!-SX|FD4B?^pI|vkN=0zza z<{`acX*k@K>b}IOS!Og>VGJ;F@N0q$f!wnW9LARD(F`!11@nS?!&+kio0^;ck3$-} zzMS}hFh9=Bx-y+i@Nix$&CA)2q2&2Y8IiKE2`w}HCE77+*eWgwK}G;iY0MUJE3_UG zF}K$kpq{VdDJKTyb?nYf6*M@TbK0ZLA}%S-jyMSu90M`)eLY2Vu+UbR$dtr!GVsUt z5p$P}_Egrfg|ixzn-sNl-emz486U6*d})Ut7xQDK(XOlQVuh;Krj$No$22D%kSy

    TI>I8@%muO;Fy?&Ps{zZ*<%L?UR!@O8!&X{AJ*VEop<2bdJ6#^! zQa2@9{Hi&J=yYz$B6VL$45OlSM#4T}^4?Z_leE zpnfQGVFwbx3a>ceC-y;cPQmz8k>F<^QX{AgX=z2#m)^iDnSU^$S&aOd!jtnu@R!!> zbHv<)$;Ibgu5=L=A@lw$2}TVr-H^eA7kPM}(Y34t!2lTzewxBB?6!MaiZ~rX0jN6s z?m8`JIlyi$PK?JZ0t_TD^7cm21@pA)6&B4B(Ft|~kSqy}*l`5^qpuiufJiMbFK>xn zSN=(rS@^$$adNP@l!nFw8ZA%bfgzN&H~`FW9oiW>15sNPeOM9n*9`xUXLsm8Srjb` z+frw%%xqhsGAZCvgikI$8Mwp8dFq#h$QTpjMM~F7~GLywW{rsmR z%4GDSLtdyA5@{L>tP>Z1FDUARCHOd?S&Odt_4c@WhwHVJEJNMKK&0HiH?28>+=dN@eYgMsN9roqs4Gp2K!OBIjcXUNlseT&1<*t2}o~K z)cwLOv01eDodOZ13lDP%U{sLdHxBi{pAhB~Dnyh6mrAg88C# zx6Wx`yzJrohh%b%s&daWiF!dAewdDv4P6m2VSGI1^j*|9RhXf{1Q3B zVl?&}!5|(wH_Y}`W*3)tS}tzx)G+B29D(|A&il|@xmTOD@C&Pr-H;u%JZYzrrCKR7 z*VOd*Z|aJPpF3)3=5A4A!>5<{(y+Tk1Z-WwO%-SO z4eV2q&Lhn-)^DAe&>K~6!DA`+R068wH7Wg1E0B|oR-)D{KkPt!3P4)Ys4hzfpnx5} zri8l64BPDsbq{m|UYVcD1CH%#VE-SgeRsDwM=NucS~c5r9~|(UoSXo)1sx#N8EJ(w zk(W)}rGXq$2NYAXrj^ZPUmZH477^Xh)Ch3*3I6AwgQ-56EvK2!+x_fmJxs&oU;dZ( z^%(Qt*m$MYE7#}x9BS5Y6q|a4^y3vL7j(FjY`Gc?f3BJ%%s*1qgF05}>pMb4Wn>=##}uM8#^gJ@hrM0{ zbfU_BvF<*i_k+$4#Uv%@UL+hyxJ>2{;o#t)7Iu7`uawpsK}JOtY&Dgi;{;-TMtSJV8rfj8Da1ahhpLU6BNoC}W7b5U{uxx9fs95KO?V?5# ze03wbA}HM3&~cjTOlZoBnt7&$ZLa+qcZFcZYqY7L?+8qn_>X7>~X=5u|T+{S^sQ}pS)XQW!hmZfihPN4lEyXEGWbX^0Rhz1zTQ{ zFhW9ziD-={=gYK^!cAB!Ow82ANHVH&B!Ng!!*VfS`|i5i)ITWV~{Q3#hT^LkTl&{Rt3qdw%g<& z(7*C@wO!&IxeEBpEEZk)mejqN)BJdz;_t@W687qP!l;EMLiEL?$2yGav|^GfBIr0T zO;mJZtnR8M7pu6v@bN-2&9x{N&Hq7*4bssJViliz>id&;vbb&lH&@HmHZjVVdfTz) z|9Q0FFXf_dq6|Qy@a_qdA{!XNdfy^hugUaEhL+#@V|=Q5bg(lT#;|skxYPrtCUK z_wI+Lf@?MJZyh)p!rJXG;?=h-?_w-bTGV9NYKIes%fscm;rLF z`s5jDZLd(o*i&UF))y^rmrS8kGx<*ByM|GJ2dSew$NJKamXa1XY(7G_NrkcZHhJ{} z>L-$O`uBFyDM=#+z3UZ&Juw9pnS(8ae+dTa5&FEnI0NpS$7|^Kre@L=+I-e0IUNLY zkA>Ry&h}>ef{6Aeeh#tBHG4Qbhdjpp;dlSQa;cE?WNWN|r9&Ho|JUKO@0`8l`V9MU$nluI(PBQ68M{PQehMj1^v=C(HM&e#<=?3P9qAhx=hknT8C~B3p!`zh#2Jov!`j-=_wNZsC6*tJ z5y5C0oi{NtOh;p}vV6|i#6kR&dzz)awZ)nbFtn@8Wvt)fe^gUzJOScdh43jj!|s)C z0*0t~m^XTfhA&KW>{FW}Y2h)4%i}fbiU5L@Qti5FbjFiItGg*|`@Q3rT*6?2>DCsH z9*_bQHb|EnyD#EwbUDd$d=RF7VRid#f1wtq_VCx&1BrWoe}KvO=IhOKtqKf$%IA)# zRevlM;8`Q*f}(V6ZEcfl(hSo)WS=3b72;lPwmLPq?o@HL``zDCfka(9sp8n-@L+KN zB*%P%6PftU5sS8#tu2>j>@QVPWe%{?i3xh0tt6 zg~wmcFXlNLc3QjG6=Hl8N$7>Y0HfHTQ7XGDJTp#>*S1_*_DQF_v~MazZ#A$NG}wQC z{NQRXO{c?mh}+AKAg->8IIQ_-0Te8QUUoATo${Ku2j(R^zhe)QZddgf-pv;s3RH7f zPHlHEWx@q%Ax<=Kn(hXv&;}!prP=}e_Ry2h8I z?JmrRNEzNKs?ow;kok4Z!qn&A)5V&{OD<#o9MnzW%IN*j`&*=x8fVkNu~wgMB==F_ zYeo}kA+Tl|`~@;`H$mi7lQQg?suH@bze3Nw9Qw4rP74;c-fH)SacW@I&Kj>r;P_P6E)DurR2S^IM(fXp^kheA$_gEh`+N9C zTa|2oW}p9nAZEA|b9$&;?}K~KAn}v{us@+SV7w}=&3I~#l(=?7Tc63Zwi z{yxhy{llpql%ff2-BgJ`#11#eM*s#vh(tZq;GRMEe&+C<3(j4MM19NOqn` zNAI#DeO|P&;xdg5bEk8&-0HB{Ut`@FyjqTPJg6uF(W~YHMYhOr=|0J(@o0Znp3nn! z2vqC%x3QdO*=r?QJ)ruQ8t2F2;nHW`osjw+7p#_P(XgK8josNqM;1zv$v|&{hsalz z`+ji&9ZnY^6S9Yd0X~Akodrs1K~JR&WP!XAVdu&|uL#Dr5E?eEWrP-Mx^lh%Us$j_ z+b|;$W{U%~g9XP%;H;Fv|2#~ap8*nYhc^`vj6ZP~)+`J9(z?o|$`T@<+S4RLY9G9X zPpa*CG3gzl-4TXE!qUnzG1CC==sFY;<~*C{$R#fKF-f?p*nFVP0O>}BFAm1_zY3zif2;5kBQVxLCd9wvU$);LC%xE5wnyhm zhyL}C-PcWy2QtgNPvOF+yJv*hBs5`eVFz00NQp1E*CdWM=c8cc53}N#ChkTu?5dpJ z26stWkkHfIs0f*+Q|yGdA=Uu)_pE57A#b!$`s5Kg9`Gf4Qsy(jx~BEgL!4zIMTa+O z2BJggGT8YAgqQuaSPs5^;cSO@5kF*=0-tnd2`opxyr_Z(W`!pzzC0WwoLyK1~8!T zK09M>880vYbps3eM*2OaDPd*T_%;0Jfp&87N22969Jkr?3sMHTn#hW-{(!|EE*CFK zvb6WOz6GVeCc`xx?qzMJo5o!))XKbv+2Y`v9`l3s9Z}pm7%s9;3>J!>eVh~NgXgOT z&qfGIzv4Srp@q#^Ur>G(>^y|jY?AR9wuG#Zc=R5(uS@($D%YVvPz}q}?az~bXT51L zW*N;ABWNFsZr+>ivJ=&%K3r{7q!2XUH8mYbrFfvwS^KRRDDe2+)ACdL-MMQ~20M%00R7{0QP=G+^VJr?os?V`ld6dS@^Br~6ij-5 z5puoLd>)NQu=Uj9aH+w0=ZW+BVBoPsTcja+AIs5&Ak-qbC8FR89bgEr)QM${@I%>Y zYh~xi#I&nC9ln4*6fD;d*4M3n3cPp|ULMa^=iY}HTP`+|0iYoITkxw+pLjRIhMlhy zoENvg5g;&j{i-imwiS#G_~5Q9rEzM$e4h!S`Ipxm1Qd-1z5J!FvNc7~lsO&XU_II1 z(?6tP4!L^O3}8a^uexj{8ZjXu9KD^6lQ9~-rKldaXprnlsa>{lG&@Pb3M~nK?R29QUYWe?BgUl)oPrEQ2;W0t5~}MUH;y_Tx=8z2bsE( zFvKim_7L}*1m9u~nbqhkvbu$`%AyJePqM&1*x?pqF@Xus^=Zl4?w&NFHhMM`sU|9w z9{sVI#H1R{Am~x}@S&$$HMi1a$~@xgMhyVI6R85viZx8OHo5sy?r%{f3|4=nHR~5o zwz|5pP%6ixVEVKz)XA=`7w^==ruyYDT00u_jdA*u4H{1okh79PkOjvs1*xpm?Ft4QSX^+Pq;O zQruztkLRO$hLU|8N^97ahSz0WR+E{AJB!d0r_Grb5&vh(ACmUkdhRiR%5{a3)t}Qc z3VI6zhD#LA8-p7;|7{zLC0rz~=?4@PvQh8<@h%BAnG8D~Vtm=TzXt9?W>>x8%Rlei zZwt<#)vFlZ!WQPwZD-iY0?!H^<dC$pK0YB>+5hM$*lymR{-ir;Ai8rvCH!Wd1JnDlPQ* z@|&1kL*(k#l-NFN@7Ao?@WAN+vBAxeldjg4&q)9kBVtTg0=Pk%^bo$tb=rZ?30<}s z6P}dfSzs>@tBy&TJYuuwd0#36A`jt7Kt$q0NwAJ(mQ z=jYiL2T|sE0)uJ|Ef2>a&(pvpoNj{(+UdASNmLKC^Ro)#st(F|#O3v_8Bu5f@2W1& z=V#7368t{&D;C)@K)N6>lW@?Un<-r!2kK340Pg=TdC<~`o(r?*in#jTg2@y}?2j9) z-lv6Zmv8=2;_^UYQg5Qm6QSj%5gUQ_G~ZzR+4}2+w{LG404#p))$AIc!qA{`opNm3 zD)F+k06)FDnN+0fS+rk#CD9FL{9H(IvH8Z-iavSuNl%$W9$7NXFF@YPZxa84@FoKCWH%vh8yMD)=GTs7qL0mq2qa?ZV1F^XBzx~*ijPAA!sW%%<5c%V` zx9BXgxmG){W}^w8nPjA=+bifPIJwa}us8^$u`;mT+Y5X6wT*;sgKBG2wdl`*6!@*4O!BWkrR`Pj@G63|glP6r{4YdAlUmT^CiV3pmZ@avudHMNGd7%FcS?0Pl)d~s1OLyttH3 z<%hg+N_vS?<%N5PWCT4gZmxm1w$K>xz)JNMWOg^Cs2z8~11`J=m?0;w%0U0qlmC%) zCm~ZTGd{y%z6pfmC$=6d2wa%7Y(SmaL`S4gZ@>!YM?3uq&p7$AkGy+<0m^mS1ag(G z%nYLb$@+oJC$`c^u5w$3#+(hcc?M3`CLEdw%F8JrxArX%_hWX4$DTu zO}x`BsbP^Opmjm&;nZKeTD1Fs;P>3L%$S)|56y9#Z@MsQadsQG5%PEX+-D%+y+l5T zLkQM-nx{}F3b*q(llq`KZ6;Vi=WFSWIGy&-Yq-nE!b+q=fp#<~U!{dg#CiR962T@M zuA~nLPqpW30muy=%HEtUd3gfe*`S(eoazGnEvmY}1-**qyUEi%ISqFv zV}R$2p$<hu~09qxM5b(rms^4srs zA=0m^V+VE|Lh>yrI(+^vGhNI@+H*K8?x8Pj4Xr3k(lpkw7qK}H%H9{$_`y7fxo)-X zBFQ+!aF7Ui)pwbn(k_E{F&Rp2O*ZL$U?1%Mm|S9a!WR+=B;w!`E=9oczl<9cDu`{Suds zeDOW*zs>Wg)_4!zg?8tb4rs*H^RNmVPBp0$kU#&<2n@&Y0zagQQ3Tp<0Qef~`#>i&}IWkx#_LHJmVl)FKxJGXjM z>7MVWKsJNc{4~wOmU?5L)`GfpUVJykc^eY@Z~S&Ct*Je?1S0?}&9-A@|KG-B)~EBB ztIaB)`RomdSAOFkF7}B2&&jzzGQ=RggNNz#ys>+JWO@cYEA-Y6BrtZD?B+}c&dWE;$_B0g7tR;-u+2i zc*ywq;+v>P1{L-jJL@RH7&%_XKGzB$?wGkS_vDD2w1I>f=Cs1e$U1L~X?WkQC!rWG zzhOevg4PP`;OA1!!Suay;s!8zejpl6iGQ>s0B54c$_3D|J%NZt8%5&gEGfp@FuL_* z{u)ua`rcY=Z8?qO=dFH5bJ!*gs+)!pLy8NS4ys zD|8Hh)=0R(nzw3MF79YS2IFtnc_oXp;4>YaTbk`ea>VrU3Dq|l>i=-%$y=N+;aj7* zgUcQ%?}n~Md9QIxkRm9$+{CfFO^z#g1hTrwZ?sSXa!ankP}C+}!I-lf&1;@3a>cz{ zQ_{uyE~AzhOIhqPc^?4r6few=`RZ(+(asW{m?zFMWyrVlJn^A}dkp?qCWyj{ASPW( z4u5=@gxBjqSI}NxA}AQEwIjZuy~QjNoM#XSR7*{&31frG>}HY2|zs-Uj;fEc29;>PE?{J_F>tHQm2k~>{^4=~<4X_8zL-zBB zDUT*I<71e%Z<(hbacUf#cV}lMC5ZJ89|Fclv-?F)CK4EOIL-GHM|73gH7ck>PdkhB z6NzJvaQ_DS^j+$HCOAkcn9ke$SHU!n(`NUN=D5u$2zO7E`1yom2I7In_A$ zc5Wirc-2X;>cO^hu~`%vdYmm4>~6Vw_qIf@5ue1vQ`%ijfNf*bPZ+JKA+A|!QCJ(M z>^mCFml31KeXE7&Ttnc?T1+dLU)5ZAa>kVAVU4pIKRPbx<2%v|LkF zl~hl(F@twL23k;6%HOZ?V|jILq2b3l_AJ&wv}OBps6@#C{nsqfs4?CrhcU6g6=WJ7 zEr+JUDV&r*?>_!i;*L{SdX3l4ctf$JoF&`%LcPLx+WM&&G0>U%wC_vSF4RiiCK-v5 z%*>B_k$j^y)uzVq)1fuH4-|M|=8JT@dQc?(C5N$NG1w^bMZ!*3aV9Y2vf8lw%u8a% z$nr-S?_Bwl3w0Q!N--k~1PT95ul$(k*7I~bh;inQS`>(aq9eZZ#7*E-|Fe^{vu*h- zQb{z|H!H({GBFjBS}^C~OOTJ&|We_u6U^8^cjdgeLlrLmo%<@uSih5k+&%kB=^le=R{-ZEQ>O zz5O3=e4XJ|1+w7N#eGD{1$UP#lV2UUbeq0IN=(a9*|Zx57yxwX>2H|RwU2;lD?nNW zcuwAhD!-TqH48(4ugmf7!7uz+`{HEDJ&k}Ccnm9^B8Z7wDrlLXNQVa#v*l>S8vrfU zyR9#_D+Mn;YHaR;51}i3J0?413PW9mbi)Hj@H zridESvo-i{7eUU&PYnK5wy5<7!gVvh)fga{>x+k=Z!8X@Tg7_EBYOh@eF1WhHSXta ze7-skpCUdLZ^0@r43Klj;ln@x*1NCm@zEh#ns!q@r`!Ze7(7gNBq%CIDIWlF1AZ>g3}EI9oJ` zD~0qJ`g<@f+m+R);-n^^C6j&FGv+82hoqpzSUL@dg#GlCujO{!@{522Z7G9T#O7C? zbTLlwSOR-=_In9$Ej|~)s-C{$G6CE9hbH+xq?CMky0f<-wL^)BVZVa$%N>U2TBrF= zecZ|*XBwSVgLAZ2-yY5n_$AAM-n;9lVu)$pS!L%5p@zfHU?Kw~ZMLIqy9}q8!)i!E za8XtceXhJtD|<&H{uF{E28JaF(7}4+lwEro&=UB8KGjf~s@}FQ198v3wY=Hh{G}_) zZ#Cy5F4RWAd@JPMKA5`F>^C1;zyKo`BCInJ^*HAebeIbnp%V=1yz!OLskaNTxITS5 zvQP`>w;WqK#TcAQa;)OXm3!?M2dh9-Hj60t(gr_S2^_}xU%!}~@5cy3fLlawgLvkF(DVTywoY>Mh9 z>`OW?;GBAcV`h+p?jVjB+zV3Tx;c{hMPZ8Jy%z#SSTSJ@YcJE~o9wN}u_~2={5SY? zDT}Qh9BmI8+WV&PaV2yx@equqWeENdCd3^ieNiyrW%(jnUYsY*b&c%_Iz3IkY-oB= zx1x=9Eug#hO1H%D?-XQ`pauOt@Rx_uT?=}DIog4eC#DAqq&mg`&Ll7mE1jS``Pj`D z85ped)~QF&2W{RHQB>8Mo_KpgHApM3e8m}8i)hR}Bd-Q@SG8|TA_t8E7k^2MZ(Z}__azWQqezb zH;koKG&ee=DsEcrq&&aYe^|2{OaI~_{x)H>yCvjF*55Jw92CovGldJQPO#N{dN75U zOy$XYV6;oBpmnQK9ZVHcC!%2h+Iwx!1#WjfW;XC^E9ZKD385Uz3{c$SDKwq7hVm5s zX&9;*{kUK#yzc;By#8YDmv#nN%Hh;>UR=%Q+)|op*PN(q_N3udOZ-LIKz}^8J@Ds? zAXYo7?FYh#x&lbVf4pmT*1Gj%1XHj!^Qn6ohTImQ-A`0okWkhBN9BO)Bzg{gN8t`d ziUZx6b#J@Vt>~ujRmx^<0rq*lnt(sADQdGcS;8;-1qATpdub{4%>~g*1jt=JXlo68 z{CU)_ki~2>QxXyy+7SR+Km?DZRakBDB#U|o21QXvMP+~yqr20O>FA0%(5rFm$D?!( zf6E5dc}>^CFK+uurNlanKgIkmrSm}8 z294&8vC1EbRXb=Puc&y}`R0D@)_9OaOMd9LLOF3FB;&|{Y^i)Eu`S>SaSN`P6vCs! zY2JK~p6Yz}$EkF7{la4Dtxg6LGeg_^)~($wu+2HW?P8rirOn#!2mKk~`lGu+olH!v z*?8JY)4hQ|;RSj!)KL>@WDBUY+Eu6{iMDLUG`_`wb_t=y(ED(zB|kCz>uOKTp#plt ziWN4EhRH>oAHFN~of~}q%n9)KVvv@^^fk} z&*qRKlbtLis9WXhS>g=;==p7is>5h5CCv84!7D34zKh&6Rb!b`76MD$j8&QxRa~Kkq&BG!vR<}b z@7nz9(_Y++rlNnywl?Lv;-1a=+u1Mh6Ux)By7vmLW+gtmh{7aiycn;4O3v_pi-0Qp zi}EdC6?IMVA`n;BNR7CY^42VXn~xr=`cu zH`xWh-~zH)EKzSbALtXFnrx3DQbxF;eX$t#TI%s4Qx_#27m}2=Jm4@aNfY zT>ymV49H0=;9n$HT40qo*VE6{>bNkre8Fow!J^CT?~$1l`|0BE98j1sol&BcBWUsE z`*ZpKrgFp>2>abZWCJ6JirmuSH4zPh*H_{55OV;d_P!fOkKVYb~V_5)h!^ z?^7OcnX&s#P{pW@$Na=}jG-J~xA8NF^-N12mR$%FK+^*xre0MQy8_lf?5=|w%+`k! z2>xAgX|ucWU`a|LpvmSxL>Fshn=Il?G}wy))K&t(AqnFPVCeUEvM2mID;~hllgVhd zUPCEMB*5UTxc-mX^r=0xmd9ejj8vu2&)&X=0SUbfVt>5AqVU?TQL&dY=ri(psZTYB z$A?+H^}%9UTobLTrR#RC5-l~#R*y-2w64Gt*p1HRd0(QEh%$-U9Uw6+c46gTm189h zIBVcs(cfQ|EWa12%wFP(K*~KPYto%rWK!0@)R+v?-+CGF8%2>=nh?D0L-8IVpP{zv zkbQvheg*ts-aX0ia>;Ha0F{Zq30eYalQf^FL}R+|ON_ZF?@O(qJa5m0oHXE*Zc7bL z{j_xKHSJXszRRiC7E%V)f4tmL2R=m+1+{1)PKS+--K3L4pW=kfX@|{HPG&@?C9`nZ z=g9JSG<%J%Fbm{HJa9#(KMQ9svpSQ9vo)cucDi-np+~bt;-Ob!_$FZd;Y*$WsGR!#oh03T9l`-hWJ$95QkF(ZH&7LP3QQ{gNOP7ZATS(`_Ijq+bydgT z3AC$jD}+_-$o9qAFULsrY4t{ld=AF01sK_hhxFRNBT&O|f6mp$(0b2_Dl?v%E`w%iUs;>n0Vc zn(G=w!N#yak#h7Sixb{+rH+*PBOjwuDeP~nFU?kh4tyQ$sxy+u#R}_pknwLbn@&W| zYBw5bjjljdQvsMr@j5HqSrLBRUGgZ$ZJJ>hbH)vuj=ZQ>vuvOBw)0QIox#cVVhF9OT(RIkXaIIJ zxYfh5v|;OQ9+hRC#gEx?yjn0Ns_Nu@{8-0nYfYv0Q71ZSdbDvKn`Y$Ndc=59Op{CD z2~gHPP8?1jp^*6J=PYOoY;7)GY;-N`x5l!+{CCMS(H|FYs#gTmg^QJcHo|(wx{XUo0gQKq~2*Ekg_}~F~(|UgjfkXS*v#VBK zCh~K@DNps+JApqXFcN^3Wo`Y!Tb!{-vp5LIDX08LHH-yyTi)1hLDTB>KbsA_D;MD$ zeEhlA4gw@n_-fs@a+M63L=TRRoJ$0p)yhxgP#@2vQNK3jr5w%(A7l3o19DmQ34g{E*nYnsC{*%Q84q^x&orHr^4E9VgH^7HsLn-Niw5#*0%1mJDrpb$>Os^>q-|r-_4XBzbb!Gh5Hh%%|c6uYK!fj7b`(jK2 z`8@-<)JULK@ImhWj%O_+#v!hrCir4=C=k#v;brvo_GBMOk%+`Q_MHy`Je}yoT8CUyAYY8a&Tl zJb6;a{1lG=?*rhYN7XxtPq|D2t)a8uzVQ1B-5i_D`;+*m>TzJ2#VZ zko)%Uf^c6w_Eu)dQ&GFu;z8fp%z(yKSMDcBfc$*Y#tjz_$gQfA2h*2zsTM{kni+s;^R zG4^J$?%;T@yq&i9e)YZ=0gnQy~)$|iD(LxqvU-mJ2)nwME>U7; zEx%G1ofSyF{Qst(kbzJ#_9K;RV!(eThwBD-B02#>%FU>c|E;rP>?H2;XnY-pHVa)0 zO-5|+lFAD^8IV(SDSQ@ns{H=Ea&XB&z)Btebo-L9?cO)i_-1d`LHbQ`M4tl?3VrK! zKG-Sh;S*1~NK>@pghN?=K}k~U`(^Pri(#90ox_miWA_-~s_k^^KLbORxfUxj_q!7U zfNagsWKi{q15k<2*W7Sw{#iw*z7dtrLbsxUqk8tM$zFR(4;f6$XHRiW3pLDnCTkY%Qi>Bg(B zRE@htHHBWWZzT1<7D@AN6TM&>AZGDj$KcCL6n6yyJfyf83P?abVz@?{y}>+*z{u*;e9Qb>#{1d848K z`RsVoCTqUnLhXqsl8HxxGo3?@#bLwc&WnOv|RM7hDc3|i|hUh5%NRU;Om(}z1F7v zo4T9JiMl-&J<+*KZsT(+8u-VBEFJbXUkpwzh;r^-Q%dDm)yA5uBb}YbK1R`v@6nprhmX4SZsMFp+Rf8b6fcKLd7XD6(I)*~xukSdRCsQ9k|BWeiF|}% z_M*h;V_+;?VsrC0c`GT)FEK;NezLpP4unts{7ZQfS1-_u7^8YWgbc*vp#`l&9NMmE zUCc*fdu+~rKPuanf9&a9{~4>vV=w5>pHFH#KH`kd>-}E=LRz;)G++-Vbe|>W1<|Rq zjywl}e^^bK7i#@t2Hx@--q+ks?u}+Ou#G_q`ul5#PlZ|&Wz0Y6__|A&Y|7Uog2))8 zn2iMv=@2_a&eY zG?>WZooQTaf3(uyGUPM&W3?F^<6F0r*DdTNHP2eUw7hj)f6=O2agIyO+!WkQXHfDUwqJ3Tfx0{%8r3ga;{oEX06q*x z1v0@o0c$U@N@M=Hn0xX-fCpCe$x9B+iw8Hw<`E}^gQ~l<3sQOohn2l?B{dMfR_yXE z3I5rHs(|D86><9@j&n^5Q<~7kY0I%Irjn)?RppRop&V|hl!63x7xQAPnuGEYZ^Nm> z(zp#gam`#FDOT54lBZys{EX_ky*9SK&|~3(_xu~183BA?4;o)XDLZ@%iJ?J$(u1K& zqh-eOv5gm7T#ty0i-j!Kd)&2^SN8@6oOnX89SSR1#@)++2Gg{K-P$(#h*Gdri+Y$N;12GW-9+FP;H6dovN9Rq)y8m;69vdwV`Tq z-|;sMoY>F@&;V6LZ=q(pxb<`ypvH85P%$RFxpg2>+dPtKhr1#7Vm@J2`Yx{FW@JKE*-B_!|^NDEZ@LuZ`w zEVDo(qrP^9V0--(yrtfNYz@5{Q*abAHHg2BKN4GJX?^$hlLyAkp3a=XIFKF`r{4q= zmKd#KLuiAtNrI;Je*3EU&f1nJ2_lYH1!vA^Z?r3`dQLOIceNfe(Z|44*nT%?sfCz*6>GcwrBbt0lhiY3gbR$@T>^ zbS7|Wn!=;S?u30Wj^$eJHq!uq3e4r`?SOpzSc884T=gClhMoYgEz8s^X-yXm-K+L1m5(_fwQoQ*B7*tr{@922rN2D+A zzHQ+hcz+oBlt(q$H2{e?L2$97eT)zCIlokSn`g=3eiePDrxXDF_*+~%=J)6cfwPUJzhcDf#*1$9|dO%mM=L_+xEuA{#SmWd_+QK)I0kl|4#z8qYv8 zpX7k1EDq*K|CHm@{O3l@4h2-xIjylQ{$LiDBuXs78nY{*YT1@0{w%T9qV=Op>+O2N zhH-7D4qwg5YqgD%vb*&i5wA7H=Y-WGxMlKG$4Uw3<#bc0=BEM)(Y?)9bwrpPg{tsc*j#keJxWAgSKpI^7z{M0uZ z$h_*1yYL>K8^7F@39ocpR<>Me1D&i71rH!2<9c{h^IyM9eC(C48AQo@I=DP+h2aKck&w>xhs6G1@++^p!8z8{%wqNahDY_HRy024 z;stfu$M`t`!ZET_5ozz5O-~XzyKdzr^Nc$lcb!mTQ%>p*>-uq07R!RtL;9Tt`=#W| z;$#^p{g&!X*E{69IMo`&lM5y4XkDm#NVyzX38`#fojVSGk^J5NRG$up=`=RS5}Ta5 z#(&Mgd8l^b)1TD-<;4}iHHzfh7%b|1aAuS$kd64J8a(LOjh#!J8>&Z{Bg?2mX4FDv zFw8h2Jk|U)!zBF&ms!i&#kamfgn7eEBF}#5=N9bpjz9zN=17S2DP<3_nsY(>H&0Q% z$Vz6@8>WZlO3boiEq1>$&>dxrXUqFLva*aNf{7ON)-xFE z3SjNOmnF3L+ zQx+qg`zidd;D~6aiz8!sS~th}$)Cw0KB7%-$d?2{i^EXI)Q-m_!!Q4ro%0yJconil+Y~Gi^itAsOsx;biHYRFYf zdI{u6c}cq+U3x=vbcJk}D<82dXZ?Rfopn^yYukkl5Rnp*k`fT|y!STcm#2Hm3-yCZU2oj!ljfMxq!R?2^$T5+S#wsFx}uDo zyt#u%Frg!s!+j?%_7c-7N9=}jqKQN9hhyH{c&ZN{QsUCM7oa;lfwzmk8`WBv{K`nt zGfJ1Dvr;DUpQv3Bqr^+JQkbn25mBua^)}Vqm zljv{JvHNp$aTqx);-%-kUDvO2sQNaXrzWWIy13jpmd(ihpv()LC@dMsIREf!4V0lh zgW7}q+0b~^_n*r2M@TAGkJ8#T4x~=sNkdP~;*x5oRXqNson5-BvE(ZL^lJf~7D3t0 z6^q!dhAdtsKKZ`p04_#+t5{2x3L94pn6DB)=h#{sJUdnH7*R!mE3r>Pv%tRA!{4){=9apAK86(vt|$?sk7 zPW|lXYbT(_1?>|u{gSWuj1zXu2Dv=*vE40h#6wHx7Mf>M(gk((|GnapCLB`;cJw&| zMFaS9HbS}nRaF{_W^aBbx3{!>8J$E8q)tWlys|zfn=4d7!-p;`E-LGyLP))Jysk#j zEeFMblz{mNK`~*^Z#~`E+=8H=&vQm9_yT7>1&CQi`JP>Q3TKS_Eid?Dtqo=o^d3Tkh=SL)_mXqDi~MG-{~m%@pAvZ%U!BnjXPm zS3!fYgc^)_YSkPYX+%;AjWe$euWuiZ>n`E9$m$GaONTwz(0_p(VY5Iy-M7hM?${Mwl_Wp zJ_6c#4I10TnAZ7lXk`)20H!?UG^IT4fRSrMvwugIObMlMaGk1AVYd>d3_V}{pLu=} z)pZ_gO5vpI)!$pfV5=zljzrMvm4@TS-}sfW8MoaNDm_ZEw19rTpk-h0dCV4BFAY*e zuBzC!=fjj(E)`@hr$i+i+=Em>o|1-Aq$llgX>Z`>`j$S?u>h(fzO419PD2#cmL2UK ztCZ!#*C8a|GR->vSggM$+j5(_``u|`?>u}&tHurTy-@)y$G4ff3BfEqI6v`3cfh& zd)QW5^`QwTyk9b=POtFfvLzqmXM*_Nc#Soc2BYV1oeC$`eC?XHcam|-flcNZ4aU>> z??V~kHf^n~^Hrj?x3i)8DN@AE=0xP=m&VaX@$T)G$*bOzbl=~i2@3p}FNEryj>MJ_@so9%H0WjEGw+Rq2x8fPBz{63}F&T-qRnyG4LYKg+OkO zBiUKR5iLdhlp2V1t)Fy~EWsLFh9^`tKu9?}fH`LfA7)zEY5_Rii z$dpIyDu1qM%bRr7$ioxnrxS{x6h1w~Kd1TYpzC^L+u-f5uATR|lQT!jExD#DvoiTW z%gI&8##N5(3552ouv_8&s|0&}*Rk-VL9nuGJIJ$Bo^)mqJ`q`ygn^gE{1ykMoei_p*lQaE|(bgGd+~zC#MN z;n*+#briz0f7(9S__qKNle2i$_}dsuK|teNecj&(0i#$HJA2C}_PAl9aGR&cj zgR&!a**&y|?`q@fsd}8e(HW1xCOZ!{Vo)so`=9CO-G_bTLRvXsm;-KZcN55FjN+%$ zvsW}ipw|C&i2Zo=gn(e6K%Q!AvPy}Fl8kuuKj!Y+M`~&qVzqgb>ITn{SvAJDK)sqA z#QghYSHn_M7=IjgE_cT&>vmFJHolsyvQxL31|DTf&{o4{O1ru-lH;E3hK61ZG3g(YVh1bX5fz=VRJ1)*I{rmW+1gl1s zxRm9sI{PaVS1yd=!?mE((_Yl=Hlj~a zJLhxOU~efl_|GmPP9}M{B^w`KJ4otQy4RY6k5d0L>7uqfObEhYmvA+l){+RaInBHt zW*J7MXb7RFeNwj8`(XZZe{?oz9iJn;ZBn1WK!Wk{16WI1SFsZ?}(ulBm=*^A6y z1Sg7YHM|ci)7a1J36s=A_A-JF3d{N@pl8U*#baS$j5%`1$#vgG|9S`uY-XTsZAOw) z(ls6V9yN0^EJ=dN$)hN%kKnUSKE;e)LsdYSagtoNQT1lb?ZIm0o2(!Oq=sF{IXfJ3 z{ubr`j$-a;m(yeNm3lRpDAHZBA`;G^(CA8qWCdSo7dg@S!08_^!F@E;b?}MqR19ln z_HKuO(U#g07_}WB0D5rfx+9>TS;bLpt0bq^lQk};0v?D7Y1b9hmR>h6b|_ z6|*+8wKzq_Csre6vHsW!+ch%(JXay<@25uJ?A=^V35h+&_etKGs!?@cCK6RqVIt$A75XJ7&}&2SXetiz3*-m>Sh4b?RH4s2gJO{*P|%O{V#cw z1eVlWhimvgBLypvvX*_5`3BwJ-r;@1#Zme~DrdvRWvZOM{%)qrd2mXTlXLLkMzaU24DZuZuM2E4$0Njw~k+4IVVKL}ay??1W~9 zln)*Jh({$8@N;^t(S=bu_1tW9GJP&(*r{B#vQo!5*(%Bi#scB7G@rVW;5x_3>3+Uv zf!9t9(+~uqhHLg9$2qRlB2M~sC`1QVQ9$b|R=R^#p!yiHlclcYVe6seB2H8en8e@{L0k)dq})b` zHp_~cj1PRyedMqHY7%dHoxkVyf{6x@%}$sckI$DH3vz0=Tzts&G%wO?HP}nqoz{I- zH%H+te7!_)t1PkPsv>~AGc7RXcuWIH7Nh=GczWjyI`eeFzRksJnjg+(@DLR6VK7=s z>0iU4QtES>;KS-8?p1<0lte8c|=R!R=g41XgRt#XTO)(3Kw7i7eQ5Bku;Dc6t^ z*OV^+4-DXl*-RrntI{T8=;&#Uma<&iDL|`dF^k1fwsL}PjUy_5^4VfDp6H!B!sGcA zyw5@%K6Ls*%pU`WvVB1p2o(qD&nf^>I*LylWE6 z256`b?{hlBQ-g)3cPZx$lMaXH(Nt7e)%wWv*s*RX4UVB(&mRt{LA0%Ok7~alN8#eT zE2gpSxUdVoK^Y%bXpo*<29G>JI5-X&rb=Lfr~8k+Uf!$5n)M2T32V9057OR4G_9{% zq=O5KEYEGbmPaXq20L@;xnb$s7+qw*KXb5D+#_)yhx;HH|A8<`6svWK2+b=464hcVF}K|aI!Gb3+#J$9mC{Ir}2MUQ3|VFLDsv7`kRy7 zx2+>%R3E3;6>7tTfgA?>^nbi#`ryS?j*Ev)sm}7j2Be*R(exEeTMNEHT2MiBYjuYd z95_#R#1}oyn9Z?*9Qa>`qD8WWgEJeiAfn>JO<>}30YzH$_(rmpSoEo;{25Fc#-_*n&Tq*O3IH^VG?w`nVUy%)XW}0ad z`K{uXAo;dDA-7)|ApD2>T258tfVTB-OG^fB+y^8BykY-}t7!93z<3OPANexXDNm!a zUY#3$bA2&hZFswJ&JV2FsEPioR!%OL0$g5R&OIbcJ?qJY|MD2An>_GJo01%7_4}$( zoE}GyWY3%!uZ7*!2n*atm>57)oZtS7*bsEgH$DKdRchi?&jh!bf5T=HStoFYcs~Jk z^0U27Hya`10@x@7e;G8sVSv@&NkC3LrV6GAYWDZEm#shD+ABT){EMzWXVj#xQ3%=M z?bF*-CWgru?D%fieU#hNq;x?<&6xx*_1d;3Ol(lK*{9&F6Av=#-R+yQHt!RAjE41R zHI$$oxWF)}fQ6>HNmA#QkWjp99n0P>VL7<~I3aANE5h;9Dqnw)tJK>2EdF-JWv4c~ zTFSVF)Hn~kxf8Li@>@J-`G*FAG!I4u(Y6$Sz)gwTaAj7j!gHEPOblQAoHzfI3{L-y zx$nrZ_)E7gH1H?(6uS!SfuOmF^^V;}2SO&2Zi)Bcn|c zX~7-Wp0j++&JvjzVtf;c?+?X{`%bNp(zQ;nQ^U}xK(%hLyyzJk)(WB>bhvypo=P*6 znVv27Wzx#RF-I3PUrrl^4JNFUGB3L9>+u+8D}Rk}ResL?vL3q!F0o#wBAV$7YO|XO z1&bHrzf~aE&og#|QBhFlgNG2+<2-dHY5g1$+#Jz|o$uV}qfS^DG#C|0Q!3;V1_D%7 zsV7WbCC#=r$}4_8cPhWjRYi_y%2M8v@)6Uk3EcK&#KWg}+s%4zWh$b%Tw{3Xp_rl6 zrs_%d`;*Y)d<-h7YRP-c&h1^;Ux$`;Jz~p*1^9jQ4mw1sx>Re+ig|w~WCtD?)FXil zNw@3NaL7p3)0!N22N_cRF=1{S@$z}lLc2nN?r}{fkD^{D*G3qLM}5dSxrS+o)Yb>> z=xyasu>G>VvvJ$#Vu>;3yiWA+CF#~r`GYHp^WX98_$U&p&Vt2buPm*AIzob zfiq~LyPMK7FTmAKK zeQ=JVI46$oUz{wa1dSm42iAy|6_+M0qif%B5jQEJ})&m|d$ORmUsZcO+X$ z)5&vvK~i`yHNC4)qH`sLY;R>3zkH(ty754{xYBkv-2kx2^lT|9SRBno81;d2PQk)s@>ljQ;$F<-wI}Hl9)AY0gfDdWkek7F zsHAD3A(`$E_=Kjt(%m((AZq*9rucNMp?z|Xt?gSj0}#n5WnHp$%CtVK+~^< z%&}kCaTqe=5_Q=o$MiKpx$~J%hHJ~(t2~RyKwz}WB+ZWMK z1_)OPb$q6<0X|_3lkqfx9ZTxjY?^fqnrqpqC)CepgkQXGqPGrdxqKg~%#}}1KO4+^ zs{JAQ#qSMw+8+1S-=$(X4D~v9aVAnx ziEY$5UTWT|fB~K%yN^Q#j~|#l{}lng8Cl9w|BkjM=Qlzr}#>L7kGPUzxK+t z&P_blO_GDNNS%~_Y5bo3>Lyu{3{w4VfZJlYkh<=|lP8dfl}W~VuL<43WDM7SwTCmA zPu`8nbZ{~0(SLQM7B#@41Ik{;qmzp(lc{o6B6jUS9jBd^T|S6($puO}U#sfQ9ROKt zD8%1a7MDL-`zG#Kv7Phxh4+oyU3mtm>d;@LR6#6t73crRHU&Q!r0G*_ek{Wlt9xVk=K#QQ^e&H(edCo0LK^5Lm&Bp)jHcEeyf4aXR*4l1 zb`e55mG{j(wfxO=+qCc4fXL3F=dnPr`z9;eJqWivxAes?#9m#>zEI)uxEfn$0Zo<%3x4 zsb_ia*j=ob?yZ~N0tMuw`>$f=muiXOtD?y2&OJ{wL$aLp zS6M%)ejKC2V%YyVC-LUZJqS7fC&Du6FiIC_$K$808c|i*A0obrv)2d52H;~e_sEb8QwL)flKdw`&k(=+M_%Rm#D91;Uo-IiU@<;-%p!#J4ZsvC^b+$AH} z`PlF-20v~4D*>v0gsa1VbEk@+u`d1i zT$R-XeKJS7m>sm0J$W{GUyFr3cP(%p%`H-cTaZ3l?xx2Qb389Khw%;4PE}Y%1G~Kv z{o&2)=UCVchr$pHi4Nju%QDNJUSycTW|H@((C{fM30H|VK?BtIOwBfN0Fb6PI4EzlnUM9Q0Yxr{5m@OPU8BX39Z6k6gg`RCh1Z^qjRp^u{!bXuuCi!PcP||JRywUgd!NR*cCB3o*w!2Lm zQ&XHU#TDIGp>q`7dt3Ke1U>b(w{x&pNwJFReBl0sNoK7dN6aHp6=WAhQ{nytBE{!0 zH=Kd1iuAie$E|q-P}gw*#O7NV`XgegKW5HdO`_!24>pKvC}#U^6#2F@c*N$~jy7YC2^oHB8-nS?}98C~4z!^>{v1ZF5M6jY{qG9Q}XQ zx3hD+*tA4fgB^dK6wk^Cbo2fG{X%_Qq|D6sx*l9^C(3{we4+7pTiNbjVC)o%*!;|U zb{fkVF<+sBr@QEfb80j|W1wPD5AL*tvdMzVL~R!y7+>#S;C1xne9mvWL|(YRzIt#} zWV%Uo=zeZ$6t9w}6~N1?nWkKFu{Tq^{QC7E>Ehl|^46uO&`1T>;LwKiq=i_Mg+Ry# zE?!Puq&2By=7Nyr@AOCyT?IWoz1_YV)Pi@IsG_C);lc5?$>?uPPay{ez4P&prNIRM zRl|jw!3TSwNM9NRNd1~_FDay=%LAt!(~SaqN9(1O{Z;8 z;DCH6M8`_;)x@W z=2p1s$GQ`dc7@LqI0d-t&j7`?3+X8Ycv-&Xd7A0c6;^Ti97}gfD-`D?*L{DRjb!_o z78@f%PsGk23!UsVaw9~8Ko@op^?NzFwn-|_pT1=lBVBZs zTj}jXB74hYI@Hp*RqciP(qU=P5~I`gcB&bL9vXw`@NWS`@)Y5RE%Q%lD1|!A{RQA= zA{No_TkGq7uJo#_L0#obA`R1mT5ck){PCFz z$Omy%1p~m=uck_&b@X6C9PEAKj@@;OM6o(oTP!XJTUIgLJ;}xP@E~No_d>{K{m+lc ze!Y*(FtMBUFmXZEo}%h+3}_zI9zFroH*6ok?pFy}~UAGE1Bqf;HAqd(uDC6RL zK`*$P`nq*-G_Mw>W&4KhGD&Eao`%gbeyl(4AK})n#JP>;H3r zF1x?n!$MS=_VxTL9RQ`A~NXUF7%GZO}6?m1! z+TW$VRF3$`v^3KTMXCA;lDLdvwGC`q2W8YmBk!U}?^Tn48MJ-Zb)x^osA)`xr%Pu4 z?$BzF${!h7cm{Q^n@eg6+xsUkFRg{MW0=DPokw1$KoEKtqn0j){W73OQj*B|G_fMM zjra19AYZ%oKSli}XRpnBt*ZtrW!IPApfqdUI~7Z<2JX2zp?Fz>W>M0sY{^w{W`+X+ zuN4-#WI?(h0;_4=;aE^_@wBZ@4EF+;vH`?m!dJ_M6V7&KEAT!(<83(&6a6kf7?U#^ zT_5&)$GOlzV^dyeeYrdIIyD(=EP~Qh!}zSHeV7AJvXr}3$`7YzGE7U1)XI!-0twmK zuDR_CdnlfJO$P9)7k(1%Kz-{R}lYI{mfH7=l zJwDp-SiJesn(T}W_{LsmBBlM$eYWE#oXhFeBO=CNQY!h^c6t#Tu0S~LfN^8M$OO@9 zr^Lt~)w|Er)RA1=eNZdJ6|wQehY^$~fLC)X-1s+jMX~0P@`3FHIBpsNnB4ZbhZHmx zF0p^QbB6WwHE90&3G7?%vbIS>uaAh4B)r&nY#udG)!&4&uV&Sr2=9*rgl47fn)`AE zv%#1-piqKtNAnf@pNAnrhb|W(dYwu!(NQ5}{tr%g=VB=OzTF3xX>eCosFo$w z6*${pZSNn0v|`;q*FWx2KV1Swo=a4{p{><)t^bUcI-&rA08#okD#csaL3~ zXP8@u59h%h(1gYg_LPgrL9;egQphrNWXPTJPVTbq5AB>Q%W#4yU1MfbMWaGt(R!!K zAJ}vs;+dk>PaW_I7Y9&|wGmNcN<`;5Sog3Dq?@!bvthT47PseIaAqD-h(yAm1QOgm7 zwhQJ8L(flg?M_L@yafYX$Q3e{eVyH#%D#8JjG27 zL&7~nLK_b+bU0=*QZ>dvMaH{PxL=3Ibr&quizTp#or>aamaw zH<>!Zcjw%ZxP*Jsg5^H$sXK!jAkE`IG1c|;ZJn=NO-A!sM_JT~KGf(nzB%;pASrHZ zo6HC!{C@J`@H%eYoZ&#RwxUb0r9*-}IXmRbp~QEFL^TziuRIA@W0HAdIjo$Skt(i8 z+k2>Ys-kRBHXr7XiVU(8`7ft*-D`JcEG7%_Z>~<*!5f~l{}x{k+{-9UrXI+jxDxT0 zk3B)+4qM`p8&$cV73(H}5AEW%vs4$X9j0AlU<<+wt?m%T*9v@>o>uIS+M9!&zAtqp zlL4cI1OViG`t@4;mG#8EFyV6sF2m}h$XpXKb<=4D0FW}kjcm_tPJj^7flJ-u#A=EJ z!Wh=Z7eq9Fb<(x~Tye!N>Cz!@A~QbPs)juCd zDSh_7ZWn?x)(systp3RCc)FFsrZi!=V94c|K7Ym z4-S0m8S|95V4UO2eBlJKSJRTlKlt{fx`=e4QTX(HJKEzwW|3js-yJ zj)p1{Y&{C?mOsq!gMb<0aKqO+hD970=2>IE{j+i8qVWwRYR#bvZsrpz*E<7U_I0bo zvS(JLW_(sp1BaYjY$n1c#-2PW1O=5=e`OxO0Ty?3Alv-(otZrq9j;xa3?@6EEVn=g z*I!@CjDbcRaPaRQH5K%4Twe^Ny;r)lU+{@EbMrGB$w}|L>UCHQcD2T~QVQ-YG3sR! z3PVsLWDAlc=6I6AUtofeeQ_Zb7=iWaL-pJ9wbjn9PT~ZNc`3aXFynRoNddr~S*XGs zmj-+m*M_UOJts@???cELnB4${1cn+3>gLA8uO;WVx#PtQuP8N}nWVE!q67PvI9~gg z=Xm6QtB-;GB~29pGx=1(b~_J&{X)$zYAV>?wD>q4bc4fx=E8RW%W4VDAfKqv$wlb~ z#n*V4biN7FX(DE`W4?}x*A-)4kp6K{+s$jPyfEWTM@cC7W`Z*xm zbwo20Th(myZq0h#2_o`n7MbU8JGNlaEXQv4uk|yALC;rS1kn`KUkhe`7^}3^FGcm= z9V^h022b-(U@Hyg@Xxgc#qYx?Cf!7sk&Kye6E$Ez*(7XXs3XNAmNLzAIh;Gm{*^4} zAc+~4@8r7nINE%vh*m@RRa|Ql73ckUQ^%Iuml**Oafv(X2hC8p)>v{!!CP_N^Pa>X z;SOoEVzy@+Bgb6}g~HGuuDcJ%Mmm~({t`21q6oeesAA{2Eud*Qaw}5i_V%a;9l?e3yN{50ct#9*vboTnK@o&glSZf5~ ztX*cn%0vL;P)4e3Km-#l_8pf>d-_t;xB?;Mbo`hP}>;br3Vy=bdWiT zS@v7}GxHc6&`6g})+jdZXWkER--4?Ct@8p5ytJvsFj{$-uuGw~kb}#q?yzXDGm+?~414dxctmLu+e6gPtx&&tiHxpz)CE1KENBp$ni%YlREZ>$V= z(Fv6*diiVp9AWlIZdJBRxEt|KT}cAVWp6kwaFgkLlqZu6BnnSPB#MyTe^C2lTEISQ zA?8-?_uLxid>8SX@dWsG*RH1R)oRVkGV%ig#<08f{Voz-g(uU$c=2Vb+!BcWZkD~q zyUY^1y=et|7vZxKx`qbnI^jx}(J(zEf;b;n`j2I~fQa{rsu?DaYX_pn3bPqRDyS9Q21b zr>4|a#|1Ng|EWd4>GS^kIWwNby=VTSTh;Ut{^FwLG5(+zjSG8jG+f7Om-1lN+|NQg zdCOA_eaW_)>)`U?`-noj5i{_kd?+t`_OwD*dZyaZX175!N%V-_Q*57X1(6tZeWmjn zfF&Cfa2nHG46nL(gPr>JcMra?~mg{de!%$ z%D#@s4@BnmNHEGUMk*gftv4TVvtTG$OF;rpBNpW2k)eFj@%|lgpP=l$$Mn&QuSRwF z)AR|EdqiPcV(tmje}zS%Vk%IP^e@KdY#N|FH;g2b)rs%>E30uNfpSlJkf-!uf+F(WGKdp8!J9dRC>Pqvv(EdJQ_j+SGtRhUgtr7x*okCw^uX1Kz@ zEp=o@0DFyoLy)&cBr%SGfG4*W_5_q(_n_hYxGv3Q$+!cRB zL35V1(Bz*RU-C;Ky{&kcLaf{CR0S!voJw~xdXsmAiL$H1P zq+fghwCRn_h6%ASzw7tgf{wdalVx$-UZPCO`8~nT8^et5duOX_vjtR>rDnb#mb=m+ zd6W3VpK+o8Z5?Q`2ZMkP=svF-FNII zZnK~M^(A_Csn^vJF~sZ25q@<%b=zNLCZ;*J0xL%+%-8;RnE!!2@l=!kg;cmLMtBjU zMlMO6=7C5Zr1G)8RBd-Mn{;0i0|d?=NX#CZ)*6&*GN8u*N#oxb&hZCZGr_+_Cu(&L zSzDmrm$s(uUg+}^UW*?@teScwUD2t9rb-#VT=w)__Wr~g*aEI^FhG^hf!CLMJ6LD8 z-B5+PQQdMjxe;J^SzAjIAIkVx1lZO6XP|krKYvC7_;Pil*mWpX3n0zWK4@AW-#xt2 zEc_yVdvfgs9xH{pnT@sp_~FB3>*-2-;JqKv;*fYN271@K`^w72J|F;k;?{T!oU4z5 z>aL%qcpZ~^BI{->%N(eIfQDusCIZGT(_+u8F~%2AjH6Dk;1GUpVeMC{r3|IbxO9Xb1rqH z(2EskZ3UFKpy@`_S?F5V;^$W%@od`Ta{*NQf*Rc5=`&>vPpdtx8G`TxQ3*-uS4#09|QOAt&!K{nbV!u z6`8Q_vnamW^TgRz+}SRV34z5Ky^EUgcH_M4A=&PDVrI&2`v(Q=9#VmHApjgC@vuxczb2q@eS*KW z1uJN()IyOOr*ye(!PEmlm!4nTTQr1%$OTTaptIDq`@2`9q(MWWMNm3j^*qkQkuJUJdGlj_$>6tXycLP{-+hC%hEy`Qpa-FrY`F_` z&BQ}uxI&C$xzqvC7iRmb_*VDP@`Pm<{P6wX!7_rm?zjbu^jsZgTi^}1QLcl_7mLb9 zq_Z1K#Ebdu@N|xq+7q4v)!a{aoBitpowg}UOK*&?&W}LP!vg>r!Akjft~Jy_n*_k& zzvO4@){eGHG7e-BYL@EBuUbo+*SwwekT|rs`0;$pfn>IE;qyF)o9SZw>z7dngkaPG1S;3YskSW}ZV(fTtcgSWT)NyJOpmbzNE z-K!0h?Q*gEUVFc8^Q$3WuL7A^M(;3sxrP#XG1?$*6VO=HnM_7O<;nll2_eE`*hVqS zrW0zM;>PB14d|L(!Ef)(b%K?s)inPO*E*>c2@U$E;Rwdi+eR}cAkFoh-H2w?z z!>ozCx?}7@+JhZ6vX`d>Z~;9uB9Ei8uEKQ78mGSimK&SI7AE|C098wm75M_Q76yF) z@^BsuxqwSr%umK^(Qr#HTXmkc#Q-M{TM)N%U(%18$H-zyEEcWGJ5^}4)kz|5ef)|f z1$F1yTbE!^KiwLV_ceGtARb?AXt)|wf7V9#5&j;SB6bJo8o>~V%WF9U)}~kf_vU|8O?rldr3M1RJP|R8V zy3?&~jiy1emENqx@p*e7k*sn?~QEVT>+PAqkoOgX&8$S|=tPOX%`jQ0Cq znqvcNX-mVPIej8 z*kdZxJF9S67?3v9^?gL#RviLZ0e5t?`yLKwdlCamGoG>wNc*eRLkT|2c-F9JS?^HX zk*eo@ls+46z5N8>9}T->(}93{TY2;$hJku&e<>1_+6*hn`}V08X!zoSaFoIAtOTYa zDJ`ueLj*Yu_)Ot~zqSFfy%5?>bSqWdPK?J}dmyKWalAc6pQxi=Yeu)hXVQ@QVY1eJ zDO|eHeWLi2%g%IF(M`i$05H=eN}0i{B&GC>cZT_(-}EFOiDnRpsY=<^5JE-^bizjS zG&-BW`4O1yJguuqnTC4Mog@1=PtdsB+B`-AwANIE?NHzCl^$$k#}hHVO<@1iZ780< zfARnis%$vUX|+Wnn*t5P==a)D{=kXOW-(bw39zOSt@RN$lKf#~Pmw}w5crP(Hdp57NvY!A4*;3x zFN;OvcXT#WiCqBj^$Gm?Lb7Ln8tW7t_j^Yi+H(U5W^`=U5RZtK-APbCZsN*@Wg5U{`g-+cnh2h3?4ZC@r-O4Zag=S%M3Y*)ASP9I(|cI z-3-TmJUWA`j%G7dxfrLXSgw%n2a0TsEhoOWn!f0q7^!u7^3}k#tZ>Bkd(?64!0v^6 zIdpq2c6m_)f9Zg`t<#hkpuVtUhQz|dJdS*`1l&w$eztrm|T<>n3n;t?67tH?zWw^@h z?rrSqb|moRuvm;Z&(81vkzq4kVr7F={vy3@1mUlYNB6Op-EuBH(F@v7uSoc;$g3UJ zX%L$WlBMQjxVMXSnEU?wiLx1B$XjI>n)7}t*cMD00;Ke=g*>&QdkB#WmV0*_tGK

    z)AZiyg`C_mUGMRF)aWHU@)mQ9D9&m^Qe03{qKD zoLXN_K^>E(gZyX$|^u&`6Lt8w?ZbS$%=_)?z^< z(ncP^eo*%0zFR2ka6p?s$5roJsk9aZ^tcuuB)3DkAlV& z&9PJ>-xB%BD(!eKHPxG$QkdT`oE}dC>8j7auxR~J6qGlBLCWV=ll&W961RSlty0cy z@QI4gbx!@;l3zP6yWZFbXL>j_|2ld^nX8OL&lmp_G$GXr()9&&c_KMCKMrqg%GbHm zN);k@va)YyS7EBY#OxZf6-g{%6u?p=vmzVE zWBzhutbjh!>u^m0+tv0txMMS7-E8C(*z6=tGo-m5nVp_*7Ca^<&irw~voV@?Tb%T; zC!lk+k%(2pr^al!VY=$z4U7QkdO-mqr5l4{$w?g*IKGsDx}c*22zfV;{k$5 z-$N;cI?}uZZD~ONFxDT{2g|jg2HL~j-8*&gWK_yTQ8LUJn;XWn^H&31Is0>X=Na_E zskhSS!r3Z9a&rT=M(eq7S3m;+*YS4pMr0IWj8Gw8!niQTfh#F5lg4sLf}Ib zLns)OeVTv^h0}c0B03oGr*y#qa|>c(xg0OjH$kS+`N6rK0%5uptidXe1l>;`!Zwfu z#O&F%UT`5->>3M=KEa465b1;7OG|vesWzaz&sedehr{hwq}r=pFkaea%h{#Yyv4WtXzN1@BMF z^E#(|y~AQ-Tp<$wmAKq{FzDlTJ4o6iGD8eH;iZ=)Y4+lpZR#kFXa20*}$Js?{~!1F5pT zdv}7NC)%&9C!f|+2pS0B)d+umd(Mv-7c{u3^GKL)6!rNKvp^9ErTBTTBbpl{hUNOf zZR<^EBDXQ_WQEr|qT^QLNc?vn6?z9=)z>qw^d{dn`E)L1W@Icx%D?l(m$S*YKR;J00{j^AtFnQ{A>kdUq32Y#gM2m;0?^Ra4-*@AN- z$)}XUAdeN8A0`s2kS<02_}#aiBuGN;2SVmwN}2wKv7%RpO1Bk6N~)}CFlDP`?#T~Q z{4${s7@VKej{|k|VQR>afI(zG#-Y zfFud9zMp{VK2qi^71Bl^FYF5<4{YiZI#6`ao4k~$0`O+tTv@{l<$U-+gL0$TP0~-_ zzLdDW2CH&DUe<-ojlsO^*DkG#FX%oO)H%fNHVOeEj|XlRjOD zYSAhsV;H%VASslI(=DzspD;tEu=&DY6Dg4tMG@d8{}xZBmVm%4ctvwN$@!_yAC{f_ z0@l~=)YfDl!pPhOzsDm1Lyw94+~eW?xcHrh1CR4L-*5L7HjWhmKPqy=6(m1kYmEsx1em&yABwO*s~rLw@aqLxHyt#E+zrD9dcZb zRbi_q4;fY+PgjVs2s^@1b=u$QDN)(giWL&aPbboHse{!b2luISxw?8f`5!>Y>V?u~MScn+vJiw1j4?a9?=!Iof z7>WO@Wp?Zi=WAo(Id473ve%2o{ns9hY-`_3AKyR2G7vj|;xqQ2ptslN$ID`8_G3&}-;$mikb?k`It< zf+tE8z|u_p6u2XbylymS3`!nO zcbmgxHzi%Xfwl8wmI`#Hs+VMBf24s+d?s^#J9Rx~6%NUrn%SM3e;Tgg`#b0U;`39^ z3ya+RFM%pcIa}WRrIScNjg-}!@b(WVg^!kox<2GspKp>Au{i3-4}jws*lb$FYOzij z&5CS0>^n)~x9^;c$xsUSJjS6EcE3)<__{f^%qERR$h71qg)fO%^xsb?wzl|u(2@91 z{O|40$J}+JfT5(>{<}8h$u!AS;L>)KT=?;^E?@ZW>HOhU(SB0xsq91R#bNS4#8OYf zcb~GHhZ+Th@I(nXt#QawUU;fePWo#QZ`+8}Q>VfBg1Z<8F%;Z*sKY2Al1moz1*^7R8(Q?_2-Wy z1D}Tb7HF5W!P`S0ZwO>(x;e!z+s`*izIaKOHeZWlFu&O5@l`2PZgT@tW=0rHD)MNm z!m8Il*@}k>lx0^jn1Zi-`x@q9vRgBEPrn=Qn`7d?lx&HYpS~w)j z<^Py^%c!c__In$HZV{vrkOt{4X$2|i+O%{@Zc;(ILAnH_q#H@;2Bo`u)0@u!#q&P? zp7(>pFWw9t_gdFFuX)aSOc}McA&(TpM%FH`peKT^Qg5hz?E`>l<9T+u`LGTguYQ4y z@f)1f)TQ4LkJpDwiSA}IC6bqAKdldi6Fgi*y)`SlDj?e57*vP{ley#ky^cCn!`xke z40tF3pSE-aX(vmqQmgOQTmdzN=$J#~%AORgp8PW=id*ek_S-Kj?^mYuWy)+|(#%>n z__SU;kL(&nNZSK3JJ*IqJLy(Mqk3ar^%7u9$*0T(QcQYBhT!*sc**7FtBpa#cwHSrpk-}X2Go# zOGEK*knnZV?2A=zk)*`cyf+NQr>%AUa^hdkMq*_tkdOtw;ybM zDi;fAYh(@>A@zpGKZMEk%9;IY4!Be^)m8Bw(7o zDArhy2_ibMi0oiTp^lwr-;Wn~D4fS1h9NJopN8JLlc#kVF+DC)xd3|)7FIo5u9%}B z7{q&x`Fm(k$Km9BkqElwar`HFlp6+Zrx#%&>lX|JE;6d+On){R0PJLg?x;G!UZ1~; zPE8F1*Dcb`;WWp0oDPf{B}K=LkAiGiM~bvKr!T>J+5S}4ch`euzx+uuT(c>CpRym8 zpzlHz7316eJe@IjI1%dTK^VG#cH{k0zgZ|>JBKQn+a|1=0_K~bzZ0nufFV99BbW(k zs{hILAbfGgV5cDaYY$}O70R+CmC6v zFAsNFiZVST7fdCDF$g=^d+Y>7A%{^+lysKpeQNJXKx)d@wwH24cNh3)c5{ok!rbW} ziX;~f0PeE-{{t}L*gD)xU}rfFlLs8#33xsou7Q14a3(P_A2!qD#iB#u1o3r5ig1cZ zvkV^y!5vW1w@yz+#k+8ALFhATlc#zzjlc5a@=~jxF2}yj7z(&9X2MB$ZI=$0+iSK7 z75d}Bc;?2gHMPc?yK;(K`q15zSq6^U1buHBo0R99VDgS-98ieN1%IvIWs?DYZCHR7 z4ni5kxnwuTauqhF53@Rue$$?R)gThRGoFSfapVAlEOJ5>r5ji^VSN2|wuz*m+1&u} zANi!9AU5b5G8R}Z6b0exCAC+sFgKN^vg?UZ%MAwYntog#B#>v?5`Jk^@pNrEm?l&& z7D8+7KWgk3dz^aA(HfhueI6@=EOrYJg||BnLEXai9Y<<9r0agm2W>nWxzn1j!xKd zZmhS_?8yYYYK5Uyp%jJJ*`dW&at0Qdi^b!SS|-nm?e^5+syWCF*`WXJA{+E=Be$`G;2PgzJUQRdfluN)~qu9*d3m9_GA5vrrv4jpQlESEF^;O3bqYW2vL5lk*k z16V_Gd>63Tt}>r_&J@o&DG!6sbG<>~r9z?8%upl84cmw$go}g!yIa$@3|dodr392b zJWNPfTTJK=;%WW4ClV@ZLTfrFrvTxP?Tqspp`UVQ3K<{sjC)W;fgJS}Xkbd;KFX71 zTaGhUm{53grL!=F0~g0?FCBS;R1&vU&HEbMzH-dMdsCT~uu0=UEzJe~Iw7a04pS0= z1Yp#hDl^PxwqE-Dh{Nya$r>G(3=;eBSO@R zXR{O+U$EV+DjqHtsJym#_unI4@)h8Mna^(c%O+>*e(5r}w6^h)19Gv74*%`UghDR)Jz1#UJDa1YIgvQSb! ziWI)0Cm6`Equ=ZbC#i6ZFApRWn%;9rx&hwwfpkvnTm3DNh+Nkh@H`#ot!sL3I;UJr z3vl?)ysj}jz|z1d-dI}~G4FPW(#iMAf&gY=`@);;ia6yeH!TcL2R0D5@_lF$ z76tF4E39U3s@keHa{x3+_SWpzx$F^7lr9>`infut>|P)O3letPwWo;F)}IBba1@A; z)tq5q=O^j%{fMX0Coz<=ubeBrxUpBu@aWD8nsP)JEy;2MvB(w40w%tN`4Q=V_x#9m zF{hV9_{EuAK|5SKj%5w^w}O^SZ{@`e^L)$OBHW68iNH9|!5`&IkJ=r8&TdL%(3_1) z*R~)|xNUl5yAu2Ag6sMz7W5Fuy_u(Ut>-0|%_jV5MR{9Qk9kD3{6{n1b^ zp3&2+Fe%7s^o22IVg6(oGOh}VQGa8(8dCdI>{s;J0A>A37`Hb^%y!4c@Q~T|w{sK^ z_0wq|!VLz#kLOu3IMEd-{m+FEjyx3k6v4pc=IlL&vVK$W3;O1~Wl?(%h-s6*Z?UV5 zbAP3$hLz?GOC}(NmtUpORCuQaz4%D2NCw0f$;YgfGYZp-kd#IiV|={ub& z%fP?JUCV#$0-gGCA^TR7CCkwzipxH$bX5HOL=ROrp2w=s89xagud(>X(%q{SxXFp6 z7HLyxBTe5}*J8mM>Z0625j z`r`WW^-$QsL-B8#m+S)VXoh{?h98WT^KPi9s9RT4t8Ta9yxAY`4gbAsyUUjvNF;Yr zscw735ZI|uZsh$dac$hM!Vi+aT{tNNdV|vuRl3_QJK${((iDdUM@GsFYF9h0>NY!} zF78+le*$uzT4HRvAj0>_QDKu!pp?GY7`lFjNj%nGM5Gc$E#oqO0&w#kFuO*Z(AVd@ zR#|8s*JAM==tjU@T~|+A(v0B8pGE9-mq*c+0jSBX12V)VM+EEUs;G;K=XLo zp{UqopnQTHQ){`wwd!col{<&BwCT9c+m@zfgGo=b`dG(l_Yb*<-@!qh z!$G!0cOW<2=f<#4)yc2sg{n%9&YZ*5fBSEW@rAydO9hb{Grdm!LDx!PZ(2e3* zFR;liKBf{Vyg;}I@5#(5O!DZ^@hRR~>VO-=j1Po2h9)T^U%Cv6O0G0(o-X0u8oRf_ z>i&k-u1L{+NVGtgjbrf}5^iwp&A1q9sEfBB=ZPfyX}<+iAQwd86k5!~o^joJi9Xw6 zM}dS%i15b9eP}{}s`s~;AI9oHhG4=pGNkH|NlUk7G13?D7!KW48szk^t`~5$bcT&d z#xWiv()tB9KEx;AG$893_jgs-)rtwva}1_(zv#Zdn_WrO1I=kC((%k%T*ef9hfMV% z3wV1oq9N@v-e!F|4f}L@dhOd&nPfG#+JV4z!{j6@p&X#jlF~l{a%`l4gz$H;eT$c< zp$o$W?6>^a`AwoP-#E&gksei$s(i4WqR+5@_P;KXw$Zgg50Z~VsR2F;q@7*iqDB(~ z+ivhd-aqxuOlorv;z+0;UrCxvB*4e}D_&5GKAyWmR{7JyanQcV1e zhR@j3wmF;<$O?7^G3s5wzzQr+qy;dW2P*`h;}r%-p$?@9lN@(3ynEK1$jqu!s9qe! zX{I3Ke0J$c$i9oOH3f2ie;W?o04*5+SN-Fd2v#R=TMSP7auNkS7_ZKt>IC=TyODU2 zbh`N=OVZ)}t&cbr)Hmgzg`}Kvy&3b3t9~Cmf zz)$Qf2F3@|<@0`@kIimJRIcuAdW;(JB(y3R^ESQlJoP+1iJ_DOlZiE_q^R**uhzA3|HzMX zVY;M=pQ#gRulHok&=?Rd+^lLUbbX7Xj+^Z_t!V532678N9>S{Xf)2GZ8Wm>GMt3E{ z@myfmb2{LZ^BHOsuYkH^AM=SFkbTIvG3`-W&tXka)NextF)aUvpBmkI75AGRpO~9Zm~zT8CRIP>dXesu>bfsAunUr+K(=g_-hquUgT7a zEWnDT8O6% zRYC0drLy3H*+S##RgbK2)&6n~#UwQdi)%nOhf~FxeGxI7jiI|s;pl|t3~9f^H_5E2 zM?<@*S>x$E&UPR6-^o0-VPC)Mh^}CJZ_}8B4~Eb+IIqUwZ7+9&j77E@Og-_U_bFH@ zsyPeepe!Y&3zs`?Dm)y94=YeVj{~VsAD&^6M%j@Z4R8+62`CRLBc8M;ZI4w!za!Q= z?FS+1dy}E*z}p3c;MNqL9Nwf4S&l489ILiFMMSd%qT&w{`0b?K_@9%D3FdQ>}5x zb{WGnljdm%j}Q>vNs6h;!MkadY`@&VN&|6Fo~j}VZUl9d{@%wy8Miv`LIR~u|7AEl zjiv-c*w#3VXdr`EhQJ%lZHfoN@BiJ&y_oMmQ6h=7p11QLoOsu9f|a)cY4l4WY2Z6x zTdo{7F$C5ce-STs=*Z@43)A<%j@OS?`dPtPst&)3giSnb>kvS`_G!`0zTktQh#(J0 zMdmYA!K?L1P@i2vVA@MywqCdacK9z57YAb&YBv>z(wGQKk-_Ei2|#Y+%GXC;T_6c= zM|L&1`W8(8;YS(c0#F(P_l2*&_ugU~t>u@yIZx^I3`|Yr$i~Tn;yt-)!$1U&^SxL= z*-(BAbUk*K$RK95=jnnEDs#3>CMtm*q+5;VJWWAF#uQZZ+6W{{CR%mvB8f#yx@yu59^~G zR?w0@e!plO>M^e6Ueeb{HYYQDLjLtJM_dKj{QaM-2#~d=rR_J8wHs;-n-LytTyyY@ zd49Z-$>5ii(5x#@H4&f7>FF&8Q6%Ov|BiMIEH~DJ)Y>R-M)b6p-(3|>hmZ8HYF|H| zxQ2|S z#k=l)fHS(7F3^J)>Dv6oo&xY+sYAEDjCSF8ckD9?WHFN`nNn zo;RpK>v*4cuz2sELXM~jmlT4_#4HGkc=@8wsc=7#!bl_cMrrVdM2bDRwzP#--frq2?k8d&wM2@x)8Ww;lxz+pJ;7)C zhNq-(4Y8eWR3m^52_7uP=kGQUowh@LLPr|Arg=DgVo{AOh9+A1onxOfs-pZy-1R!{ zm2~|U{8)0MKQ*9v`roZwlAHh2+8eNcGBxa0OtqIG`u8oOl0BNWGlLXD8VCq(st8;s z3z^Ur>*IsYAy7=kg+(;8JHhp;kf(-BrC1~ZAlzSsMsc@eMJTu-{DNy_b0kMDQP?qe z`vhDmgg{@lVYajx8k>kg~DqP31vv)kT z+!pfbGz5w+S=f!m{x3@FXU|3cuIfj;1e!$)tO&vH`!-P&hO%} zTV`z9@PZvy(2nNf<_Vy%fwAeNdnZnmKU0!E@!xvx@$J@&Lb{8O522TAo2}F{>5rC$ zvoh~L=1{!c5$g}rC1}zo3ftxXl;6f_+V^#`D?&`yQ|OM@m1qB_iVXhdpJp$&%L4MZ z?-U67X3+shcL8p`dp;c;ts%j>3cm#3ZInhH&$^u%LA#dlXuD>_-Y5QoKKrD!UROkn z38fFG)O;PlA$9ku6|z~5(W@4zqH0x`h=KUXVJ{tp@myUwuP~!IbOGmV>d_f#+R9s1 zw^fup^JwvkIv}=0>#?d;JarY>Xxv>8>PukqqY3?Jys+C#uPiRYI&!$+{wXb#pvxnB zv5llXknQ;6ydXE2nhTC0Bfm!5legQN41;Pk)B#U*vlRxQfT4~(xF%Cp5~i~>h>ss( zkaLXT?FDKa#&k!#B)XiH|96EJ$aHh?cj!J^{a?p{x6Ym%*stm80cR#f;rfh1GC?LoeEWF( zNU6J<&1^v&v`hIDiITK8NX1?gXE|i+%(kbC`aBYJ-Ba!ieh!oZU{UZ-o1HZ58Vh#2 zo8EW(?(A4)ca8ugy06||9)?2$y?kY6OjknpT)@@Jy!i|4%BG?19wgs^92Pe9Rf9n} z>nRcz0Q;SdD#F-3hIAja)Sk7P#|lf;!bDPRt2xw(T?cI|UzT>n3MZz-8mixFk{s<= zOTTFHdfB<6)T8hP29-qTa^A3|7ii-U{@8XswpokSQ59K`ZS=?TpmP^#OR;;fBQDM zzNt*2d#`s*Bfu&r@((Wkr80u#$a*qrH8W zTy5hA@k3gHpW6{bRZI2In~*RYlU}nxbgh{KTJFiUtLy?bJcJ`F^ArP3n3K6l;y}~V zj7yRLKuXBMkZ!&e_k^I?=M-ne$U79MRG z3rEGoJVv;<8?*3i`A7z)tiNEmBD~zNdieM=%clvPFBMA3nHn*v{rvn6Z!vI~?vX`( ziB^Nc#`eqCuL+!CbHx+A3S1j0D@K(o%MsWQzXICag-7La(9NDVd*fF3Ip}*4QJ8)!WE1hz(WiUhU!#eks81BuktManOLONPWq%RZMP^axjUxWEskvG2x*OiJyypr( zyWN`#oRZ%q=X~n$NhoJb%I+ppp-?#8sP~gmSkFjCo^J9nSC{wPa2$0p(JytJCQ+*M zfpGSgSUxrKbf+vw!k_kd&h#_sitb${ka{x5bV#}sdve3*dt*nz!kc{yGAaSJhD}>8 z+rOmc<>mEI^`Z|%cJ_B$Y2Olq{722Qx+TZ}exC@Tb|Y8|-U+#Y6gc@_Zth8iEes>P zn$*ygS-DO4k`ZVv8t3)1|`@KgGCE zh>+f0W9{~i!`Uvs6SPQhC{-8_jY+=*13aBnp}b}b8Wr$r7|&8dnum#@9)*`CFY9Yf zxT!Q;Qhns>pO(!)tXo=m{0{t5so;Z@-mkt*s~Q_zBA+s)ac&xyJ8y%2(h4}R&JhUL z_ct|yuqcjQBmYPe2IjusU(S(2QFR{)E-L!P;>8fVoi}y9cwMBn#6eTxDS$sO_$v$V zsdjjT3f>c!_X5G7_YjFiovt}-ZF}29RV3#zYI?@W7_L&#RD>j9z&({4mvHUN@GXrs1^5x zc<)!?vny01d+)>ur19cggGD&E_c)e@=nv2QPfhEL$0)C|gGWVGHM#3Q8g53e=5{L) zuUA+O7^-P-b`fi@muqAD8dFag5)^h(Di9@TdLi|>8*CbyEo3D&VY~FH=DbL3rwlT0 zMHf8|-7*OL8yLS|ELbCR?(M~2jerGUFbjI*5|*+*?IE@+{+sfW0nMz9pMf1da40r5 z_Gmf}Pj58OJ-oqW6V$zS<^f=k{A>-d1W|7cW<-7*OkUW95fPdZ5j&mno|NI+57Yb*hBUdy?%&lQKAj6=}^D z?`|vLBRO_5o;CIdSSz2xeL`1AXLqXfa5;Al%Wwn9LDzvlKxp)~H79alf`08$#e5z0 zA&<`ASk%~g;lgvjttx;2n}l)Nz(-xoMIl}A9}19d#ecaps~s=Wp6F4I3p5llCx}PYj8*Npq1CE zhAcW3xCWeJA`halYLm;5xegin=WxF@-pDJYc!Ie(Ua61@;fchTrFS{|cU(-4| zI}Ino3OJsWgJbP|3QxOKF$xQyM2ou_UgJ|YRtd|!B_Eiy9ZH>!d$W5-2$jcRj89Vp@``RucWb0d;hcrC+UK zt^m@ubB5Bq?6mzW>&h$M%m#Z%qj^13)R*|yr&r=VVXIgT&f_DKuZNkXA^dJ#(n1`&XIF;Ew$QUO-}wbT zgu7qp>H?57DJSK?loa}Ejy#DUc8y#dIBBCQIkl#Dqsdq^jrZy7k{@>EN6(PQA+DYsB-suJUN(Q4M`JHe z;M)~eIdb^e{Y;HPyD5M*O;B~Y+G>0xh>Ym&_TU?1k;F9*z2-~NT!p)gftN@KF8ec~ zl>aUZ_@{Thz^O|HfiYEM0H}&&xxUPVoMG(Wl&HK60O4!aSze z262Q5ayry1VXsDJwN8vJW~C-r?CHm&qoDQ5qL>{ z7P`J#Akk?wIy?(!K8c$@TX>Esu(cx5eG$e_I~aY_5HfphpJ)?m6Xu^^8IJ9h>k>W%%{q{L9;l!(aOE zqtncWVh};%Q|1G=FV0E{G54Q~=Ig`Ndgsit0+2s_)9UNCKAst|;k9vMaRZ*hpN9`& zSy}vZA?@dknh4;Y^8Kg`*|4`l@&HQ-;Y+2}CVb_H3-u3=G0ank>Z^NSE z8v{wE%P?N+=(Jks_g^KCE;K3lEbiyNk@ujjG^AuIuS=>iDN_%4v+F~e^|>R^MOE=;vt z-dtlg z6v=MLwJw~?1s2MDSJPQ`w0Emj2nbK4sn!+Nm5V8^M!<%_S~9G6ZNXQ zwK2B>hbwrWw+RqgcMNsNN0PbJ)-B(1t1k`~$zZ1oMN$Zdu;vX%>DkTvq^U6JrJ^5) z&RxtzcViuJtb|X3;2gr?Uu25!la)>OItz^`Zz^tX9)frjJ~y~v14Ht_9!%!8IvkA@ zrNU9F?E<`ttKz$;BK^&x?g#?_t?fLVVS2)TJdbUXut;=^3!-cR-Q?S!Tm9Tu%VlO( zXAdTpZu~<5;aCi8+wzr~F?J@JksTb$8HHc#HB$+eLW)Cx`l~L~Lp<0zUthwtpRZXi z1a8n_>x?ElP)J=;+swfm-BptI&F6^l6&^g4r);hZ34G9FeJ!{06{%{YyxYRnR%(=k z8&)OEkY zIQIB(%gujEZ$!EGn@LRj<2nyyys`ah=;)|vV}(W0bAf$06I3IDeakNZ-O=fa_bg^} z>On9Wia{-HxlCuiX}|Z10RMOd1Q4HLa~HmSS~~!~wA0X!Df^`w{Ps$=bGS1x{y~-s zX(Hj%m7B(0fTXk<*`hibia!pvP;*pXwH&y=(bEM|smzaxYLf?1=+1jUsTWsDt@^K~ zx=5o}%oTfcbV8)H!p?-fyw`QVV8QAB6(32X-DF1!;~4T3AdBZiHF00}OW!)3hzP7? z`c-Gwyo`5ptk^F%ncXvO?`l+q9u|=s><;%FFSS-(ZJaJ;CTIwBwVM`cmS^qW0K`Qi z-;qiq>3VF*av|E7u&A;9JwvKT2NR@Pffj(qlrvg)o*e+-usC$9++jQ81 zwpj*p+-z316XvfqO#T_39=B`F8+O)%)rohwg!`2yhNgeu?H8KR$rO}m5&O!{cctWl z16}y1C&e^*surg%Ps|iEq%Zs8zM2RIlMD-IyX~UXZ2r|;pbo@dy;$rvHME(jkpeyj z>*)LIXzIBDGO17kDL`S^JDqCX>y_}n&3QCT$zw%;0d+Rn>MISzTHXBx?@2i4@k%15 z4{!OPqEa`)AGko8h|2m44Ky19&!=#@_0KVM&VTsXB!0xhDyEuclDjpygo5|V0K5c>Tj`OI5S5mL++Q=V=v?KWr>q9ktbMoFbv21@F z6*iuyvsL8Q4(t{3KVBK|V~}AYx6^5x9qL`Ze8sTV>6b)Nt%K`3e+`8d^O|lZu!G{tJVXoPXIPdJ8|v z48O{us<7>XFTDD!w~CVUe!(GRi!dlH@9sh(SRESHMI<}#yNa<+`4!^R_CcQ-rEu6V zhqZ%xbZ7Y`?Ewo63Kw#XK59v7M|CrW*Z3g#kYo)>SPylhl5wDcF7p((a{YEcJ^0s; z$5n+s%PKe1k4oQ9oQ7@=8E%-xni#TBSO$JDO`U~^L-nCpqtGPx{hPhl%dbHb%Ljja zdQBX;1ZJ_tg(l_V9kRMgOEu=Z4HA`OI{*h7NaR;>6da@Q(G3J9i{*zHLb`B+oqLDU z&~X0M8)-+LM^FLNI>xjW6uAU`M-0o!pOmA}_FS7CF48$jMcwYr)*@Zq2oY{-=rdv8 zFmgfi&i=XZl^m;=K7SeMm8yUxVS=PP@;*DZ@TgJC$S7-9$Si`CG^f1O$42gcw1i(s zj~|y%L-Q4Y%K<~tSfe^H-7;Ufi^RW%1W`$$p}*y(uU(!Bgp-Z^I+tFhh8ufltk`3h z4#NH!U&T;Vnm}B4=cJ6;dgRCO(G|?@O-LK6`@c724%jyl?J(VmPW$_7A;2fx`h7C6 z;Rn80kTU3ERMRXl>O5LAj(niX@wuGPgKoSKktPpnO3GGM^7DCD3h$e3I<*V440-X> zo$~@B^asV_J%Smko~~xv*_K09Kpn0CUW`5 z)7q)}OPMP31q_i2*|S%m8R2E}VSOGJfmqWm!9wfz@kTxM(df^nP}8og-f*wH!Id8+ zRE37SF;=SN*_*wU#3Lx_?aT8zO%SmSQQ5_K;#Ys9j|H|Kw?vnC)dPe3%0hn&a z&TWr=#XD8!N4{M^y2hzc-e^sFEhO6BsJX86V|6}51?_7mYTw3qt2wI@v~dXyg<)(T zc>qTCuJK1`dl;bz1_;MS|B59A8A5O}hB>TuwI1*`zvzogmQ^GvvU-Z!5o(N_uKx0a zJKTMzOo!IMj!5hLTTZAtK#R|)Oj}}%LnQf>@0z_aM)jzj5f~pem{VH)^o;Ri)*90rEAiD))b92cxI=AW_^e46`Hl7I6SI3h%G2Nf6-5isW3A0w@D45<&>laIfNP;g(LlH|Wr@d1XC)7FtFM>N!^F?!qtT+J7)OejR1Lune{* z29BBC>Gjz|-9~$5O{``4tCl~4gJph)0-(4_;!O<2SLfZi+L0k)#YeYZwR(L^pkOnL@tfU!>euYbWGs`@rN5Zt2<&9zx^Slc&B?JeVY3IFF%y_0{sV^kb zAwv7N>X^rCV_FMC)3X83CZJc_>4|&}zI}4tfWQGda9)7fo*{v&G7#Za94~Iua0y5A zPQAkYT)qp*>0VBiC}fwAiO>r-{OZR2)}o=gM(Fxm=vg5PtRtG}VCuK5fM6G1@)1i| zT-aHS*>)S)eTQ*=V>`#-IUQ8D_CSuDoELx-y{$@KmY(gc%1Uh&ow|haW-I;e$>02Sx$1hlXoG$D|jBYm6rdZg5 z4ZT5+ggq&vRZ_=!4Og*T14x*cKliZM6|+~}4Idh)sO+(A(ozE12c ze%Aw^fzNF#sEm|ptq0eR^j&MDs13(*In8HZPzxyzMne|bw&2Svwt*A5ammBb{lnWo z*^;Qxd10@sutbE{^N8Ji0iQ5NU_ERdkQHPzFr{YLusP$!eFr)>li`Q?^~gvqgkFK zy2RG)=dtTxqF(eJ&K5n!`TA_39$W*|owla_et-bHwhBmaB}7{3sMZ$f*XHhew$oM{ zCg6g~5b?iM{w9X#1h1=qB+%RFgxZuo29)e{&hcD)=6tq&J{t2mF}Hvin)NO+uv>F@ zhbR90ptb}p%VLf$^JZYv5x4&r=T5w{@Vi2 zw*~ID>%Nn?aPly9=Q?l`>~#p$TSezwMU$9+sy-7`QnOCxQ#Nw6)k~_+)>2T^jBDoe z4qHZ8J7~*BLMycc*N%d=->xAL7ca4AQ~^ZOgfYw4^4q7$)ui6BGR^LtjKK*0en8?C za(N1`4k1ey@C}OZJ^*&g`z~Y6mKRa>>$3DWEwOa$Rj&HGGu~F!-k^z|(aY15#|o_h z1G?MeCiJHU6C`mg2oHuV$Dkv3PzJbp6~A9IYN_yqFxi^fA|%%>$%^^>aZ3R4CN5q z^up|BB{ilB1wi65A@@3q!|ieX_~J_q1gNmgI`kO#b2Ynwm4esiO3V@9o=jGy~y z8Mth#T<%VlKWT8;i}Ck5K5}znFCPyj87qM%46K>~tYpn?qa2+-Po(IKn2Yz$7?Ed- zi=XMOTd#)>d6+v4f6xk`*+ww#*RoyZ*K=m#{e@g5Srmot@~y>G!G8^ae`GJXsoaiI z9Rd?3^e?=Q+ik+m`ZwZZ6@=XA*{yzcR}k}3_XE}lqg|Ss-cDIhOPOrx3zy>jQJr@$ z-YJqXld*X0u<0u2;cBo((54bZ)Neya-3v@NB+YXSxQu1`4IB`EWEVb`FBt)LK`S)$ zW99|Hce4Jk3N#3$q>=H?V+2|8m^esJ2QnizSGU`P;zv=b>WSZ!>4kiz0~l>$5LF{{ z0~`$1N#JZ@GwML$vYb-lrp%F?97S4FRG#jxE~H@fRZf?_wV&6vikdJlT9Pd0{Rx$3 zk~-~r205PTMlT(rVp`hsb+x99sohGib6>?&24)?@aKYW9G2D(7-YJ5EXUu7CScO4r z>l>}Vk2%?*OOpf)su$|ETB)B;erI8C=+iAE!%cfZRfP9tp~}o22x-&aQHn=qh=1*x zVrr3FvbLkCdy>}|slaULjSwR(Tec)N^kzk4Fpt`CMv$t8T> zA#>fzZ2-1;cc+60tI50J1!%X7a+Sl4D{9mq!M6#tYRbu6$~ZVU+j>9Hr$|DAO7Zcn zUyeMDe*Kbcn@sYJlDjg)hzB~i|6G&LE014k^V19s97-5QN7$~(25)Tv z)O>P;wPj%9_BEiJ)H^{5bQR6i7sCoe$lsZAm_>9^$1!f5c^+Td0|hv?-41QNFJ5}Q z4|{tg_f|-;CqVD&%$Z-M~V z_Mx~d;1)OtTUVW7Mt?jGl3dI$Jw+ang0RWr_Q=(4o^hA$>qNVI;Ujf0_l!(b9!- zlS|^A-)OaUo|lXOgdJ=4T`v#P*8hhP$yHaWkE|dG5Ww(%Ij)c7oO&&*=~C_6NV#Qd z(>#unC2v)QzlBlDL7xl#nEdwRTPzVbd5D&Wy4A0lBusIY^`$ha7hC-iA7oQX!z6wKG8MHDY zBP234g1pX;3k7dQjr{K#O;bKg zpS>-p7n=9p=zci)e!AE=R?39-{13J!$T)O=k8GA4pOG&=jU*#&{_IkmLdnSJckk-y z`MBo(S4v@#9_aU7K6Q6E9wX}aG!gCTQ-d9jjqcf>%|WT?ZiUu|6Ex0eKWW9ktQk&I z)-Su3uBErT*6i`l(6R&m*jD6wlJ9UQz+7y+=}YPL>Wx`^-nM9tks`h*@~J$^2N}am zD$@QEJ@!8@*ni%$ItAb{KOEQ4H=3`ne)y;u-j6eBuw5tUvt7h0vcWfrrj91{>l{{* z#ur+jARm5O^v=Ipk{}db@tCXgo*z8K|L^OHhDbMA0_jXGP;s`9*RJW%*SY_b6Zcw! zV0$Fz34(4Ud(Gx(p5g%{3Qa1CUNs*Fsqy;tM3G+TmxCs=5T3^9{P?U^1vfpmqGMHL6rb z8J;JXfTfQ?{)6Q6V}u5m1BoUi7Kf!JQC;tM>k%MwvV(j=|ADN@9aJ?0oH|>yyoLlY zsF;1Fr$R*Mu4|`l{cwy!RNfmpnNT(Tr8?b(&7eSx&s6a>QaKTkZGgbexpR&g?~C|C&acE7EQyQo&rezmVjHtrExeq{zjlq!&TR^k!; zL7Gn**~sN>frO@iJ1R-0(lOiA@{v?!E37{_QP&OPXzcn;kkyvaIjZ<6mS~LS%kiIT z?FTJ(ZTBJ<2t*tv1}_Zk0WIH?&m4y_jww=6^a5d1cYomcGKS~bgtd0OQTqD$AbtN8 zkNqmW+aI?9kiij}a~V?a=pTGlqi? z+geIgDKD3Qlr6_o1gn zlb+9yo*)_YPZYbY1k)v6f<;+aAO-X(DEkxx40(AtcpJ&61APK5u|>Y3iiSDz8U25s zQSeh*`;YxYomxn0%_HW0?t=SYuXs z>U680s6>t|wsG56RA6}Uk%yCtcs@PA`c_H7Nd8lkfJFV)x49{Udspz{oU*^d!NI|N zx+H<}_832140~m+4vL;PoQn5aqsZKF%&iJ-q9i%+S!d|>S*LDG1n(7gn?xK%``3(I z<%IOX6kcp9suLe=5%&|(WzFZyonDu4!;^f&`YN4T+iaQA7xvd@HnI#-`EOtBAh@B{ z;*N2d?~u9RbEI})`s3OJq{qS-ER{6e60BAGJGPuKq^~zPMI}8)q|f{Eor2l~u!Jht z%@2$EZGL5}BCyF!v&$SggvqXTMkRch7*AZ2_DwF<+9G{i*^+3kVNxbD9q>$W(g4S+ zGV0g9g{k@W~Y8R!?NcsQNNL>Hm&`#m!f3-JWP;DkTQA%fH zUS@4!Lp^gIH?9+-rfy{>n7M0K>qUey#eQ5}=yzHBc!6k-s_wF>^}30M(>UFKoLOPi zN%qlBEmG+YVI_tPKdvta9!u)XvY2ObSp*B5tXsgAcHQRsc{n8-MV`%2O}Go|QRAf6 z0h5nNpJ41+gz1(4yU+wqf@jm`jTj0uXtc{GuN=?>6<#rkYPSLXIglsa&e6m z?aem`zz*JZ90$AI&Qs7~a(t~*j%77*Z~N9~Z5f%Rzc+@-42#QZ1|c+|WTQ{N*plXC zYX+B_yx14K)!!JkNUbymC9V~Qjc3ou>Knnfy0y@*F%%Uq&v2-ymtM2%b4gRYh?n~} z=yxuxyR?e1{(WJOqpj@m4o0!6N4+%=`i!^;s#`DooA{wo`3!N;1F)PI%&?;_=&}!x zFe2dx0hm&Q#N$K+VT%Z?%{NG()YTUtiuQv^|PJ^)y6jGo4TW9q6LVKFJ!DgirnD?jbaHNDZ~>Kbk<7M}j<@ni_ z2hz8<#U+~b2dwmx1-MaNNIMH~^rxQ}yeeBr3)tg+@-71lLg7;4p`~A8)h1XL(Kwty z_|el_=yzTyu9ym+rOCuXhOuhfW7KMcdjPsu_>=2#?=J+q`VBp|!+F zJxUV0<}s+j<)5){v=g5=500a#ojg11?m4pgZ6$Zs(21o`bZXPnKOO(VGseF2%{5kL zTu!H~USL9@u)=TaKu3`SDIP;}n>U3lsZqg;`_ipvzfk=Z)7|)%PZ%n?s^__9v<@_J zw{GcHYE1h5SIH7*9Ci|CpppHMO&3pr@xY<$h0O{?_?2q0TC5h&(Y_wS?@st8yTyR! ze6#d0m&9cVet}mOhYlOAMIfTlIB)+k<&5{b#b|F$i_}$;g^1~OOR?t>eOQn#W7g)R zerXU&7_l(1*>GC>-4kRs>kIp_BDK&(_I^dlr(oNYsW{ejz@L#7<26E_5vjC?LN8zl#MRs&J zwA-(3Ibe{mlS?#Tk&j#QwAG)cuv4>`pgO#QG5Xo3;#zr)ftL?*b+t66ojT zW$srli1D`X9@1+dD^5Y7vXKyQS=>^nsSuwv>QCk8%vHR`1p_zdZVJB@mgS_)SCCiI z(mXU8St}rfw@2oy)Ka<}EWj2W!7T0s`-?1+(d){A_vL`tQmfT2n~l~Q$@Sj_^*Nf9 zWv=S>2qC-sbN6#tq1;0cQ-G%cpQQ5t9Rvj_;KA@b+kZ1ge~G~O;&p(TrEtLiW9loT zstUKYQ4~;Gx=TV*IyP*imG18DZZ;s@rF2SncT0D7cX!7ozUBSKx##|XKQI`Bwb%R3 zIiLB&0l)OmIg97Mgs8U1PYeL*@$veg&@J%gOsKo3ce51yYzv)b@OBp#ey8ni^T)tR zmZmeVpnwMOjzRh-c2O=>4}gV7B?kgmr=I{*Ou^D*_84jzVlDSm%|=GFjM}}`E84vZBRttgOX;q!U6}v}P46C>fjQ$(Ar_S>u*GqI{yWpP#@&cs=MoW{ zDK$i%GVI8bhzEj60(lBxJlPO`+%X28V;HMRNkW-i4l!*kQxIkQs6b;CD(-C>AX8|C zwuCniz*77Gw5Otx`0%6$D^ft@8u+Qws=4~jnx88KJc0H39i5_H-jLY*3{$8)CUXVm;P3n7ujK&y~sSeJOKANZ-o+hQmcBZl3+;EU?eb z*sml%x0`7P%{DkYvmL!VM*rK)n+z<~g5@ z`jsK<9-ryQ)s|k1=RCK8LP|IXt#)8y9;{ZR;U&(7kmD@6)fP_2s7Lcicg}$++dHKF zd%&R#bOhuNj?fz@zE+4f$4IxxZy$QChu(HBVFwQUN?-`nvl>23z zSeX)u_Z?%p)f%ydN^XgF3Q)729G-AlOa}D(ZH#2DWWYw>3D`6~*VyOqd*TI31uD$C z#2WMa-V-4l4X!_HS5_<6t+2PTyPABv)OFFam-F^%sDx`!SR)|^2ei-7yf0WIAU|=P z;AqdT4)OiU`eoRJ-)`f3N_v14i99phRK6*+8$ae2=E!qryE)FC6GZ4&Xd`a?h(YxZ z{N+L9QJW}*)1VzsbVQ-xJ9;YEO+H~3kPtz*-E(A1It24hj01;vJ|Ai9OX*dwjATbr zpQWJ6G^7x zcE+*IfNI(D)h(D`qtItcIv|>b0Eo`;e&E)2&6K%hdhueb8lhgC>&ji+cw8eEZaevG zu-?;QQwH=YPhY)0fuWlgD~)hJ$Kbtx&S@VcTvMzQGHO>567e|I*DU3`pdvw?3u>=y z4>@yf*Voqylo^a*mfAd_vYCSKH8|Yl28sSVPi&9^LwQ#}iP!j#3MwGY1h5w*3m5+f zy8Zi|?^?@EZWzxz&KyMZZ(uYyEW25q4)uq6*9tgRJ$96ae28F>U_qBS^grKZKtAJ_ z3C0-HXf@T4oh;Vk3aKk9VxA6VWUy3F-2CGF*E%33l*9q%1wiBpF3UZi)*`SZ`)W4G z6xnMm8hN~MezY9oy1fy4JWOS|)CSA29-ehpO7hqu7JY=}EA z2l$#l*^GmlYJgy^W$F#Jr}-jGJpB&|Md*YZmGQnmtzd6nik^+PI)`KP5q^D z_~M4MKMl(gMpcEz&0B?5Z{XD`<>#7aE2`me?iMu&oePW*bV?vKxC{o>3bUv9jvbdq zBGJnEcfwp>yP~k^_T24!5X$kWH~cu^sLv>WUxIbAybS^|qg8TJhw&kuU?j%Y&J>03 zJ&di|E#JV}OTUh42KDZ^M|@UH3SJ+Cy|ZD_Q^%M)u!8u|cM1Id)_VZn(O(UlDc^o8 zBjiS6nE(tY@*dnsqv(s%MC!ZddReZdTIF%A#AZ1IDy^SXFwgEj+CY`dN^Y2qN^hhV zD|z|XL5wYHYR+vs5b$$vCn8CLJ<=GG`RZ)Q?$jV>DRE7edbnW;coVT3^@iKTo~rRu zeY<|et$fay6&gGXjezZjF73 zOP(cC%~O*z!c3hLNo0XcwYwcO=XU4(PKf6wcJl<$fdzXZ@#H#l@X;~LTlE%q4H#R; z>aC(mv0*uDAf7yqork$YNYK;O)-WSqZ(GR>Fkf2+8+v01CABLVlw)PyaKkD=ea27A zyD|RVSoOrIL$dPfBBu#dR494ws3&LOH|eqotn8x!?)Tdb zpc)9)c74VcZF#slID5U?W7_QO8KQc)*=cez3xm5k+#i%qVUEJ9#@OypM-JPQVnI6Z zPhdg?V5R~3b}J}KjrAR&^=g9;ym&(Qx(wXGZ3fsA+XmHj?X}sd*u)W9|5CZKw%6e@ z?DsR<%8@m7U&XP&X0oRVn^qC+H&ys-Zcn#n-!mnN@Wdw8#c117GDwoCEKv zI{&L@^c!+LoZojMjD;f~>h|k(;Iy{56Bt)5&l( zeEqkfN+!#TGsVbeloD|)BNptc{+7^p4$<(fCCelwrfagwuckS90z|{O+=~ZUX`9mb zm?DnRK{G!QSD~yuRA*DGVk`ab?~E#fNJcvdN}g)$c?mFP*HJ?{$XM(Gp;Q5XnK8J% ztnaTW?oa5Z3u$$K>(8}d=|SShS2e0U3G-kXbUpn=3@Q$FKJ<{Hs(Utg@_g<0+Y!Q~ zexyI>Q)LuQN;EvPMy_@sb=N*79_oOF)8DH*3B1N8THFGR44lj^?Ce}?HBjjjjZ(=- za0uwOUB>q^GR14n1EJ$5J~+~XU^+uM^mB6e(vYXLzt$cp_3aBSB_4jdXwNB# zG9>PfqFd`IFu1(;h^a`5O?o#OswW929(Fl|rIPaQjVzQg+zwPL>Ebi#oduP@?XGqi z%(C!$F;=-$~!Y{kChgV%`JMjtT8!&-7K>=vl4selhGjp1wbYNhdfZ+MP8Q{Et(B9y{n`9P#GuLY61U@pMKAY#Klw z5lxr>K>8El3hEzD9|%NJ-5d5tzloYD%aaIe^n$f%zvC{N^@5X7Danp6z3lf3tofbq z3dhGK1Qd-X{yCIZ4`-5BcB4S+L^lcGEh20DXY)-W|jI=RLbS6 z%&tgGmoh>623zM{`!+EQ8p6xHZd0inCu#kB#YFa-G^YDDodlw!+G=ezghwx z4LrM!qa)kn^=YJzv7{i-ZW(*3PPnzG(vXT-0<~tQ#}oFBK^7MxFR@-kWMKP)cgdU z4^%8|xknPM+OFk4R(Wqt-uK6$cGAO4>@{B7({mVOR2O|q*|7HA5BJg*#^M`Buw1fK zEk8RhRBpwe%$G3Xp;Pg55hWUOA#FKfQApuXWO5z6B7H>eg7WEOL0gOF1;SEt9Rb`p;hvAiLe!K}?zp_XRqv zC{fcG#PN*15o^l>OSc8Oq~4=0fT>rXoPo6z_=jMnCL#kIUPjgY0h6lWp_Z0xfp)^F~%>GCC4~%|oLuQ5X~pU;sl`Gjk>z;2~jxkmkRn zyj9RzL<;kQQbiDRGT3i!o7J35qfSXBXk3tUj22AVK_e4=qcO%GPw8Tr{N}XXh@H&& zL+yTT8l zZK4aZfN;lNqcnqDTgk&@5zJSciH}p)N*32eLP;z}!i_ABpv*kM2bO!MjxoU0I*8Hp zq$y(m-qNPVYz7HA9U@qNmcV^=vHB-!133{b{)+;YV&T(wG;(GCU+{GCeUI#6a=xd%cZRiYPO1XM!Z8>Z?KB7Io($ zhl6ohkC#9Ic|0vVGBp-Y>Un9cSLN1 zI6ko`SEY*Tx=q$IOJAw>Upnh*!M1=CRX{?OT_5uoyMMR=!|Ksc!r0b^!U96Kw-pP- z)VWlUs$3;m>XJv72tJ0B)@|5~Ii~LiqECDVw!`|>_-m;USi!T#gnuhdz)EPrO@%o! z&mh$44Q|&_dK1g8F79N{D`TCNOL=*i4pR+bwCL}EE ze;O5IkT28oTPo#U_uu^f=*4KcRP5HgZ7l0!5(kjy=#QNpD~owK-R)7jec>{GKFX5_ zPVKJb+M^1qC1=1Th)@S|`vu^9iI3P=E7aL61zP1Xg?_^QV?ie@CZ;#&Z!=z_Bm=3~{~3 zi*KUI$zeHxAFvubjsi~Q_E-iggNDoH9=FFWq4OEwrA(((h2qOgoVSJ&ekQj1VorPWf@_mD?8P1fN6jxi{kC%*XHkD2m^4XVRqqC%zMjL;2f} zBDB;FyThvW&RG9!EO{Fp0!My{bmTx5Px1BOF0TzQrnBJ#b)%7((2_=bsmIchZDyGwMmN6O%0hJT}{j$Aoj8M z35K3OPIIhlm-C=?TzzigsEfeG1w-0|SK{S|i|3Qn4YYJExLGTu70&rt?-N?J_@5Z9ITCv@n(jBbTi7t5uCQF+JEkRhtmpsNvnW%?oRiOzZqx`k0(WxIMeE~0Wpj*SEEyveNB2%Z}RtI=!a4t&m zY=NFR1q=CGop3^*r*0CQu?)T-JPc|)I3O@EV@_KDIK;mUbnDYpt=epVTDn};eK1;G z&+=wM3yHYHqn+AIRZQ1bSvBYjjS1{;MCa=resM$|deJigLZ9uEkd8?&2`n$Kt*R}@ z#}im@)f}$d$012MdxJ@^GSj7zPY(`9lNcBnQbQ{t5i>hsk)tqW&f;G)r`sVOjCwEuV3CY zS%D@xHghm77(%%zHvng;aDzJ z@Cl9k`K}YKXi`I(U>3nKRWG308ze$7rJ0Jm*3wyEW4>Y>Q6Q3!0T{A>%{ee z+akSgp+np*`^Qw?_Y82Ww+;ltTW7Ok&V#7ZpD$ExHKLErFa*MocGPaiz=z3VMH#W; z%AW5Ce{-z5qIgVd!?)Pl;gNB8ls8s0HlispQYjlFsh%i<&6C0%e%B(8 z2yl^w0k+ptizj!$zAACCVnK&y2HSu9cgHs_8t3y_J?Jh&)7lQPJDOVX!uB>Zz5DBP zVy!-2Q4;;yTbP9^lfL-_g^S)lKEdJPY$HOO@%r#m1D&J2XDP2dpOi+kXQD(3VtKwp zc`tI+b5YFs!y?*Dzx1Gd>krJy3X*IGQ1!Xr@D#WQRq6#~B~ zOr#}KIpNDu@q^uE*aepW*z-mJ_TuE8q{3^Ak$0gcgkXafGgNW3kabVCR+`Rn{}F#s z#mtv;?U`^+zYKbpFOg4zwkyS!p?D*#^?WH86h+uos9=(Ys``_Jdh~C@hbg<$<@d)c zE`Z;~Gui5^Ee-QuA(@G+fJ*ZR-s^*vk&5yohcq-StN<15(|ie?W^35n6aIDAr_dUy zKqS0#$Qi&0+v=0ax|k}hbQ>aR@R&0so#B&il7%M~_wU2FcB*%x*!V(0wGuio5;NT) zzf%!5WyjM*MJ1(0iG;$yzyZ23!P>#|v@}m(ubTf9IpLnQ(;&7s@aRkqcL%99NnN@j zDl^?8>iX(+U_dK7dT~YN?Ky=>ty+Y(dq@m9#7ajv!UNSL@)Emq<0+ve;il3XD3Mra z2)FW~LkvP{RTr2|Zzk6e z86ktf-NtQhl2%xZnz-K25BaZm2MYNC$EJfHjiEKpSZq>!@adL z!L0=6QZDAM_@p4a)04Uog-g5b4c@ve<`g3B#RtGD@-_vFz$^;e;E$WK)AL+A;@ZHp z#i-CKsN2iC$<6aeb#mk1rx%a#mW%*Kosp~)p7 zulsN{@WYGf#&sukly8p&E%?U|84iCdp?p68-TbH{FzZw9hkGc~t#LD2l6Fof&Qv~v z>E4w2{{8Rh$&!z&u> zpA{!!3D(9T?^~=wT|tWRvYP-q9@P0J0Hh82r=mjTlhoK(ohk^?s7^y{?BR|6aTM!U zn~jf1?t7Gu=5t6f!mrAcRrI~Lqe56ipZjr@>^h=|@iXGW%Igt_Zh=^Yw|1Ch~CY;Q_=I}*(PcpE$T>E1!J zDOUg04;9|vG!^eWcvV;!D$%?e0L?h2z2}MiBPFJf5Y%q-b#dw4t4BFMhs|nesdvtG zCK1;;Cq8)QE#QPE+VS@NKUZL2_(29X9|HN2Cl}*>SR)ZJebEn6r1y15e(m00c2NG) z`3Yv)`T*42Bd-jw6N_-nf4~~d!XVh@yLI5XHr3Fw6C?jS@tfLzB}2A4d+1zGPGsb& z^gSih!7}w{gEP1aB0j;v2-BqsS_!uoCS7)$IX8x=oP`R_O=geL<)Z6Dfe6vs(=PaK_!5SrD#oF9lDP3=zBk*`J;Es~k1vJD8; zA~*tvmQT^t6F2X;|6D%0kYCrrksYM&fpa)v2)pb&LIdqBZCxF3WHtmqG&+9o5evH# zarQRacf$|m-}CKWQ66#_Fqap8gli^jHxC#~F>PWVLTBzTusB~tfiu3TTx_tU%HUsg z%jrSW{plI8s8z3U3%1=}t*l_M;%^Mt3HeHrb}Ex!&Sj|#wq@7B*3KAnG>G?;sSo%B zsx&>R77Xkl`jN)H9Yeh&o@K?%s(NCXjGA6s=J&?R%4Wd^Kc=?BtYUFxn1i`et!E%K z!R5r7#W*8Mr`FGDZE$r>d3-Z_!QOtWlv+(k9WmHgr`SN3&&f)&X?>OmM%Px5H^6 zF+NyfIl0Px9iPQaHBskRAfwDp`Xh<_JcC|(nJ@6lrdmds3Oheitl<8)>aSeI1(yEb zoaI){Emj4#eTAHnk_0Kdfh{L;>0rxvDdp6XM~wxX5QWdi=G;DeK1L+&q%58_S7))A6685`@l==$eXKY|YbcMt0IZLkK(c0wlHTLZ8(&L<`=u*N0 z5%1_$cqN%ezTr<{Tok-_#913jxeOb3AV7{0&JCg^*W!8hMPs@?ifD1W+Mir~UF(~# z^@uVJ@ck*_?N_nVej1GoQ+Ft40lKmtZYa6JY+&Nw9ZL$-7YVZsXomFAUc0}%cP6P_ zX@>@oWxjMO{O?%^W&06B{)jsHFZjAv@P8?>$No5)B>>*|*s8(?7q|V6kM|Z!?vTq1QeN36OxD*yC5wxx?m~fJ`WA$g4x){( z3E#Rt7$G*Z4#^kWXbX#?F4cBOG-&w+vL%SdUQKH_-x@C}9HZSFLR_Af7M|%~;+Z}@>uCr?5y4EzV6`Oevzi#kUBLfsdv7Aron?0*o+7ycA#YnZw zaX@u1nG(gy#d!=VSulW-2cBBLDJBUjz7fh9k*Ozp9m(z)4%5m-NjI>` zLox~>qBM9}q-+ddV$EV%@e+eWXMegN*#QNBBS%HMxm5TA<>HnhguwsCv z{68MpWYJ6wXM+&X^Qi)oQ`lT_%$eL9?^5cQ)kY-P)k6c-Na3wwy)9dY-6k?VkGc|E z{zF7zQPIQ6HLo)VdexfF#=qRa<3I=KcH=B#0FN%{hQKSK>UHCFRU~dO3ZRO-@?x-> zjKo*v%fkGSsGTlX!70^X7XwIhdi{GO1}~b=q&WbuM88Y4@ra`~3(n;o9;YIE=a0;; z!igT|!_933r~Jq-XZVGM?^Q5CKiBD;8+KWrJcAS~yJBJr%#fC7@%1(-#-l>`<5}KI z6#C`P>nTOzTjSEW=THH7g5eQG(YU_9H@2uNIdr(zRN$-0)8wAcTL^0)Fr4_m<&1@K zHGXtCn-IG5I7w#zM|_6~H|ezOErq)q=1e(~Ng-=GWka;*AQ?Lbs~Mr<^X~W-&UZA| zaU-e^^Um>|cyXk_+AMsEbp=x~j2Jv}>YqBV1(?FqA(Zt}GTUzgm$vGsF%Xr8gx&i8^a(AxDX-O^KxJ(aG>0 zReJ|r*$eMI>gtOu>V-_a?*7)ioXK~@P$K;NNTR9dQ5PLAOd88nIhz1WjfqHoF)lJF zCqj}lN4b2^cTl=YTJ()om%Hc%WY51=K8WI@V%C|csFx6U(JRbd&Jq_3bnkf&0ZTr( zfwfOwzkpx#-+8_4sA#r2ugH$!M}bS|>UW~FXf*f15Uz7d$p^cF1DL4dxw5S#bbj>0 zAC-JBs_fzTx0RX0LpD#S0R4v}Vn(VB?~xxEx09OI08*gGnTqZ1Ft^?kKv4g?9l}p) zF-zZcJ*RyZMInZg|G8mib=w?qFcukrQ@l!Sw=`{+FrSu$ey)fjA7#tu!aFsaj3yfl zQR$Ry2;q9&(EgVuxwzhFtrY_#r)n)$l(&EQKhue;!^V@_${F-y2=ehi6#SO}iKm}e zxfd`@Jo{#@g^QixtcmTYm)h9u=oieSjmedH(iRU+#Vf{ zI&&r(8`%QoN0Vim5gk7TGQw24setT@?&yv)wcSS7i%(5A?!a1}a7YsklgW!;6zNqx z!5LWTIi4-RUtC;t-eP{lPvfbXdDgCVCu$)d^3J!H-&seg$D|c3gmkd}nnIpIRp95~ zSZZrDE**HVzjO^5l|+CS7BvN(_(@cDU7$xgi;6B1DdDxXmCk+MPDK}Cpw<8{BlzY; zUNfT;QsN_#* zLTr#<)hk$f2a<+ABT=G=x>>EFakKJbiW6B658|Y?GD!dlycd&nOh=P|EkbxZD&h5O zyxUbnEn{sz66ZohfTWtXQPgzSd=?l-xuDkCr1P@x}ikHmZs6VJB>7r13^vU${5eRmrVb0^<}it>oP>?^=O0dePtIqI`Sx(j|Y=&W`0AVfvLo z&2O{RK^wIi=-M9I_T;OhqJQ5X5~#Vyq}iE8dE{uuUd*2Z^*p@hPUT6<(MNZ9h8s<5 zH$A)U8`OU;@voG79vskALIwU zpw6WinhDZPHGrS#MpqO#Hx|vx1`cDu%bsqsNFRPAXzCDm;1-`9BA5UHV&RULyrcW+ zji+wco_)U;5ib9C5XUSGNC%7}JR%93YBtuq2$FUGa}Gkk?sjsw+Ka6|JqTfj07RY> zLT-1$XYB_Pr`NdRImh;)SjI@gh3dmeFX*!j%-{B)sDnwqZqoFDslBZ=q1U9x3s>HI z<6v#(t-c8%+1A4@?1^SSK(J7-kwHOF3HdnSviAB@q@D5i<;93f#+J`yuA>=68%Y2b zoyv~|1)A@H-kiPBbtYqNXu_ZHr`J*Zl`H*3X|Q-Y<7}gt=7UGQzU~^3$gD2JiC0oQ zd8S0!?xtRRK2WfHS0E&lWvgYPA*3$Sbs(3=0kM5#QLtE7IwT#sq;<0W;%wSJ5K+JQ zDU?{c&>}c?OYRP#ZW8{CnW7vOyo0(BGu6Sk=RfjOZQ%2;3F#Id-31b*NzBz~Q+;vM z29M_H{X9jNUiV7)X33r#3An!^F zMxKEtYDdh1X`Rn$xW?D5egxBqj;AwNTeG=ld|>maaZe#)Z5AhH88gJIMO6^_^aBL! z><;`?1}H3TRg)-|+dU2KgeQb%sd`XqQv-Cw@sfW975E0Jd64eB`sJ^8geL6z#bx9_ z3$9QXtm}zAbe>L$=A~lT1%YrvjMOey#7LIOimSfq&(zsBA@A2b!l7naS^sQLle+2{ z9-HB)YwJ@58!3vqR%TCkC?44YR<++>-DI&qEt(wrp+t~2VAVg7JEzg$wUrkVVMx;5 z7#1A-trQ<9!Pra^@^=8l6wbJ^cSUB5XQ;PFt9Cdp2k$%d|Gn@xhD7w$;(6Z=RsHMGdsm-d zvz)H->93~CQm&x)Z$0_RqIi?Rd@mVBLRRN5fsFx{(=oqkJ>pmY>@ij((Z z>u^Gaej7vLaRQ2k6uGJX^6;d&{TqQ<@YP5{A{2>-_?fbgXkcwaaPg7$E%#@I$m;NW z{7Q9!;yql?mtQ(7UGf`UEiD`g!91X)Hvy5;2y`!9K`5DLSDvyt-vnY?+POk8X@Vh* z_St&RkGOp1nEG3r_g=bgt z&RmS)>^QVsfsx z`NCUpv?{gmKDZfQPobSyI0}C7tLLL!Iu$P#C;I$9Cnbq~Xj9SqOF6BGxsalWkTAN; zbWkwM51LpW)jlz0H4#8R*Jb^hSuh!Esofk(qz)bgyY-+vzjO$RZgzEaNgaQlSPw~d zEt#btJ$iE7Y6=ei-uTvLEwbmQfKHZsTquoJRpO|qqa)n*$Zv&&I@bjr-nnM}PoF+H zGK-3e${Rdv7Bd6ee2!f0(}^Y{1tjR9!4R*<9r53k=QWv^9*?%)`*Zc2>DG&?33b*h za7Y9%t%luMTr0a$RG$Y?I1V=x_2Ff}qpFs?XI^V9N(mGa@x%AqT|T3)$fCD4F#@Ei zk7xh6-aiJAE7?8W+g*FMp%$qWhN`g|14i1T?PEtb`p~))SDU}h1+sbc@$}kRbScr% zKY`^M9LVrLFb}z9Bjn)Msf%^)zgqIH(c_Mh!|LUC(`zA|)A`nZM_v(@+tqeAt5o`U zu2jkmopQ+w(Mq~iuEg47ALDQ;FB0GqJDvO5F=vbY(d~HKbS(pZMp~+$8jBJX*g9Tt zxWBG0cQkV%DaOh&73|~VZlJ&oCCXGBh)$*W^vQKpq+rwK3O_Rv>}((<;*;c?MX?&N zwUb;)p)o6vv0UwPJo;j1yDQ6boV03t@#6*898uR&l&)v;`3m47 zV~70r0Vu>Zk3iTkNz|MhQnm50?qKL61Wd?h-yPlNJc7@dQL<25^7P`^8(u9tW`Xk@ zkFIn(zOJUiml#UaVv#@#K601Mqt%#U3WK!djU`g(SE3<4Z;%}b;C?$Fpu;hw-JVcZE4IFL~K-wKmMES&+4xGmM#0yv3v z$H!~+W0}GKIa{IAG8_6LPVF+p{KQ$`|4A}-?nb=j?y#~8Oy0>ijC|$onJb*M|hQ zS#B8#A7TL3>23IcDyGIgIhmS+14XOpcH&v@x!9&nY(sxXu4UT~(2bxkI_-z@I1ywz zufVCcZ*Y3I^&L+4{&ZQLTmS(vfZCon$oG?%!=wS(0FmI0L_+Cgu9N8+z6^T_Nmj6+ zSP}6u8IL;)@B-F2QqQo-{}v3Q=e^O3&~~|O0o5885S%}5HcA_sn8}J!E#ZCmqM@9Z zdNhc|IL(?=Q#>!w)%$x4_;`M-X|{1$HF3kn*n!n(F^$a;k68fBT(Sox%${EUcb1sB zi1GkMZp%yZFOW84c!SF_&YJ{$_+olwhm=J^iq(bVZ>F(4smW4|S>FYLX2kE-YH=MX z=1teYx2+q_weC?qj|z`hui<1?cp{LKAg~QDr~{1Ajk&vKPmk4Clbkw!)@j!cKQ38+ z@!*Ry1ZH8lYF&qr9ax?AjGw&xn)1Q1EDhHHWNjr z^CbkzWiQB|`I_qj<{Q!ca>)ts@Io#O+C^Ha4`r`_J|F2>8-fAY$Tr^{cZp>{d@QIA z2>{k%-_%%*05np|ZeRA7??E%{>OYx}H;hgFJ{K09=5QQFG&Xj(9NvqfByGKH4GAzw}rw&?~W#m_#bJU z&z6Ev`CoAFaF`+ijbqk?f{fm9gWdhzKDuNEH|z<>@moMB*2P~~)IWxWzf7mfh}wub z=!I+lmz~xb)hQBo=#{~v=WQD#nAuRz<#GDfX{~T-@BAkCha>?&Ez1sSjVe)N(b!$` zK97EVJ(?{HY@8A!<>47zzdCM21Zr_4V3%J@i<+rY?>wBiOhrrkku@(;WJioRYgett zl^guxNCR91LL=Fgh6nbFp4ZGRe7a~ru8>~_Rk#QsrFL{%J>-Y+etnKw8OOSVIhwVZ zeD=E80r#iTQM13!6wd+j3jPMy0~UubJJW%EIEEuBKY!VdhYGg}cK4HM}qq5s1e7RK@ zjZxV|AEx0@)8=nIR?Hj8K=#d9skIFbb>e%y6V1XnqnDpAnage5^BA3*b4lJxnL)e394gFyaLHpMSQw854?Xk23>Ll7Z;m7r*NHw^E7f6**B5UVmf8+Tk{}Oz7H+~JT zge$MDH@}cO}?9}9O%{u+z*Z=a(y6aSy;=^zSs~~ZLls?l&tc` zu23w}jdS`V)gPwdeTx<{5aQ*zfU8>8xRm(`Ub^XobQ+!p8i{#AonIi-ptqD2A#gTV zwi7FzJmd%7ktU=o*%Qt zy!)J3I-*fuY1jkDsIk&qBRn&B%|B24aQ4}Hr72sY z_|+A8IQ^CgFb6a>YX%5ns!i+EzkP@@DQ(i1Rk>VfKVtUFj0m9|!~7JvGWiE`3j)+1 zB418nls13k}L@A#oUbd&VdGI(+NkEr)BDrK7QVE`O+IH?&ysc5cC z!{!y$V$Q0&e`4#|4dc_NuihK_j)#+o03s(4w!|%JXg~ z5@D!v#IOroOFbVksyg2kjQ8ohP5R7US5~gYMB&O*lRcmh#sG0L{mmQkCev#A{!bkY z$c0559WjBezCXYt6bZ1;jb_wp%CRrucw4&K`SAmazQ)lxjq9(Q7i;unjj2@Xs{yuF@BlU7GT#c&yEzftg#ObK^4C2ZRow6C zG$WY-*6y8Q<=1^t{NcKNmiJVEkR;N>|3uXnp&uS+y>Z(aRD0Z}F__CY67toa-MV6ib8gs6(Xzq0 z5dO}gND({|XtROK1^KwNTLPcJ7s}9ab$sP6m0)=B@F|S|ZS!Ul{ySIr|KEL6&T-lr zWX*&v`v$8mE2k7!qV+AJXH<2H`IIpR%j)j}N?aD(Xp5h0)ZTR7U?;*3F z7JQakJJT9eEmhmK6K2o9h?_(szg==Eq2@1U*uO}02{qs8XVCo9(-*38+w5=2Y}*=# zXQjVtAPy2U9zT^>^@y2WWTmsXL0Vs6!cS34(zU$C9p*Im0`}=!h1r45k^=@3VM)8c zcb1C0s#5mtHWv0PlQSgJx5UCEJg9kULr5K7mi@T0BfVgGZqi1~sBJN7MHt59BI6S9 zFeu{Qj}FN&q4?i>G##XPot&T_9o> zAItlXIDtW2@O>{Jb2ZT%KBK^$+JI@O3(Q1anRbSLafEPxkrE@)uJC1+_QW;ncN$09cY;Y44gg+Fz=YMew2B?^uq06*p-^ZPp&db*rW+~ zZm-B^uIaFNKOQE&bOOAR3e=j}x^dG!IFcX0mxQaV~@!81?aH;tp0?Gex9Yq!? zfUYbc6niH3xz5o=fStb zc%9^|D?)D`I51X(`;%CyfeKn80kztKJEXVg{4*B5qj3Jcl;Fk{fz~6sPi~ta2$Zqj zxGtbr>F2WBuI8pKf->>Ec;8f-KWB(Z{x+9RX}Lq;uk)6yj6&JlRew%N&w);DBuDhy=be8}>J zcOuF6g)jdVgh0GWLYG1Qq1xct)sNb#J>j*9E0OHOhT_tAQOR*H0ch-o5%9ti%p0-E zC+1lrlRHFn)u|lh`IT`+^t}|7A)}GclcYWsxoqKj0P0{k7RpGJqSrBuu3D)}*u=N= zmg)NRr_ojKdYo6w z3D@Q@eqTtB9HwU(?LHIwk4{I2MW^K0@q^&L<^3N3R+Q;juwLNk(4%#dis`Ot{ zGbU;V6q>Fh2skt4bA!-`tTb-{i$V$0xi$;{Yj9d_avIV;4`mbi$FMYe)|Y&`03kgb z!?y6BOVwCTVDd+h_?&1cXNp`=1@5dY)|+KYG}~=a#h0sM9FG%G5P`x3DKy18-)RVR zA*XWHwe)oiT;WCrrN45Wm!#3C39L?dNMa%3ErmZ%$HZnCrP2aNN4>Nwt0gVbk9lhF z3+0E&NZDgUq+d7Kp#nog=eY}pvq4ko(NcVD@8PJiFqdWBqHhi^Dk$OJ;7)^h(U0w& zJs)R4EI6(fJ5n^@aH?Y(j`m@i*53*@t1-759HwL9* zDhn^g8dp{)1MOG*P6`KsHe5UtUUiN8#3+;2mkw^78uwp2Y4^$>d#{4A^Y@?W9_g7u zDlgDv`(d*_)m^WIM$To?Vv<_zn&{x)iI**sU(hzPQ#nz0@bHHdGO@#^`j)>o(T>>H?B_KI4sC1`vcRGMH14wrx z-QC?a(hY)0cX#(N@LrzhJ@sCJ}UGT3`<9ytRRE?$qc2>GO7#5qKhn(=B0Xkp}*weaE6z0ses8nJlG8Ru-(+-%kRBTU#0D+0?jhb z{v;-%a^0pid95ZFbhoyPY}%{a`E^2gq7>0+1GWjC&2k9zHl+U2^|QfjoK1x^qxNhv zHDo%_99DsRYctP1W_Gat*0cw-Dm@tC=mA*@>zO)qcW^)38VJ_rt6mMx7nGI0az8Wz zP)k@pn11lweK@RGwIQ?Z9G4p(6%VOq#zW52Ih$khusxli!-9V;9koxJiv;{yR5TnM zf~~r2PDcy=oMvNdbC+cO(So3KmwR?VPk_DdMI0<{CFreOAO|0cAmPMWj=Vt`N~q`r zCJ5Nl^D;NP^wx8==m04mh~9a=d&$+4<=+354>WV96)tS|TsCQ}lgLicY%~@9aK8RE z$N2FNpXbNIi+z@cUA-d@wI)ncnO}xXCplWLMHwV0yeR(dfA24$+GpmTwTxMjW3)}_lDR`?V`ueX{3tG|1bt>&7YdoPcN2l=6?3+kLBSOqE|^Lk_+Y8N%! z%`oqw1pNA^hyD+e+vjxr1>LKc`GsF>Z%$nC9YaVt?9A&$_p@$s78b6ORPh8o2mCkE z^*%9gpS#4EiTqL{H!HS_8bkIX?RvYZv33c-3U6f01STe8ulQ>GIgLxvgM)Yew1Gy` z`ErJc0Est^Xb9TeRnk|EU(@vs4d8~@6x*~n*3ZlT*wpjwhN{LBq@aN_ZkbK?5Xn#> zvh$Q>D}m|M;Tq}}WjIdt`mgu2E*=P6A8s>-x^HuDtH0fwU+Vwq3qBtVNr;yVjTZQ7 z+LwGy%zq1Y+DmNl`MocOy00}c$aBrZ+jm`Cb~)t=`$NKAkF9A0 zArC4V2i=T!*T~VjbS;P9C|f24e6F*uwBKYz#=l=?{O}Cs)U>1($MJQe!Db#XF@2zd zxcmK(AH-RPUr#1|3#hg6I_wGg10y>`wY?j5oph_l2O4dounu12zv3lrwC$Rcbu z0ZJ;daN>}19sV0-A^4gW6l_e~P62+Y^?399#UMc50N5G0g{p0=I28P0IP2a-zI@_~Z1YzbJ#7*l@H43d zLH8pXO(!U@KVGtbpMCP0gX1MFjLcBt(QwCh$UdP$yQ8E67CxYyPj+Q<=YdJ--umR> zx}%Rlz3{bag?7l(=NJGl@^6Y^=`1jXrvwJl?{rXm+QbK5UgxMQA~s6tq}2i#NZtJq zD-jB#pK$yEcSjm+Svc0VU_BrRl%vQ{AUC7T;NV0!fPcD{(%qwlyWxn4h<0)H;3VA; zgKfH{)b{if>h?i}z^tLzN&ObfKPHe2gR)bK@(Egmy|a?&XQ|+h2-x=-58aC0x0cdk zI$d0JxFOk+nxaRVaTHms%A;H6E-&%8a5}2zpmd}Tc}T0L7Zwqm1@h}PkwW8n1Z)~l z$+#aQRBh&XxB9H#HW;8VGpGfO)R+EomSEtvOpSR*%`r&nL?G#6Mt`;f42{C1#NMl* zs!O5DS%3JPwPVmvTe#7og~~IjvLGNX$K>k5GI(}pf2L{FQR274Fu`eoYP8O_Vo&vAKiH$pzMu6>nrw`KOvgT;Iksk$ z`Vdb@lPM69~p*sU1iRf8tw2|H0!z` zDW1sZp6`D)wwn-uhhKd0t@Ky~*_O?mjm)_W+4l|-e2bBd>EJ|LEz_=#b$sn;j8KZu>VO2@H7I|E`&8<*$kE_B{0>gFD2JvA(nU zc_{!w%xgmBUhLjq@=G7LWJs{#bM@9iELH^uU1yRze@38deHoyuEXfiuFmw)?&q4uA zuuvj`!34*iD?lhwWp)hKZgfEhr2e8M@gvay_(>-nKxbxs*^f|f@XTRiXPBVWX{nt? zfZeYIwua$cBIEt*r_Y<{YX1YpQXhYG{|^)^-s2p^4rWUgt6td8snpo^QLE}g*HgVx zr)?gfnN;k33YG+^0$qXi=?{7{^~88;D6!Z_tbcBgt-=-TD2NrQlv9-S->*>+Fo1?3zH?-^!n0J_mvr zCmw95o#!5-@WtVMVU%$*^!@pa;}-3cl+^8%w7pQpfRa-D&F7h(HA)k%XY=*DsSu?m z*%r2eZ9!L?>N&sM?5HRfVfRVWc&TV>jT-eGr-S*-Pww2ab!5!y$F%wRj5*Ndh%v*% z5Q65~j;?mmf}_R6F_ZCYKIyt1bUi1-c#rFE*O8dyY~%H8YPubFl8jQwFEJ>--o4n( zA@raE-8>Lp7#y6N)^WL$;MQ`Ly7sC!g&Jl}br;_CXC~8LU#M;4K6u%T1^B`fnu#%nxXI z%JuqM5b*tpvyB1YNE9#9|G7!-q0=Zh9|290gT-(9`&a+>6iB5n@cqg&;)FhoTM=Je zoiieXVv-un>r8H8j|klGWwS*r&Yd{cFHdt|RF)EKnz=9XWhq0OBmdZUk0n;E zS9-uR62Ue!fCD>!o(PCl#7S#jixjF?mSTFv*m|b~Sv(eWzcvD@MCuT+2o=FbUv098 zy61Fn1E<=gOL*;AMW%5a26bPzTA_w?);#3wkVebJCBVM&P4{7Rk2pG8Y?^q?h5Z3O zIsQOp+K>vHNQ`voLH1%_nH)pa#yJn~<= zw&jyK-x@E|^nDD?C=JXp?~AF+BWRosc#|IOG-zk24U03-dP|i^Skl+7P)eW@Z_8v55sSroYTIaw6S_V`uuV^$B$Rp;am=ZV#ZS%S3K zWIcq3jnREe%(#nho02lT=3EJ9eL}5M0-sD@7p7Iq#SyvS50&?QOEo@Cmjs0KwuOy#xl?05wQ1^wKRTpzEO zm0oQBYvS0OKn4~YzGlpGiq+RR5chD@`9phDsC1uxSA)$nsPf)ytt2u)HkV0o>@Ku( zn4dhSc#xiM;!wnjMERFyce@2~=M&ngRpv4DZaF%Jbebo4EJECon zVd6t{bhOKDk8X1<0n_yAu@_Dt1C5>12v8F-+9BOrl3TvvqFDO2CHX*vrI#gO)^Rf8 zF-)f-(I_q+J3|`I0Qw!E2o?#UxR{-1{S({LitHDi?Dryq(Uo_X_L9*hsxe;zZ>LM7 zN?3UjO=U7ldHRLgS=6PZ@`8`P?}c`Jk^$|G@6=n{?8C#0 zFgKoHd5NFX!U5Sf=Y%KEyI)XHgtNgA@$CTns`vJN7UljitLh$c_8>`z%2iZw8%R6du?X!N4Ey7Q%GL4rLmuX&mwpG|xh%r93J&N3;q02wlenC=$} zJdqzbsoq>nELma5;JB z`s+~rX64HhyaOT?#OTL(YYl7*-HLBx2B1Oa);$QvW*Yd~>DB(h+$)R7SPY5@DT)ON zXxi+RiD4*xg^cI8%NyvgAC%VJKRe_1zB6B}|0%W8*%(Yp8yajnPdHII*PUs*hImp< z*v=T$D!aTA;K-E^loX2i=-{9tRXrA%pyy8Hj2{yhKbXMeOY+-G1eoO{DP{<`VgOPV zu3X#)C<8D8@NtO7yD_+ME>x>1|N5v0a$4I|Tb9}zTUFhwoPGP@z!{$mOsaLBJ*E4bE9)`WGH8@NzmX69|_jP5-;83d9N2R$kNth8UPEt7jSGf7$DFc&47JxSfWHjl1N~s$d@6H zT)W;+m`Ma6tzu#1OeT7EkSGASb#e4ZG~w!exCyAJ?1N}tuBVePP-l1Jl`4*B>2~p} zSQ%TLFgQJPk88%~G(H%1*wk$Sk&7MA>!u8eQG!A!BFfnN84|*j4s7vTOk9pF86#4WN? zy3*JgU=&nM&7M%LW(?Ekq{LB5EWhSHXKrTA z)$W4c3o(J8YWxY0TRy8d-+XFQ^Gd)xs2E$}TB{g(>TwBLoF+eefW3H0MFf!;ilPRl zYs3OJJ+=XaFmtWs?RLDc8Q*lTl`tqsBN!at4@QFy-k#6G}?F ztX3}QNQ8@GM5|mz&9Pi+5)AZucNiA*N~2g0HHWc3SS=F4AhR9E-Ln3qK)VElXZ7G? zYue9x0(|dNgtPE7!)ea_gr>iQG9)(p%|QUdB`X%soaE>V7yt{@>)ZqtW5$nOs_tb{ zPn+h=#{^SA(gYDxQ zFhbnJo<2Xz@tEIgu1$h5|6&;@FV79`ps@H9Ec_|(x-~c=8JFJ0l!u=SIfg#NN)A!nHe+uJ_Y#aeQ|9z$xIm#H6)Bs2V1Mmjg!CITqii!=y z$Jf*8L@+47r|thr+xApa;Ld<)k9oPbAC^<35zf>Zq3!{xR-)%yWLE?&kDkn$RZkJ9 z?V%34F&oOhc`}CFuiMSAO12@gRe@Kp|01m$Q# zD6*#$$x>2+?i`fIyJSX$#l~rg_efi-KvjHz70^FYYryww?3GM?#V=}E;<6_c+B?oH zhgzw61GFSt)^fz>q-P4&C_N3HLVTP{5k9E5{)5#(s*>W#i!X07SN(j%-@_)ms`NOC z6PcX-oHXL~wcRhOr$Q)76OH5eOV?iAl3^uCp~|v!v0P#?Eia(e>&qT04>kjS7q>ul zxUFavElXybl^|sp;z(FP^N@P+=x>z?WWjaE%1{2vTW-P+WfffRy~XsF{*0z8{du~4 zr!HpF%?iTV!L_`nJJX}@sL@jp#ngso*F6+-wn7uNJ;3JCrr(;Yr9jsfZD>Hofjg1W0{};{%AFUahWvUP@{d12QdDTqPTQx!@M=V#XsW| z5iingim|wZQ$f8^nS>$$fJ?_?oy6f5S#tc6hMg7 zcwXD@N3L4SahZgxF<9XE`K_Am^^wj}F~bpc@fQzb(a@da@uqXaKn7CJPA6OOMxe+z zdS~at`;hlsWSlbz(+Yx>~t=F|RPejDu%`hNp1g(4pdU|bTm)JwQCjI8>BLOZG9d5F$X6e`B zTtC|CZRV!;?xs1)o6j;@>dhK>Ge7od_9ZSYNxH`51(5ibYHVkC-aKK|YtChg1srVm zye1s&WP%tP)ux-*o7pj_uBwN{fl(~zNh1~_rcEFM+C$zDa%EkAr>S z)BH1UNe>;NW%54otCZ9G5FIT!U^ADQrlxDYnYJLZ$5{Nb(*w8TUOP!|d+Hj8B_m0; z#QhIe{5v0}{`&C7s!Dn|_L$BR_#(-xb$Riv*&?Ym;eX7EV#I2Xrb6kKWjX^0O zMfqVt<>d7`7D~3%)YprOnjc7Ik%2)m`p{B4Tk!y_3lsdL?cZY7tC@sdRc%B%2Hhf{oY%aSpo$iKtxW}3k?k+7}`B!)oLfD09|(; zYBh8}Y#C|^_74Xhc*>Ex1`MwH(FDe}?>^J+qNLJnur8i&akwQ7;3J28G=@zpbA3pu-m{pe8i~y3OPW*25zX44t(sz3kDg8Tn zJsOWYhvthmIIh9*gK%>#%;~6a{80{tQ=kvvzWnt90-TsU^V4DFd{>}03Lw&>_&%{| zmHDORE3d~LGt%FE^UYO>y#M`()(I13(H*gBnQn#jSSlaB=j|>1#z2~MLc4VjslZ}3 z{@!MY_HescEMgK64VMJyA>K=mz_UyPlPPjeJclSkIR)7JlTwxfPBCkD5{AQIO zoC@xg=2 zXF{a2>;;fJwmv`H+X_5_7D+Y69gULsij@k~Qj8tEd}c>OGZ&AhBQ(y`0k4)vtvRlm zj2u>6`gM&+KRid$467_;tH^%BBy`A|cvoBGhZklwzP$ny$xv$;UxP_om+mh_EgQD% zpYL%`aV73Z&l#(59WYq-gG2o*h*GB8cKNcFkngH%jTpZ{#v=qZsMOe@dc2556NAWG zo35Z9G1rVfLKsG%_yBzx`GFYt?T#>dtNX*;wZZO5Wpa4mA{4qhQx(dS6Vu6H)~3pr@vOqT}BB9Y|~6pV*%) z_tx8%7mql&d&2cd$?tO~U)QnZf$D$HaKzH7vq~q6b@bxP3gD2k)!oEsm;z%tcYZ4B zvh?FPZuYBwX4K7Hfm@`WD~`<1_ilO>Z>ZWN^A+d@{l{liHtuD@4wV!Avb@rHNZnYW zTZd1)o&>tELRDAeGM^|`0dP_yvY0!r=&|f{eS`1WqdnO86y-rKPmjTHT}WYN15ON=q3hDI}sr%Zs-P(|Z_|@ zvQJ=ZOknu2+S0LwX`4D)s(9f0mIfdHx_IfGFWaAsWJ#qK6b^n#1Vv6G-u?hBs2rdX z3Lw^d9X#4BJZ1zCE8gru3=U{ajtX|RZ(AxMCAeV*a6F;;tKlaX6I6)fK;aBkm*NN zxu~gXoj&U;VhiBy+1(3lq5?9uz&(E904($S$=EwvD7f{04hhO>9arvIXQ|p(y!lPB zP~nNr!?$hLOvT?9PGU&s z@z{M_ETa1(T=bu2-s(Nr;!CcE&nxOY-}QaXXDREJTNWnU8>Eb`H<5!cI<2+Ay8j}g z*b!+JZfy3ccf|FeZr#kaoVZ?Ae7pdBP%Is_DOrcJ{vfgkj_kYd`neLDy|fy}G9A^)%@E_Tzp4$(Okx$XttWkK6d{GnR zYNvVIX_1Y#8PV9NE=*aC^K}?$Q|b8iS|-8xDAC~z2oI*bOj25(AG^#cryVNirAq=N z$)JA7sJ;6YK~m>v6_cUTDUnu&^R4!dG@@<5PG3Mn`HUTw)Nd9PEz&7e0`n&DRi&cW zT+9-9R9Wsmo!Eo8=)%ML#`oF1^2s8K;xk&g843a}M8To7E0TO{lx^~`itnu-rMO0{ zlX}E-Q6FZ1rw4sM4t@oB@IZ2hp&ar_P!cU(W3FojPMO;0wmNF?UE*LatH7#VjiDf1 z2wArz73l+<&d(#2aST1y*O;~pC_~he6E2D z(_;Q7n{mkl{sqcyoM@p!(?yv_pa;f>*ICXOuSyJG`c&#M{?Wa+-fQhYGS*ix3dJvRe9lIe`MA@Hjs{;_{M65k=7O+f_Q^WjCASD!duEO)f$qaY4sfe%S z)A{j%mm#dNF|Iv5m9N@pwUyC)>3~rOfFT7wAcK1YGoX3O_X@clPKQg7WJ*93K~h7w zQgv7Mt8Tl5*ZxA?7J29}%!Mm{(IAOxRn+G2+T>vWl<>I$0qqF) zVPZ47hTfdh@#_}V>~kw-rDbZTDZ$O~(CEl1Ckz?-jPJ&9X7VZi2A|Yj=bIDiE%8<& zaMu>0Hy8@u;pT`$Ns5fuGyT@;3C`H;HR;NlvFIB)AjX)E#X~+MheHW3b0~NeRJHRr zc;Mpkmwf}gdaJFAvPYuTEZtW<7Gv2x!YHV#@tctiU zRjXJ-hvpt|93DZM&SgJcSOz|}h~I;4+O?1kD5#eneoj7v2vw)azNIt;i|MhBH4Ub7 z7O)=f{TR|VNt#9%w^p^&&=DS!EwM9mFEmeCPBhP2sI$q*PufJzH(%Y7eCV`@8cp9W}{mRVlZo zYPu?eOM#0+L~6&zfO3J|!HuNxU|ck*?Bd->l#mxVjO@J3fF$90XndAzpl+kF1ZuFJAc+SEN_q6bbHAV z%Y7Re?ZDRqWw9NQ`DAGfnc3YAe(};YHhvS}b^vXg-}e+$~sBX$urJh5~-X> z_`jnRur0Ddd*?)aea@&CRH!)frF!KH3A*z{a&{o}o+Ia{g^9>-k{Q=P+pV0@B@AEg z*x!I=iMZzorkB9&aH8+>{EuTQ0PC8iHuFjuoiXkHIC}3y)2`3(o!2C^6gGTX>7E|G zN4n)yYPwPL3QPS~;~j-=Y;|uV5J+5XVLnL67(&!5t3u(MH%y&{9(hqS!@+!{dXEc?{taJv@Xux!y1SM zMVtmP6YA!z=${<((A40Vx&}3p-%(<>UcJ$(_dIw<_6)g%*n}Xgi-i6QI29mfqW22g?S+zVpX{UpE;@rM(2Shw57hZTd z?Qkpr{@&0t_=oIQZO=Dgm|ZQ}M)v{%N+`3EU7I!YsR^OL5y+qrJ_ZQ~I5fK0(~nZj zdtWk{t*<|}`2!#j5~-2oHnio)^q^nerG4v|XD_Dzv9e)$E`L`t3AcJ@!7GT?#ppt| zR~o=tLD5njBuDEud*;c1#@ayh6k?=R!}Qxg8qrVdrR+mJ^n2st#`?5Jj1YHNe2)9W zkt|vI+sd=k{tmv)(4-8Vcm&OQcz6z$nnp?^cmHcFRJNI-k}|QlLYt2LWtd(jc~#^+ zGOyLivqRi1Rq41~KZBzb=An>*#thwb=KbKP7D9n^iG2iKFQS&Cme?5Dh|Tntl{b

    *MPS|0x=1hG{`ZJt1YU=3CyvPPs+*5V| zN9cy<_I$NdO5Zor(e4rt`0j*f;s-pyv{Y@zi=^7}XOB4L5Ortyd8t$FFb7}JDgdswhv@_t=6!o%r!oKLRB?~mu&->&>^;oFv0m=DjnM` zX8#o^YSU+y-}&q=t49_TAaJ>bczX@6zWK_1ZV=WX>=9qxeyXv2H9W&$59en_jnG`_ zZUA$l%&I>dwC{_;x@?&!)iI6kn|KQWQ!Y>0=u+8@oB-jIh-{mWb0y#TPJ`DeI@@DP zZ7Q`J$5qi99&`&pWAlj>*Uh2@gEMYI_Lg`rva7DgnP<-L@>*e+v$DUcQ^!{duizN<{RtN z&P`56d0<$6MJ3<|JpVZ|yK4-JtwG;_ zw?%-Q4IOX)27O{~%XK4qLewXxTjjLP`?|+FK3`P|MbXwGQ>Je0)e%E44{?D6z3i_v z>ZkBf{o{zb6Bhwq+Y>#Nl0TC$$p2Z|J}azYcENuBJLBY18ZUrLjYvI37;qlJ# zq4zBT*0j%sJrm;w8s-jO{#}tQlNCyX>~~&>LC?!P)(0x%2@O~5jI) zLkSTJrbD$3$8)1X>cU39P&15rfPCtE2ULM+1fB2p-XF?O09_j?(>m#|Q-M$_;a&OA zx7AL%4_~K9Qg|T0Z%Y*q@k(|1kwr0xCO3t=9%x8;_QM>>xV0u{)#+Ku_vvfBMrQRq z>3%HKygV+@U7Q2R8_dm<>wR+kjoRVl)KveArOL{*INx%qRHDgot%WBSj?sd6NotfBJjI@Kfs5F@S4rbAZu>Jl@1;sEh9*29htzySS5^s8A1 z*LtpXq;@#YqDHYy)reW6nG|rii-x@F9?auZ+|4>`hO$pXR*KA;SNzqMyLN%caTfsU zcFqC)Y*njgQw13hpo44CO%!yl60TFueG3p60BXt3ME`QuYgwElwBAB9MSq;;wZK5D z*djCDPBJ5a9J7#Gw|(8gM@AFp+x~RjC}PVAUu%yln>KwnCt*?fAoBn>z%bCLn1Whm z&sI1~c#0?Xy}}1OJkA~r3c?Tyfk85XJ<{sUr{^K>To;%YI4x&Q#&xwG1>4j8`A zxZ-+4Omhtah%e;%(A{1B>T)5`lLQlCLSDuhLqy*iFx$y%u#bI;Xmds=^A%I_f~KOj0=#-MyM zi&5{53x4#9?8WpKI7MT$CCOZVk;h4)=gB*-lVXp=BF{uduP6*E(-6w9j5R?EJR6P@ zi@X&^cbC-8xQUFeQP+?V!Fs^WvlgJCalURs8Jnb&ESvFYm65g4q15?)qh?N=rZAsc zNw<-?pfm7QYdvwF_j75<9{*#LIWCCU`&TQ8A=ojNcppGn3Qyt{GvXIT)(p1RRpjgW zQSnY*%cl!8*Pd^WK0lCe3!zYTvS7EpPQjOlBFEPxq^TIcujv~X+6y{EVS9Rd+C6wU z)=kLCErlfj{79J!0>ls13#wpnJ!FvMB*@(_#2TiWcW7}I`LFU zYpyC{wloavdDuKz|2911Bf_nY51RWo61LW>9#_}J1PQ>0oNW*IZ-(Y)$(32&6}IZi zq;r$uez6J~3-CPFOK(u;VqcLNh9_RjN>4s$~cts4RJo9H|0S2aR%?=$xsJKc}|2#$^ z`bv%Fii^jKUWaIkZ~k@>PC+4q-zVrocPD^h7U5vRAGXlyrO%69*uXJ+hZmyo~Z_V0Prfw3-Lf=C37kQ zUy}=B-7mD#HuiqUH|wMr-RBys;p-wdvsxT`Ea^grEWRaUpn>SM!o3*_8lrpj@q7_o7>QK15SV5KUb^2Uq?lN(IXQG@! zjvVw=OT#bqi%s5SRtDc!j+Ru86af?1r8cE57e+h{%GlPk4j?P5GoW-L@CZs~I!({? z;=BCp&x5(u0Z3j5UIbYYaQZcJ3oR3w9N)OL4YL61lZNpKl8Uv!pfl8>D}%f{gS>l# zJpE4|+|TTc7PTf5c`KaH9@xo|)7L+ROKNxc4zEW>X03JevkEwA94EyYiH;t=jT02^R`{Ft`vl_LcGob9aZ^a|>cex^uo%E9q78imfWuxU>e2e@Kx+xI(VA);_1=Jr(U#I|W?Z!Do__aQAx$7Y z%d1hBJR^=ONtUi4{~B$DSD@T-+<-C_^ghSUM4Iq!F4>!RdgXCnuY;A%PN8Es&9w+r z+^YSb_&Hf&t_zeIz!G}v=h|~+he0w$MUs=VP{ArlpQcRudvdmmUCTXoeqp!AN3&t# z@FZoiRWD>cVL)z=p47|vZzYA%BRGcYi<+~` zIkqz)mm%Q46`tPd?E%D8{P<@@f%JW^?!p&jc)ZYYL;?Ez*W;#;OCj+i0!SzZcnF-$ zwagkEsGM(vfM)vVS&xv>@@U;ms~w&k<*avBMjOBnjm)tudrQpuH6&};mtZJcWdM%& zs~qxaSVFuKe*}47qqc^af$Bet_&toR3yo=huTO)o0fi%yq*wQ&(+=v*jTeOEU@Wl? z$%Y>s2|OJ9S{kA=?2uLa#p4iy!9<)F7~H#`DZZnwa;|BbAAo61m&RvWzwnUBh@P)r zF}Ts&-rWSvIK)hH?=88$5%1z-4~du#@MGH@53Sxm&;aBhfmJT7y-|d5Y_ zdA3AUus(|dV}*7qA)M_DiwIafSD zgM&rjVkHoo@we~eF%w<88mH=JwSe5lDLG@m+Zv!r!S zmmb&b9%P2h5}>ePlq>((;@2hQt@fk> zx5-baKEq>j6;N7gySoQd!6`bL+qm?=*~eW%WD6(QuYSF(oGaiI(;f?ekZPv%7YpRb z8ehX>_fy95BVR*lW2e2|r(e%;kql1r%zDzLkPYh`X- zl^VUZCLY|mc85x`jIUi2{h3V!d7%TrpC)^&mkI7&>!&w1g45?;tZ!str82XqQftRv z0LxLebnZLFQxG zom)Ke?Sx68zKAokCoaAKGHLXWd`y~ax8!*~^0wwo{M7sX&$adM1Os68?%X6!h5A-c z+39`u>G7g3R@Uk*tRm6>K3k-9R{Nfi*;>Z`WCw`OQNP^^IJ))^RXaIeHEOPPUw!rRBR}ad9@9vT6mK0xz_mGwpz_ej4iY(Slnvj?Xy z*{782Ro&%je>hQe^=e}~Ed-0EkDh!l?{#GxtM+^LJL3vIvEW#DS-x2N1ruvZTJb50 zZ#x1S<*})R=)7M8&OeeXx4^reR3sG7t)9Q#Q=tdKo(y5rN*=xqt2prn|O#RR$O?PqUpDn1<>)mp&CAa zr-=J);Y^$d{@-i460q@n^uVv zSH$*NIosxfm20h6GWL|97_4{!F7AQy^In&$1UV^To~Wn((fO>3E~A6-`UL_^cOEl_ zG3Pl4>fV_*a}ocwUhAnxlEFf`XnNm{Ia7L-blJ-eBX~X-8o$|dkfM6m63;^@=25mL z)9ckv-W~2N^z*B;CL_&^K#Qj_+&1m|p9Zq8h|KJ&Q%Yq{cdor1ROkEKK5pQ65)Uce zos?Oh_x6AD;I|7$Nq+4S1Fog7egazdo2*VPGbs~jVmGqOdi+j^Io2z3w%;)c?m^*v zdv1T>i4uEX?a{{#r|7m0(xrMi9lOH571_~y$>L$7*%R~G+L{~;-$T{W%tIZb;pfLN zkt)E-RBtx)4Q3flWLc*kRJgL5uBoqsTz&vn>|^?u9T>PfgKh$p#$gfJeU_bRpsJnO z2Eev(*CtdZnd7EjZ~G2N3byEn8LSgJ>|c^n7Am0nf{8=A#3MA+&2&(&GJLP##5y|! zRftk-u$T369$cr*lI|6oD#L3!2fKnxSm8g{3gbBua7Q@v)vRSp>s!(X)i>5VXiz}`lNIU9VqBxcGYtmS+DeND z>4XZyo9lJOB1_~C7E8|o1fHrTWltUr=tqqnu2Smh-X9~eakvAwolgd$3(N0fDE_?jC2Q|T_?YpZ z+|21#unClNh}u$rdF?XEs%|0yEH*to32-R9H)&{^tQm&KBg*>l@79SI)Q!`fafE0s z_J7>95{}u4VTExi)RE|^a)Jix`UmQUu$l7S61_;9-R*T-|B!-rva-+C{;l4yU$)(Z=>i7P*n8 zD_c+LtmBWJiCgJfcCx@yh@t(wci4~n!^`nAx;$~s+CcnZC($>XOZ1U8&vL>kh%ra< zg&;I~SeoVi8y#j#8#8rgkZgG-D(~)%Z;(H#^-2D(wPAzY^#$_ZDB5AC7q>WMx zrOUD6`VhSpS3>zgI{c4m_16mZga+j)9NlKwrDKTJVEhp@Cw3Zd?02d)Sxl78fWps^ zXc3V%TH;Il7O#ZuM&3pFQ6nDtqXh=#cJc>4+g};=m)9eON^NdR8UdWD!_0No((^Gd z*>ZK1s)PpvH)ULt?45=%W}>S}WZuNRHn!myN^FaYYyc&;UMNXJtbNe&pIk5+-0-p3WOff|38A!qM)HuaTvj2sGi1cc22k{ox6^ zRK^l|wZ>e!o>?uA*M-Mm(JY_aDwBDfgN3?fepMe_@GU7_D3NFhp)3+oo-{#Ue%<-W zWhK@7%k8E{`^#jDCLnpWr+2xYM*b+(rCyorVrS|}o}=^tulpi@4$8qtnN?dM3?s_( zJSH_q*!hN}stvj?e=%(MpQ{(*o-PP-UTvvp=U!CXui{T^F=p3wL)5(%&-+J0`Lsw; z3-)1XN;c*5U(z;k?~b~ANhgx)Je~mXmGvHQn&U2$>LX!kWn)m2XV(YB-XG${V{N`~ zEBn;P>(_PXw)tLt%CX1oOUIAj zWRYW6tj1*rgDXCx*b^$)rlKfv3I9yEC={E;bBl;Ls9|51Fc+!nYF8#Gz4Q0X%_pmz zuCsO$zZNYzRF0q@4Pqstk=izXsJM!@Jfj z-*-%D>zj_;xUb4$(D%OjY!p6a4sufn4#_=ZYxlQ2u|v%rA=}qL^P6a*(G{jDCE8yQ z|3eAD-P!l@pxeZA%MQ?1rtXl9h7*3W^c=G6Qw3eoT+3!Ai?2XlDisNfWFfy-+pWy0 zG$2a;9Qep1TGX{~w3)-`l3RQzYE4#=uDuhb)>afr=adt8eTs9lD;R(9wcHgls_rQl0PfrXLan_X|Gs5FqCH@&k%ps*JuK>f@3D$V~U!c3d-r#)@xiP4NqPEW=;)$ePc^~5jLkE9 zAh!T2Xw3`|8VUd2vTnQO?6WR{zm4IQP5LJ|_t?s^usFNjAX_505hW{r2rj9&CFSvj z7rKYL^QUG9d#`+4vOJvE%yG;Yy!i5pYy3i~Z$2JFI^ zxi=oI3T6QIzEb5!PZ2Rv`W~HDhv%H8k0C!wc-E%rN&sTrOMvrDBCF{jJS%m1+Cjg( zvg1X6G7hi6gbO&_2no#QBIiE{s3kmn+Hv<&RhZMDp~~(U-1#&K6>fX zuFMI8fcQXV9ojl!Wqd`2C|A}w&-j=i29KFEKAJ}yY671A*&kV{?2MgDIxPU$ygDA( z;dPK5w_|3|n_M~RyZ@M4;vV&*yHXw`F*Qlq$SL+#0J2ExMa)c4;|=fz&ku4nk%E6) z&DBiux(r%Q=iy6HNbdHUn@^{vm`hQ7@W;Vs`x5M%wPG-S#BL?%(x$r*9xCjRFLM(0 zj^zI_b(T?Wgl)H`cq#5qk>W*4ad#{3?(XhTG&n_y6bVw?-J!TcvEuIT1m_Izd(L;h zA0e!Sl^L>T9=Y$muPyf2^L_^nV-`(F=$^7_^&LH_v>9K^U0oAqVkNnHyJwD`Px4<~ z07;Co*cvW56jl_c6xibhhgPx(_ZdE*>;Y_E^0B~uES7zXuO^A#Q%wvyD)EdmPS`3q zI{wa1a!9m}F~a18`(2TMm&yA<$@xp}CsM2~87~+_v>T4e7d09TzuvIgDanua*9Zs+ zG!FVnOgiv@HE;mj;5L0$q+4H-#E)CXr4`_Vu6DeBVWd+n+Tbv4uSL!a-5?U-YEU0; z462W-0#u)ue7tTa5qAl8Sct_IfB!Bu`SIdDuf5`?H=cF}DChlUM8Uh&20# z{W{@h9p-Enm8Sw}bOJaFvTUIrc03Be(lcSwFTn7gZC6U2%09q_7}{SvZEnHnrI;d2E11B~c!Meas|e$Kzsdy#4O77V?8!6#lgXu*kbwP9u{new9_eO64H} z5LA8nBNFU!z;q@)A>d%V`1?!_p*Yp)jLtpelwUFLfwE<7Z^EEER?kPN-Z_6e>2IIA z0`h-6C;wMDN4V5rJNwL}Q4Q_9J52EAkQFa#a@}QYVA2cRLU`>O5(POvoGVfF_L2Lk zVmoh-ZLK-)j-+3|dL_L07*tcx-U?a0#H#)G(;AADS{v*g2a@H|&o_@MLs{OKCypr> ziMz&Ab)it-1JK9@mP1P?Yqb1aZI@stzIrX+HcHJ((yv2ir_cMplc&BO5l-{@`M3++ z(4#G0Zhh_Pcp__fZYBcue+Q{!fGgT?aNXl#;8xFaB?pat4M=*dey&{E|Cj&y7e`4P zz~b4z6;y126?nbY^Syf^(ya5u@&Cn#MHDa%5&W#(hIxie99;?W&+eeUiK#2Ip|N$Xyf#Odi* z^X4KbWd9Va8sWZ5sk~L&2j@m5)4~D0wkHKMnw1N>eVJQ?DRUe1`e^r~mRn2aqkXks z>C0BjVxGkPH&S+#2VLr;`eUyoq4_l^Anh08GaC14feDS#zF^bnbm#3Y zJL#-OiMFD?NPtXPPY|sr|KtTBFJ?GhwGJs7n~xlJssJo;aw!7`+tE0x>iD9 zMYBShxODm42=Npq=jO{7vjtZ1*^%RWe+|VaLG-g_Kv*SY(p0lLDrJ9@`0BhRz4?Gr z@yz{9Zuqw9wIlSRTM>qIZ)SqL7tP&hoPpT)k`Q69p3dNcKN}fA2r4H*o?!_MgL|B^ zU7`*n4=b6*>2UdcPRDL;Y_uc5c;jp86UrJj{R12#!0=NqITUL`JRPEp-Z?#FGhB8n zUx(&ZWsOy1{6j5a%W4g_)vx<10v*W>ZBhY`<{{K(rZ}b;%m4Cqwc`eD8Bn_%ghNdXSX97 z6$Des6;CK1a4W@wX-NATMOTH3F~$3yusW`G#zGGS{UUZsIgE}&463X~41giPYT*aK zY3}7TXfv^oEADv5Rlp%TLoRu1d*?OfQ8d?tWo)uV%hBukx}x!eP0}5J@i86p>th{p z*yil6G~18?TyxOe1qNxECN7W;(|^U!OX+bLLJp|H_mK(yR>-l{*-Rq?ZZP4!J$hn9l%clE~Hz?--~RI7#NaM!=< zB&ss%Q?*?>{{Ya(&@qg7j7q11cUateEtX}|s|L}3){RC2vk=-^Kp-IabNRW{dgkDJ zdGDCy(?q)Zx-Xm2={WRbFQSE=)#U--bnu6Miokv_`JLH#1^5PFa5Uh%{4n^JONl_1uNC zGVD1Gg0K!L>ol82cyqo}XbNU+(fE!cPmm9LneAMODNb2>-A}6|@p4@zTGchB9qe`t zx-H&VV;Vi@M&O){my?o}U~Q8(3k4oSf($XU9V_i*ND9+7HPkh{e4vb5t~aL*uUdrm3iAQ_2P^HN9NaBL7Q7Nd@u zHQMp}6kgJH(e_NiN6M#kqD1@xF$h?E1p5d1S9Sox5@KxL)U&|v$f{~)2mZ*2!}vDXYzV5ys>$Gd)`;hpD5{+#d`KbFjBF>2u3lfT0IbFFNs zPkyj!Hk^>cv!zBf9C}qpK6p0el^VwXt33gpHn15|n5LhZRDV|;`~lc9@$To;)W)kG zdDWiBabbQ3Q-zYV{U}Ng8z99ZSGsK`M>2ce_Q=$z6sMETPrbS=AvSo-6uX;dZ@<3Y zoSL6V5yq~^A!*a7?gSHyBS2lL;7c;T2B1_Xj{FtKXH|=}x1x&!!d|;>2L%fXT z)HAo~Xae^ncmR%oWE_%H=+rY%Z9Iuxckl$wlv-pCi;00@gg+nuSx3F?>feX3K>RVV{r3Z` zPSHxd)h?i<2Ct)4YYKp`8<7vHtDW%q?UDR5zQVkfg&Xycf*+%B-a)+$orMz_J=ths z`@=x9Hl5I=#r5C^uZR3hu@=f%`_-4>pP%itBt!iGkRhlag=`>tfv$vp+MH3Sb2U>G zUyV@h)KMfSc#ag}$$?9^hzIKC!8jP&{ zX7H8J@8u@DY{@=oeL^d}lpjTHl(h!Nmym*Gqk{RIF6Gl~AdldO3|`&Qz_iV%)hO}lr?bBdhFL?s<$I71%gdZz2%q4+*9pH<^ zkLKu!n<+Xs3L}yk51fb&b=lp_*KdFBtTi8r^c#2)H8nMrO5zoJG`4yiJN_U@q^Gvx z*z~G8D8cf+kC*Z@y*roJYNq0G_Wb)j0WXTM)FaNxtxz<8Y;%^pz+2~!Bp7p_s_5-l zvbI|B5cd6Zgh{~0rWx|HIWhIOtQ~`vFe(5A77?-QH>gH07+xvhgT_u;eys3#HZQGO zvWo95m9d0N&dCiPvIuF6nT^oRatpo2Pr

    {*UDcTN%acT>`3hCqyGddY5@RFOMR{a?1Uc(u{2r66 zI>#)wV2z^i$TM=3V->{`OgXsu;;z`0?8Cg}eo4)VlIgq_zRh4IViQ8ip+*H7^+&z0 zzFS}u*UZYSa69uz*HQ3w7(P{%3vs*UdxueFgS!PWj!aq99|C;}_PR0mnig)d9#*}$ z_)g`zxS@Nw>qh0yh)HuPBzjO#{s91iXK$M`kO91-r>3XJ9fi{89GM5TS&+azrQ$HW zNG6H|=er3*5aWM|>vaE(43NuLaUB;TeZVAq-Obs3fhOtCXZG@(TOZNgq54TQx|Sc* zkAKO=X*KT??pex(3eQ_5&XPcQH zH0vCyhv;13Yl^f_4!gmI*Rl;H^As6Clwg-3M7%5J5_bS-C~HJOKs~ws^)=3YDg_$D zl%+t0^QOa6`@!bb7t60V+een`^ZA|+Us}aVillZfx%G|Xe z=>@CnKiwM3{-2fq$Ep5bq8$!SRhHi4_dMqf;rOp6+06S-gy-Ba*(3`YfVq9cQ_b5I z`LIUh3DO>rY>cLL8(LrnMEHL=1VS+XgT+5Q<0ogasO=CdQ-2B;zeX}@*`zP%J0C(e z+-a70)Y3x$qUTVyEHJdbFHgcLL%Ge|lqABXdwRJECSuy{KJZI(F7$`TTt?+^?Emup zog$vH9FB0GBW&+nCr>{kAgmbx@5Y{!qKrw5NIC{g>YxURz39M6M0;RpsvRReEAkyB z|2Oq`yt<5vpMpI4lho>~<^($D!7KeZzDCb=N^ScFk_hc9%jwN4XgiUxGx`uB#<0e1 zz4;Uy1Sbewf3gVOy9h5k6Apt))?3g%693$!)5*tNJZ`6?b?2K45!!~sKdYAD0Jro}z+p&f2j)kQI z{y4Ovml@s=_ZW;Wq!JcA;~PT3aJ-*vbNo4uKxg_xDlcfE!FEdbWwz2Q!RoI9FE6Jp z6jfssecpa8th0U@t&{_g8*UeQ8zj=$#+OLqQTP!H-)cEa%e$4a%e~u#I2$qPE1cm+l`-3K^N3RmC4(c+NHQ zP-et#0DNbBa1uk$utI92`Mwyj;t-5ECcaHbN|7KSnIjZ@BV(a2W^Rxjr@|}mpm!oQyQ>y_f;NzHh@=J_}Jn4&^>L>*iQm40cIsxyI{Sf=c^;ZKf7OyWh`m)h!WC zZwlN#JK1S?iZ%00JJe)_1-3AjsquXw{HdwpN7{0_R7?tRa+P|-r}86#Y2qgr- zX{Hv$C_w))q_V;}fhAlGR-u3UcHT56qbtQEfEeh%VV;;V3cLEkQJRG8UOSS0{t)ba z(h1Ki-eL;rdkQ6_pesk`a#3&22(6{N-vz!@-~a1iV|un6X|xaGBzolD3^RZrv^5ohgNkG zijOWDJp$>+kR^9DWL~yR zyxvOfC2N{KFyW{{WLbCj_s}l4)%)jJKJRsoxMJs{046|6*V;0(H_mVAIcb>G9F!C! zY$DrD_b#{a$u+{v>W_os&Vb3!VJ*jUSifaDz%PLu>5bp&BhQSbhSN*&y!*{gQWAc5 zVp8Q<&@1law+ENMc44ZJC%VPc!c86Cciyhs6Wq&b+J8-)=&;iS-4@(4=+|A@=I!B~zwcy-F^$i38BU*m-Dpz4y*76^9W1twgVFa0TPSyx#B zB=V}eD6SkVy0p;<4zVBkD+nA)&9MA(CZcCo#~FJ&MRp{X{EJStrs@5^CSGTq8c9@B zn}q9YM>HwLc99mls{Faj5!_s9A8urgNp= z^j>RKzFllg|0nhILG*Eu&U<*-3AdJ9_$hz)qPc8K7XU5JtvVJPc_7-T|LAfOkjvY` z)fM4@pTkJUdHvG9HoPHG_lBwcU8p*mlylEQmaoHRp|>~n6_-WB7Q?ng^WWcwlonUY z74B!Ja=+_yzKTHMxdQ8TXyM8gZrJ9) z!Q!O`R8Pk%@%20sPT9-Tb^U^n)^1QWSecOQ#|i3R7p; z*@LyGV>5&P8-j{ebU8FwYvvDeqB)1rg%Me(cdJ&=9i>EPQ1fdliX&sx6iG`#Em0QwRK#tz<1Gn_cwM$}0h za%l43)EYM{A^Vx{&Q;1B>`f-?s#IshVBY^r94WqD0n|NQ2l?n^x{ z`uGbMxiUTpO?@5U@r7%3xIs68RG^pm?8&m%}Z zp`7NHfFrPZ`dvog=EIgJOc-+Dw~zQsM!7M^>ebu~|4G)TfLzSjfkN({1LIwMpKBGE zHDswUa5sFRweTgI#2^0>M3w6LuDg~UHiFk(J(nxDlbbaV2|e-(C&2B8iDvVA>(ysb z@ax0!g=vRzc!)l7JQ@v>Oj&g*(N?Bb1V_zj(=e63pU5s|N$U(_oQxzH7n(gKvRiso z)Z5crjb;=qEGZMtiSha@98-?3mCk6Tm!(Kv$kRMDGs~%(Q zib8qqNG!wgoy9gTqaj1oM+{HZ;wwPbRNa7MsMKr@(HeNOoB>pNNk~IM&!C`ivE-5J zH?0lhN%2ttN))JU78dmnwRWe!j0HGAlzQu!zX=YfO&P zJNZ=CRmfqt+=49_zl!Q0!Tyr96N zDV1j;3)d4kpjG5IO8w5rPd+Wv;zSO{o}k*xf2r_OamyPzpd3=poc2#2-MQwlo#?QL z-Hk%K)CcBMAPah_$O`B4!grY&-6)kTd4(QvJ}6j_`<~z?rwOCe6p$Qse=Rzn&CeC( zs<5v-ghkv|eMhzXVlQVV?zdlv_*X~k@;=q>jC(Qt0jOA-E^!EXE~b?b4|9Y}&VF#R zH^_DxP-Dkdw8`Y1^a=$-XM6>1>U*luHpivr+djH=TB}QG@Oo+;l1P3eLbBW%&;B++ zD>k<%N-NTw!1R(xO3*V!o!t$08Pi)8XNe4F`A~u(Myrjn%=F@WY-smXeMq}`s08*2 zxA&`7NNI`a=)Y(w<%a_8#+!5!ecBi$4>X2K?agZw7V1hE@ZuiN13UtXqiP5dZL1zQ zg)|h~kfPILxrldePJQ{+MPg5}JdFG~9gqH5Sz|-uj!zHXvUp1_h8XVCYh5qY(G?Kn z(ipvbxJR8b2~eV6$AqC326?OP2Hm_X|dG?e{B_dseO=BlIS8i{B3qIqrT% zO0kmxz7(4I9+uuCvbQf;J0bSZ47xvlz-xbKChKa5eF#RvY~ktcApI@>o#duzU!*=r z5b%@w3|SS0LFK~k1DLEV&wEQJdDIK<-WI~|)aNvu00%$XAX9-b`0^nB4;byOjQ_lp zW->6ZDbdB}xi%>HNaLOLJF8sqnCvo`C0C7J?3wr;boqXpG~Ag8KqV#rK;4CkOXbXG z73ht&hikuLwb!Btknp^mnLhPsVDnMIdUHNpu;vGKyrxr^sxBYsmzU}3#Ja?OZ z+AK;OUT%F2zBb*9=?oIJggo@22clV1nQ{%Ac%p37kvCor6~6JAU2iY6!b@UqD)_G$ zCI-#~Y$6;&En8Z-9SW{P6$j%>(yd<6#VUSjdNPkXL@iRWq;#KwY;ao}q>>EIkTjAP zo-$?YfW1M$!f-CmgT_qsp*1;>11#%TJwK<5u7{~A2crR=(pV#JBC-YVp{UBA6yMn< z{Hw>XVA&=5x7v4tja^&>y9Lf#O32%>F*kcyqo;XH7yeDe7#CD|9H=xiuUH77ra ztoaT?A>E4VG;qoFmPoTJMjUhMpx1Z`B{UfSiDk^+F-cHoCVvS(kGL4RQ}n?RT{ohL zxe~Jn)lOX{GY6r-AX?HD+x-IdxBjTU)&XtTHi0xpSmjWbCg7O)?$s|4jnAA_!dy<> zkx5qb?Z#9ju+H ze1p)FzYc55;}VB&JO^b3{kyP8y6d#Bjr%Ttw|s7qoYDK|YMxtF(xH}oD!VVKZ(8`C z^j~wp%5AcJz;s-n-ESk^9I!u8s|7{3>iUxhK%ooe^GI7ay1^nu==l-g2G#h-D(crFvfZB3?2T{lI9&_+~gOKFEQu`wSqiE7> z_RxcD?aP2T%yEyMX{)6BPu|4lxEYsl3GO)HZ8e|eD6dY)4BT_i$JgAPWo#?XKJ|*w zK##m9YumHy#d8xRtTU89OxGxN?VYS}RG6kRmX3+6ySRQ8ck8}tJIE0hryS=EzSq4Q zK{nYgXxx=;fPy;j5ZIcVnkUUm>G$~hrFJy6NPu_Np#SHxmZ489LPFNJR!VSoyvyKn!|?{o-YVypsDzb8 zvqc0&$mOw-{*kF2?ssmAZAgN8`$B4Cz{)`{!`AeBpp>k0IUQeLjF1@aHKVs#_Ok)A z`7@eW`%UgF-0AhAyJPshKK zs<(Gnr5WS(yuBZ{5X($bMIqp+csl6XFayBa*L5r)dPH-Yilp$#Iv)0t!1wn|((L=wGN7x%^1CoFL z2pum{6yf?LK|6`$;zmbD57GE(*xS3@Av02>M9Ij;FJvS?oJt%yG!qL z-c46V&lTEf_W~H^+ud~YBcCf!>M+`Kc(*T^H{?(azR(O#mInCS!ShVrx{ReRmzj1k zk^Fw@r^Cd_O_6afY2&7-N8Fcw$U;mEA8umm+QrwBSh9S0#fkJp^QfI~u)_w5{Br@` z_?ZIBe#dxMRs41-exZtvlW@s3u!1#H3Px(qn?Zce1Th>gd!F`kc*JmrEkds0V8frJbjasNnJismz3L8CaJ{Z^%g4JA~tJJEe;D-*$x=-P#|ulUhtSRI;{BO6pZoj=}DeYV~*Z#gWD> zs&R0cTrSSn%+?I_hop=M^x0(-umUq@v%-sBbds0Ko(VqY;mKFm#xWyhWBLbJio%@_ z$h6{#z5-@9+{t8ndg)}o?i1a8m0|Z?T<^;PM;?>FTx)>}*!>vk5g7W{(KVCv_5DkNDTb2MCI;Qk|hr)b~3 z9Q_Po@|e9+49nMuEIM*@Pls?czFf>9w}W&$ zXs024LBA2F$a?|Q-1B1k-Q*MU=7wBV#y)i;iT(dA40k^~95XopPXj%-A2v08xBMMM zcar3Hf=%75CXkk_y)XgMPH5@TCzDo5tQ(iL~5A)27I4)Vb>~ zBvv!Ykih5x*|3svUih#um6!Lc3T-xmM+DPY`qSopK=IgevX#I(AGdaFwNDEkzI7}V zFP1*Hi}_Tw_~ItMo5-RG4D2!eTyOLTNM7_YgT!o?LZBA*(r3F`Ex(ykq^7Rcw;Knd z+4W;M>KfmE^zcV5Ki=^z&+5R0jBI%rb`$oOiWP!>gzkK^vi>dtO(KN&{wLUmn58~Y zd<)>@SHZUT0ZEDcunG+b_S}z_ngQT5fE^0PP6;hp#Dy#*e`qvIvFSR{+RG#n0W}J| zpkdxQeP~a7$cPyRODpFetZ&=no1@2CXYZ88oOfXjz?sd+%s~*g{=&; zl?;Np0)|RmO#n6x?`#hMY(~iuZH9@R;Hk3lIDm+ur4=}IHxTg}!(PI09mLwcl;Djo*p zHF_uCo+#s|LE`-?|J`QuXCAdiC4SU^3E_~4^=i)Z{eJA$=nXT}Z+c%vQzIbt((9s# z?MA6Hb8S^NvF_hAxXV@-6wWR$5M`jNGOmK^jQ4v*-0d3^sp8$9X&=*QvfhfD@Y2IQ zPD=uLJ!5T7GHHZ?LeP60hh-2ng9;)fa(7Rx%gE@N$dRp@jQoo2D4_f|{&|;4O>^@# zgO4{)O7Ao+FS^|MG0x~nuipQZzo&J;6QQ4Uq!G}G9dgNST-i>ekjIf6oJn?92Z~|$ z4s)TRPve?DvrI3!5j9C!aSIDDW+xb1(|1TU(Nq}+qThqZJL;IEwI0pc8a10|X$$c% z#0ZYF+N-L6vAJLI?#)j9Wtp>a>qhvlelC8c$ZY>3I%{T}?5%nJv|(CJfgp4kxd(I) zoc!ZCG0ryW12OMj-qZM~F)^2qNUdr2{-o<^y(uI29rLuF)c+S6Exo(=y(A+eyc@Y! z=YNYxL z-|TOX9Z*v$P%%eVZsVX+zywYE%>Q8N{V&>mh|S?ufajxO-`krF zTVjM;Gk05(1{;2)Yj=iSwQE~C+sOsVD)fV9>MJB-zxKu=5Y{U2?j0K&bx2Rp>E*)i z*9&k)bzP56`gWU{f&x$1Z`7>({ETgYM+9}qh6ggtDxt7-dk;a5f*YRult^Xde~%!O zVkyIF$FWf4&8VjMCsKc@=Ro$}CxA>lJ$I!`j{nz~F(bI08+a`WZ%hob?f;dKOM6c! zdM&=4PD2Mz3Q+;j`)%72`@;qCQ<$Xh`0r-y$R_tez!-_+xo$;BZHH@T&!1KB51TDy zwEtBkHw!Y25u%K&*H!Lgw<}^#W05cR%HdN}HYVXtZWl2zU@{V}!aBfGxL+MB!&d~w zR1aE{sjopgChC-&3EorABb(_mMyt!Pf)pA&r3{qx7=L0XTUQLGxM||VzxM{}iX@k# zHK00rCwE-aA3?4-GEvL`4>UWy&4rUv`@fxTfT3cip|HU#{-n6p_!XT7e0XekVvfbS zGN4msMM2Nr$RHd}zlWcXyObXG^v6+G>78uZM1sSp*2^5l#uCqUP7_@wu%zywiQ?QX zt?SHzsUjpzcGx-)?H)auQ=&b=UP>VXFvv``*?e{w$}AXw-u< zlfE`SYA>*$IsWrvaMdz9u`)mexC9l!cs5#t=xjHRUsh_Yxw)3dN-5OUy|omHUO|2B z;m#iDvT7U3UP)7}B1_c!Q!OKwd!hg&^p2#|lF3go`F#|!_2-@Wr=vY%g=JzQ>5wi; zj@4IMrwq;ELqy}Xj?6@bSQ@FDJ{Lp#7IZ&7BYkKJ-=uu)RPa*rV#f9Uus~q>UI%de zxl;d(c~ktg|FMKlZjxNxYVMj+wg55G_avs>cg?hnl|R~8>CWF#d)M2$ ztvC5-KzlZh%UavkZWcpn`s?oZxwH+4gmh-(w+o{pv%$3&xmzelvnRRyo&!tq54zrQ^I+0h;{LJuHBjP zno0NT{YT?D;-~pz^CqC@@o`i-z0cgQ_qY-AZ}+!@q-ool6KCfoq%WmD_w(1B`)T*_ z$HyKCJLLTG;E>1A&f5nN*|x^@-^|`(e~`GU6gb>_b`(mv2tLQXL-uU5Y~Nj1!ZV=t z7S4T*k5{}Q{Q0B*8iP(2@{{XBV12u-?m^!qZWMV_nxK1vYx2NjV>>hHmoC9Ia3%%v z=G}v<2JDSHoh$mocZOz(NG;g73Y1s$*dK>=!>%Z?u!d17jA5^$lph&qItwOhMr*Z+ zA(X2$yK>UCrYq>JsFQ!0R$E_(E#? z_nV%(A~;yb6ugFK<_od%_n^?)=}Pdx_W~P?ylyj$WrPFRCoR7!{wj`!ktzc5S#c=#DL`ZzE8h0ZW0UKaZ>&n#IVf}~Ld?WtmFkB?e~i-Yi%<4YZms)l zPaE1!;xx(xD>NEnGm{t;8XPj)2G?xUFP zTqNw>z6Ngmo^@#P-NWa_wGtyc3#*Mdj9RtI6ipZve8V{T?^t^*oudt3>zXM*siQnH zKTEM#&s2K~!8HH8arF5lAbX@f>?{y$zVb*j`$a$yLel8oP=lXYPWnT03%LL?lp9?M z{?N$p?HIIfwD|98H7JkmdH$&I>JyLe@q5evhpo2^YO{g9eOshJaVZopf#Stoi)*2{ zySBJ{a4AlK;_hCcxD|JT2TySc!9B>$`=2@YoVn+I3i-m!!!Yygz1Ld5wY=*^L|)U9 z>#!f4%BA6n-~capI!wEXY0H6|mzJ46x4$@a`>1m5KcL_6eLZMth0N4`Da+&`;@;t& z?&34Wo^eBislEq|VVm}#z$Zu6WqrH;nylh7j}56Qn&c_C(e!G@H+ZP18)bY`CXKf3 za*t6Lx|#r*-74O^oSQU*lnT659;!qGn+|=f&F0Qsn)A3`@Ecj4Z$^j}>mA*rC0pb* zkv&tm5(G@DADTQw=Y&agNMf! zrZu_{hHyG27t{+Ju1k0>4{zNgCPbUSF5$QgCAlVZmk)WL{;wh+1kG3Hr{CH()y1Z&zhq^Cmh1RY>cz#&Et#Ivp)Ude z&FV3Ay-s!cei_D@kW{pBD|SDc0AwZKaG>?M0K&&gvmBXMMbCnhZVOjoV+h!i3|03y z?XJ#K&XK$LcbZ^*(pA5y4|>#)KF>D@GwGLm?+MN_(#*k0%Iv| zro2W@5(3x6tEvBw@#tP|jKKUv7jdwKmeV87B6f&=B1A#4x*kG9d02-$-?Rj-T%Ncg z1eCm&D~6}e|LO#RXvgeuF^|AD$gixJsF6&UY|ov%PpjfrG_J?EQ;A*_7y9>`UaAO8 zGO$vS|nPyA$!u4SC~4ZEt1i(tN?&?gzJ7k{m~B*S z9a$H7lFxW)oW7r^OJlyHOH1XmO`jv{$nWqfL}FMrL(E$bfBs6e%>t*!KAwrYKo$XGjdg z1F$f7xpa_G7ddW-)LGsl!=k_HnSx&-#}(qh9?J)6EkGBRMSH2?MU2QPztio$BElIh z&kG)3FMNML6?h629vEC?76LyzT_bgciV^&{Tr!vKd(-!`@99oyK-zkPI~lc+)>J61 znl|`@!cxlEpLYiU*^hTNb(Ew<=*-pu=6`(5;}<>t_Q~2sXXp?O4jm}XmSpP~LZ=(7 zNoRKP`H54B-xcP4B5PV~aOiP4S&%T&8;~!fRk9||5c=(~X3(HZby>_HZKH3MZLwEc zh{vR~eBUcPK83rX7w^5AgfOu^%CdDlg4JV2BD^5Fix5-f|LO^=TY9+=pUBI${n#a} zN^QG~20O=y!Xi-Mg$9>6Zbw;C)$^U4&*oenO^1jtzt5#Ggvr36;|nHBG>ol!a``(& zy_Gi|_9D({izh`eT{c5bnAiyuG)rb9%x40vZ+mWyy9Sy?+7@(-da*E-fPdI=Kw$#6 zeuZCtDb^}WCrkaE%EG|>;&I5grqT{%udvUKUeuRTRZE!?02lp%^GUhcYt%00- ztz{{nRR>*{cqI#@Y~Y#bgxL%pgnpV7w`4|%WwfKHmQ`itXG)Z{C1vKdL>pf_?qe>X z)r|E4jU_fm2FDW<^q0rroT9P2ecrK@mD4x3;TE&T>mXEx6h2zmq>3n~XBU#4Y(ow( zNu;tNuS9zZk>4>%cRsG15NL|*X1A_`p`;=|+04G(dnUpdWaB_lm-_W;K*I*07x0dX_~18u zZZI)`$z>|VIuB9g06eQ-FE4qk26+VEA{VvCFH@CcHCJug4IBSx;y)FWDpP5-VdN%k zGAm?U_+|CdTX{a+1X-*Azedh#wb5a>YN`8~2cx<}g7G={ZI_b_xP`rxv?_f^a_qC*Ce;?(?ssUI_!23$-cr;2Qob5*s< zaOML?_mBEu>{7ML4h~4M@ND6N%V(os-e^(>*X{8eI_G*Hb{ns?Ub_ii8DBD7XhlDD zAKLf<$>ttliLkKKHcDW^hc4N|{nYkoU2~tjbLi@Q_8?cv=T7YUn@d5uD^NuoRW@#> z4Y6yTA7mB9%kX-b#p-tWc;gSUm5dmY0Ru2TYQ);Wp6$O${XfBT@zgVLE%zdndkP^c zKum5#|5Y?xH?Jz<+D$EB07Y^3qRxl}yFqEPGcbu-L6?tB+62e!Q5n5MzM| z>>;v?EnWS^7Hv<9t2;(@LelX-7<9KFVMXS~yKkdm*w}CM&24K3v`onSI4V7B)COBP zJEJaAOTg*LF#OXlsjs~_Z@|Y|qZ_INT^L$DQmLV_(h&n)^Le+$7-Nj{C{| zoNj^`?hGqIpmN~{+-mu!LaZtBo3-BRm@yvd%OHmlpqU50^teu-#X->@I4|ZZQ9Vde z*=VUJ=$xAFYRib`|8GL+VMg=~G_N|cWI4D6R@fyy;Rt+i)eK|Tq<)B!8)-z;2MZoK zI3+vf`73H}s7^fQvan$h8L%79q>GUpe(P9f zki6ligsyAblvA+fHTQqSM*pr!Feb%hB_)|_9-SIY2 zA#90?$?wgP%`(q)ooIeJ(@~G7T4X9u6uY#4jr&YXj1V^0AB_o&7`d5fztVJ1q}1^4 zor4AJxw-8RsR_A`><)o0Lr?6)*x@EQ8Z`N3Fs|_k6 zB|fMl?09_m? z|C%+ddn&H}%;xk7ups!o9p^MO_w5mFAv%7%JT+d!I+O}=Vo$-6i;AA-^1NIwg2D9v z7Nt`D*rQuSrcDSUn00quib*LbBazwR+JYqTKdc;b^ADd11J3q;v`Nj-%%7+u?YJSO zmKO$zV~@T|dOz962n~6U%<;y8>77Hrl_!zIUj6jsX>3TLAPEe43G#lI$dJ}kB;~j& z-o|$+H+}`@WE5lXQqJ^xtwjBn{ zU#W9vh5$bW`q~UqR^U_)xDmYMAE#hoA$nce$LqLq%BbJx4OQ*N8LZzs_|j(zLHA_A z=I*coNc6Cz2Oh9aljIeCg>CrQHcl3#W&>ItXX}7H)0x}+WP58%F~O^EK%=B~mj<>2 z5mPR+`=8|Gck%p5l0~xYZbASc3NqQr%6VJL1?s*7{Hxp( z|D7RmOtcTja$v2ET<4x?XTOHh}Eza&ytM%f{(7I6dI z(f0zF8IGqH0-EMXXk2mn?69|+&%5|jaRWCmUC)$^Sx>z$4R&T@*!ga?v_6_V&8F?l zJNZi)KevO&+>9^G4^@@#+>~VGQ%!Nqr|MiQ32o-T<#S?S?0Y0q(!O-0+XUB`O&1m^ zZKNr!LG2kNMW8^Gtf<@?dFR=LgS|I;mbVth2^K408;p|i@^#x!oZH&YqInWeqkmya z7}805JZv?*74X1bn3EU?oSHi$%DgmX5i5TB?%h*@1Df^A1w4x`98Ly1R0CRDk8cXT z1J@@dHfbQ2L@Ce@G8X&nDVQ{h^3R@TfjT3gLZ*!W4s!&ck+;!zZmC@Ahut_mVL2~m z?^y|EuBPEaMNLzmy7kNGrU_jPUo ze(hpj_X;$`p=2;>xQ#5Mk2%xKzY+G7%0Vl!IV%=i&?qJAs7)P3w*PuE0ajY#mh_#b z{S>hJCr70F^94(?K2qESAL&V=C6}H^inQ z&-Y5%2wd~5k^KesTvd%B(08*EaNgM?Ve;kL7g1E1@QXy`<^Ex{re%W}jnX-Y!fz`O zA&hSbOrJL!uS~goECW8#`5Xh`BlvY<(!}fr^tY?7N_sD^Z_~c;y(C0=yz)@b!i*K$ zu794YW)SK2wTwI=VX^Qfe*AK_*@s)=>G1cjW*p2oL#VOf)Q8d?@!ykdD7SBjN%$H5 z^TMA0L^Rcg{s)!$ra z){2Mjx1jvObXeuJ!r^_L7%b>Z$Zg*ABE)t4)8tbi!Y}I|c*vb|qL4jqCR_;Ght`YP zHBPo9PVzwjrgC-C-}A0t#lq}YaXeI-F%vLqtL*BwE#TyQiNXx+PnmFMD5X~-`{O^q ziz%HRS>Qt#Hz6x z-ArzQeew?*A9~Y{y6dCa#$#>Io}9yfUG}t%JB``Q=Oo>F24KfHuX1gE+>l1Xox*S3iN&hS#q!tfTjXrYhz)`v;zOtL$D;n)HYWaQ z*W&BL6TiJmeghHErvF#j_@5sh*%Q&kkIfJbe4rFu;GTx-t=|UiL%_mVs_T_OLJEV3 zKZ)S>YMJbx!~dgBac#l%UohI3#?A7uztzkdjKc<^vI zthPcpUMYj^{-AEVu83e3A~;Y!aR;BL1knze)MpECbXqUc5+WL^SEWPQDs}kMVjU(p z)0Knlsfj0Tq|Lb`E%0{*V_lPJjugkzcdb4pZ5P~87(``$k?!xPe|7PJl|S5Yud_H> z9|a5DE%Fjbf6)-pw%6w>V@?!-)QeP{CEuw+N)y}A`!=+EaF9*W<=f$VwuqKH8yY}; z-Ebi#d1O<;lLXeAsQ5()0<96)3n(58#zv22LpC9Hk1-I|t&94}D%&!AjdPR_ ztJ<49)n}Lw-#68L^AthxH(gjta>U%oBCh_~X2&bN15e0NJ%g8GWkSZK&iV4nGcB*N z^cp9HHg~c-uY{*=WhZpQ*p6UiDW$yIZ)VEcWSY|QDmlZ{!J zi*9}=1pEnt%=&6y!vGk0KkVvFN-p?ZrTJ)G1$sWx($;q)GQ~CQv>)Vt_$1g|hz$wZ ze55J!c4!JHvq!M}fBdForT7A3#H9*#o_vUMvCC_~?rkUZxblgCtqeT5Wor0XcH`pX z*t7g2y`zHj(iWGZQ_fiwlNRWs1?Pm=1y>)1y3+ojte+rGm(pd=b|MW~TWY5!lU4`F z=H>?T64a+D_`%;}bE=Rb4=PT3jN#SQ=Y6os+3@{JP}$t+C@C`Kc9}*ss%>$?ny%s+v0@Zv2ksc*Wtk{Bn z*P(5{DQ1NOp5g|yDelq$;`rLk{%ZJ_iqSQTe#BqM|E0NTVS-(w596+RpxTS4E*&*S zk<3c!R zoYEDBcgYWNZ#=#@i$s*&ZP-3oog>1i;oI@4a;M#Xf6!{vb5e~wH}q-|4oE8p1X_3} zqt3UD;xSW4scbb>=43hRs_YBpbWe=%`bf_tR(@+FOvxVv=jfp}eIa-NYU=n>nZkeG zs^8(O@ox-&#p&q?{uBrA_ZYB4yUvWrCExfyrvAq6&aVJ%bKCRuyZVj6og;EhzreOK z|03{r!rm=Xx`9B4>bCFkU@hAO4_{@~2t!G?-pwqYT|wZ!xyJ$J^;6ij%CdV2k?V3v z+L&X>n%hz#p;5R;HXI- zinRCvJ|V~-k38%?cj|6=J(hY=Mg1%d^nM_{m^*#>tX-=+vvTv5ukE&L_hX%J3B_5p z&6s!L40hG%Deqixmlb&NsQwo7X$x*MKAQJZXm~Z3H_C|qUhv=^lpJ^B$*(8cA!~g7 zi+}w6i4)kdmF;aq+>CK{2g9h%@Pz2`YA{Z2&5%ByB^PI{KfC2;znUYj@BIKB@L;c4 znmL1V4)s!9;tC6mJuXLwCSwH_!35S1pS1erOzj18MzZR-r+rtL3`js&lvwIGQv7A01m+i>)bQp@=#u4t4 zvik?y`-A-GPurd|?RIMF#5Fxv%^2?c{3Dy}{uTe@xVS`|K`@T- z@Z0_|h^MLs{tLYrOaL0H2p^IM>U1<%>d7Na7{iC+@Cy63>!)jOIlddFhAsl?Hs0bW z3h8WiBJ8X&RU9*EQUDD&Rt}$w0A1%XoMX(tpmV-_oDNuPKTt#zV1@hdVT!PwNNs@J z!w4=np4Ai8?W&gQ;Z+e8#p)GHd55XD2MA8{94LCGbQ>m<)>q${q`GidClV-pDC*YU z!^sl%pE3&UH&s#lMt+;=>ovsofaB38(BT<3POnZGB^-Zu#F&hch)(2i2w0Cyp=Kj8 z=0#sKr;7jyi#z3^O2i9+H#iE_l6o*KEs54^W3g;;bxDI%oa8W_pIaLkLpnE4gm1m1 z)m)gQ#pRB;8@%N&tpj*m|8$~SofZap7~21#E2>x1k$vSRT`i}bAhN1pwd_#bqAcyN z$&v06E$9{XzO4=2J=9{dTsQ`HJGLf)MI!@Fh>?B0>Qli;IpLMBDy&gzLbU#GiJ!>j z-;%6IkrG~iludgSG`Q8)y2X=2EP3kzHE9X2rpTx#Hx4Bk3@8nlTK*?oX28)MTe>Gm zR0Lh>|>YJ@=V@$EnCigZ?I7CLAgy z`-BG_Og=R#5zdl%NlKbL#in>IxVn|6UEnH*L{uAb&uaib-L&iM#z* ztwKHN;i+Ss8jhVT7l$uP?9L#Mg|yI$Xo=_BqSXPBtfDOq`4wm7jD>JDIm1Q&AYzgq zw6z)&)Qw@!RmJgNnbqh#k(f~7aWlhA(!`Cs?ucx6|6Q)0e{!T)GAcyuh>8@}s6uo2!?iei-oK!1@=I%%3Va<6!V#U+r^BBxWK4;^| z?tYH7>#I`GUe&(n%jPQo_5jJ++LXLU61=p`#aK66bLB|V~CLFQi9RCC6tBRZH}bkY?zdBwFF z2RTyO=(SA*&ct@q>(FWc5~3MyZ}v?{vi&a%Vb72~%-k+lk$D&O#Yz;VH!W!n|A?|k zr9od!>$#7!VKDO^e3Lp>vf|_?51z=-`t3%+ygKz}s@3;+6ASNXW9V%7GJX`s;yE$p zEd5CLtN!P^gs`b_!ol`IB3gbJaE*|H5+y%uxNF}{7=(K@Ptiz5jMRFie6W_Xd5&1} z@~B&fUkvH)RiB@C_I6_oOsYFqkIiDWpWCs)iSMW%HN90GOF4E+I}d#dva2~TKMv=B zduUXDW<6H5y_7PY8n9=;*pvT>o;np!vpc>2{lg41Ag~k&I;O_EA^-XE)xj>(8~4_O z3NoKNs!Z8gYcXVTGo!B!cBbS=riFf5PqW2UJ`}ZHJ5I`(`_q3C;7vcJ;DD82X0+5D z`e|?Cr6+c*Z=YQJHlqIMC+_Pdc=k=z?y(opP#MzA zPP-oLvgxL9ltTnn-#Gn1dvxgn)6>1Dz%Uc%Z|uv*F+G-F5VgDuH+@VW)ga}PYl)7s zDYhoQwoQ1|&&#PR!`JXy8pHBWVrKD_lv9GAvyjUjk{s28H9k-A zW0-&L=M1Ky_yRG^_M!OwKbKZFNaXdkc5PcaJwF>&gpH;i_gWAc?njy<gR5F!|L`;iYo9LWe(>? z?SiU3*Me-;P0<`;F7Z-Bk$|-5X2;(65(IO}U6`2fK&qNln*2A`_ zs}-*J4~5B>OddnyS#q(T8^dgbcw}sb{He6HxEiu&UPawpC!^Ymlhhvr^f7lj<_t4x z*BW?k74Tz28vt~8gsfp#HbhnI;GudScRNa%6uF>O`#;kmf_W}s-#shK=Zrwy zB-5>1ZXlTxPa|o1bZr4i@#to39uRYB?7Kb&Ir5nb;0i}kz168 z5~#sU@4wQcahwQfCvHRl0Qg0!KPs7HaA0_!J#@ZBh+_qVi(eU{5R7m=k>ls%Rb0Oa*h&&TH!*zbMU zic3DI(iLCrnON#Q5S3MTbN9FtDSu^Mp}0$;Qjq^>Yz9W#$HSfxtq}3xT-lH3boO|g z`}#B7R20=$dY`E=WU1@bBx%VIvoBz%<9;=bi_9ipN6}p7EDEVwN9WyZE<9U~cs9p6 zDP1kAltRC*9IO4Wa^Q3sZbhYKfY=r}3C@w?o?X0%jo;)+67W~YebVQ#2N2A`Hdk%N zNgF_9VU{EippTo4XjEPy{qcc-HU<72bLpCVfz%2N!#`x&Yg7EbfXzftDoz69_De}4 zaZ`q=MS4!h-o(Z3zp%)?jrb^Us|3!aA6DfQXyN0TrhMI-Kh-5$G<@}0jFujfWw*uWBQt7gKZA63;@K7l8{(updt@_dv!iy!KK~+n zIljQ5KPse@1T_5s%`l^^N`rwS(EOBVz~YzuDw3~Y1+(?}q_KSsuVz2py5AeE$@p}5 z)_3Ix>_yXlG8xj<{X?6bof~^JeydJv7sXFEssDK-Dd2~o5gSBPvR>o&CZ8dbx(<~M z-w`_*>7A1-djPKg$W%kW6g6N~)Y4|z9tyV;TQ4xs*ml`nfgPtHwGS>*xw*>kFY!rl z>3S5(U^P%}yP-I{Frf%E_TD!(%563pVF*_)2E<UTUUsWEw)xpK`#Cq)Nguz{8_@m$l`F;;<+VxRPJzE0B{jNV>WrY4Gf6!=hL_CwHXn!PPUmWkq8B{^s+ybpi0xyV+*QBW_30Ec zA-a&P825;~S^0Z{6IJSM1u0z50+R{>Z{B!4Tgze+yCXu>OB*mE7`iup&kF~w?nLo` zC;5$)3n*y49}90fCTAr6%1UqZi7cS{>vJS4$-dpK?WveLELh|bNuZ2uQI0RM}Ckz>yopob=CJq^xcZ8$-F;pigaAFqb zb>zbH$lAefD^SpeY|uu-GoLtrO*aw#VTEcxr!M2R#HHvW)0ElR-aH@hXS0peJFOYZ zOL)t=!H0>I$Tsc*KG{oA_XI9_p;ba)>%D39CPOGTVP3#L=B`Rp>zs%`b61 zMkCg}pg8?fy;(xWBStr3^tK9E9#qm$94bAQ7k8U)ZzK7F%Q;nh+=j%eI7x*`UT;98 zz7Xy{7B8~-Lp7xLDk_3|O0Hv^CbOr}g}Qd1hh`B( z*g+>TO{(+O;W5M)%U0hw5tl5OmB@|Q?8|K*3G}69-385OvI9bs<3FIrW{kYgN? z&^9cF`xF20bzttN$F``wh$*q}bH!{r9VSpXG7UAm`%gHqiN#wNJR=;&HJRtRW5*e{9A$sqcPgzij7?WG+-0oR0r* z&0#kXV5K)^ha;+~B7RHZ^AAUu)%&R%ehPVb!odY5`a|XRN4QPy{1I6Xgs)QMumXN~ z3K4#CZ#yeA8FOg)^n$h^f?!i2P?_!X{@sXnE3n#^XVGHDw?0cno=Fy}_Yv7wp^ZD| zszCJ%Lo7W5)8Xwr&mjMMIq^gWF(@e_N_s-S?x^#twx?Uf6*P=E6g>Vk8H>3AHc&kO ze0l6xJr6V*+O9Wr-tf6V(A&~3fj8W3wI35tUqY(g_TnAYs>46#Q`Q{JMDn@*aO3?} z%(V0-NVcq;OUAGD1)Bgrz~Opc-h$*K-vTN@!+q|uw1^P6&{d6p=Vc2W{I!$^tWFz6 zb>x}Zli6t8ziza{CqoEub(?&TUbM%40r>`m`V!Gx3a`AQI;Y!0e&6fENk{(*9gscr zSu;f9GwrCqvB1@&>u`L-N^08>C%^rg(^uTwjNWKjRT}+neTyTu&p;9hzY+--wv?6$ zZqIzI+bGRk?)WRsH@Nwbzr`H*ALf{5#hQ$r*(WQa@fZ`;SCz!)Q=f+fqI_h{J{F?A zgZ16l;~Aa4#}@!)%7gczRpsl^=C(t1)7kmhMW{nQ3JNfZZjo622g8^6D}K~3pK(TNVXll+=(C0Q)gG5 z`xDtwaA5%EnY_4c_QW!#pk&5gKIoIu!CoRZqv;q%-YB|6yvXqNbVle(tyUcKP_|oM z#}=4Du8x+ydCx#aIuz%-fsVGhy}w;~$D?{@%=1Eqa;$HGHNT@{zmOJ{&$HNnBTet5&RAqWB9J-6b2qP4|kd9nrl`6)~VtA5a_J~@N6S`hr= z_q?{A=Rtuinh(c#s4xT8n29~&84}}80OWX&n36b zw0P#4Dhm&yy18%F1!>;qs~nr?M@mB)D4V~E8Q09|Pe+g1{Np}+qB?9MO4eGcx~m~P z{tlpey{=GS?rcQznVSjo2N&GkQW?u!t=7|vop!Ht*HZ0kXS^tskY*5>7$awRghd&; zmS+W{7gDe*lY)9BcR-}#pO@rzeb{~KJ1-}lk}iQ4gnC)k#%=0Am#MZ3%t-qqME-gK z0i?+BLNItPt=Lns`MV9mj?C7Py2N3L^18`pxi>yKFPe@vP9Cn17TVDdVm9rQ_wn$( zlIX-r!aoe!?y3XT(je;yHFlgNdZ1B$S0^yT2nqg64ytJ9=R$p~Z(UtQ7ZB0dgq1;h zd>zYY&G&i@)c)2_eaYCmW~U@&1x)^4*R#b%y=x?e;l*fK$0gIf(u4fdWIo(lhtTjI?npE^0lpxyh+~$wU-wCT2A);Mc(bX`P&qwe;m`8p#pUs!e)J zU+gEOfYWrl1!?*oOuE5;Gz4(-zgLxf>{kT$&g#3r^OF1}HUbaW&xOK32Ik$7IIn#- z;IwC~e+Eu+CQ|So-T}y83;h|$$iMGa^$jfiJ@JZ(Py4wmMKVBXLQ$x9U#5xlSd6$~ zbOkz?Qr7LwaQPa!`k-mLf6~U9et3=a=O<1HPbRShMZb9503=`R#D{m>^#Xp;_WB|g z(GS@U5R8lKIB3LzeHEHL3*_-R<6=1>P*Ti&-p>-a@gRTzG{g>D_*`_ueIqD2npz)@m$*P>YkB6$@kp^sjmptYiIKtYvzQfTNbU&eWE(vWc|!x#m<3Z>oF(7#22gEh@lcckjevAaLjP z!c|*f@rItBOYGwOb4CW~rO)7)Rs$cD{u`v_Pby4SZ2UK_qozUEo#g^;MgtLPI9q*n!{A* zJqzUO@NGK28<87{*|_p$BH_(=yA*#&s6n4VInaZ91@UlqSOUJEGcfwc+>IcgReQ(C4Q3(B@E>u3-poDSmHI7&qW~* z97+4NQELR2#D*r8Z#=qwkjb0SitM)5|3-n>MZ=2>Z?XVCP%?K%C7KFGe#XPal5=U4OMF4 z)>9ml(o~*gv7cq0Q~kZ!m+Q5zMHdpmX!q{F=in84(uAk(#bJH28fTc|%-RZ(bv6Mi zS`5BmfyhGkORN}7JrBGYJQU?oX;zECR3qtE9tBM5D-Pi!!HEr&Q2k*l-}KOnyKr+Yc-pH;5(*AGjiNB<@?wd- zi;$jyz}v)K65%9KZZ56E^VrH^V9)NWA=Hq>jOSDh5trY{X<$VD6y_bFx z-sHIHkPQ1@$ea%Qw?I9D)rKps@Em)h^4CVf<&~Csg_*|eEvMlXY0r4oyeP~x=}E(k z_TfPKGD#Vo5MuJ+nnwT%*a`j@w!=~e)R+=`!n!}*j!(l3Jjc{Kxp-UmMYbr(5ID-d zdx5CKgNSTaT`VW}uo;M%8sK9f0j?7aUUeAGu*-x+eXL}CatY|4HLQ*)YJyMor-Pj<6z=llK&4>8z!l^h;E$9PYo z?;IafK^_KI4fXO6kIAW;;Y_-5@}!qiXF6kEG4Q}LP-k=hM$y3o-tt*Nf5gPZ==p$u z4ox;0$@j@!?0ovjZojwVooswSEW^V#EnZaL#C?xS{D2lPd?@NW)gAdP^-4=XyMm-X zQS$>)w@p_ZRDA$u?^gFkUBc@@_DY`XL? zpG>P?Rf2+J?Nw_|jlFsyd70;dE%uP2aMzo+D&W0@W}zEsBGHXu{KC{z-qTwe?D(u7 z81<&~C#~@n4pdGawb{*?yvH6LI&vc&dr=SUN9QRDeve_GMopAG{%tDX3GkxFOo_R= zUD3AEaI`cP4^wBM@a}KdfsD`ICuxN zXE7!t>wN3dps?fQK5vM{+?Sn0kWce}ol4!eNp{ayHUW!uz)QL1Z~JnG`$FGi@RGkeVvP-KG6{Bn*@x)TEi@>zw&Vxz zrR=`&LxReVTrBkDZr&b+X>QBMtT;U$Z~qb&CTI!Vjp68cQu((%jcHmT-D@;pg9AO;Gp}P4hc+euy*)tF41(f_3q0o8VYlk|TLNA2p2cEFygKKseN7+%MicE5(Xw-B|Sv(ZMY&S@qXKRi;;` zjsGo=;N_pWT?s^k%<5@t(edx?_=YDH;W31bjwVNiVFmtSa|IpxKF49SWjEm5?sPw} z^#6zF^l{vX1(6xZRb4W;@$cK<5~mt{r(;NymL1q_co%cIlZFUvf>EbV`SgGYKI7IC zP9ovdL$19q1imKI^uG(AF9KywT9&O{uZG3xoqXU)7pd&jEUKah-?&7%X**!`!#Qkr zRd5Id#f#!HoPD$xP28#X50+j-H6|(68`om*V`}rNjj81ValD` z4qRk7yAk3hv|Yv~#@D6%Bfe1fWSQ~BQAQs>l5Yf9_>4!2e*k=$o^s|$tOGtY zBGA!P3%*Ya2rEOp|IGMKC_^6O_PMT5j;6q6Pm4bkLH^Fd^@+Rk74?}vNx=UP5>@yY z2Y==-j=T}rb@P6IJbXW|4)O&wG2)5qpD*G(OletF)A9>3zGY-&B*DCvBu;<{evfYC zm#V)4GydVkqgY(!9-NBBDM{e(Ma3U!><|M&{Zs5O^Asd`x!UXF*wSc|NY%LK8P%lE zkd`;3UGJk7n&jSclC`7&qC%R$T&YZjVt!p21I0U`JP^u z{Z5^g;B8^?Lr0@*LmXUX`P5L7Wg3#C z`DWfHaRL;O@ZV|c!#4atg4~x+oKMrL*O3_gx%@rn{F*#2r z(axSmMQ&g4dE`Bkip&0X7Js|=c|70KeU!_s}ucZ$fK#0 z;ypiD5(ynLq|D%753JY59x{0 z_7$~<&Ml91G(cgRfPhoYv`fFJKyx5hM&hXExlYwbviwIzVahp}1SNW)b`ISwsv-Oi zWLKk_ER7|s7*{s4(ajuP*kX_QIv9V6M4&Q<)_tGM=x`V_;9!_Vf!po%3!jl<-Vo6& zCtqhZ#cqEjoAnH-kkU)$k zXF~ktbM5~%Au$))5-IM6dvHP1mmuoOLLj!xXR zFq)T>K8hL&WJj>7$PJ1~vRNSo%1-JAN~QN@1PsW4D_@qFl(1^e-M=aIw1oDsSY#}E zy#Ki1o^$hB!=AoeJwVcC>}zpdBUCR*cjx6WK}P)E^|Coo$3U70Bq+L} z*<`r;J#s{=)-!5txriL}aP73SQwP-QKHsey8#TGFe1ssf*Iz84z!Sm!5x))RYLQ!$ zXTd`8lSf*3q@%3RuL)p>C!50c>(LdlN^whNU&?mEk_Lyk+q($?6tie;Vk^cAEv#~7 zL`H!1P4M1YA?7`WWgpYqCy((Xi>1>HS3jpdXhMd!yRYPJdCopT^vT#cuk|2>`!{l+4IV%6Onx>x(e$jliVVV=uh8Gev%8jgZ(Lyira9oneTMAJNym)u@&OzSfmmct6`6>W_A#dS{5$#~R7nm84^%gzyf> zrb)I(+)`)MbVXn2w&vT+FUdk?^u<4X*Go=(8alDX%4EKIDO>r z4!y#<(MsuKGMl{&6qG^AGx{gQR`w%3O1Aaa6xw1`KpJ#ywo%PkprhU|f)AxENixs!wo<{{pVz8ig07C2&{Ue8QsH6``077Kd?rN{Nlk|-12Es{!OALs z`fZ$cyB5lA#zZ}yzT~pzyC@3~F}q|DWBjXCEnDkP~C zc4tLiS5$r*&~?LS`BvB_HNPKz2@KT@P6Sg6em_OaDsG;H>^}#&ddD5$!CCh~1!dcp zizSx6e7hb6g|;-A8Zj0~#u5Y^ZRjkoJM^>XWS_1wtZ;qhb5$|&?wS*zfwUl?5By|V zu=3s75-{w;B?qHRJ+OZxH9Ys|mpCoFcL36*+e&o~{D=6vh7Fn}i}H8qUc-0Z6oTxThd;xb$k-kif^ z__4DGQ0gHVHD5RS$`W8~ahs%-k=czn@~9j)Uzp<^-iV>CZ5`;avrJy1w?Xxa6Q5*; z7rKHJTzyy8ydd9!Zat1axpQ<2uTPb?&q)0$u*F(!^~f;5tNR9Y*jm$g*lmuhH8EbW z-nH}E@-{VaN6k_-s0(^o`??Kq@xiZGTX__Dw5rG^jcXIo>$JO|<{WH2wxRVZmg<#G zsY(iq;(r~2eLP0hPWanoE)vh@o=aJ7AVw*-)c|*ZO8noZJiNTFvPb{*RqSs%{&?iB zZd%lS`uaNS#Nw78!?Nz5Vy+nL5^{H%t~&v{zuL6)fiE=(X1qElNW=srFTXf{l>p--ehxPP8N4=ru z?Oc=aN!_y&8QeA(;WpQqpq{^Lo~s~+mza7BI4oxFRN`S%JY?Dz2oEHUqa*GIKIIwYL9`=EEEmNmQq$;P|od_GQ3z zmom%>oi>qH7M)e)yY2OfY=q~8s@+Nkxgw=*1b4~(o4)K_t|rZaJ{PUO42<*}D_nkj z%-c4&tg;v$g<1KJFg<0aKCyc08}TwjAlci)ZP!O0?-6g6T^>z4#yc7+(R$m!{r9-0 zq)z;9Jo_F|Cb@9Och&AiXKT!A(3^``}mry$K6xKBcFzd zKB(`olmpXz;!1u`&7IVoa(GCcI4&fa;Kmp}C zztWO1?mvG0_t2!fT@DKLoW-Pd5!J)XlUp~p5At?iMt)8{tX+#W~JogD_{ zMZBS>0lbGwNxnuLZOy(L{plvWGPe$W47}YI~qr3jeJ|13JL|j z6P~H^N#id+g-Bg!C#9!&<|qf$qF>SA-a?E@kDuwkcxuL??lpLODG{oL9ck2qeZ6UJM4hOz%9 zoDve~a%s_JKvCF>!LxY*8Y9$oNQ#+RBbrrEa(A-u6Rp;@nw*ueN$?+P8NpcJhujCZc`g?{}_W$DBs3f-w!|edge=}=#LS@gScg zlYyKELu$>WXafHa&yCYKt)6R2WUe5 z0O>l%Un2bU(wU-FIOqN6j#Jkbmp5j8A}Im~Ig=iV9Z}hr+%ZNv74-X-$NCb66BaN$ zZ}`8ahZpnySsHwC#`j+`&a=xQ+y`7TUB66hIB*^C^BQU-S+bGM! z9hdUkfl-E$Z`hFWEkAxL57+sP%db!7M(uztjA3cgPzGH$K|FT(V{j!g3v_;!@mV_6 z*5XXa?^?Kkqa1x2)#wcY?VLhT0iKg>7?|@R5d80% zNdCft>+1Grtb5kW`{<}~QShzE4N?@sym@y!dY*uVDlGFp4_U1ngKu~E$D=l{f;tv^ z6Zzj67CbmRweGdTRC z{(oom5xmU)Br!P)bZX|9F);tfrZnP}R)+KDDHcwsvIOBe84@F zo#J{9rIcZfFq6%^3KpH0Vd+53eASp*oAE{#`*;Ljp@JY?VkjWPN>VdGP?(o3;V38p>Sneec)R5Qb8{2a$9bIRxmZh{5wm!maNq3 z=cm~6g;txbv)F{SG%O9w|B-Rfv?jc9H*Y$gZ;|jQw%F%Phz?WJ%Zv%}IXckDEiY4jBr7OegvG(sin?6kL`UImL4vXzY2rsZk+|r9 z$>4n;*!rpV#dp`BEcu_(_m*;-Ky3h3)9SJZH&Hb+k*rV**RXfQ?$|_T#MiGhEb2ik z-eht_9|ciX&Q}>KxSp54F=t zi~ML}Lc24Vf!rV6N{UT?`)0I1vWJ4k(EQ4dFqS$>Oce1!XGMXL+V$2EanT5^H%}m- zW*#ff-k>QDwyR1SmJ$`(W8af>SPOf!SriOSO(S&W0)u53IgNrP9~@4V7@( zuz3f;o3oJhN)=GeGYaXwE$uM)YwJVvnnz|})$p$jQ^7sEl(G>J>bNevrK4!uXZt3G ztnBnmP&0wJAW~&}>tjGnWfaX1J1F6kHhdPiB@1g&+BNuk>C-sOT1ZGi=EK)f&LKIummV_o6>6ohyxH00H>f8iCLZ}4jQN{h3(6YVuV`96rl4cii+jk~ zmc~?I+&XlZ1MNapYP;QRH|jwDH4c!bmzMGs$0w(WBCOmD=Q&L6wDOPqf>Ah>olYHD z!r7d7hKk;;Tk#gqs4W>*hB_@*yFU+rc-Pqv1vAS}xOQ_~xD*Ug9)?cg(%#TZPiD>r z{~Rro;1tqV8nL*R#wtYIf(E6;bE$PXkqyl^3Bfb^?xfzQu=y6#*f_nvO<4kzMZ?%- z*+a4u$qyt$-W{zn@&dzhCGX2;An(ZKAb+6MOZBq zZImc!@1eBquO=rRlM;cyT{@@Fl>JPf1PD|-IsD*o`MdBk`+_fW%8>wzWm$7s^JQwH zPY3`4Q<8h%!zp&vHfEh~s-5qznri>Ldi4e7P;PATS3sZLgGGB1m21dN-?9my57cyL zNeFLo;h9epJZD?{`HGL!E;4x4rd)cGy7ZX4Va6jlOND=T$JugJu~ulr1zb* zX`(&wXS-ch7We$y?(W{>#7Eg_qY8;)=PDh?mo5U6K$-h}>}Ss-UBo<-bU%^vDm&Hhm5C3`|e_kz-cx8|8lPrq?RbqR?(@(qA_6p93K z6~YvXqwVK-pjuVpazswk+$(cP++*CF!1uII42~?7kIEgp+QBbkq64IH@nGs^N&2?$ zQi{kDA5jsdEXS&rfT!@lpDjJm?Q9DlLzY8k`~##`2fuYD#n!%(t18>GQuM;TEa_My zyXlr8#$6Z?yTO|&qWb%j%T!@NW1@*V!i_kkSTHvMg#Se^q`k9_)a#dA2#xplbAgY+Ms4Wv?JhWdZk5@-uCko6Vt03_g_C_4=OdfMbe6l*vU$ z@e-G6^X`cGTnZWC#5RR8v2g06!hW$;M;(VZ5B-_!8Gm(#YAQwqMZG)eej?FUimFe1$FO8z zM&T6qxz1bj>EbIV_pXQL1s%avI|Sa##r)Yy%+tHVFUG2Wyvyn#_ssf@XcXhqFHnBa77{M?9bRc-gEf{Yi}Lqg_LPN zJ!D`oeG!YImjySD&iFuvPriqxVTL(G)(f%J05MhOvQvh0Jv`dJlt2)`2Zz^l2lO9T z6O^jGCnb>NLrSc;dktJrOq2gT2uW>eh5Q|8Vk_cb|73pp1Ze!*=7O>gfIQPrV&{2i z2ct0fYOz!>3}`#8CnY z%g50fE{+s{g!BJPZG3q-(^#yJwFN!=4i>&^f+7v`uM=&?4p-?fZjP~n>B0Bq!9w0* z*9V4~l2}<&B9plL#mNDNFmzU$I}gRO#{u0WlO`0?z?$(Ry$lX7B+FcBOFLva1g;w8ATwT*X zf0HR%rx*{Qkx{Ds?GH!F;1+7T*8KVza@I0IG7&V`nSKv`UrLJ8RgeWd?KGeM(iF4{ zB=Dhao^2bI{Fo3|FzFdJy?YASZM*_&X~+6dW$i%C)DU9E0pRMGgAp|8?K@X;$;Pqx zKOp|L92Vcd?nRyGyzjazG%}=e^mpR%2q1&Mg|)GG?#P0TcoU@0C--ai<7q9caFgpM z)xCFnH=p$@kIkk#ewBua*i)-s^y3ZC38~ePywhW7@M4Ftdu=dn7qYnDY&$G1-dexPsJIXf2z1qtq)Wia!= z6b3>du9#u#kpp$=XIT2f$Nu=>53t1@zb15^-BqzAYwkVz3!)uEjI}fgx>!vg+FqUt zs0}=evrKmfA#ztTVR;}du*v6}QDwQsztig5U1D~8o|%S)Z;Y|cB|j&D8S=q$gSYAC zu+9BUOw*AKBlGZ^1tp(^Yz^8J1~OE^Xn$K$w)G7U!It!Vi&|@IT;Ri~0RLZRy*Gr= zwzfIRTM5l>P;TMY_zQ=LClWCm?+0P--B_WfxIe3Z?n6#=^VZWe2HHz3se{gs3kVi| zyt+C4>oR;iznD(Hx$)dcd5NS~v|hdP^Yn*AUIq_nT@WwqWW^pVdHN^3kCq->+N8eG zCi;eyTIdgD@`im``%7B_;zQ5*sFbPRwsUljYYaF%tZt?+iY+*Z|3rRC%b}1l=#@G^ z*k`V}jmY~1GTd2lfBVZQ(fo&f9iM+s_nSr+7qUD`siO@3v~kOwF^{z>i`R&3b1hB~ zeCzzHR>OvM$IK9Prz-kk?!K zF0AN(H1Tm=STKE*APJLfc6Ga`7KGaH1;6IU?tN^3CJioKLqUC0=rHq^qoD+525!N* zt0U!r%gR2u_O@ImW1)?)0MBAGjJtc@9k>yg z)P0u)8@YGCAw56B+~4BQos#lTtbD$~B~3S7-%or#+T`v(o0~JWiQ2f@ZYsQoE1tsv z5!j?Yyz9?qdZwP$UT~k zrg@xoA6Bx9L-`&P9LcrQ=Fg#guKg=#Y8px~nOmCh z6UPKMWk4p1cT8cMyEyoeq~kO1N9S7crdPM$&P|4*f0JdGwVV}#-U$C3O}Wim)P)u2 z=Q5itNt7aLk32VG6yOizgX*KnzlU&WZ=3SOcB_$zwUdpgl{+gSUVN0Kb&X+}_KhxJ zjllc&I7VZ*7*Q2^VuMe)_ehR2K>LEeBt1VNaQ7~TL><5)R6B&0jI&WKU9l@v<=Qrr z>3Z}T#$s18`*jubZcZSBe7B%*(I|i@oZxY>pKyd@(G4vtN+1(KQR#;l(4WTMpBLNvmZdBKM;&<2)mAPZpFGztDZyZ|#%G9hQFg7`S9> zBN zSqB8ZZP#)}HhiS8uC2~raLuxW*$NPf$F0nC@1s#QV0c7j1X2b1gX?tx-5x50g`==w zHPecqCfORWfP-spj!t7zLFXYq_D z*Yo~6JL%kev7~Qe+8(t$$&|$z8_mI}KyKg6nfR5q^}~?*z0PV|mbEM_>?REKw3=Y4 z!*B6m`N?89fB>n4sh;qTf2_Nmk176K1kmTaWwB8}Zm_|02aYJnm;XWv6Lp%6WM_Vz z>|xw$hRdd(ouD|Q-R1=oAZ|#`>N){HW5W~Eb5P5!A**yF?x%@C-dFB{5r zqXco8jbxu-EFrGPOwF;wSf|Z20{f$h21W+}GX)qyx(7+J_2jKC8+2Br(JgIM4W^0t z`Q;PseerF3c&&lmnwjN9OC;Wv{`)tNq;OnpaBjzVw zoE=B`BpI3XDEw?qo-K+K`fC>85!dux!#!1o2d^HlDs|O4;7@!a*;%xS^8Vwk zI*^xK|2Mg(AdR0G!Y+X4Z~=*RrCS&+KJrV6-b{y#cNV#}I3VzV^ z!83DfSxrYd{u5G~9huK6?e-32ijds>E~G*c2lPjCt-QT7A0_QKvT|#{#6WEGtB`2% zLzY|FnyMU)oT|3TQ-l9 zlf^^w@h9s##uM9dmp7OEAVo0x2Y;?dV&DaN-~u>$Gaj-T9qYfVYZ zy)T~q_pJRqDvj5)6$`_lEX0vZwmX+~!TEW&X@#``Q^5=v>yExq!&FWH%J})ar*U`U zW3M(b;G4ktaMF$MIaJGBpP{2gSDjI77Yl9anKD;5=jb+iV9-gY<_79p+ZZPRDd=~O zD16=4wQ4^2^wGIx-p*uQy=c)xvqdMY$uG1j-9)15PoB-~J+{ z>qbuqPo-n-mWHu{3_{r>;f#UJYx^uVw6v*l8lQ&qiet%X@u{AMeO@TeD|#ss8O=BO z!@!u$F*g0^N6}vMqlA~r89EWqmFXY0?2~EimD~U)b2{ou8bk9Kb)nAT6d%$7MkFo; z{UQJAJC@1!f6hd|P<)?~6#M2>duarC+=qGgeqM;UZ{Px#Re@HUc)&B7;WB9x(+R;3WkXQLt6av=$Adoc-yFuymS_4^o-w}+~t5bc6CdNaJdgUzJdZ=aKA`z9zY z?g!dOsHX~3O+4#QnPlEc&M1a3S1-xta zWK`jve7Yw;p25!1{|#5WtPsjl-MqPy-CBo#!1d05c~2GE9~VSKXe_6S=oTcOp0>5s zj1+4|z2tuxm>TVZ+M zxA3Zgx77{HRKh7L7NMl6tk-niY@Mq$bFSy~!Dx~lCT8Au>lI~%I^AH!`ceWRX1nFEYltSg3{lah~ z+2E*NFD8;2&38A5T3w}QN~Q8NExmo}CSQh9cNtsJYGyK-lQO(8jYZ%q3fzC>$1RHl zlQ%ZC6@=2WyewR1!KCK`h>PuA)&t0-tOKoJ71-_#;QMe!!ej|o+ZRmf@d&cXf@8ht z{!b(<+$m{Af6N}5TAku^>dPccWTogoxvCy-YLBvG_R0GjJl{!H0W~40&As$B*>6N~ zLWw3(eC0tcEmza$^(Uu?eteSQTuq>1@RZou>fPn(LvbKThwt81acGH@=z#WSlw!zA z)A>nQ3rr8P?DtfA{o~!+O-xpK&Ajv2oGell{>(8>%aPAj`e)xL zw*v#(`BPk#RLsOd zpewtN<@&+NeNQfE*);QQZe#m;$9WpC>qDJ*f_yDF{v79)8tg8FkCsu)k5fAmZjmR4 z*nld&Excwee{JJo%?(1_kCZF_j5EtQdK-Y!QJ+zs9yAzcMbV|u$R zaJ&?ZyyAT&SuUOVuH&`q>$vzrkB#a!ay5pGHiP+HU!>Md!8#%v;tsECg0L$4A8{LR zd9p|VR@RL1A4BOZW|qx54V%65-Y3x%#%GpRwOt2{7&0lnu|FRGuCpT0=?WkLfsj*% z*YL@%pLNdzIj*Ok?Gvtz1{oT`dU#%WDq1Or^XJ)K>XhMFK34|jCfkvY!wAwz6<^r|^O+OrvtD~29 z@dJitb={7I8Ov!Uq@Jx5FrG1^U68Cp`pX!#UDhPVI-oQ`B8`jf-yX1?%O_TIuj5c8 zhxtsV{_^1*M@1otsWU_NoHWwci{aMU$WX$@rq_=fIZ`juaw}Y*t$9W0lgHT!UG06d zeL1;Q_@lLX=sNJh)E`7@&XP-J3=#82uwV%le9~Ujm6ALKR5p$-SOHL~Z!+wkoXjHR z8sZuFY&HutInI-#q}3DsN5Xez3-3zvt}P3bXSDztL7#{Cx8sGO$xTx-rbZjrv4*1> z?w^S}W;X9@geI(B-Ut_JK)qf1^5lE`awm|_obltSeh%-`cKXgWg)}EA-nLL9GYqmO z`CJLcF5;o)yLSQnF0xipM8LG_r0e^Py0kh(e1Y+dgz{>=#pxBrRwbOq81?L0?@!&b zUkxfYYw#7d!$IGo*Mjgt7yyb&Nb$sC%Yn#8<7}QH5CUSCYh=({mYDl0J3TtC$`QV&=v`Sj0jSO$ev;YLz) zAV9~wQ+|Dbu_Q1MHyDaO%0AzYk}{LIhWd^RTU^L>j4>1BJ{y+62?*D>k2@=T?jv(( z^a|H*r|u9{!l0#YYBLo^>+K-b)~OnFdgMO-T=TZ{23@Z#rT%l2&4#8F!R2o`pzf^y zJd5tROH`5CzWVtoYhtn?_+b8gixFeAK>5fgI(ouE*bh5aXLa`*H(l&G>HAO8RQ?}O z{r@fw?6!8y-TkUYIl)c{4!;52g6>ce?*As?ZTJ0$Cr91ZnXz`e&_)f=>-gIshxu5937lqj@H-PMYhMT7 zUw9oL0_Tr$L{4v?1WNCL3`mIg{U=UFe3lt;3}_{ofB3Vj1^Hg zGiv<%i(WHN?;i!#!A5ce^OX)iYpm3Kw@*C9cVLv18q)aX8E)mke$#?bG`ZrFo4nqa zXJzDL(Y2TQ3Z>lWg_o~s6-!zD1*9kmtxaDkBo$d6u(?Yx|024nAs_$uMnY^{Ec7lT z!i~`Hz5JVM6~-bi79FQVeWe=aR7hG`Q}jc^;%b9eKP43u<6co({_(UDT0UB}VO=zT z{^6+z>~|^nqocY~gvx_3y#VEsT32zJ{+3n^k5A46MRk2k$}#w@G~Z9jdDUES*kaYm z)1##HCLa`AeX0Fq=PlcW6Z+4c;Ac3N;a+!F?VR>9^*2rr!5cxCNDbD8?BE>2{Ti=G zrrzMWd8R@Z!8X6(Ty*~C*maSoa|rLdutXDQqGx~D4(Ng#OjDU-$Z`MfQfVg(_;7iS z%Dxd~Rtaf0POMB(~FETAU=)NdKz%ye=YM%@z zR%x{MX0gZ9hF2tYXy!l+XZ$QSf#(#S02_KQbE=xUOXOlN#!89(ax3IFp3n;ww)lGb z{y#}_+@sJ);<4>_@oHc2Kd^p4A@;;g3ZJZeaD}YMS`y4(>A~VP*XjBkC)juW1h-KoJ^pousv6SRxxY;T|(z@R!l9)HR%p4!3tM39F zIVqKP?lXGuTxWd8`^mcQ*Ou>F&fcLL4?!AR)E942CjI_l%Or|et;FoW({+q>t>oQa z$&M<)ODpg=bD!7C{i_hBmQJ9KekZ_8>`0xQ2&JZ!-zX@#73$d@h-PU5b0zq!M%yYnuNl?>r$D;Cf9s{XBUNMaliPz8hSao z%6d>2xe#1!t?e2}06+wuxHq^Bc0Q3w@=GtcbX@G4ELU7s3NT00)$U`$X5_Wct=umQ z1vBe)_2!B(^p2C+w~D{Y*UeU?ufYsAOTRl6;yx%#;l|VF)#bsoHG-;CWZC6T=?;p4 zpCZZ(rGL1{8aW4@sHj`i>*tF(-Ge`B@OXes+>ciOx&~(oSgc5|`@1lYZoIM?8R#H8 zFxI6NtD^JvIpRfq-u9N6zkjj#YUy|6UDVgaBzH)`^OgHXsHvhR=}8?WM~6}OByOK5 zlVs%vTHET*WohUZ@KZ9Thd=Lg>#JP6T;m{QXWv$V^v|ND-BfihgSNRtpVa*GHfQsf29hs z&%r=fq2AJTAo9(NQ@nqj2$DsiqJLc$e+*ys*9px|XXj%;U02E+asg#_Amx_n%g7$$ z8+{&JV@#Wb+>J|J}lu+LE z46OI?j>1#E+rB1px79|rB>(G~-#iZI7eFcH!`pG;Am(AIsWc3NqZQ(w4ah{0Q*Q&^PyG!=m~B$}QdY}9R?T?4tNAk^k&Xg@0CSbY^ZmvQ$+VmJ#UK37Kh}i$wV~JD?;QRTA z=S>ucMO%oq+lD|%Ojf5{FyaNBc=>JnJm_nO0_^O?zRCq@6Hsf|R{UH$DS35cNAtX| zTuMU*Wywi6E($@4-0UubIrsx_g?A$VLu)@WKW1r@xFBgv*3`s|YV9=ZYGoAOZ)YKa-KOeTq@F&zX{&o$&1%~?5`;`~#a5ds7?~lZIRC#eQ>w)mYp(|JF9L zx`BLqv#%A*Z799RWXwa2<=0bp^kvj?8MgO5+o8jf^)kEQ_B#WVn#nhLQ^frK|8K-Y zUi0pH*d8Vh%Q`s*U0c)X<^B&;x`u4=;%JU<`?(Vc^A2ed?7nIMl?0aO`9d|tx8`OSc->K8)W~?@8PCn0d!1bNs1xG8q!p=c4^LgD!|!D1I#O@HeO2UB{iztck6tYq!QB zZdn2cDtB>esKFqH_1(x4()>o3KXeKit15p!$!Bakkc?<0;#}&VF^aq(&D3UF6nG(Q z)3>+F43k&?+<`&b^l=92eO8yV3@zCFAdJ7LX-9?fiZ*jaF_wnB7Vz%5i8JHZt z2~Du^7U6S5vluu~Dp3E^RbY~Cts!Rhokg&f0|#CE_Bmw-`T!3Acb1lCyM!=jvVvo6rb7Z97+M{ zv)>77+ez*+c~Bo8n;3h@kTw@n!JBYr30VnGpK}dbP@N^J0PUIUqbY4|JEV>E#06E^ zpK!K917=_G&~Vz1e01I1p;2Et#`U907XQ1#40*?<6u3Nc8)AT%>bqU!`V!|bdrqYv zR&i60G|K;MhvtT{Pi~Lt1Ip;o>)*@ov`$^AG&I3vC3-lvc+9Zka-*{&`steLVNRmX z!Q$q!^hKRXakBadSzA!jGobTf-5HqT!drO+_Q;!9f#jCeChJA2-y-*lhqM$^AjjUC zac)OHz*EcielFzk`HhCM8qr6gdKIfYr@K*LLDkVwVME@#vv)SgY!gr^TFdI431_Cb z!ac7f2bsBArzVTn^TVwosXZ3l+k~0?0k<9R-&9X*!4QKLC8Z$C5C{G*1NT=CX}?(? z=`eUBQ_H|lT`23ZPxiA8QH~vxr$Fr(T zM5jrbU{(isWYGF_2!M!|Nl!3snF^$TFDf@xb1i|>Vtg~naKYfER^64gPUy)fg9*E% zK?=9<$pY<>@$E_E`YbFbeyg8%3+OhtPqve*Ec911#xo}TrS=%E%?ubFH#P|eTfU2k zaFm+Ou88-^Ft@d_K%OC@t`3m}7GhIkC-Td!Wjqe88e=YG(z?c)j={e!KRVXo($3oz z-ZM^GPu=q7@Dn$dyU?Zzlb*X~uk+8%L*ufHbzL6_s_x6g-b!2s@-~uH48CGFA_W5e z`i&omnp_wt#`CNJC+-Ba7gR5_Fw!3KC-`*A^d8Q2B1@u1Wkrh4Fjn|+YT?(afr^o8 z(J_v9dlqJGzu%7Lgn<8g$A8Y5zKyW^)V$(;%6rysVkXOY>s%gD`=r_&JLCb0B9kn1 z4B9Ip43o{Sy;o|u;DM2%sSm+bnKKM)l_2#e>Y=pLu)sQ$XJpI@@f7tA2hm3hTHcH4 zr_SY@hy*HTUNkK_FZpWd@Q7&LG2~aM5#4?@=ex|U?i468y)pt0f*nMJhImU0KM*=B zZk3~Vv5{AUQtCK423iB@kNmzsCK{OnegNOK_g)*|0(s6XYXL|0`V_m=oqWrM2DqTc z4Y!bkAsz>Lm|`k&w%fG?1SnYBcW*d7nfCMwd||{#WSlSH{XVz2*AsR8Wc5KuXXoZn zAdw8=K7?=nGVO`;jv34dh(4D|^%g{Vo?TAZ5rzVaWgbAK_X8hX?cZCCuz%Z$h^aTB zK*szX-?}5i$$e}fJV9W8bulRGD^ZVbS#pj*RE{+%7n`pE>tR;-whkhM$nK2I%(%-g;sHe}t)s*BeO~>Vt+iSJ)+!h}4_cY&j z2ln)okvO2TYMgeZ?2^cP9fyn$mYGOXIOG?B`5RZeq@$*Y1>o^L{h>U_vJ^gmj;EZ6 zZE%AwzqJozLv4cyujwZ~PI)axy#Is3a5^KhMM@cBh1B636!lF*bsajtwiWgO{8jJ`@i%`#0*hY!#@UTdhLi6EMx zPfLYPCBXQPhp4^kGK3YnAY1c-7Jah0dEDyJpL>`867@TCG`Rcnh)8zpD&yrL?Bq49 z0EETF^G~2?bTX&3cB}hl!53kos%i#2|1PB`*%^;aN$Gyy;4S+tw9@V?ApsZKQ1Je{ zLQ!+hOvt}yJ4>RF_YCL?t(5Cw@K|kL7U&Cd_SPX7))?(CyUrrp49me;AAak&ZJBcN-ZDauM<^i9Yxaf4*#F z(EP3YG7DB-r3tIu5@@+`v4KhMT01Ogv_GWwoD3x}zr@LC+5ExJyVySS)DD8(R9x)Y z-yHq5*wJcsJVr2v1YKqP+>m5)RzdG4)Mbn_e))dm=7(@V+#D-2Fkc1lg$KhL`AIzT z4=oHWFrAC8O9;`tj*>or9O7$54Vun=U~eV)`5N;TyKXL|7cj4@>v}Z1rC)_^h;a( z7lcV^N=ZJ^Zm{Tp@ugqY6rFSb9Mx^8AEFeE)EJKBQgUjH6znm$@)??b4v5K{6Y80*aOSyeMuDQJIsD(}rw0NArpFO9<4~v~Q zkPL-*xj9iXXv1jn$m!$4s1&1$t=Qr(bc#Q0Tz_!UGbMg+lQQ{^XY&h6s?@N1Ze;im zId(w4n1&$f=i_%O@Vjn`@G15P>tP%f@_?3DSRGi|JuI2qPP8dyI7kuF z@1-`&-o{>49FER>awJ%K?g~LLVd@ivRWJR9Rd= z&v!lsxnsg;ZHqe{_3i%7=*un^5h^J+k$gpS43RjP{1b0G-JI%tyL`wKSjg2!=o$7h z^>MZ9#xQ|1$�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%0D#&OKv-y!URq`zCK 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›Rd`daj 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

    Oul60#Q&Q|G9Zz|Ep`v?$EWw0 z-yO%DK1a8JXT(Z^#x8w48N(aVG^d!|4DJ)F)xN>x==>#&-fG@I&Oyc)yc*?t2<3$V zju>xB`p3S(0Vi4jtAo{D@F?nw%U;TZyC3E2C3|*OAOCueC-|W`9~H>3A@1S3*P5kh zroM7cJ>(;#Go@rUyV{)&EhDx(_)v=H@x1`?5h&Y0A=g2WWs+ih_vs1s+PpIcHY8P6 zkCIBVah!Q7f;9FkgsH!mYRXRSx$&_)%aJsx1e>Q~{+w_etD$IsLCdm!&vTet9<3ujc@hsoxPU>BND%&k^-tDR z0Z_hD>P>%^Etk;J8?`?G5^5m@&?I+FGU#KB*Rq6%)tFfJ9jpuTg<;+^D*uxbPiLY} z=MM7NHWswc-s){JW4A0FMV9loS6>)15&{M-et+UOw!Tc#MAZj@%VE|5);anGd(R4( zDT7sL)yEt#LNl*od6q z_|b};9wl+gC{A!A0MwWTd;gYwWkQD6-@8w$(`Za}wu84M2y}}fqc}+MR{{`n>O@W( zkW3_VqDzIG7`C{2&n<%j4-h(!W1Qv*XZ(eU?{{J6+xniBUOupO777?L{Pr2|syLNP z4Ig_Ec`*d&)Bl}~pa^d&5xqjr0f+f_95|E;J_j>ShiMDMrPK^_0D$V13uVyjc%;qr z5(WY01Pkwsegq!kW1BI9dE8_F(Nwnyj=|hS(i$|>-gMynqls=d+HIHzI_J+HcRhrk zd;QNs3*WOnPYXY{W`4TkGqa3)>ZBhAz?Oi|brAFJ=wo(uf`VBh9ngPqGuC7tdcY+W zabLh)_h>!X1w<4I|K;We5$jW`2K*L zKbal3Z^<{83lg1$F z6WO4w4<2o=-IjLt$W1VO#cAEdYDHE=&y(9>VVi;T)jwa>|F(VyP-Fe6(Rfh-G`o2 z#(_!O*=}u{DHA&GeQ;68=#=d{+d%%_YUIltq0zgX z$6nbP%iJ%rO$V-l06!E+LX5XwlhO`uw{uBOfhZOAw@BqiHLf-@m8uq2q;X;vPK2|h zU-wL}_zxzQp5oW#R{0we=^pyY;oy0L$Hn-9{V_=qOdbKO9u}Z=k3REU?nVE#9fb7E zGhfE27lSbz!}9{N&yG}=5yb6saw5F~O=yR8#P-i+!aC}KE$L7I#Iw};z)Z%}t% zC4qYu>GjuyQ2E;*@pr_|$${5J?!LF*Z>}$sJ6~kHpBm3nIM)?1Cp5 zZ+7Efq^AFCmigbKL&;IL@msc$=d#_2opD{SU+h7qviHEBSk>zPAsyuV{ZlY5yWPI2 z-R;t<_AFKqxihkDv#X2+?cVIdiY9Hpw6;wG&ue?qKklDoFwy5hUzf$--IJ(G|2i(F zlqsCHw%9TDP21;H7=DIy{ySR9=uI^@0$-q-HJVuKq}=_}^fL|E@mm zU5B?&%M?V=AxdX_{yqZ$cf(`ElDIv$T7l?YS6_q>STDe+>eMIn4&#y{emEkONon!t z8Gsa(TN!IVIbx4>+>Va{23E}#J!BJl3lrgtiO%+Kgsx#mHO+mj!3{6@KB9#jpdmXD z8&DR5a&aEoWR@egwAR0lU=BN{A0l$V5j?u^98ImBNEib*gQDs~R92w|_Wj#}5kQsl z1@(UA0F%8;F1OHWpfRMgBynrU6G}Ur4I*VcaaOgcEr<+CUSV`T>;QpYGtfF3>+CmO zi^3UZHC-2++MwxIv|F<97JnpcMhCr!US>^Bq<(XvdwUrDAWD@869%#nC0U^nZX$*n zLcK(M&LmAkZ|;mF&n}h?K~)ubA-swe+edAc!gf@eVkLIKGkJ@^t!gEE%I#{XH)eGR zuf<)Yzmgh-Df$M$#p_BV-}{}KZZZzq7}6sCcmzfuRW!q`O-^{AIiyjNiybeV%FGze zP1UawQRda{kms0f8S!Qoh?VBOX5|=m-)Gz*B6UbL%dD{ZPhIbkjA*fn#XdT1bSdFu# zs8wPL`5Ggu8?ExV_obg%a)+2T`)ix-tNLF$h;F+btsz7Y?D&7eiWZqC_{xVXW8B*2te-2 zeNlMs8Y)w+nSTEA_szZcX0V68QpwmbPD*nDuR>}izLHw|@Kc8>0BT;{46`95c~}#d zC|$R#Z=B=X*#_(^3z3j74}ZrqgnLLzowby{tjm74VjAV=UUKs|$gOPp=*KYvQqJ<> zAs6+xwmObk=kWP{W5|48^lZCK+drr0wk1Y?hJV1msB)Jv8raU-!l`$a3ciUGSHx_3LN zf)7chG<{e54EI75_=ua zxKik1_ZTdV*sLVZXSGL>+=oqbPGe~M}0xxJuzwN{W&j`1&Q~e z@G1MO4-E1BT;yC_FzUErU%HKz~bDafmT&2-uNn?&|({&7V}QV z=+DYS-q`e;$GA7zbM$VXKAbxmKNfBt_M}vkz+i{UHKuT_U;6yU-{Kj<1Elc@@}_3l z{CT}1-sfOb!1#Cc4jkKz5(ynDuK9mq+ThHnw#k3)#!Uub&Erp`rw0%=vP|p7y7JvS1Q7TFs9AoIAo8{<8j+K_EH`Co&k#eIkydC9vOVaOH_a>E|LNK3uHK;e6p%}X$b)vZnB)B!V)X6r{(CggW!?=5 zmtXhC-zO{K6&N;)(U^H?k@-^YDKm#r#YxiZH!0a4{m#~O6e$5oX9jvVWw~$J&l#N z`Ja%HxYAd)p{`oUDRb&c|6%1wYB9qVOdVw~hD3|9LmbCRS9j!>O`ckL^POE1T7mkDJkfeeD_QS!eZ%MbZ zgUs-=2lXI9uDe@bu;aYzuZGCDfvZHX@DtK{pqR=~ByMB&pC=gyJF zzYw3+_^Z)TMp6TsG4r{4gK)R;a)Ax=V{~?%b>gc0*;Xvhhzl9`}&of5yS z!GxkU$ur$dnTENgr+WrPYU%ib2i#ceF0EHnnVy5#FXtSI$i6Qv=`)CRZqKU&Qy8R2 zTh+N-U!${)bw$Q`Wh~x0P5*zmmH(G-dA#_~y8g;MX8wy)OE595(D4tS145%m-rD~K zBN!*v2>6J#J3X_jMeY@R)M72BOsw>Jd8d@clDd0Yd5tasG<5(Qa(n7tPAFg_S$0!K+G~YsQ zz*0v2MGwTkktF!A`Ptcp7~z|;s_Dw2)f=n7YYQpLYh&IuBS+e^Q4h|~Kvf9r_<6bx3)?IfS(!?Hc8CNH^!xVHQ32yiPXQU*w7q(gE>!+9{t0ePmVUM3zlV zDaA5^kPW*93CMay42vg2M*CHjDeP*;$e&k@?r`NK3Y!v!?jfL}D>X>R3Had|ln_^Q zN539hX$eSb!>5OUffOQqRI-jhh$+%NCLeSvV?4n zd*^`n45v99R40??o}fo6m8S~*n>IO}VWIZH@(Z zO~M$Ad}zL#uIcw2vL;#fx7knCATa_R0YRSf)Wl`@QGGL?j-D;uOK6_&i{to3n09$3 z)F&$hhrRXK;pppH+Sr9RY_xi$5ZnV%RE0h@)6_Rcs|mJi6cUYq!DTWnnBIOHNFHvNe;MZkAOKGM07gF)E{;~$CW#|*P?6kYXZWI?-3LAO}P zj1MAHkDZ)FB9I8`ttY;|5rSkB6#ONdcm@1TMi$s}Z&sK)b_RD}UbpXCh}K$+isOde~D z1Q)@7%+2i`w8rtLTWcx{nh+#pAg`{J@GY9in0Q`8UW?+mbEv^;h$j`}6h<$kZ6^IN z4bmDRcPQ)(D|C1OT=he!BNk=@@=x|}N}%OzbdAvhbT4&_dNgNm5B>sZ{(Kh72_m5mZcW9K2{t*eY;S5L@|X<3ujYy)W{+*WXipctjGn}^^YYRf|KsXy-$}<5%Vx^+ToZJg4j8s|9?bkbkC+o`U1g@z2?S=!}BwUm4rg`;Y{st$wZ-##^ z@Sq;3*h0(6_*zSO@$1dngZM$w+e)q9>Esytq7COYtbAXNUkC0S_WV{K#?jC7z-Q7ZB*3gE zt)VvTcRk>ACLKB_b&;M1$c^J=L5!%7*f8CGs2Wy%n@L)yA!w)J&Y=>v0(qM-{6ovsg(v!rP*E`dm-)sACL62P zDkea*%8S6F54la!z34>QqV3vm4g~44qq-($a)OisQu zd&Ug5uP5&yfyjK{g@doAQ^Cx9uXX+br@-z#oNv0B6^?BOz@&$qzYR<)4b^YR-{(UG z32gpP;*m7Lznh(cCoJJxz1cFMI(4D#^Ksv=%EN!A*#?b^QG)-I)n7}0yF1%~Dfm1Z zkda;Q2!!$8kEZ>ix2gpAa2sAf@XoO(@WUM_;PiYrIAP2G!>*PFu^04Ra1H<| zuL9`e6TR9n8%4kIIAs+fvc8yHZ<&+&b6vLW1O!Uk{O=vs|GLupua{70|0_l6{?|)X zDsualeE_6yd2HZ3Ml6dX+)mlXVg{dV5Os%j-D$@4l`9_KkVV^4rutMy{mHanx9*T~wBs;560Q-)7Y;gc#aVI@M zox+D`3c58V@`wA9-njfwB}aI^)LMhY8il}eiG9|yLXHh2!9MqvN0Kisk4ZDms_m8>$8<~?GR`Dzs1)4o#$i!g>l>f5&xqtsw z=*^dhYBUvx-Ar`jBA=XJlY0`rc%6T&B^94S+9RL~#T}c%OzH5enwpwi=S=lzN{Xwr zo+eDKpW@BeC@#Opg+2xI7TLl#tn|uGX9sJ(7eO;$=880Qa|_n$kgo|1Px{34ce$Lx zXp0uxOUwp)CBP)X@=p+ow6SDtQLQ$)4qz;0h1kCr=iZPY@}vggOpfwNYOHeHa@t;P z*)ZxQB4AVn+ugE~F(__HRvz#nk0OHIfB^P(`YszsiC4C&rsW?T$Oj`42J( zaA#y=M6P0G{2O@+vXF8;Y*uIoI|Soa2dywhsYH|hQ>6zNPD2JS7^yT)x^moDT%8{) z>2TtrBg%`CiI}9Pm=eTTT(TF8V*-^Y#q#Gv@dqK=U=6Qui46W$aBqBG&j2}dig+VAD`6N-G`y5EL&SgWH9bht5Xr~k|A4W~4lNwU@1^^`_EEqAf# z6f22MDOYSSD^K!;D4IuX4R{8B<3C;%r^}AlCSX6Ez}KG z7kBf1eKcZtKsUm-*YZ7z+dOjIy2r8kQWpMw1y&R=8~8y#NT!|6-35{$eIFm;;^)`) zZY{JP`NG%8mFQ~{L&GcgDyMn;O6uXJGyFuKRev_X7EE7$96e6OV}lI$q@U69<_iyN z01NRTy9mq#Qn$j@VxfmK{a9xauf|dfwF<1#TXraerC#Zofv>^GjH*na76$-Busv($ zUwQ=6vHYB*iwyeV#(4ZKhXUl%uGj}n*uG`-8~we&V%}u>>$Uy!W+1olEZ7#(mf(x> z;;w!I$zFP=*O~e`U{@U{Z#4>iN?61jjC{V{)j*m*=IPM!u@FZyi8n%M)z*RNV(AEC zxpXo$(2$DPMLQB3Z_>>CU{0MjQ~>~b@W<3O=f_N*!h5hoqk)zb6!HQFG?b%^JjrH%(T#QI zDLLJ(K$&&HPBQk8Ym(jt`z}n^y`g%c^=)A6#zr3-Kjvj@Ere zPo*>=v)75trG~ZiuZz!mmb-ZR3oM-Cs)~f1ZCATM`%pQ=I3@#61}mzStAL^l^b5%D z(f+xEERG+faqIgfSfdNe483Egiu3RX)$NKEKv$89;k>QTbB6QWcY5M^LVNvIFI++O zcCw|eB2T1}KlhRlA?#NQ(-5#QL4V9xtP!EkDd}AskZEF+-tx|0J(~?)BBJQJ?p;?t zjv_m(I27Cq9;sJ1bOi?s5qWIlpog2I!h~@jBG0Qy9_jr&wpk1D!8bWA*D8sujv#l0 zFL3V=x3{QhFmgyl1l>JH10M z<^MepNgXFt13Lgk@mky#So0u!&CTD0xt~{P>FPv38Zg^k%%f8iivFd;QQqUU* zSed=S)*CNnS9b%e`Oh-~o9^$CK!44X6MAd+E9m+r)eg$f;~nR+5Q>wyJI_A<7WmyFOzb4aGTjctHBUc8QjKLBBE2nm6wHU$;UNyjRtF zXRyroDBX)M+-#icux0le~&*8 z0ig)G??_cm6RXs=-)uBnXyCm$Z&H#xS*5kuELwJc1$q#AeGmVQ&F7);?atO3|NrlR z##;|Jc3$PyhQ&`jlkO*nPd9xOi^ljga@?q+%FX<*4!B z^ux|FsHp0X5FG@!ibOwAnQE0BYK}&XPoGNRdLWGxRftx$p`~UKRgCGLfvjc_5jXn} ziL0CJQ&pwVBM8l{;I(xLtcoMRIszj#QI`FYA8>G#X4k<6hV=ycfrY}EAABB6B*8^Z zF#=VfGXZ^ZPAFX{!6kq^p6#7rFzkOD_i^(7A?q!p+KRfi-4=JZ;8G~=Qrx9Lf#4K} z;#La5T>}IyUR;X1dvPZ~aV^CORvdyHp6@+leCK)3zl`jikv;a0wdR`hzVEC2U{vN> z)=`WJ5kI;g4EG}2tL*dgnL~MbJIjs>;KZ}yKvPNalwiBSSWyiUSd~g^!*odKOKK67 zOaCiuHJ6@KLr;kfOAHhFTZ6Jy%>XCnWni6j+9^sCrk_CEx{UycYAhnWjRgy)JXSu6 zUkDZ`6D-FnLq572Gj4u(;rKvMQ?mf$FZdLR;TpmGO}Z}<*x)9Q9{j6^?tRCprF034 zHbp_60NY!73E(2~pgSKm;~aumFi=F|f++Xik_91=f}qj#We~(G$$E^iQ}`v;r}>fd z!~CBNY3c_2g8d@yAITZcK@p1G#-W`aW~UlDTL-ShY{~;Z(i-037V~(G<%$?BfUcmX zdA8cfKjs3ZjHfHz7EFNO_v*yk$nAw$3>$?S#`z=o!f%P>fYI-`QmUkeia$pRhqAHD zBr8>eCXl1P%qPAUoTVDx%RIwK#mEcj_@|Nh_5}BWT04G3Bw%u)AnViV5KycKCIR(j zjBV+Kg{&Ey*H4yO6k8^yTOmSmGWwy4`^rL1e;p*(;E{h~kH!n{hkW^e6C8p4=7je> zO=zmJ1b1Pde%ZqgJVsq7=F`VN3US~UEtt?I##E(%Zciwg?+ zi?rJ>L?qbwR3}q3u!qi6D>>81ZwlP0%!2ROl<02t@K7WCo|9GFDHG<#8!34-(@H8y$ko&pa7@ z8X_5mcn6?7_Mb9&;PkjjfNt@|E7;-{rEUi;j+8u5BBy{wf6_2N##`4Eif?&exYuDF zqj9NzsJtYx)M9m3V~(AewKZGwyxf<}bQ`7p!g-RqYlDrA zQso@IJ)!zVH(MC15k9Rl#k%`l75No;^@^-u+IilJlLqpxQc$&@`n-3P1h6e6a)m^r zk`{3T*L@S_CV2Drf|_IBysn4tXf5w^MTYOy-A^gQ@#Q=QQVfRyZaJ}|nUg4Nv>V)m zg<2e2nr{~UC2zaZ&24Ijdt-T)DI0wyqiARGlF*BNsx%7LE4_8;+9eH2VCXjS}ODx%ndN3V%-{ zEwN`>v|%Xwd%m0I_QOI$a-R?$YVOmA$YTUVVF7Q^=Y>B17K{U<+PAgTnSZl3*fgg| z;~N~%fu74Y-DLd#YGrTo1v*6C+4ObUEq_h+xs3A8u5hPFX6P@$;f=R z^IN?noW2A-hlZzc&axIf!Y4zMU?V~HZJiLBJaG99QOG7-#ARg7(iHho?%Y`Jvgg|_ zmeGs*(7MQ5KIW-x!|_T!HEJ{#ZJE8XgcXeZR`eN7?V+cmlGmMi?!F$&H|%oSIn4MfOiQcO$BpnP3AZ?$P?5nOma*|1hO?-6D}KEL>qsT3u3Ee zK7w!x5;m|9Wf*dqIZsazYhM_96S4Z?K}3d(v?L6dxXAQ01GFa$CU~SJ*c#nZ*>6>e z&Dz7Nkr;xVYq-T7cLIW4stNs2>OVK!XL=7e^QU(-4LBo`e9VFy;PPP(XjIW1oVZUQ zD}}b9!sM2C!)7oX3B;N)drEpcJ4l|Zipwko{snZ4bw>8Y7A9o#DK7=M@QD1lew*$? zZ=Fxv!Ii;Ug-WQKHW^6&e^omfpTls?yy%WQIS*U$A^!9ojz|DeOqRTIS8JB{}G!? zh(4*3Ma(q!=U;Od5PFeXl$c4WuHn(+#ucd~gcEh2g>VuKiRToWl=MZjvckyPWqNAB zyAy&dJ-k!L=t8f2;9ov(Lu0b)lu4d-C;U4%33P2uTF%d~DJ^ly9NGCAy>u0+iLxZU zsf))_i;{A%mgd27E|`Sk5Au6mDZbfA+^X7P-AgEnT8q=pU1!^eeSL|6;BNvx zr_9~a?>w>VFXz4daU)%*$P_sS+uqb;IFvk_R9%WlhEVU%isyJUei5z-V;t!~<7O(z zE_@1G4N(7e`B}ey%lDr|49)WnX|I#%^O5Jlxam`zU-zC~&ufk%EX#9R8ue5W*MGX} z$bTt@HU>)dzvf;S>YR&@e@WfPIa-i5yY2>Urc+E`*u@`OEo~hu4c%=1UlH{87|BPg zhl650q)jULxvyrD<9qIAe0q9voXD$XXgG@j^>r=7b7lSWB_d58G+>Xf*Av%!fXDq; zHts_MZ1Mjl1O4Yyz4#%=RZAf7V`bCToJ~*G!bbaZjdr1=I!SgL;$OFP7NlsIF@-M< z286Gi52a>v^U$;2`nSo5-HJhm?9Av2s}=1#NyuZ0*a*AZgdgmTp~ge7|fdn>s6p@3-&iL;V3PU>q<>@E*$WY>ur|9 zt7p7=W!!5M&>aEO*@EscR*UzO71I~kJ>_Qq4TnMEkM_Nf_x-`{s4FFt_?5E6pDHi2 z$pu=1ln~qB3r1Lk^OqKsM!_P~78A1=-JPbLsrlmcCubB`i`cU(KJy)qkj>F|%Qh*b4CKDt>^gy6wKA~52_C?~4@Gehc_fP}Tg^I>sP}{sayn=Zk z5yV_5`N4wE!XqeL%qVc$m*o`@6v7eCV6Zw3XM(s>#RR8cq8aSWI=CT$*e--ejF$@y zoP9!Muz?k`@dB!EdE=v#On?)t)AwfhhqemQC-Q%f?rYM)YYaO69C(PC^WkvKL*Zj# zTiN$A+9wC%Z4k(m{Vhk{2>_kZzj%6-9GeM$Me1L32y|C0D(XlymOzkIor{GE=}TwT zx*Kud3JJr}AU#A*-@(T?Zev&u@884-N}l+IW`K3YH0Z$uN)046m|!;C@WpuU=(7ED z;kLVZK%QM_qK$P1FjEJDN*9_GXjU$oj#@q|#GNEp1*VOKW8sUEFxG&MaMpP1aWd-) z69i23x$VPFrr}MSvNA=;SEfJHoss0}ebsDHQyGESgpCo=&yfK!`kVIx(XVLo;**`7 zcP{E^JRf7FBWw;jn~5Xd6#uX?l`=6EWnb1Y&sEC6kS0f*F9M7z)G`@+%iqnqLd9>3 z8chEYM9$VWhav5*)$*PNS8v=r8%dhV?va)yO;$YU--u0B82!S*zQrYGbf63&0VUG1c^UYT^(G zQI{*y&Hbkc5qfFT-(MZiHf{7=oFnja?=wNuAZoA;uC`?3V09lL$B8j~BWBLK^0c#hS$Vu* z-$akYuqxM#t?-EhbA22Z26QlPUbP@ky~4-Y###SD7+EiXxYLLWu#Sl4Y3d`2?A+zi zzc5Xdb|4?+Hk|O@Rgm zO^MvfYNx7o$a8t^{kvF3BQV@tZB(PyU~KB;0Lt&Ik6Vjc(PdF%)12`FTAY3Da^{+cYm>Q+|gkq2E$D zjg|Mi|6%9gzwz)nO~=B?-WZ(EfKxlD>#OlqljdH#kh4!qIN^YIBsMZT9Q4lb@!2s6 z#hiuP&h}&37~U41bjo6sP~D2|-D@8$j7LnIAc_maDKhs?+!-74(t{mPr-z<0VC&)& zOK*uhW<4%-8wML+*}ty=0+PScYkGSXPHPMf`F`*q(D;g`d;eN0Osoxoc=q&fYFmpi z(tjQtQXGP33a|i{L|&thFVfB*lKC9M1(fPwL#(YSz=<#P=AcpHeun{=)?)wz*>8jy zc(aAeAo=k>=Q)MgNJkWpG2s8i1uT`w5+O89LDSfbpF zZ;@;&%7ep@G1lu(r@ zRj9SjK`e*_gJrd=a?t`p7UB>AtqH5gjHgZU2|%nSifE`s_nTF;8c)LufSJ=LKKQWy zm?{N;uZ39`@2eS zZvR`3qE;0;`MztGxwrt0s6W5+vaZ<^+g^o*Rswm0^P$?=UiB~~8{cEQa>4@RtWEm< zjO}Kn!JYVGZ(VuNz3c;`kD6bSGqqKs;kes<0!b^Q)^Xtg{0I7m-e_$sqNLER4k|3Q zf|LEyPvaz`_RRG;eJCAANJ|K0nX7g@bnT(az=nPnx>oGEg8opfv~D6g0XIUvRjmVH zC_ILa1{uhS^)Io1G`+}Dw+z_V{N*_%j?zq8c}p*b*o#{G4&5Mi=G2+8y0`E1z1r$v zwjTrO( zl%kDwvw*=e?r5}*a_+i{Cx8zIyYO^|*gpNWnnJ^If13CpjP9ti01cij5n5<$Pcs%C zzI;u1>{dB9O6JS(5E~P!h9IkMfEr`cpOLy5IdWn1^8Tu!R!+*s*1z;gvG_N`hUldy zu_Skk=&TLZ73S23Ok!2|Mjhw*&aA#!g!I|pO46uo{0+pLEF#Ef;y{9MP-62Vx`gs1 zN^#)(E7FbC{-K&t9NKmeQfTD27@uarU0eb~z1Tzxzki@UZ{qEB`pG`4FH?67CvV|r z(vmN`psSLR)C_}%$Fy!~sNV{DIZw=cQQnpRHS>vmB;Ji?tHzR`0nPcpBG&%T<(Cf^ zFw>{ZoQq06#%t!C5TR#^b-T@Vk}_RSv9_%TSSWPeIr3)1Azrc!!dBgy6eqC7KU_fYKNK*{S)CekJo*4Ur#&|yPydJQOh4xGL+QdDL~L)#oET|dlqNUhx{fD+$xkuO zk;#Gdx~*q})QmX9Q`B3*!UgI)Sy&%(&nmA>!5{iIc`p@J3eaA^&1XjzF2)be8dl@0 zJrGH}k_BTpW|R^Lq^+JW6T6#Gt$bFjU#<=GLwa9``L{HhRnfgP?-)-Ah6Jupjcfi5 zF2|6edn8Mnj()DGNZXM}ee4utLf+H=)U6rL3J#6~j*GXX($n6tRuUYraekH;QEu#+ zaya;Ih6;)nM&RXD;3Ysbj%Wi$jm#x>hL~9W#5o{n8oJ8NOcEO|KCqQzqOYf%=jd}C zy3TnDbK%?aIiH|90OK?3Q4zDu^noCJpa)?iRC%k(x0+Q6RJ%#UY;q=H{;Bkz$rHzz z`y!YnHGx}~brkz`yh&5^H1!mc(%bg(B|}2=c|kH^r75<*XOgFqDrV#eip7!YQ~EqG z&t=k!<{swR5mV6C6Sn#s*&vNc6yE&hWg51{$ujE(_{WSxXH~?FfoM6YPo!W#uaE;u zP7qb-YbSMJoz6G1c4P@dOEKk%O|IP#V0zoNA*(@s6KZq=xzM503O02x%L6)K)5F~O zkFe2LX;l*4^zoGM^Fl-fb_GlWA_9J9YJCgT=BrWkbD7V2Q=k)ABdOv!=z2Hk;r8WB z3i6FZHj1QbUoj^BZqa;y*C+Nawv-P-CB3}J@Kh(!6w3}zUwAd&om~d4OVTw|~_}{jFo(Glgg)fQD z4o~Yu%d;JM?O0ezxfWAE?)kKP#TwDYX%%tQt|k=7=G>qAmQjKvz> zs!~G`i?$qgjB3!AGVUI8u^=$^j8aDZu;o+bSms=FisDx<1@!l_8M#?{*)!rvY-!^p zav8@}Bli^aTD8MJlUiB__}@d!?=VFhm;$-wUR4sh$0mK>P>t#F_?|nksowdT+2-m3 zxiL<3+In)f7YObE<0|Nsk7ENFO8NG zzXGD>(7Yx}0!=R>3;ol5-2~^*YXSKx8XgMgqS9RI<3bvaw}T|Evb+VzObA zO&-MYN73!ZsktX|lsbNYC%bvr9qaG8KBfi^>qvBK9Lf5E1w1_>&iL)f zDZ0Yrdok4T6xRd18J>o6M-)-I@B{IfIbMjy_ka;M`fg7EjPWk&kNQek!w{%Hq#`hK zafp*cZy(Sd@SfO@hwO{HUzHP&V$7(FpbN^2hg?S4J`?f6swWk zT80)WQeUF!2`#snO}C014=`Jq9{``<2{SZEBp4|Eq#IGjb}AH*( zmMT-iuFbVSD_U#OCEJcv%o31-Eo#PsSAFO}HAudtCc}`vv)z34 z+l7312RQ^g+Q1DHzWUS37z#|;!cnv3=2z7;(C}@PeH@;}?Ihg-7@?Y@nC*N8?&NGW zOyy{Fb4)-jrs`O+GUORf+QpI4zEz@D63&dCJ2efxCbfS`|K93i)$`ZwTkkP*)$2$Q zkH6iagu{79qS zB!g=4y*SX*k9D9DB-;Gs-ewEs6<|Yow6LfdUFN>3>D~CWjb0D}(D&jD#(rldX=^z4 z=P7d8q{x&=7WcvFa(c23d1=9D(eB3Z)3BV=-QjXGPZvcf@8CX0wtcV95IouEeN)&q z@c=lx`qAaSQ_|CH9mSDp=T;8#eP!P)60+4rnj33I$j)qSJjc_Z%LH&VtIv1R`?q|V z$z!IUbo;(@^HKx_Q*`gG`wjaUjIc^auF~`oPQ~pIJQ;tBm&5nLcyHnjHvSM!T}pq0 z)cihv%2aK%_*$z-?`F;B*+}<08<9C=n*i-tyio>ucs4#xmrYB z(@xTzj*z-P5Suwrp#?`|k*e17PtoZu1( z(k{jqlp{{=8+_~?-Un6$Qsx?T?#88nJJqHMHdUMy@JlLmxl&*4Gm>`o)M$8}^eJn& z1X4&+-^eh(rmgSArQy5>?My~N-KhE;}1fvv}njHX?R6HTw$4eLzqay535 zp4M9R(k65yb-cIiGg%NpdeVHHfv{pH#V__(t}2BTZ0xL7b66{(S@TjA#J&3`dk$JX z@U?v@eVjg+DzAkT^A%%3d$5|y2Mpak>H=0BqPr!*$cKFMOEqtK?HF8VTA zXt@MQDt2}KPMzF4Tj~PL>C?_yO|B|9p=jphMYr zX|@)5+UvJqCDMU>YhE*!mg4B@-GJI?WaPAzZ=iUhK}T9N3yPzo0Xc%FONP8gL^oV8T5n6!hh?vr8PPU&ml{rsXHL?stbkyDn* z*rt;~T(*{zN084Uz$&+GY$R zL+>kKQY=5UOi3eYZ#5-KAug7T>H3){&5HUheEkzovOzx@|LA6Bn)?H7XGt87%7Xp} zmqgK4)~z;eM^09SgTfgbQmrzEg(_;k*s+BJ(!5R#7t@h;99X;r5^VI1joDx&nl&%G z&SQ!j}DrR2fBWK~1fthvUWt6NZx@h_=-RI&zw{OqlovZ=iSQ{=G|I`vlADhga zarRVL&1+_D?vIu;@fzIisehfkB{)rs(a$7p3CgnLf! z`ru^CJ@T@|`tig-%2FTcu+;Pwly3`c+JI`#9$4G;T?4j56xcQ%J+fQw^aeZQM<}Mz zJP#)n$|!-z`Istk^A9>BRQ(t*&EYBT<>E68%T*lGcPXJI^<~4BSX?D_v^;!V36XOL z5mEHYb-yHTQxb$fVA$l!!C+eo-EqJjsjwK%t@}Q9mtOOk+aoU77Bz!9Zq-8qQX+#FS3>ei-Xd1XAXh+=d=TV5D z2uNjF17QwbGp75^o)X-Gn_)EOHfBU;l!aLxPK&oHNy#Fz)_Z0{IJ7IhiKY$H%hsxG~)@S=G~TFUWn6L!l^p1nb1;rW;*gro%Bv8iheKLXa;6Kjt(Mr(E* zgQJZj#MQGh%thsMD;m0%GBbYAAoE?kPL(p8KP4F4ao~G<5FNWB-nr8+@Kmah{=wbr znT88pt##@P)VZBcd_q3fi`9)~Z5KZar3$+iDy(zj9kdFQ@T|nG-3=<%BV%He6p!bC zWqCNH+)4+7QcreXIe0nvTb(xPIhM7(kHf+``qo~t@)Ju*7d|8DTZn}8C+z=dzE(T) zgudMqXpk^>GO!-xnYjC7fO2;XouI~Fz-$@CqI4T{N@NSP^5n`iqN+ciT=M~)vr9kA z60HV7M3>S@vrLpa)ZXK^NhYB`Q~8#1AOu!bd}uHW%Kuf~K;ai>cJs#{`y`jutJ7_9 zn2gXIC0ty8f^avTZEfm9qyJNvSt0ND@^lNkMGGy4>@A!?LN#eLf$iGZLyEoQTW6~{4t-?-J z#S8zYP`?8PWD+rj00%-$5$Rl2HEFc2`889#3+2fId7bPyLI=fKJe=E_Cs-G1tN#90 zw#x)K=W9I$3<%Q0h@8^Qb0eOj>+1ryDshe3ZrB#rr(UF&R+ZqSUQe0kBNrCtF6ExQ z`o2pw+@|N%dkeu1m>cuO3-Fz-3R|72_53c*34zWMaE_w!pbJ4aYTtqE<~~zCkF~X3 za2w(o@~`L6`@Hlv`s?R+$Q4#5E4c_jr#jhk-i*FY;)h$dfWWm9vJJK~an>_Q`f_ef zGicBz)=l^r0bKx*xtz8m!x&00nSO1weAVo8httR8c+DvreXUx<%ddgeRu6)};;s~l zMf%Nt=$jWbzr+K>`TfEnWBt{0o!G#y9TO8{B?flyk4!EO(VtXCZk)JFu6)b!3ooZ) zBoy>a-JH(X%b=o(Nv+yYD(mQH*nj-r|Lqu$eQX$?EmjGIQV2PlWJUj0a*%`{Kvk?s#= zB{TDS#sU|&@5OgnuJ&Hn4Hzqn(*GAj_F4szC|1MQv9B_P!lh3g zmr`<|?PV4|7W~y)Q^_CDx&($mjLnTFlCB-17mDfDuk>|IFybo=q3Lrb)=(5!U z>q#R@9~9EIgU=uqD2&UNU|zL2#&mt8&b? zIICCwrt=!HHKzQbX%OyA^QBRu@3Zv0Kulca51B(>ZZ8dO7fN&)?I)Ar&LNnr{z+?G z+2NEPgx`(eoBxrbxm+s)U>+DjIq_Fq=DP^7a9vXpWylGtw@J>*y6ER_buS_r za%*HwuzdKEt;_)wE$rt3fP%_A_%D9C`$*S9Zpm7Xq9x+S=id`d3J<-8h-Ly+8TdhtpA|m^xfWli}QTEu9&n+Ukj@X!=sgxD= z;-5`esZy5q8l5$i0CqO`jlau9<_mt@IcD&d3$-*4gNIj1)<9GX zp`J(W_dl+vc-MH_ACL9Af}IWbGZMhyjmPruw);9N;vE1q5eut+dnW28brr%!6TW~N zv;mx_=K`azf_qtekMIv<7Tzi8Sj`3S07iz+rbA{3ZAdeU7Ep~uNs>F7o4`xV)ykZD zb4*nr)nA~Zkgn_G(G!Aotn@os&2NTGxA3p{$;a?#Gnz!zY^VFw0%Y0U&_jvHcR5y}b~JGMN^RRw8n5SeuCLT^Nh9=}^@6=8W=Q^#CJziv)A{vljsP7~~|KCFA*U>wLXY+$j`#0c&G z#Asi0MdMZVr&92==G%*UUz>GXAUGp3?4Dx8?lgXZ>{fV@B|77iMXS_L9Ulo`)GL}D z$)v|Dkj&Eo&kg^~nz#1$A3>5QpFJcGH?3Gjes~k*=j-cQXi%Kr{th3G&FF9`J-%`# zT0zPYUh&5j43~CD;Wh&ttBB>?ZCr?iafvHune+q7On(iZN`!u_MqYSltsWe3jP^q_ z=2u>X0Fpvg$o`nZh8AD#TI~AhIwm}HQH?SjYl(&L)2E`X^a2_Yrkfi=I2q=vWntCv z63vh*p5k$o{Ty-oOn1|Fr!mN z2aaDleg0KGTu5Jvd#Z5LaFA)P zF6Zs{>y|AEMd9gB7Q=m#TMrCB>4+70hHH~E?4p!-wY+W)rxi)rBAUw2wBVc3<H^1}_C3n^-Pa-Q!xHfK{as+xAipUc$qg}V2e8XcEk1L8K1UHbiVLbjx zoQ-8b5O&}GgARns7D>a8VH%bgms(-TS0+7QpqB1tEEnpqfQ7k-F%g3wKYc7%?kp0? zobU}ZeZP;hA668i>;YnK0j87kV`%NS)eCq-k1ovbb&)_GwjSKz*4(h&u%H#`A`ZoJ z1pI_PmAjp~ zzN@#eupiYA6MiMou5o>f)?BpdQ{t18&D)8$j?ika6Hj}U!^ROFNXK(db^-Hr8`Q*0<3=*ll{K2a;S z@>H7JpC6+a4h_Q;0&^{-5$ZLUYpke_;Pj1Ly*NmsMh}G{f6t3kVWn{V8y`?-m)8R3 zdR#jQhf0LvnV6)O9BG)j%&2g|CVc+=#HQh}krrUQRDP(ut;pc(4xQ9lX+0Y_IkQf~ zS5=g{rqa|2_MpYhVDFwi*D$oqNvbO}xI028^b;tE6Tq@*YFMe0L+At$EpmSVBtiN4rsd&m{aWiN^XM$MOncFq=0wW%S9weFg=@MGczvQJ?9I7@v~5VKh;F^f8M4Seea zX+jy&9`|~3|7Lm%U^W)Lf+AEP?3IZ00|tx0!svz?tDYbFeK&ZjzoSC~PD3w#p#8vJ zn)(_MeioJZg6s#CKc7IC;HQvKvBH04zAgC$&z`Co{j3&AOBbH^pMO>1e-hkwy z=bn9l;niJ)-`LSQ5ekx7NA^-)jyMZVSuv^8yIkV#8ez~dP8F`HpI7h@P>NXDVnUnC zt^oR5jz8JpzDIakA{(KeMoz`f4^&)|+VL}Lt>?8)=nGgqdD-ya z`={bHwLTHQhAc-Ven&`$6OQANXs2KYC5d42I#~*H3TZa12}SnA)z_x4#!S!fnMj(W z2?>s0cH_Ss4*QLGo~=6KOCam&DY+Q;Ow;z)$YsMbJl}2v&inYTa+6(B>pwZmAJd)X zUQZ&U?=&~%>Ow<-w$A-7#=rwuoP%fICFmE_kw=SiR035}>mWv5R3$9<*Ol`ho7Sgt zzQGfG4^2;8GEv&}TPJgiZU4;(OR4bR24=-gqnhS6h!lj6{#y1G!z(k)@K}S^z$*Lj zO6gkse208DXKHqhv~irrsV|iQE6GS` zhaMRJ=ZDAN4t zGlS_n=W+|}mlVQv=nh-TjRd4F~d-48& z1m6Pfr|J!PgQYMp>q&#M0qwKReTdl-86z=`p7FZui|qkl*}pT}p0rxwpI3pKch4ig z;tV4C2s3`dW+--Ty=}M{b zL#Fb->aS3X`JdbEceQfiB>r&J9;|NP-Ue!U-;=tP-z`G*vKMvnfk#~JfqoVtCJ#wz z(<~*OL5^#!)c@U`>5;rY5(?0`KV7msRVVGfR?W=JB6A(yjAVa6A1B!6K%P7g5*GqdlyK8e$cx1TF{zSlHMqw$ zAxkH^b=*I=f)3<~Om&Ggp+-%p|5VK^G?0jM{H3E5vfraTkvvnc$!>`w7f7|`*pi0q z1Wi#ZZ$|J&fTiN-<=!#SFYp=SmFT6QX~&1*Bm!R{7iU0H{zB6*EDlRz-6&wyZW}NY zQSM#h5z9UXf4EE|vwQcBK@IJ!%wDt%nOY^Z*Ad^~&_SL*MBt4oBjs|3&Thy8L{MA$ z4}MGCm=7%iKhni29f8ok(XY%Wxx#E#TWhL+s5f!5O$66y?qx@!Vu+v$*=rq*Du@z` z;ur!jDRTSJEv;fijF^}W3sq!Hd;ftqV2y9|-^merc96GdY&|>urA}CZ&ItErdR|-( zK7u1A5WVcUJK7=rBWo_(1gcc3zBA&!CUKWg0p)1^5OB>Xjb9hPRo{@c#rh}SM&D!p zp72o!{-o0uAS|p26~9S}GF-_d8&w@Pgb^AlGiy~GpsM}!E{%QxuOKkdbB=b?zEswV zSYK{&@z_nO?DKXm zN-%PaTS>XURKb2*mYjwObuo>3KW8$h#=~ggc^!(ZA)^1`urEJbT-^e!G1K-d?Y2k= zb>xQDvBdO8P#dEm4lo%!22ko^hn6{-zd9Cn5h1@1NGZ&Q-#iQ+`28c(r5%%}dVpPt z_pQ-ki3U8#Z~fB#DwtD=$0l$9*=A)Yf1P~pgjr-0q=P#h8P z27Kdo+W6F`wWB|QdrRlWw3x8c;A%SX4^%*hg}B3yT>HL1h^9auxpV9|6cx$yrEd4s|?B$b_#rP(V{wS;#> z7H01lc)aSBy5Ke~Z#B@On`m}-Wn+@l^9X2zCJJ>J4oG*<9*|}Y^LH4FMwePmV;qc~ zldmP|Xc!X7cY@1QNhb*Oe)9zy{m|L(Ddg>|! z#tl)g$Q+3CRi$ZdC|Ep!h2Mvi?hz8Z}-N>AWviPw98_6z8Zj}rM#Wu^mb+$1AyxhXzR21H>GmSZK zbZK3Z{@a)*K%|-iOHb0U7-RqaY4BQ(P1xm!w7TGvGJpbr%MLX1S;gPFnJhP}sIBVy zP`8oN{m$Hdf{;eNL~`pRzp!8e1jin~KkyZp-eAq^X$qyrpssnUjxS~3I0MkKTBu-3c&}ZgQWME;Fi3Rcc>Se(Dzvl>-%CRGOi)_6U{1{ zhsgn}JY!}(hu?DFPgK7hil+xv68zjJr8ru6=uIugQcFsDvrwF~~Ge3To`j+18 zeuh^RoJ?K0W8j7Bn5%Ml@pUT#GwE%nfWgk&0`*C^C|N>VsKZ%Q7q{aX;lG%oQ}PeS zeWTF5@ar~NnSLeT;>;04urCvRNz(BIFJ8~ii7ZTl+**@+vgT^A<~2_(Bow{yufsVu za{%DusMGVtQ?MjHMBYp(2s>#=w)}2%uDdZ4Mr33D-wk z3HIC!T3~4)JIP87XXdPh@-aVXEEU{SFCk?P0^#MmYxx;TiqRsHp=GjNxTSZ2#a(pp zAEq&8(wYw@JP9a0^ZvSLCp+v~=D0IwJHN~ZOi6)d7!M~=KJPXXM>oOUFWU|w4mtO8 z+Xs(AS<*vf<;Odpg*fPG9sd7`IL|Y?w>?iB*$d?c zJdfa>Yl-Y@*INdJ7vv+##o+~Q4e^oiv*V`@Za;=kib%&W!^gt;Y0 z&PbcRP4r=KDSp5VbIWJ4XFu^|EMj8i(F;SElgWv^Un9BeGe_1bt)2 zh+>*U8&#TbnA{vEY%{>@LwFr(p9;xsE6YiqvdM!!2JF^4Haf5=?zDVJs@neU^luaI z-}-k--Kkz8o_1QUzBUQ~gIhoJw^0BCE1klxnEfMSSRB?@W$f(8^*6p&kp&bC!FiB#P{xnMSfLL#6C)fNKU>odJ4%9*;_XpgP^DH zCb9gC_(=1#{~E);2F47*p}r3Kr+~f<&P+D)0psAfO&b7A!)uAGajBdP#~3*r@z5xc z?MV_dz&Y4%LjYnbd!0GQBKJ!rVCDNJuty3T+03jN;QWEo5wY|;{Kx#k#vlAXdtM)$ z`>t^Oq$2~C!fUq6P%G{6ycR!di&NZiJj(J80S}@1hzJYI;3D8oqEpIReIP=TF*HT>#!9O5USjHHF}iW1Poh_cO!(NmQS^pr zxdcfk4(xj?u)<{(H(_kbx=5len%keLLG{oXf+00!3#JwLDqaKok_0A;P3};-AW3)} zq8qO7cB@C+dhZYgRF1$m9|}{YCb*h{*4y)DGN-P!bmxnb0ILgs)gU?>6HUtMuiC!<*gm$g01O+J$m{mM9Ri{BxN?S7Fg3K=%C`}{zfV`M>GBh z-x_Tl!_smF*1nd7`CgcY0!R5OCP-WZhKyfqVJb@#387kfz7gchi4tk=KKn>069JZ) zqG3!fhv1$mhUiLTM(1H=8A@EgmY!NJ!Jj%_ObS%=X=M< zfWgcRR?_8+fDy}Yh5Up56fI-TUBC;cIsh|A#Oxo%yr) zr*4Nulk54bJlM5lfUVFHPxbt@4V0EHA8*V;99RVXZL~3{r9vUxzoTGxKawI zmv#T=9#%^tQ0VECTm~Ufv7>3S+Rn1Nt|O+#4XA_7M8X!G{1@=+z@M#=6b^|hsUh%%YT~NS85-Nn!Byf8jmB2ms@PCKA8SL zT)kCP9MRS-oB$z$g@oYl?(WjKyE}~&oZu4NgS)%CLj%FJan}HiHm;5ReCPh-j`Q7@ z{Zymsp=#HjYp%KG3=c!DGcWP{$zW|klnbr(`@xv~Iv&hmbacS85>oo@s9MTWDxnN; zy{wBZB+#(aGJX&Ljmyw5l!bLhxOg6lwF2n7Hzy{Lb^ZzIByMLKGol05^7uQ=HNZve zv^Bg_yInYx8r#Kj)kNg`P`Xo4M6`I7B9QL(j=1b{g_A_iR^}%%^7&LXN2tx?! zAl)}giEE;lhd#AD3}0YbL>^ko0|52g!KA{Kst0}CY3}S$lH8wXjeU0y2D{~cOLv0O zgy*G_GRot-ZQYNeZL1Gh!#w-hbiagJ4MY0!Sbx_tP6kP!M=8M*cM1Y_noA^jy)g;{ZcSD0y@IK`VP{1i#Y`SDhumv76IYCrk~G zi*PAIvNdb z$>i4RzdCAu)IP0r`x4;t9>T=m9HEb^F~&x_(=el-alNMAGi>N(jPKacbfq;apG#$% z_Q3o6@FiLqz_*Gt0Db%9MG5~_xZa2Q80}ad@e2%TT9c|a1hY2udRT^;pQuR`S}f_a z>VkIZ`e!*kQ_4qMfE8DCb8-+wlzh9foH7@={G0YfvTyBfHZLtF;gN1BjBQ+>l}~A= z=qq-ZTd}~aN9Xq4xS*Eka7=y55ssJj@3z4l4xgj!isdAdPmixTQ2p%$pnOQ=E;Gy<9Mnk!M6 zfWXJete(<~`TDLk%+7DD*g$U$IfkOvncX!oKFzqGJ3p zI)uRPnWEug^Z1eAihjN2z_0Ws!{;>*RD>xMZaj=~(th!Xeq=M|AV>65{RhXb*VQMO zYc!L_bypFPFA{onj_m8FD~CDQGHs+7!eGDjQ4wlim-FxBRr!DZ-I>KJuK#oS(_(UY`7TfQ#i)c)XxOmO(`@Ljw9qwDeVf&+qpOpb znbi=gwt;@q*-IW#qP@?3-`?Ly#`tXq_ai*M!@>jw-v7J2APpCQc0{K4g*@grhXW$J z^$A~EEru&zFRSXC<`MCEyPQ_~WWViuBd8!xM*CuwksxIuz1i39vORp>7Z4){jIgM1 zl?@|ozkd5s4?msoOdd0?C{wZ>ZJNO3{yU5xqBUzl^AA1FK8lk~9oSEX!>ons+?7;4 zBeko|LUhz0R!w-+^@sHf9f)zZRHUBC%<{X_6i$!|m}12uV#e&6?xi@)2%iT218En1 zdw?sk6RmNOHQpD>Im79&dqHNcrOYz}_B*yCK?wxG)iO&tKo;sSp-SvmQ(qwf1o#6z z_vK5>E2om4=^W5x)sBoes4(vGzS42gsEgXaviyA6Nla~NadS#_%KH<5eR_)0rzl@l}pbIT)qR z>-$)XmulCzyMYt*P)DqTU9;RuXfc3Qe=8)4`twR1M`i$Us$tkge0I!R10`Y}vE@eq z;gV=>4GNwV-Wb5-g{#aYgkxnm`9lP~OgOi6t-|~y_Xm+V8^bHyhmoB7&t*{{zSmCY zy;#GSZyr;DgxK>V(t1CAn7)A7D+T^!4u{VW%hRWS@Tvf|jLH+3tCWF?C#m{~aE6i- zR&mQ5l1kDH!w@4@)haooU` z(;_ICapc~b7P=LNHg?87A>x3h+|-Tf~c@L)y&97-Coh$ zN<6*J@TE+U9cPTj=7O^B*JJW*KAwHsHgR>1lLomkm#900Y`#E(61;dn#Fd;Y{207Y zBUmlHWkPwPow&?{kk~riv&Gnf)oMv5S$PS{-^t9rPWf$UM3CGMVLB&on5RT&waZ2^ z_j@w}<%|gY51wrSD?>cR090txGT<$p(Imq9veGyI8c3K9yCNI1Q3cCndbJX6o>nS04Ga10Ud>RR`MwVAS%IIrFH>q>3hqN`CGybgCc5%8a|KSsRx}`b5KF(y@q+Ha z5pwNM!;+trBLF$GAu<6D3&)89Z#h%k76)wekg!EbkUQ#8q7=vZ@HM&?cdUZIkrTWe zJs%6D*~u2pPtDWgejc1=gQY(amp7%yP#kPk7Vx5M9NDmqpT?$;7X%gN)gx2#Nh?vr zXMjh76D4NCI5+JsP(5(?=n&!xvhMKi@T!hKgrIjc6zYHG7AHd)R zx&=$a+t5^)TEivbQdEVKtvIJ%Mi4s+Sn=et0f=mpT@M08m9W(r?5sogNx2;lQ8O0~ zK1v#;J^l^?(hRu4UukzH257Y1kZ;7xigNzdhS$)%Y{Pf$akqSFgqOG#Y5CUSuzhW6 zf*mGpFr*_ti6q%GLvTvF60CkC2hjTka7!5JS$+=l%<}y%ShY^LI9U<@1!ZG0hUg7eub`~vzlP7$-pfk&0^M!2^9=+L~Utz`DulW-i9 z?rkt*7VMK10nPBPgJ;h6{~X{Yt@9IsGBiN)HpLXo9jEw=i=6@*ujn=R=p7XY(JAUN zl9G0~Ir#dw{oA#8V(S46TzW<&>tjk6ODv2AJRc1FkHm;a{A z_3HJmYlBCP-s^yS-vvH6*LG4cYcDm4t>@9hb!e4_g;n;D95plc-c8M>20xrogYK?~ zN3eGGTLGJXe|SQ9bE0IEDYO?gkj6}nOqPFamVkPD$R*=J-rI79XADA;++YH1$pbV3 zpqG-wc!b2Z&WSopfqa&6^!}#yX-?a2EhTav15U8hityBa__X1gSV|qJEJ5=?v9jx! zxZ{Dr$ga)fpU~OJv!1R}$J%{1%?j$#p*wOb(>065M=PW5nf&Q&X>mVFAMMocrEV6n;zYa{ldNV`nraF&92mIiC)TN_7Qsz-h2m>|3f(&FmTWsdXrtS$DUhf<`T@qw$d7Xm zNe?!+sfec3&tapG;Fkh|>gN2-{iNtV9)W}dp71jw<;}WD3Xw;nU-D&aet)%!%|=s<-()edyrC{`#HYzO&^R=g z14`{*;pg3O?_P8otg%LpH{?`yY+zQKV0BLA%!(jzjA2i;$FtCS+^DaTEb{8N&8Nd{T&m(~A&Lj`VzP;vxo7Q^nH< z?!?2f4^wy*S1C%E#m9!hynnv5=Ii8mSn5Gs}s18vdr3bMiGSs+TV<95TPlw_U;d7k^P0!Go zwyz2A4~XF-tEQCAMmbB&4x}1bIDkJviTrY~dm*oOx^-!Kma#g9?(o-@An2rYamC`l_5jYq@=W=5> z{OFF0@xCI5x(J%cSXJt9;>wGtJjVEl3^gNq_J*BNZD@g2RrKN@0&cjW!j;^$GUk*5 zimatP(%X7Y)D4z1^N*W5Scx*yI!KvcKerF!PI)i{Cm&(qsz_j!qMMIS`Lm9j&VX6N z%B&9<5Yf)X)pJ|+H{!SqdivQtJ-!(@g$D^PV7ymSXPbOtMS5n zDr+p*56hjydgp?Wks{L`+w}2lxoya?KEU3a^Nxa$9rK&)$Z7DS5tCyt^*%5Adm%T& zmUg#%-T~R1M}Nd2hslKn0h&Ey_(JTGHIQXpfXc$VW+pSKiK;`H^s<_3ygJ=Iy;vxoQeNCFj~PC0fz7qmOpuSRT2=_eOa8n*9xF9|x=0v-g1eEGIvK47 z36>UCh*TJqTG3V-w&$2d3?D@FD;xgwezvpf6*c;CgRx=+rzBH=H3K`N-14WHIMI{M zOb~(<2*q0n9cxE>dcWTJm+lQdT-pTPR~ zH~uy=XW51`DMpHIy{K4?ok^bcGkXhZZ#eG3xQ$3C>ECPln_CzrH;sPhqUy?TciZyZ zxfp5dKxKOypG{Dc>*%|P)grO+8`~O`Osmf>)!z!hmCi*Re+sh%ISOwg!FZLKuDrqi z@P0|f_hdKC#l9EqRO%n|Q>+-i&ZSv2&UYMs&1a0**GYAD{5s$tfTUiAIvLO}f>N%E z%?=hu`ff&)IlX|BCiyJ(7zF!yFe8z!Y0&pzXDEjtfaz!c$lkNMGS?_inUQ{E?5~D4 zBeq2~iSyjw5^JUPlm1^r{D)TNAeV5ae}j%w{iu~)`W}WBGsx56xo%0Lor5$2=rr3m z1G!a<&VK-di_t~E&1!>}dUK0py|$RvGjw%>!p3IE%oV6@yQQ1-h;_jw|9oe0%RJQl zj3f;*BB$sO^B0lw>LEr2t?fTd6i!!enyQ=@`S?>@-UHLCB>;%<`FRCUOTpji$Y2tnG1{3?qeHfo@+3J3*>sHg+M3r0nd)=rDQ5b~ zY+m*`-jcTyhktufi-qo5e86P0JC(ovFlVBvDYF;yQNZu_R@FDpKcFgok58lD(3d&* zFy?)DugkD6O&IR!53mk$g57q4Syp9Pn5q+5g$AA^X)^;NANlVc@}wzk(pP%7i@b~8 ztGF2sdM-9{yfFEA@+>USKCzH>rs88RMo7H_L(!w|*eki@mDw+N1DO&uBl59N4cQUJelhhA_#k@`Y*x**;hwBN^n4%Xi;pW5 zgl8Cg+yc}y0hz?mGmGP#wmHqFcsQV2WjSu(_fT%Fbav7!`zNhAVPW3J#|Wo4z-zaA;+8LhnKYC9);J3W?2%!J z1M)EjhpXHI^gQ7C!iN2g)&qEP)|GAP6H~i)%5_4zyie9!s}Cc6zE$gfEoe@0htzYi z@X;P%9`3lM(B(e8Ml1hi#?v#eubusgta2;`Gzl{lHw@Dnyvp|%x>5X#Xfs2PEGT37$^pUhr4&&mEL#T}mj--vM@l(n~U3;qs=)28C9a})TMA7Mj@ zG51-{tn+v8ZyvZKUhB`sd0cEqohoH<7sEv&-F$1;`^fD&#df_H+>B!^YrjF%%x&Kk zKdSIFc3Vc!4}z;hUHsub$JM(yTnY);xR&oxD7kxn_m_*RJ~S3JYIZ;|XoP;pE+9aU zvd88~IlQXkewRiGS!dmk-aW; zK}aDk;)#x?z;_{6Wtk2Qi`M~VGgGm)ExulgbJiV*y5$b@IwdM zc!&Lhh^*lGqu*gv`F~0%FPHuqikT5TzE}Qr5~I_Y`$|$wSx$C=doVT%7~}Zrdcsy) zvUN93EIe}FBS@*Q89Qy8v?gJe4!GI9XVZ^tCfdBisFM03nM;cCA$*A0Q!P^^m#zX6 z$A+Cj1Xm!#Rhv~R5uX|^dQEK(1jso?ca?$5>c45C`+cmU%-m|ylK;L_4ROFZBVRd= zv))mVm6h4e2t<3tX|Dh2fXc9Xut@`m(ko}2SQ?Hv|7l%yU|`*iAc;~Z^8_J_GEIH$ zpjfG9r6K8716_oYH#zRU@O8eoUOBPplQbZf)~-$3s;|^YB9n=%+=~R>GtkAhb9DeZ z*94r7+LOZFv6?*}FOb42Q*Pw)y|81UmLz3n12}-oS!;#gRRJWjC64e&qvg!pr0kiA zQ09cCQs$P!ucc(Gd4oTp*Z)lovjIGRQ&!PxEYC}5z7ya4E?}vOrA?k{MnO*$N+FL#$nT z1p%{-P$lKzOu7OGfGa z0nGfOAlXxhQ`y3yGzDwRvnfg6Ok=y!p0(f!yk(ISPn_d`)-WWe>)&wBb`a zX{exG+I9n1lsL7Wwjb{9XUg6o7|J+Pps3wC20&*!c zz6_23SXt&ZEIB?QZVX6l(x6WeC!M%iHc@OM34eRlQ0=jFW4me3G|VIu4NLeg@(b69 zR%4ImY7R{JzFPE?Z=~!0nlP8x{`v5!ujsM<(mFCQ^wlV6ZMNFR^;rrbX600~hED!H znGFf3DgV7LV#WMIk?c5ltymDF+ta_!Rsx4xVa=E}3pFM?13w+ZORRI?;T1~qeEg8|#} z4ua&-Tx0aes@XRsf9f->_+GB%{RpP_85xoHItU*f9zaAdZ3iHGzXL8Q9sIcZuXcW* z9e#B5<*%%7hUVb%&aUE8+gj>#+XgY^NW=-NWo-P(oOuAfsYXB}LZj{sqB`5xaR7J5 zRd_avm23cK=exfGRl|cbs$bk;!IQW!FX=kP?)T`miwaK-2Nfi%J&315s38f@?ucob zG4Ig94_)e$jbM=b8=`rL+<4*VyYSKH*#t4!J-lqS#$T8UO^zO?GN}@mk07dkh8bTl zzT8P`QJIyF1dLO9YG_wX{aOnx3z$4gBPNcrw|7B-eGYT1X6Zv9nU}K~L5eap$L1Th zO`o>Ux8R?X$GPvZoHD`VE&Mp|tiTe75O$Yv&_EHjsRb5((Y6WEPq6uU*3**1+50)y z;};G)<4qc#A(7H69adP_up@RZ9~*Iw^sktvBbYgnSa;BPk#LR}lT*xxM7A^&MJb*e zpFh~N_hr({$#W-2=*pN`gsR&;ch1<>*=0G1S~j|=UUXkh^%KLdeCD~17fwm^WXX0+ zA?kX8jm`_kVhjl=(zCnqEKQ%ToO&CU$jwZw@6{oX?YI_Nrr&F`sM;p_u+(mDBgXxE zhBkU#Na1V4nK`~MYHF#oP)$^WZ3F$ zH^Jm2I(l62_+|$jWh90i`S*$C5f04bw#=a$g1bvsBcVSx5jI$I)IUA-$i>r{Pj#hc znFsj~9J9f>NOd8}o`Ms7GJ~$jF1&Ys1bma*9O&pg-^&~#COD)q1ysZ9g&e!5F>u{6 z({y`_^M3Q%7FCdv&SA#cOHUdm!~&$Dp;iGI%f!SiNEl=|CE4}DqRJrIrg=zu; zobDp!iWh!PY>Y`rW6kQ`{=a28u@J!pmwC%R-#MW5WKmg0si8YsYCb(3z$lIL#5EZ` zxpIrYCgNs&pjkcXv~>9?RhDMSE;Ymx?v+VZs&4`B^QK6@g{OQ4fB+yM^d3oV^jaLr znn{(&`Q=CLw(1g`XENJh51R@uXSwvUzQ}}t_koz@i=!H}@zga}*EygS1Pky_KMn)Y7FZ!8ct&0B9yvUcL zQo8;rm6!G^u|Pi|^f+-3e?sn@ux~;4?&6-j$kj5l#MB%FOIucl=xg=uo1DaRBBIF$ zjy<5443f@9Ry@3eu#`SV+CD#Yb^S4U`eY^~+pIj$I_ZoPw_ogDy=?KOWng&DWy&(y z^j6cf+r;;4lH$f|sts>}X);7k@)K!SfH`hsYuC-(a8R8d9&%vsAIeJnx}Ub`Hi*HG z6GBlD^0R8mn4!+u%c3YwABU~bP7nT9gNOG)=g(WOC{5@_wZ0Br_0Q`{%~L-A@qUiB zm-F<0I!_~hYA>*8KO!IBwE{xg$Vjoao8gOt$1X38vmTxQw445`MRjQ?PMJ{F39H#) zeBDPgpyS$bvDvvB5`vM`n_4+eW|b%^csWQ?s0YP2L=hy*7gW*$HEKPMiz$z&Rp&yx zeW&94ve175%H+TLs4+)0LxP&L zo~|~$zmh0wTvG#`HGb;~GryNDkb7J%$71rc^T)THo+!M|;+X|D_hTlaK>xw(`>jEd zx_vBHJ3Je^xJ-?+abkDC8Zh6q8~DX?VX16V`+K%(mF0IQC5jF>d;? z%b;F@@Ex+(aNN>g+}_8SZ5a8Tt1j`ZNS!*KS6_%p9HqGcpE%2);zDL%lo1ych~S=t z%8^2Ry!cVZBCV1-qCN5^krY&RhCVX+1gew|@ZE)d9^NriKsbHkm<{6(Db#qyx$Yw6 zkyEqY_Iy9GR{Hm;{#%0}PTjhu{*DlvkSjH~HbSi^rT{)(J*`1*lQzCQSU_ai{#wvL z(;~P`_8X}aN;&sj0B_%G-b=G3Vn>Pw4cQwZFcEegKpGc_%CYm=Oj&Ew{Ar~AuihwP z)qFD5%)WEKj&j%FuokzxfUCuk2ulq zF@g5MwgmA~d2-0~pToY0zsviY0Uv+WlZ(qhD^vmnteY$(_}xD31OYedPO#q-{5|e+ zurHdJo?`iSyWO1XtG@{`AT@_WQv2a!pChmQmlEs6u<;P6%OZ!f`F8pwEhfzsxLh_$6qVq&)~Ag@kA}%hm5RWZ2?tI7?h#>) zMVj*NA1tk|tI%SjbvpQ*m8T1kyxaSFb&Lf0G~1eo{Xpe{Sg$n03|M;9pYMs{c;U^0 zcGguic**bgET_Da#%_>J!S_!u=i~-09bSzDU8Mv%*?;J&J7okP4*eplUpHPaEYVX& z#Idrea$f)bfIWL{yejj!JY$(T=G91@crT}nvh~7EBFuhI$&KNLM+rCM`Xrj&P3g#N zn9EK~>O7+bFO7TFjnYr{XR_T;tOT(O+O0_`9yq!0vAblOzf&J!F(c``X9=IvtM`te zT_4sYiom`<7gwe|WNA^e5>-t#_IJgWRMXz8p9vc2>}kmrf%_i4L-0b*X>8E1G05j> zc<%1uRODo=wdnf!Z(BPZozkf&^Mi{0G35tv+>(Xf$<=}4bDK9B4oj9K#mDARts0Iw zaxLUy^&&6RFCi<|C(xsjVkSE1G>h%6b2eR&Te{yuG8tRpYeNqz0-4n3M9(Z1SDdJ9 zK%60>TOxz^q{Bjy0cp#R=lqo8r2)?vOWW#!X;`$#=9aezfrt<1#yRR`Wp@Q>h0LJ`r|AH8GWJRuOD5>T@Suxi@Tv$qpjGeGUs3{CAh)4>f3HFtLJ&rffoi<-F=GCi%ye6<7THt-$^L=D6rWP;lfa_4*OC zp*vLb24>hMUp(Jn2LJbb0DA;U}f@9 z&Pg)vnMgS!Cne3_OnZGh*;RrgI67TbeFU~SrZ=icf=SzmUrhtI$r2afHeRy36T zs4G(r@yMRgQXsi@k*POj^os1}hn>U5 zC3;-Y_>Eif&LgaYoAs9x(~WHAo&(b2dpq9SDpg6DalL{WH&As02G>$7%c8QeOhnj@Gq`WLZRvhE-l1Ol3B#4yR(pD>oHm* zoOSz@#(U;#eHm}<*+mO)KC{aFnwCN26Pek?WaWX0oPE0!(g~T22B-C$d|$X^yN#=h zXU!u&qS)p=rj2_AkL|N=h3f8$skNc3Uz2H3x9?&AnB^%XjGL4@9xAw+Ag1 zHGJgVw;lGa1?3C>`;IdsnssaLSH>RazXZj6FSWZDahKlmSp)!r*VURM0nc8iTPs;Q z3VhCzv&+8DeRS z_nhxnhunCgPmSM2KX8C!gMd!1}=>Rw= zuhIyp%OoH=h4+~U0{LbF|nMdi;PB6C0s#oFPXaMZh-6`n9rr`wuo{w0Gz&_%<3ioQ2TatR8 zKZ#J~`r>g!nKfqYq6lvaF{H0ldv0o#`RjU$i!+E{f8fatMexVSQo=amwDwgn4x{VK zbW7%9C59MfDB&$2Wx&wsGrPiak+K#1O%tbiM>!kkRnl3G5-)hWFW2ABb>~=geg)Qk z8^TXy2s^N*wEk)NcIrRG;Xd1H|H07apctKJ)-8Ph+;d@s&?UZFv2Lt|SV@py%0vQ- z+*ylyhxW;pjSzDLSz!OqYS$>TsUo3a4Y*PHVjgg!r_5T?f8aT`nqNo_0BPjRbZ{EL4*@}6xW2Ppvt zHP+4LQYbh}VR9|V^mfRMS9f-#7ZJD}$&j!;HjWniO6M=6U*rhVt`Y8_FvW zC0_NHm&m5+>#^bT=ZgbzFjF$VO8cX4Um^T*h6QgX;6BM{-F&&kEDsb`ZO}*k}sY3Z4d}WUpE++ zfW4w?wI`#(oZ)cj!(4zq;rn3tcwD&EsZ%>$?8T zC86sV|1NGJ1PRp@;i9K?-A#0XQ$PNf?f70ykMRN`k~`3T#olbrbNSM`uLzj9_VHGQ(IdW#Lb$JdPYCAM;d21<&vI*UKNq=L8111QwV*X}Xfy#Jf0T8=_pv@S~@;;mSXDQcuazI(XkTa5R z^7rpTi<7zneL`MWvTqy!k>1Y}SKTSBnq4(W+Op0!9uM7y z-KJPakX6MXdN5t*Y4V8FG#jdR~wEQB`h$5xP}9HX zvz@irrg~()pys}J;8xGRH*b>)%U^Cv001M=<1yo34=>K;MDpmgezmt^dE_K-G46-I zAnr93o!Rw7i4xOKz_no&G_%>f=c>uFiuz`{TcetRWfNlCtIPiuit`VD7bHR8OnIG) z7m$ocPve?LNY@h~(oM8aqI?s;@c!6Olrn+MQDDh+${^-Lnr->_>|o2sAd8*@6wM-l zVZFYY_Z+CHQsIE8e(!voPgH0x&G5l0+ykw9^c#eF--K5;g=B%wCtfnE+uR;}M}s5l zBKxjFo~3KtPY7fH0Js(UuC*LU9tQp}H%2RWD62U>f)@wuwp&r`)30j-0EkIBx{E(O zyj+mz1s|O^iT2Sjrv+IzVeQoi4;b_f{P4#5R0GkLLQ5YPsxcx29OIvZkLy{HQ>*Nc@>(y#{BFhRI?nxj?q@%mD&Xm4pE0aqczv2gH@iveog6pDqg>V8 z=DnwW?8tvqLsKxHk-e=GdEu+nWpz79%W|&SKK9RoLnNCVF~NKk0imIHJYj+%LdZ5#qQkMPMPIWKXu~o`pVlHgJR~XA|0tBXSeuY(_~UX zwecX~&OVhv_)VrF-A|RoW%g(9oG0ZE5<=yTo}>cyCSUbAhuwjka9%N|S-@1A-jYcl z3>aOLZ_Rz>4#K`N5X3CVWU%Pu#z-d+eyDeN*tGD`SW9Kfw?2xtq13ng!MWK83~iAsZ`pYUis`_zf%rVlB(cvMIZ3jN9pk|Bs+JOR2m(9t(6HeA%b?FfCy;g!a_)nkXrF$)}(?@t=+rsCjSYxH?u) z=$5Zr{51!d)(QFXs)>me0@GJlzij97J4dZB{4a^}e;#1y^QKIG<*N!HASIpT(bivf z98y2nBq_;h>!XNo1CXuAd_NomPut0yzNUT) zpZ$?!kTAM6VjiIp8ApDiK4CTa38I}D+7cS}B+ZedZiYE=l{yge*Wwaq60rJXcr2Z@ zo8XGTx`5tBB1~fMN|JZOGG!**w&166rit-2ah&waFL7%vc0;mQ5BWC;WN9XGxpJ{^ zEDtit&#?_MMDf77jJw?6DV!AECICMi| zk^&p6wZtAw5u74JI#f9jTS)LYogyOZoBHlT0x?0dj+2vEP0yHlos>Ff#|vqj8x|Yx ze!&^Hx4OcNkCS#TAl2bNTJ?P3K=>Jct1u3#4}dX{q~`#gvcIF7SiNO5e=TmsoD(Ahd&BRoW_GOXdpAQ|TBhs5gf+QAXR2K9b;8kC0htsa z)H$9gM6`hEgr2RQ8r-m-eeuRitYP%Tt8qZEB{Me%>AFp&=Gzy_D=#nT@to~JGom!m zB{}U~{92wS!9X=QZj!?jlMy4rUp=}N@X0nqx4s@79I;Zhj~PXls6Q}lE76rfZIy5A zDJW!xIz=<>z?Q2uQqPj{PF|(Ke)u%bi7)}$SI_-pZvW!$mbWBs<37?S-AnrKOX>O$ zYtsp)$~)07ZHi57?x{^hi}Oe^`|%Bf8Nl7r^sCvmubL~QYe~IjleS-vjmOf3*F^y? z)}wPbMq1o(yNnVYYQQ451}WRI#wJ=0n<)r`hSv~jcNK$P(%(5y@_L|DKv`YKch#Zh zR@nKoH(wXyGkMa5JH6uT2vJJ+(^&V!n8B`CH`t90liwRB%wj>N!e_LQ>lXd4SMmiE0Na<;ToF z>p`|ul=e;)&&8^9*M>kCI6{`3`CJR2EW1p5!Q0fV0)*JA@+3ub5cSt{q_NVfYUmQ` zMhZt($KI#{2P1uc-YL0B9-XYzx!rk_ujoxo|D`v-lX z{!go;VSwDV`nYWzFQY&16w=O54$~eDlG2)5g1_Iaop6+0k0T5ULyB?+Eqd7T zq|zV!LkINtBg2M`-~QABNh)A{T^C~ zk}Y2EO)17ypnW3$a;9@O-Vh2|{@!DD8F2ufiTK3z{6QPP*aWA5tXR$du`6?cv+A767cE)166 z$)+5`mV5)e5hqou{9bmPNd)I!*UGOMRA%EBIaodK98$!=`JzOLr#zP3E1Q4flsU`_ zwg|nB&2-~IEyl5MBfh;ocj(LZl&Hw~YbD0;TrQ{|PLMqjNmY}Kv1R)|bjgPBy=@T@ z5-0q+l1uu${jY1{>$0o)AJ^H|4X*68Knc_xk)*30A@4grYUB4J_Rlo*w}4lS$FR>b zriMZ#d=2yxO$_N|NBorxQwU{tm&tFp_}wzm9E8B4sPyowk^-{GV6x{T zpLlHuYGaLmRuU%2p4%7cV5eaAeq4d)Bj_C$h=o@1PFz25HNIechx_$o*1?;2)dY`* z$#j9urp77j6U2-63i(|EH1%PKjnZVo6hBM%=BD7eGz#xNl85ap4CT3Dp`w;3JWMUg z@QjWJN8O^7WpZZ(RXb>sKZZx78yx(m{|TLWoh9p_z3F7l9rP{Ws0Bdbori8$nm{zE z2^j-&sDUD8Sc8sj`#Fve;ie$lS_AqdP)+ICtr@@OP&@em2r?L2aEobbNC?jA3SRGx zg`EE4{rMDV+NF7nt&T*qLL7iaO#e$(Ouw(M*#&i%?}h9*m( zQgBm>Q8yL*BI8Mw=k3r&`IY|i<`&*)5OBF=cPH#Yvn)kdQFM-HxKnSdUrN^*?a=4pIrCmsh;n! z&iICe;4Sj$Bwv>I<_uf9xu!gZIqJ~p1-+(zh4!L4IUMA)p~(?Svcz!N-aky<0TyF@ z{IcH7F7$q2Hu1L;)8;K6>X&ChqiFu|W`jdeVPSO50iEg*-0m(W03Xl$Mf}2OEczHo zvCP=V`yE}n&ioS80^}S`?Q+VM!;mawlG_^F$axM z%2Un%3xdYuOow7db_nlU-y}YYJ}a}kHJkSm+Io7K&KCa%+Po?+FImE|CjX*m2YHPq z9vvt;U{-gtQG-8?J6?(MqUT)Z(^9mW-EsvR7GX6_dlVr!c{$_(X?=jLG+IkF z^yAIJx#CWlf+hN#>&#u2hk?Tr z%MgGL(OaZXaF!p^eq=Xq+&bgDV|ldEQGfs0`tql0D<=CdVp7G$06;qM<*q9tKw`8J* z!N=|4qR8B)V1H1jK?+=6NE1^Z^*Wne+Grw?z z)ZZ|PKq~I};s$<_CArv(?)HXU0(0J(Lx&S1k`t1in%ebSRscmjIwWxl>P2I)K4CfkRSu+@8At(CfNarRK@%ncr8Bjb1CK$&#s%& zpR)|H@g(u%6lL}9U*(4e)K{MRzKYi+Uy+UllBcWU-W|ej{qnv4l>6?dXyXKB?qK6Q zR&nxOEJAdEZ#upH(AXWDrs37*a9mqtyT7jbrgGG@6?Fe#I+U@4*`gSvSMlE|3Srsf zM0xm46_qH$sy!!3Bt%xW$HANX1;P*+B+~#g-1!r}_PZv8IbF_h#Eu}rs`zo9w#x{* zT|dD?X{Ui7niAc7`@1cE`yVb%gmDVExm_bLf=Thi#$d!yLyWEOIU z?MBsbwEd@;Kf%Y?KYQhuGsG+2#If-Xjc=;mO}eAf&=J~wET|YOw|z$pcdS}j@*#?% zYptLz>(mtUhZzn(q@XQ-(}h;pZ`QXF+1R?I`H(FerC6EZ_qyh+e6)AHIg^#x)WI`; zVD^KpU+CN&Q-h>Vb7J$E`j8f%{#XXsf%UbaPAsts?Km^)1I#DN=2pDM$7ZrQbc)CQ8{w|Tc)(+84wdH!8Lh_D$X#jTit+39mX ztK2>A&$@0rS@8YBr^Z&weB(;4VsRvSh2p~tr8_0Ma|9=awQ|~kJE0Xj{9;A}!-6=G zCA3j%@hqfFQk02DNN_AIZ6AQ15+cGFt~{2P$MQ&aELb(+oQ;MmZ?y^&Tn)S+O!uK( z+I#V0RnB@BuLIu1)`01UaSMELi|^-YbN#pKNTxKmznx}w!X550%9GT$u_4_tn<+ai z#rD@Hus_z>z7@ZTB#RK8iwFNE940dUP#^HL=lL-`eP8;9&v7E}C!obVkf7-?{7=-F zV8vOD{z5~Pp&0Ph_7&^d!B+@XZjZMa8SBcMcWCN$)C?IB31~a~5A<&hr>NouN41h; zi4ag1yI6g`m~}n;^2Qa73a7Q7Zs{U{wC8{jFr&CH;*r#2pJ8j^c+WZA!BZ>_^YCq3 zJZ1S656NL4_Y~O?xA`%$pkx#otVog|@J0^PitEP4snb>Mjc}38jwUu-(`*=GZn>@` zJdSZy%BvD>X=lipEXVbP6cfyG{L!t`pkk|>Uuy~W^i@l;#@k?q==6B7gxjr9=yBma zy4{7CY5w*jH|EJuCMZMWsri^>zv?D9=W$=e&|Pi6ad~zB2^*N)+o@vGMTBsrrc>|d ztzW{pV9`c8aQJxPItEK^cpwBex*0_t>}O9C<9IEUuwYXs$|IVZJVPo3n7JI*O0>Mj z_zHZtad@S@SXVm+k205-t&9?S!-mBJohv2Ij^bDjKaLf2rkuIV6Bx3M(jjY z!LNcO^E60aj*b6qu9WrRGKx3bJ?!hEHTS<2ztabd63-da!UH;7*$*~PKeKA*@T0R01s zh`)OvS`L;ODA;a9!Y3;}?BvNgFUhw>su4^#yEM-CUn$K#V&>Nw3m!@{2$IcH;^0NOD@o9ziI=8ynf2Q9 zV6$N>mV#QAYdFOHvj2KfAYWxG;-J75926Wl_15=CX;~oQswe<5=*vLP+ljggcqXWc zCuXT$+~g-)L|a6)^YxKP%&^n10Lw@{vA2Vw>f{)=Y*Hp4lG35cAHP1_@Yi(J=Bz7L zy}pDT#C04Sz1Xd;1HcKQJ=fFu0>Xn$@`Z9Ja4H9)9l+h7HR7ei%5EOyE1eH)NKRjE zwSLj3ap>-bR@%h)aMd#L+d1zf zw(_6M)c-ai{~r&~1N)Go6vFJL_w+G9G(bWjfSZG3D5vWHx~(1)(D1k^puz9;A1}0j zWyHr}0HDsIba?sv;!B6nZ3pC3ZK2-2)z#?#r~RzB-6YM83`(7gwk{c+Fyve#NVhYC zKqI|Dm^+#E(8N_2^{(rFmFi_@*`I!7R>Pl1@(JiWwe5r&dXA4@5J^9_Pw~f4iN8Dbhjx!xbi*?~ zeJU{6Zou?TyaSPmu>gH|s;t?xO}To!BLgzihU~F_n?I;2G<|e^Ua(gb3=W*CtI*Vb zX^G?R%1gl~949T*+|0k%|7|mjx|^feIXf$Ky6&musK4Sh$<0LOU=vqlLVD_hd3T+i zMMia(4#r{6kOf}?OMMtpMWTPci~L}~&d##YYsAD||3=rbYhNx9n zv&e5YL97C5nc>3l^cp43(mZxtiw};5Kv_$AX`(o-m~aF%l>Q5Ut`A3%=qJJ`8EX^7 zo7SOF`-!}7Kl*o4VVc0X;5Lo`Uu_5QOE8btSyT?OZHv?OmYEGqnPS8gfE3poI~?zG!XG+6ee?V2RKy* z#win>5S=8A^v1+==$N?LhT*n}W3&9{bVKmbr-^?bv;}-Tf^A*5oGkt@<-xh?~eORkkX;XKZCvA}_OqWn}f1FAkRYeeWaC9BS4- zStrk+{T5=_RsOoP>{>tn8C!i0FRXHQ9Jp$rB?>#DR zt7x!~*7)kyLT91A{r&<0^*t06Mg=IxnMc<7Ed73C(C7W+SBiy2nrR_q_G7sR=yK{* z$Ig2E9Lf+HO(T;WVtQoXUd`BzFV{aQp&QUrG_$9N3_Yj}XvF}W)wZn5eb^=cJ(mVT z2k4~ZIGQ=PU5v2ra6;hLJ`Zpcx`YprmZC1}-|qfVuF_p~%yp(88IvSMv{~7$ZDhW; zd>y-4+d0r`Iz|0(nq2enaFr)2UEBG~jAW)h&}`~2f3UmR#0;WLX)=qWJTK(m1cN?h z|6bQ%2f|u%4oF++0ygkV*Qf&gH+ETVcyQ&dxfub&1SG79;s-G(0$2H~CWpzPs@c5mNDqr=$ zc(q$J=s5Lujno&@g?=I{Jw_DwNJn?hS1Pq>he&uUx@$CP_;+;->&UO;HE1k^Vx`WA z-<7)v7VhM1J1LdETj87E zP@m4khCN@9+|VQO+0@LGOZgVd#Q*AEzV~$fS92eKfR;?{tS5Hdazm2gg#Y49mcbN^ zN7@1iiFNTJeFe{O=S`_;J}|b?eVt_V9p*e9<)|m;zk=K~CFQkG3frI#RT+ zp5K&JI0i({PD+E~wVyDBXqJ9KxEz%4gUEV(vAYdH}WqMtbxbw#;$Q z_mW*cPXNEnmYp^KEEj9Cs_V-{T(85sdIz$=Mm!kn(JLzN33;pXpA%VFWX7hNEbPp; zfsx$xWyM>+y9!pN#LtKPC71MqsRunN`TbOtdE$CQgqNhYM_>8@orn45mD=c%7R8p) z$3b^ltnuFB2QdZwkKW!GiI>QKPiM`1x6JOV<8GpUtz#GsIn!kET+}^(36ylOYwR_+ z2>#ZSvW#}185LYRC-`Bl*&T}J@rLX<5}!N1aweto)ry;7Dmh*yJTiLqS&-rx^B~LN z3qLDU+MhjF2{^rW;k@N_Mu2Gp^&dIdnc@h)_%K`4A6yn}SWMn&4y6L`Z?N<(s*1y! z2u7ub3gBLlK)UX8f6a>JTEivneUvniYmZhq)}+LOi(Jc;bo--*X527#85UeMj`V!m zn0$7ZzQj7t*Tlx-mNL(W)HI)EN-iKgDe<7z*ac#RlGmg>&=7HMEu-4#&Y+`@@0(?s ze}Ac^+_L0USTw+2jA#tHta;TmO&6Z2sAz>eKQ-)$0tF5YGN5g3NUVjYGxX<`h4toxepf+EAGibP(S_ z%9S<3>O3-YV4%0H?%((KM4(8cl_>YmQf{a=h(IBK&6D zYQ7s4_TazBYOd{h=|ZnaGHF@L0-N#RCXRHk=pWhJX@l34hH|4osB+@Fe6pzjXM??2^a;1I$l!jwtA!a>n)f~nLA5l5-hzCyZfQqAKv<&yV2KRLv2^C zbEeS-^CeK4v@&@N^M&N62Xdb|KWPpCU=8)>G;yx`hT8p5D17GXX3_#5V=$!b%uy>Lm4=ESf`2L1?E4~P!(VO)UY1SC+aKkv>(?Vc#RIb+5>LAd%I5#ukp%hPfrxpOMHdGGSMR(}+l;P^ zJjbJ=PSq-M5_9erOdjj9A;>gTSH5Gh1!0bG<6ws*%F8r_01x6E=*nVS_tAJi zM6RFEr_b(Eu9lHm(ZbwS%fmS6(Go5#avTAK$Eo@J{t+ozVPyg~zp~Q|a4qHD_LC^W zuikpnz!nP%!%HeQ>kK}(g)@NE!~qVY$KRG`S$JrVhi&w&T5EBL-%M!lnn2%3iqza& zTv&cAFRNt|7-SwvB)14WlwmRD(dRfT3TKh_YIZR-* z{NI5iLV02jZ_~~5zk`xMS;2XiG5?R`5>cJOy=l;d5Fy3s#VtrB`e05UWl~%l-aE)3<8|Mx67w&O?ZPrxhL;>D` z5_HOQd<#5~@{>-&t44+~!O%e)smc66CD7XjDKto!!YFaA(`OT*=E7+0%oxLXjyBxw_QzWj=^wW;i`cs03dAuIt`g?v;4cuK0DsC&(N3Ak-kD!8FIL)19`sbE2-YbA@R{ z$oyzUqYQ&^X|-SobSLoxyvv|~gV`=I8!TA$IFWtXWWTzRd^dj2|8&R9h9;PnVS8aJgLBy4=?ys%`;1bghyqMo%s0QY%J2pM|&}+;(evg z88$rg<7#UcfffF)SJ(>k-`h$V?xRIR2^l359r7x-aj4uht2o7mNL@?Wcvq|u<(3Sm z%P2%JB5rU^9mT}uP^R4nmohrpJ;;{$-?Jt4tTkLRB1JN6hlX1c!m9khQ?`gVu$Ssf zPxPY0;T~b}cZLw`eu@K&ivk0kk5+#hkbAy}3be!E3N`~79U}E^`f)TkXO-wA<)$!Q z-f)rGypOH(QMClyV`R2>QGo5}+{R);7w6RyIJlY+AwiCwTyJ=#&0a2J2ER&>AA2pg zA^OwNl)CSZncd34{?czWeir%?z^8%n6j!7&3C#`^>C$EPs|)a;g(_K2&zHAC?QLKJ z*_wDbxS1-%pRY6J=*y{kXX?<$-u$XH@VeTlM}O_(x22;u@ALfjw@LeJ0snvEEC4{r z=Qa7gcKB(s@ zYD64I!H>CMub$-C_wW_X9!WOON;0jgJeGCX3+Am%ds2GnZsvs;Fgbug5%9izDe=)vB58{Oap}QRE|G2 znS>Xo(bFk8JOI1Ht+$A`G$zetz(yM|wb+KCa1WpA)ZKdPs?qOZ?3UBLsk%A`&&df# zO4J)_?N9@;#T#f7(wPc?z{lEcQs`p6V zm(o7EEiI8GaGIoSgo6&0hG!(jcguPD+QarSvNuLy5-A=Z@GKTD?q5PhMivLbI9q+L zOq8!-_wwvrwDW@@y$v3HEY`tACt}_;B_JBUnVJeXMgXo0{$yh?H2H~)wksxWs~dqD z(NPxnev>Ltrt4f@C7>EMvW}jY^X!dwFG6w5flljD%si{2+HZ{YH+(|q^#`f#Zk zV~r1G@e_v_$sv;!3F59_F;ygDl_yUf=3%si*1&lBj(Ww|!Lm)Fia?*vX;RtcSl@oatlQz3c(c z8@PHTAjk2&*gi$6`5YwBlHTMP+{b$7W+K<4|6j<#|FuD5@;UWebta6!BHN8ZLGE%T?%rL^Li^14clryuQ2Q6s*=NTW z-%TJznVz$v-bzOQ1vV7Bxf-TiyNGeS9y%Ymx@w(U*}kQ8nq8xLoL3wgeb{bv+jt2Z z0iXDMX+7;c8X_q}b5rA-`1HX2yOmn^f4=d3{1DFv%02G!_XA2ibt)poN>2UHlYlNEcat4D zg<;mUQ1d^0TR{SVClVwW=!F-rHClJm;Alr3kvQE_!xm6P9HE8fbDsGYLsSt6gaZHZ zifPf`pCN2_vY-}0#yHSu`U|~-XaW2o1z-y9x0(evUf)3V)1~kVGcCsaZS_603kD~; zk@TtAc)mxWSfzie?(|3k#?-V4wec3A0%qQ5;Jdc39clC}iVPA3P`fXzl*;J+OjG>q zK@P;Z&k$KG zmVJu|4b4BXyE6?#V#$4kyTCTRnQSZE8iEd7txbOnQfk@Vi9|l2{J~b(vnT?=TP*QG zijqG|=0OCdKSbA`0K$6jcVSpse}skNWkV+-V2M@>bor`qk-33(J|&|AB|d_kbiY{j zy6F<9$m88RPJKlB!Mx~Ae zEebA-x(iSMwUFt>8_nMXY&3MdNlUnP$jws8PxEk)*L$SQtnIQ9?@SYAMS_FN?IHZ`kHXUDz z+znb-$r=BntKgvEW2^-jjdL8VIJh>Sa?}@Q##0L;ue^HmN2rKjtmXjYpIHWY}Y%z|6 zj0xj1{51sUVwBj1R7!JaAZ7W!@_2iqF$lve@Nb7`xofEhGEYWX;e$bW_-)>zYwTQ?EKBA` zQ;lUg^VuA$s7vp$>F2XdP))s!e6ct0%ayL#GJX7lar5Y2sF@qMgmF}m6?e*P)qzu- zDuz30kdrJeTT#wF&36a&IUMYx5%D_wH%RAuCOv(_*r@TAwKYWZY5Y~kW5Gpqw@=#g z*5T9sXjd)|!c36`H-Ns_aQLhimyk^@qw}Gr^+lL^Q<=&Tp1Ff@ol~qlfEU$UQTBGf zLN$lsmOs+M5=!R=;U*I)t2_h(Eo%Zf0{)Y zQ}({O$8x>R8P&82BG9I-Yx>u^eV@L!=D~~Vm|grMp{g}BPV=iWS$7hvtPk)v59ywOjbf>x zj#P)>%SiBIR24Bn+aqg$V?owuC76sUD1MzN-QGz4iWF947)1uZ_iyUu zltlHBm^lp+eucjiGww~4q2D5B740z?fSG& zj5;kl@5;@bA=_#(c2#H|%O0V!xi74p_tqJ)4!NY7SFOho&*6u0fkB_inJ|U$O}6bq za#@S;%(#&jsX7ViIh1}_Yg=ckGrQ9ok4_E-m`p)Jr3=+aDo5~HBE?qQqrbrzm3!et zD8bDyd#FQZ%5f*$WcN^S^}1zm6qLlQ%_x~&oj5LO?P_~XhuDLHv!%*!y0|X`i7ik2 z{J!7DHt_oYRa76aKb+ZmWqbY1?$nw>HQVv;1y|trHpl_LNn}^Ryj-{XDsmuYA9g&Y zTGhQ_z0e4%mN+GSkX~qQBlM>flAdd z4^e=Rz-b+z&w};shi437Vd+Jxf-pdwPSS3bl~(ci^x%QMPUIM{?c-Se!5D+*LP_2$ zGTdR74}h&A&O{zb1*vqzqJ!k=E+2;=B5}zP!SHy#s0E z&$z{R%{DfaC?WMfz{v0~7zuxYYk3o;5pl#o#^72R(e%->#BXf$7r#i%ST*FP5{O?8 zu>_G>t)OIhaN1b6WBhlXR&qip&H4JJ_oOpq7?h+-WlBpp`yG z1VAja?Z+Oj*Az`|Q{7t|=KMI~6$<)NJvFG_igrdhhn#tEe2>hJP!H0>=p|}wm6h*e z41`F&@Lg@De1M>h+jw4>*t_aq)Ua>nQBSYQhtm{crlZkB)TBaySwe|=LvX3B68*dA z$N8*hF^ZEsG4ylQWOfU6)pok>B2&DxSCu;FxTI9bu^qvD~B z_1H}BQ{6#KLMi=wE&nW|0zMX%(fK&bbDD*LZ8LfL`Ew3H3BA#Z%$|X*8FtNKV;~3b zoL)ocD8-!h{Faup@IcrW>EZr&j;|AZgJ~!c=L2c}$BL74jIc>UpEb+d*b=q2hw*?E zu`h@1*Q?5odKK<{I>lnsJhH0xhp5>n-k7)K$y19a_n)C}94d^izfRKZ4%M4C7c}|S z7rHiE(G?W_)p?(B{1LNS2ycn^(eM1H2s|ZORFovWz^sRuCl}5feM0)@tNMFARu7k2 zi_4G75G&YlU4E~_c$F7umM52jgKY*KSHvpmD+k$(67v{ubD&q*6dpLNrmx^kS|!=jzlJ&U8bo)@JCSJN*6 z$i0J6xZYiS))+=FQ#ADJ*u6uV^6%`8L5H^ExL-gk>@>poMPu`+uN>tKP$8J=@?dK* ziz1I?LtXI<+-WJZ|N4_UxtBP*OdHWn0O5Txj#1GF zV|6@234Rl5|A+aVuxI{9&{5qP+W3L$s@@YR=fJzwsgy zCTU`Pk-v6kLiU0YHj6d1Ab-N!p!ki-&b@@1sb}r2{3bMNI^AP&hnPNnMq3JY|!Q!e>g4aqWcYrTyMgqdi&!>+Q~;o~yxs zOQvywoSl*Uzn_gUV%xH*BvnJF(wv2lPkP7by_;3KC!DEox+wCvxhYOU%e*|qdQZFX zEE^>S&K*ZkU7UXK#pR!!qCc`(o2#$JX}=D{8%aXhdOBk&cqE99N5ZOtIm1qe(UcT0 zCr+&z9JP}5F@8eVAH+WlldLb*{xL+(c+W&F)A!WX3H4EEiVM;7m1Tw(jN9uH&KPe= z3|?jEcIks)o&&Ps$j!DOFhu(5RK2wrX=$Rw##=sYEQYqRwBf&^)G^(u$n-hCaifp! zz+Pq`JnM2MQT+HBN>>0BPvr1LJ69#|CzrGR6+`4f{#;q5Aw&jQcv44 zAGo1t#1kv51Avzq!G$U#Goo^S!xU~u>G;=vX5O3?*%3$?Ojdd{f6BfHm+l-`1nM-# zDLgF~G$_UDhXiLlz6N_>@4~WzyTD7r0q>u@ZlD`b{Jm`)Qm8FRReuJ}Qe~qx0v^^h ze7*qW>RD9-kJq{P{RMVTbbf=W*2!y&yoke9oo-6K(5l|Gb&6b-(AqVXsLQI*42&s+y~#EZ5nu5G0Y^Yg_!;8H%7L&k@b} znJD$-1C?&_9$qJ$zY{{UC%yvt8OwHhb_3wmf28oT+kGte$fem};`f9}hjSCy4adW5 zf%R;=@7b{}GGYenj3&glgjUm1>yn}pV7om{d)@1zW&N5GSCRGy{|5GK_th6y;r1wa zE3|O!R|y+yZb~Bc5}%9k!84;#Z-QBnqi#_vcVW$AOZC%1wN?kr@jMn-e(4_)INC^M zVc?p5c%^r&=Ow|RIeLezpRl)Q-oAP~YrI&+zq9vPSOA^mtQD0x+)n-zrsW!i8$Mn1 zFrS3kpQPUc@sO(8;G3 zDg4jKw48i5365xMpMSo}|G%l;)+1BJRt4UaO)2 zi=fvvdw;sZxRh1r$qUB)xVRMEGr#<@41W11T%m^WJS2cF+$v4Ihs3ND%YvI9_=ES8 zG50u{euV8rY77~eELD8-4suB{m-D;n4Z(QFR^$*Z@82m#L9M*^c@&8fgQgC#(GH|A zebZO(3!hr}fN=8ZSsy)x%I#}6)kOK1SJm_s-alIt!!qR%dv1%GiOGB-Hcn+E9ZXMw z!WhP?F9Q)Hz&oLf@_+gKKHO>K6y^oW$1Riy#-9~LBxzrwWt}YYdvHfXo^XIoMP z1@b1kWkGJgzhtmUQdfquI>y{p3TickW|+kI7LhBp03n3e$@}j#mepo}24V_Flr{~$ z-40mQgius*5}Wv7#T=^)+xm7DOC2KXMK@zWP7|ub8_5;*P!E3&aht;)qX>J?MbsPS zA04jTP@=F~Z~p_~n|H#f{EdaH)@Pn^Pq|%Ck2 zy`RLuRC|F=AB;lAP`WjVbi)fvc7$f27^ z4D0mdOXb-xhFRy4GqZu74}_CD6suHFO=?98b_5|40FJD<^I9C5x0GR(1P6}<>J6=>8GJ&GbnK2=;H7N(%TSLy%Yp71RHM4VFEbA% zoN4q^=6Xd_yE0N)OL>EwM!FwMZsqB1rCse&u^ywkav5RAmF8M=H$k|*~U z0}VuSKnrFT{6Xvf!Xif}xO1tdTK#ZOOc{(PJgKNqz2wJM6*TNxK zIebq)8s)J?K|t_|!9!cUj(^XR$I+l2Hcl8NS;0>BRaDC{`#lW7kpy{rZ{SE113~+#gim; z7trTn15#V7p-=59+av@8dUDc#xec^_8R8-n#KTPHK)BvFpku^kp;`-C*YG&2-b;TI zB(6p(lI5Oz1kdy0wMhu2^fBuA2=%fvvQ;iiF^6g}0-RQDs>)Mb!mc-H(b64Kf2wSL zWEg4G4Uu8RVQB$KD|T%!??i_*2?7RaaJXWxCYk>GBfeLSnhSvbal z>=GyX=%7my%D}-{-yo2KVU~m;$UtcR-FGjhv+=llvB4|fw&3Ve(-arg68GUh{MOQW zYKl*qA~kMN5#@k^8Qdq&pr@n=GAESiw%5MRbnaltH^Ps^_OU@DHABVo_O6Pqu{| zrorE?p|&kcn9DI7tS{ruGD&f_q&)@5m&(n>kEUn46{)|8#d7`t$sQIIJNsErAd($} z14578iY^&Ip;qdjrCV<8aQVfh$sq>phb~#?fye4i9(;OaUf%9Hpn7U(7VcxOGCp9)I@*gmweZzfb$lk5lj7kkY|V_h;KDeOK2!->d#>VMhSAHC{YTUpoOoome9cM;t)PY9-zYQIMR zvwP>1`Ux>OUe*6Lr#CCo-2E6-$dNwVfR2oZL{D@g^Re+q;E)~LDesUNssz%9Ra!{@ z!Q6jas*V%7eGdJ$>)5Lt{U53EA+a6%+{PSw$>Xhu-8q>^;JliTMqKz-SvTq1h{#B54ezXoIYpvR27%lo6N|+h*`IRbu zLLt6u}3HTcJ!#5hsbr)ipt!Q<`v-goWsG+jn{0SuOM}rXZFbUEdsT(Cj*llit zWjwLr3dsc&o4JfdTJ-0N?vPzF*Xr$uBzHXh-DlCwqYOI_=1A5F#eC7%b?Ec{hj-#H z%_{_kzRK&UAQm3ifGk`57Deuz*uIX|SG8n$x#05S=)sjosT}ozMGW7VT|B|tFslUZ zL4}HpyO}tyfA%=^*t>t;ws!CXU~Ktcx5W0S@;@fs@RodA%m_hL;bf5K7zJiU#=%}q zfq^3zVs}+2Li&}Zi)o0c`Rr#IHUV{ZV|@;N!DrUEGjf!u5GDkPmhSIR;$p^yWU3> zLY>eElgNn3hx(ZN{h$kuwIGwoqxg{;pAJho(Lx>%6qjCGUE7Q{ID407By++x zNRV!o$P>c9wUAHJu;$D43Kb_h8X2W#GtEndZNFZ7|5T)X$y15Xfl6ko#C*4m28O;$$oDTv3HW{!|SQ$ zRv{;U`>A=eZLfZ7d4~;G5=$}$UcTG73vFN=NeI_?Yq7-2HME_8Vk> z)MdKBrn$R&=*sqzFP}ekNd0HmNe<`JpL=mv=E|h~ zl2x94S0QzoYKb*2V{O)ZqlU0r-DAHc7Ed*riy_6K98S*RfdPy~!(nO2S;57hLH~LN z8K8K|0oC=!Dfq|Tw4W5$}rzfKJtWke%?7E^>bo4q>EeBt86ti zlkp;PZ;(N|@B&xHfk?YUb~ey_{7S_vZE$Qczfx(bMgaB~L$>luy`I?@wV1q5o9k0e0MRlNnbdq!3B z4WN2@pps<(KKab)t34!je^DY4pM$D8<{lmo&)66_SUQ#JJ1vHiO@m)92Va2HvH<*Q zt>ri_4^(NG3tMI6`9ers^<7H(IWqL#)Yz3WS*4-36dxKFwg zp31_QO~0{VyAtv3T!W%N`OaciT@iAq-COO6PWW9Dy{`LU+UDL_9n0tBh0{Ga_M^{| zEOqn8)$L;xPnEE#lcQMx{u$uAlu_OHL`va&I~gSYMCf6-V7Yx`vvh;_dy~MA`5M+V zJniP(x;-?lf8pTApIgSzN=~1Fe5u`GmtCkq{S3eG+u<^9sq;1v@Ax*>ErQHbV$t)N zdsxL};N>fnuClbo5y;RxAb<+3ewfq=X}{lvDe2x%GnN8b65$%`-F+u#60(9W4T?7Z zq^p3MFuu4ymv3_y;dgx%M?&`?#iTi1mU zu(c2p3``!@gU&gAJ2wGJ9|x_rJqeIJi{Re;YIqv!!Lzk5+T-6%o*gDO;mKd&jeW5< zNlL|^3BluFpE>nelNRSkep6aF`{>H8D|xv3@S4YI(yL5)4PTG8g3tFsHj7XK-9dK- z-wC5L?G$GWI~LBLIW@@Jj(gau-9s97MUC)T&}2R)$p)7`?$IxESdWl7Lxq-e=4pk4 z;=(};UC=j2SqDMEGzP9@3IorcKYR88_+E=)nhKm2+c3bDCzNL;U8|}((50gQKX48lG&z2e-@Q;GIH;?t&gl zQE@g)u{&e8l9i7U4itUb%gz(r!SeEYmLqMntVlR^>Z@Dw&Etc3(4A>T!srVoG0k`69P~h?o z7pdZ!qvJR}841A?0-;*s5VPRasbxccJ+S*W8L@D#Pmq zd^7tD`DW7?=AKnW>QqeMSTi>WQLPZb zYrdk0#^$$8iY6>C zkV=nKDkaF51+`gEszsFz=<>8h#|TUnu~{0uVScGcSi()IUg)|Ks!PYN8qMT%IfsL$3rL532m1(|J5_gvw8 z%%#n}5$lHBFoDKPZXB^fbx;DkI8P4Zvu|q92-Vi6n2UZi07E&>V1$hT2;i=G>mx=vf_2s?zd}*)n=B0t}1$QID+KPCDF0} zk@0g_s0B=8yk5C5QRh>f8Qfh}U#p4#csi!CRY-nO_qpM}23x1YTdzNXNw+jV&v||F zu`9rMKlx^r#%lQakL%`GbM^HsM06UP_6=)sT+XEo6^BtEUT>e}Pq%4)VOD1)%F~!; zE5X1DSHFg?mWRDsL7&hRilmvO~+#3PW(bCEWYQgI4eSI zCRN@)DDtEi?CVFq^uj{E`5YhlIluk2?P8dvn%sm_e#(emq?*Fxm=>3_qMqw%eWZI6wLbaS zhecT?W;dBWGM%Ur9E%W(N*?BRLQ1Xqw{9_f$$_A#A>W6)_X6yXMY?`%n9?|{w5Ih_T`;E z)Z=Q|n~1-ti1FK(77g-5OHaaDNIR&1uD^aLqSULDw7VGHC)1J88pQFAua)>x!6aT( zd;H@>Z>rfuQth$j0;LBemOm8#1sGMOg7!fzKobcDz z1P6L?Ifa4h+E{$|*Kh5O1JB;!2WWk`gDM!}G7%?lT7BIpvU0(JUN*?oJQTpUL$(wR z7kGJm@D}U7?@S%M`vdgxdY0H3JwfoB7^D2!$%y}&Noy?=0D!IgMP(oFgC8%E&2uLF zY{v`osS8WJKZfbwkFkc%E#y0*@wK~Fgrn|Lsa`JiShgn8Or+UyQy{fZ(%Sj<#-gB4 zq_5qnr;6aZ2p8(U7~t%y#jC!ot20Nyfm9`MfO1MQd@qD2MgkTDXqmp|NJ+1+=Cg2# z`JqU_9Pui=#l#c&kuC^B-+B zKh@p;Ra}b|En3{rd#m+W(|J~)K4&a|*zzNqcaf?;8THw#5-(^C7^jhwruyl;TOcsw?WvJDcVBzHR$xk(tr>`HoNd*9vz0N zYwl(M1PUfS#EWxZBdOD`#lTZ0l@|OT?@kRdL6`Tbu|F+6eLF)#m)+)z?Ws6=x%H9( zrepb^te+p##);bREw~c$Ocjb0SBfm&O3o~-HRH8M+1rxNEB-%hy=7PvY~c2dAfSW@ z(p}Qs-JQ}P9V4WqK}tZ7?i$@C(%s!K>1Il8FuDeec)9=2`#i_-#x|zy zGjzH^RpnI96XPbakFp{*!pKc#IsG?Btq@zzHhzoe9#`ZG;s<9wjB(hv(@N*j>J`Pw zD7SH>k(C0h=ah}k*2Rg@f4`i5VR3b)udC_KwT-bQTf2@}51KGt=?d1kq5%tkji1Z+ zCGcc`mtg)(kE+)-pTxQbqxu8hPK_{*@r6Iyk=!R#bNAbhtRi)IB@Qm}9F-&;qqZ2r zaNMFSYwE^)*3fJOOHN*+jA%zwd_4l1CU2?yj(8#-g==O{Y`of3MkC?Dvwk4_t7z7@ z6>POHnX>6dp{9=MSIub8+d*RVpkPJPdHqm;zz6P)C8Cwphe$)ReFW1#ir;=%o-K36 zjgM&v`fFqu%)IXUpo}d5s9Rq|=+blXRc3rYy)a?eyOJ}J%wuhHJ@CieIUV&)3HM#k zDNeoHteaR^-CvY+3KS~6GlRL#e7AMVvZKD-QuHtLW@w1T{B{$!nlxy1tNYX>?5Gkf z7!)obpbjW`f7vY#BQ*~Q3ol)InrPZ=!2jH`E0F)|NrS4|vY{{-`{q+t+BX-5em4s= zK9KwQ#;?De_FR63+&#_)?!6Ty?k(c`u9%%@;C2p~MqvR+`oZvYXY zKLy8*1Vi{#T8ls~nsA`z$^Y|a(;l{W2MP`$7j~z=KlvDZtL=BRL&Z2=`L6DNS#AM~ zQHz@ZIwWR7NKOE(RgC)n{Oe{M!S>3e7Y1~1^Q!9O3)^SiWbI_^LFT^et3 zFkYjo{U1MYJcChi(=+DbI@>CJb8&HRUN^he<-hT`D34?zqW2EYB5N-wV06iws`Ug)j=cM^q#p}C`ak?h6y0McOQ=N#g~~8K@Y@@i3$+@e&*1DEPLg<)IT2LQ zE%bW}V;;+h6RB;@yeA^$CFa8LjHs9k$GZEMS)5Q&sIb-ancg0+IF5;6l{?aN(O(_E zH14~Tbh?9a^zW~(WONDBubeY6N!92H!&gThaMJ)6QG!J#JHn-xO=s}%fbg!3g4U*Y zA;1U^fb^wL{C4kv@#Cq@Tl<2F$xQK|PNLN>XmA~f+82+{K!B4u$=f$jhby4gpJ;)7 zY(nf$Aoa?mtM|LBfbOG?_-37MjhELQg%CrW(n=N9rRMef%TFUrV!)b0msnl?DWWy= zOCU$ZuSkKhA!TvbFQz2ZmR#0Hdr> zX(;<{q8OVq+6iP=6+03?Vxm%8 zj+FJwi^TTpJq?NOke3yiNiG-}*T+7H^Z3hWHyvyc4j~>uT`kToXYkB_{UicW3cBrS zes@x2ObA13<_|wD7ii{x@)*BAbDh0RA1|(&#*C|j5T!?pJp#{G&w24@UDpa? z72=?XGc3n-)=u*<<+0~LkUyA8eCJ<+?~j)+Pskqh470M2>Cm41J)+g(jte@dIbzsIv26((dVqI{%g(BLgy$lAzVtS) zcmov$5=&@xFlXLy)P*j{AHUM5u=&)eYOg2*N0}ZLYp^l;wCjoox{Tm+f1bhJT;KNk z6-i52D){wD#3S&*|JQ;RJz;J!D0cnBC$-N9=>zNU70-M2s~n_CSMOFVW{N3yNJev7 zzkm5S`D1CdK=|b`>ZVFR4g}|25@ax;{Oo0%wkC^m0&u{Y)}P{eU$WEw({#$J)kLj< zba{gT*MKmB61}O7SwB#F^rM5Ets1~4+coU25huyD?x0=p9Vb3+l2?;Ik>{w>E~2IN zKl4RVFp!G%zUd5Mfoh90`K4nQ*!r{oB}9_d~7m$>c6nP_7HymDerZ)4hRO3)f>V#W#k7h)I1J0{d} zIWuvTDd0){6za#IbM<@Km6!T2uS@;Riroa4eUIH@V{UYZ*SbN`)HO6KV7QiKCP~~# zGUx~8VWddHvGyUHH}W%@qcp#eX1tTHZ75{`u8y_2sUR91=KFySOkV=6m8(E@7TNG; z)!t+tUAMY9)XKUDmcbzzGD{Tyv1tpgw#m7w$Vl?M@H-Z~6=`UOwtjDv;x*n|(A^$T zI;4T|7sVUgj(tUcuZ>su^j8?jYngaZExm0|2x&H7h_ zA*ei>ci!s#bs6)VMUid|-rKQ}e*HaW{We%)$Z2Zd4ST%TK?V)XN1^r0C@`xCuikF& z->x_lUm~+;pf~ZXS6P#EdsX`wY>NcT! z%3be}t#{8qQIcT&DYtPqM8|yX)lYlBBZU)0aKCFyayTR6yIB;gZn9iWw$@}yI0p&x z7=V;eNpWJMnboo)(0^R=liVU&LaHQrE@1_q#Uo)|929?uGL!edGRSL<5lKG#{Wq=7 zLWNikPD=~ms5U9>`GsBK_pa9{K5VQlrrmn*hkXO zE&GrSYtWh3(`~(Rkw`)vVJ#U#ByZk4@s$tP6#71Q9ICl*2CwwF|UW4fb0m3%1!KRW%vG-LgH$Bl?@70h1M~GGTAoB4IKC;_8 zx9jm8CLDp}dj&4eGQ|a)0q+!MR_fkYtIN3r<(FexcQf^5Yv-PF>ZDYg4A31DL^L)-{{ zsSZ@E3j!HMUN08izughvu12kutucW=`qAA`_t>p+#QbF(Fh7*I2K=F{{5!vGL{!Zc zc;8i_lcqPYthFk%@UHgGI*nlQSs(%@0+#()u#<3l$H6{oQfwY2uv+2dSG8cc` zE{C9Qg{Z`aH;9r@os`6qe8=2r%o;#X{smg^GFST2)FXE%xit}yao*4 zIGhd6)iyDu@k|&>_SA>_y<6lT^}(#0ENm6D1SRXyaV?N|hX%CL`x270RE0Wu+SKu5 zRgTz!iz(uTRTLWX?s3QJNb+$F8uP}k(%a1RrY;CcHAW~0QCR^y2PJ4#IXk&V^Q9)txuGVgDxS`zfNa(LgYMF?#bCb{3bi;C!J;*gD1(I zx$aYqal;CG>4RM7@gFq6e8V>eVR1vZWu^E4cNc5ne|CAlR)mDGdEky_^D;E2?t1U^ET%5M4@sj%OEDYzOX4CKM40p*RFeqcXH-iJU5zl#!GJ+Ji; z^Z&VZJ;#+)vG^gt-FjxvSq77elK0%ge(u&MUOVT8LGWQtBHW~L8`>A&13vB#hD+aV zluOV<%)Ty*Hcb5QpwxIUw;$Ypi&OF;)o$#(xp~AD{%{|&7nShbBHE+;pY#wY)_X_wWO9zqM`et$Re=J~iG@oj!MlzR!J zAjG4h3yXkOxskNi;xgJADjqTgDv&zXxJOCy(k2L-mrrbHIgoe(nW9I78OYF!wT)a( za$>Q0!vLtjf{&$97FNAyM#8kjc5k&P9dr$D&>^z49J}bX+X4S7uRDnOXh(IDzGtnA z*;aqjwI+Z=tdbp?tZAlnMs84kTYZYYe+k=?H_$of1IjFQO;fl-1N(&dmpI+X1?A!j z$%Hh$-dZx@Z&uzNt6kZMNM$2Cz8Ym^1~yS@1y7+)ijJqhcqdS4{gBS*e%x&1&TDf$q?|0`{aQ);%m*^J93e_7 z(+L|%zpeUi%OGN0+zTwm^TUbCQTrBmqZduar!O; zNqSLg$BNUUW?W&nW0B-MAYZROPiem}BW`OvhuIirUo_0lvr>@h+!XjP`FkV}{Pj(? zP%jVg2cK(;ObK>&L=pAaQQ|1LQw~92>Ee&0j_>94Qvb(=FsQ86mCD_REmxiJ#Zq7| zMfy^BJZVmC*z_intFDSnu-AKpI57a}FmOBusL3(KME87I4RfL2g}!82Yc1c~2-XQ} zrd>;sh<^9gWgwaiwYkpBeO`Tl$IT|J?tYaaMN!8F9>!9PK2f`? zUAA?qL3;RqA&VNRQWbSS!_f2DT>Xuu9D}z%E+~^D~?6z%`ogIbU%v8}H0h=7!Z8H(;yCT?t5OFC+xeZGvRbtHNz_m$@Ia-d!5(E`0hUCxTr})xlG{6F+HzWW%u79uOOq<5iOzF>7wz3W)@AaM zN6Vw_G~cp(%#3{j;EPv1LOmO5W|hdVU!HY?nvNax&g)&QjF18GuDbk_O^v* zO0^4m2o95Mq?hk{rJPWj4VZsf#v;*QqF!$m%+NF7-N>I5?irTVF?y_hkVCUfFhm>7 zQL_l!`nsm?MT=lzL6NIyY_ON@jw&_`8)Q# zgGqhG{CBH=2fh)!jiqoo8ck<%G3bn+B0uc>d}e>!Jk9@--#KwR`TkJ>_)hGbmm(=_ z3Mg!MgKb^Y_USpra9IrY$JN~zuUIExI-payRsV0JJ6(c37DPhv9ktU84JnSBYbU-fSK_iL zpbx@HAd)q7@vjztR}&UA2G!bMc{q~nYw6qDrR=VCsD1ZOY# zufFT}<71sB9|leMaKJt~lZ(ye)qYZ_I-RppU{5c%lW1m!YUckCF@F3VB)OA9PlpZH zH*FQFJ@aoE&V(ZkL-hl2B6fzu__G;p1>a&3)Sq(PwtNy;6}k+x@9i}K9owwEZv5Hs z0pc(BwML0|klH@qdBy#Tx0P;6d`+dv<4XKh#0Hna!o_2s@EWEZ2tEf8#5Gh)*8et#CQJ6L^WI$=JeNLP zfy4}=i+~5)8y+*My|?p35%@XI5*sb2boEb3cQX5U1}#^bvQHl#hMqOrkJJBOMZEy* zai4%upRt}mMA7NiRfJ$p+5f&iohBK(^cdwm+%k^EEt?VrK4VHEn*JhnVc%^)B&Y&= z5X*s^F9@ywQKp_)C-e+V=_IehB$>cZXFtI;eeuJ?@-J;X_y)M8we`92*Z;{-1z;-~ zrN1<0o%PQhG4jAexB;t z?Ix!KU`l)1$B&HwV~Rfx7no&Ax|k61SemJbQn@+1xKpC<$MICfD0Hx(^aa?yrmdS; zBi$jNQ?iO{qHoyD$aSpI;+1@_UKMH1z7`u2r*bVgx-8I>(T24T$kME4q{Mnkka#@e=hFwLNL$cqH zele%H8me0q5ZS|D@j0$q(E7Ewkid~N{^3d~A2#k%NFru$t`M`_u5wqj~ESo23=kbA@Vx??VyNMrEN2lt%BLQ8$v9Igt^~^V%cTc`*%@v1BdH`d(zsAz+H+IOM0AF=WuE5O zOW%o0fAz9j|Fo>bo%PN@15`N~Zm345Go9-+loGcTmMqJWuDtQoiC8kHo9%DV(twyy ze8P2**N-C|(9R#uPNXC7u_LT>B^Fa@gxF4nWFavdW?p}?<8W`${4TL4FGt2>UO652 zMGNV-FunTB3Vj9_LbKmrYZQs^5-)XEN@owmBV~!YJUzLOQ-r+qBMmGp){w! zpEIfKm)TTaBg7zB)FREkmj2gE6`dWaI}^cgp2+?>*ffqtZdIBs=Jm1QOuv)jLudUc zCI@AVT4QAA#gA%~{;r(6#}@PjXMh)llfu3cq~fHEeaepM%dEL{cF4DCcYoRLiQ52a zg(2)Z0_QnMh?}|6fhJdr90+|<-I7;LRr1M3#t=27Z^B7dtCSIqGBAML13>a-PsPF~ zb-&umqc1@|@1f*0&9Syaq~jku-v5$d##hcbT6MCVFz^<&$!4 z#X=@;!9{?3P}n`{#zDgy+FUqxTkwRx^2_C$F2WaXfqEPX1Y^XyexB9f4nmIYq?Pj< zLH#D4t89u*mkhng! zrn<-@8x2z5?7&`N!7v~BCRR(7Z(KD5U+-$neK`|nILY{I>|p=I-d>%%AD)Uw59?UO zC7OmfChd&bWms$!xu|%Gk}DkNx=_tNmm7kjTm*&e!d~{8x4($_zkmWFD(x7cuk>mV z5-U~}ph@+F@0#`RW(?1E0tQ!>A1970b41TXzH%;EOPItR_VMFqzUqJ1FRY-Cn|JK% zGAaU=k~~IV@2g*xtJyj`7HNY*Fjgn<*}DV7A|qo3AMZ7}W{wzVra!#!B%RBj21vAW zHwCm0rF{Tlxu1i17*Vs)kN2KJFB#$4KOo8U5(c)!#Z|#bpa}I?zoN%~Jdbe#>-#UL zO}oF7Vs%iMXAl^6a5z-{@LQaV^80KEq!o`10_@80jjUa+^19nhOSi!!} z{u7%g*X+pvpi?`l-@+gz2i5b+=+&CrbZn7%PV5+@<|uaJ<2uw2@n~WkT0?`pML^cy zf{KxMpMfQ>_wRWN%a)SI9W>DN)c zKEafTKOkyMjQz0tG<(cqSN|A`aUKXxgXMMA-^L|jG-L0==tpI}eME@FH}S!^zGyU4 z5p_Z>@1vg%w+;2d33K9yj-|(~C{7ZUJiil%6Cv-qa9*KM7=={mT-glT>8R`sRxOv) ze$ERr-67iUDFu+O8-V7orroH{thvc%aiZ1mqOT|19A7^BM9=KJWzU}hkkWJ zW6z1T`41kYRemcTY(7{S-{kF9-txFN@L;v2dJTRB1v>d)Iy&x%7`NWPXjHLhOI$9X z$5P0os`(QnZcXf7DRU!{-5y%mCMjA5j{qR&9{I~rmJD&qF3nWyuPlMITZm(gEu_BG$l4? z!FGV7E*oQ5KRs#ccS&)z!($$^BX2goz8}YYA+H74RVzAEv{5i)k?T2ME%d)t5i6Y> z0(o9l4rL?d3I-B~d#N1S{)zfaDe?h|6yBiA^4o0T1P4&@B=g#x(a?F;##B?7&3p{f& zn;fh5MY!2vF%mdQ5jTn{4|!sQeJKz%!N&I&iPPlHs`wmluQyxkyk51=1!k}DoYMz6mA5gGgIsYz#Kz=&<5JhLG3%ae3bGXZt&(D8hSCr zxz3{nP`<_2v~XJRjeVtis$R~fZdRyj@Xec253xoGU%~l`_pzjEcx5GnVOlmv_!IxT z_xxl<$DdY7nHJoU>m2OY%+!{Vzwqfr&E>iWtvl`z&D4|z>ZGU)%}r`JDuneY@=ug< zMTD=_GGw;Ucn1r%0RD70OQ9$!VdKG&7AN`r5o0eFvaDv1`wy(IGvn}w+GhevfDR>3 z_usB>$Ga2K_pZH{6|UKNhptUmK$|ys z9pr-jnj1kx(RIY=*qXjUcZa$!4xe#5#wzL0{K7kg5TN)Ipj`W|Yl(kB#^K9yuyFDj zk3>thtZb~h+-PPgiL<}am-)j*xJF~fYN|n6jMs}aS7!9Ke}w&}O5It19%bfmL?Cc+ ziBc?#@2&);^%WUDS`R9O77v9+ae=_*f57zet7yg>xcPd(rV~57J{14R16AzCAn~|w(P$2r`44zEG^nv zt5a}R@~ZsG8Hw2NZn_6sHC_qxf5{=KAIl+xsosGez}*!wWgQdPQb@@iCAL2;P5idF?B)U0 z?aZEs3VZAAKcqwgrTs2=aB;`e-*5atY66j8dfXlj1Az9V`eM-_N`x-nI-^S@?CoP7 zCoKgg#O9T_A#LQ_ezk>R(v4AFo;@W}2M1>nv#ze3_@y&ZFYGz(fAVC@B{f=I4@-P~ zf7I31X=$^1{?cEQ%tar`Ok`l9nY0G=D)YSdBhIb-<{EJ&YRkbB_ZBkl&Fz9j2cpb@ zagLB@&-m)Yd%?6l6jPS zY`2faVT*(q8W=fWmAbdM2<`d3s=IWDOzFRaO%_CDd!NNgi#@)I+0*NykoX=Y(z83` zLlU=?c)K|WrTP*heNPLL57IqcfMnm8%v$~Ij9^y|UbJe9koL~}=|mKU;ap`=wm@uS zc@nu0V6vjaAx%nJ=3seWcs}yG0i~Y^>&sgaRFGAcb@p6rKXnBr3dViK2c7QO7E;&S z_BBYy#0`3I;)(2=)FI5vje8yvtbss{d8U>egKEz5^e;>o3no-gX9bL&+5SXcUQ3~j&G)f$7&V_Dv>^+^~~msI`?8EA4N z{W0B5|GNK*i>i?YtrjgG}!eq^Z2090>?H>(yRYg*z{i>})^Db%Ew^&*JRR+T-rX^Wdz; zEhI4ScXt~pjQgN*;kOQpZUS}`XM)PJR7=n7Beey6a%fS$o#jC9O7Er8Jf2h3CXn7w z(r3dM^X4QKlk338Z%cwVVR~f=3${gulG3nFmbEz{y414#eX-7@xm{iN=z@p{O}2xB z9L_1=g8+(!OU4SqiA=@eUV7)I_psR&NVanBA$j@X!m;;)Tsa4LN?0cc6y@AU$ZBpb z*JHmAAo3+r{7wcw-9nQ(h)vzbT$J}iO>h&Z8i@`tR z;$~zPxXLze{qLXl08dxt>{JQbU+ty*4=eD{9}{GpcQJ@$I&Cd*TtZVW-ZmJOBA3Hs zp^H|^8vTw=RJ!z8GL7p?;#6#GG)0y!sfv6&BJy)nrIj4A2mk}6S?b4$T$0XHN4@3W zH&}$q-9{dUA0qY9D9G}7Yvc**V}X__pU~b7rc0-~gTzF2QeCF1EDy2|lY(x{5Naw# zW4H8Gs$UWlb=EcAe3WRT6r_#I(xmFGY3ricXhQ$g*F=6&OD__^^UHMHHHj6fmekp) z8yo5mn>_Npa+1fRcGsd4#a7QQYSq!EwVGMlH?g8kT>Y8VFjhipuRW0+sc`9I>+-c` z$%kPW$Dz-IRsmc6h8i9y%8IpS29qMSkB-C7DQ|OL_~r1kEE1_`K%t7P37yW+6#v}b z2=_14x~W3GYYh?mTEvMbIP=ZlZ@M(JII^G7IRgug_4QE# zj=UE}WbhwLS;;B3+>Y0eIi+%jnPX0q?y_1pRJ;rdeDdjqL#zPmNx20zm;T0ajKTBH zzymKC&vql77l(!tm&Q~#t!%nJHCC9p={RB`J$?WhSQeUvtE}>9Ao~jKsyC{jb;0g^ zOcsu09SBG}A{Sz8i{&&^L7?*kRBz<;o+$wDYQ`C24c1uO=Z^U9tGv8a0${`}?CT zsrIBzf!as*r2A`p4Cka3`Y8H3@eS@J2AJY*L>n|g~NcDHj56FDb2E_y2u|R z_Ym_I(b&?K^#)n{N2^B@fwfoJv;v)EPUNZ8wxaVAS}g|I(eL43A3vP`a;!V9TN3DX z^mn0KL=X3f^7>VZ-udqyw#S(FgmssJ6U5cAM+3h9eNhb&>+IL2#j`JH7EAgZoyB&q zj%%%!T~e8k_2oMv)p8tI7|8Zh=~q+MYrZ^r{@mzfZIW`fiT;96w!?7y_BGxkVd-Og z-e*i68lJR|UUhlcSe+z4AOf_kQB*B@JQklc;bUtKQrx!B!u6#uQ>XQ-lo1Q*T*$s^jI`cBSdR?K9H*dkQhi0Xw+&_%Uv2w#= z@m>SXmaIzrYNj|zCuVspmcf1*-HbfiF=Or3MMHqEKBG)BlU#0Yw3#dz@9i&t2Kc9% z4+Vego|FL^Hg_+I!c7uExE+=Dn2sHF_p4Qjit>21M{;4D(R8l|38V%VbW;&f@`LJb z(-fYY6mDan&6mHVG}(vWVL3chJqdm z%y!n&`%`tjDT^pOFVIQkHgoRH^84(9-Z7RWp*j{r0o4`o!I@KlIEGz78A+d?ujgEA z!W6}X$Xj8|BOzGxH4AM+2G)DEY5%$Wj|d^3>%@qv3YK=uBM5}VLh`*o2Tc*hJqt?M zAljPOn|BM3z*S^|5bdp16e*rQkkkJbv{AEqBVtVkJL!6r z>A|(t^eZ#|N8rP`W`Il{A_kO_5U^_w$lLYfki2Q4O0<(i*x5i1qZMmW+M4)9wh|YV5BGa zUdrq%Z5tli6vg^Lwe=*<9OjokX1zqln3;eud0J%)jZR@=rF5iB+58ObC(RUbi|A=C z1(=f`O}09`#Ew`7RvG;ze^G&lZEYh%=RvPgpQHl$@bf&O?tVas$Z^4Gy$ptaEJUkB ziMA14XTv_rumNbL*1j}Rm5jRK@$^oT3K%_=N3iI;a3(w=(6sh@qKNH{ZfkoG9!{#LY`Ky2^||J0fLHO^Zd~fKd_)1xu8ai+JwT)KTnLMHU53<-F7l z?K1eB?$G8-EvL2W+2WQU@$OJpP(56uUg6j3TR_P_ewT&_|Ms%F1X*zD2)yDzsf{p= z9rLoMUn4qInPdJY!jG~s8@g(O2zMOMIk+V`K4$jF;LP}@1-z<~LD*w#Cf2>PnQ45I zY!2LR%8 zjofvPY=F2etNvPkPHkjIjDT+_aFk3Q>-qnl2OP)}uCE z-f~+Pt*&W;-3RQy;bP~Vlm&Kp8~o?Bx2L=R-YuZ>dm-FBHPB16ZEU7Z!1@4Z^!ag%Mezw0CkQ{+woRPJ5G z;%O|&E)oqKf>RW0tprI)a*S*C19Eg-?h1bT{P%9Z-Q6M;*D!?Dr9CN_79G98egW)<8aBDaZda%j zEm*g_r6teg^=r%=7r9#P36RWF$495Tf{kr{!x?haX8G`0i*YbYAJ)YLm zAE|Hw995nzgP2bUNM={{a*TE0giUUR5!&iaMX@gE9jPA0A6*MKt#v)k$mGJL3q>;) z-lMdYaM%9(&N9v?vrl1y z2Hqp{^wN-I7B{IE;ty-5OE=S(QLfVFeP(9>E7#+8vM}I}#7^rIqIfiZ0`xoungBmd zgCo<#KzjtiPb{mguIASI#x{g6gR?9^{gJZeOGSdsY<)C8OGDi@q9mFGf%IvEuAKb_ zxgn{rW9B97v}xR5R`1`G0!Lc``IS#-{sZI@ACmqkhPcfHYi8$N%9D_tWvFo;C8&&k z+NF1DL){(Lzj7mJH3lK;!v+43vnyfm$0|O5vxxxgw_$G*iBBHl9kEx>RsNB@K54!m z<*6eM)Wt0YXiZhAKIB^+I={Qgj`93NVhXFCUbpxZ-P`JfXEULSWtCXOlu56*o(D!9 zi8u3fjWU7W`=jB%PtvceL~E~||2_tZvV;B=QFiaeDO=so37AA125150WDYlCNU}W> zN-F}R6No2m>dZlH*10H8Yp6x7T=6<}_&1rZD{jsm!9!0(>V^`8`ZVt2$PV&NK!(u*cDR|?%=Hb?JS8U-cYqK9V{~N}kT3hmC9W zuL8W;ShBGw&cN&5M)Z?i{8P<)BUk;I1;IF}c{jSehVt-%3i7M?2hao@~Fxkv-CYg@%-v6w2 z12qF#SWf-(?~DIS0J5;!fO78<>2;i^5b}Y{y4+6m9ut^i-W4cG3?2v@37Q^TIK>r* z{!!k8ydfdG62Z9Il)*VN`v?>dpum9sp&z~{&e?gsc?b0s86^D`JHr>%hQ>W(!<&7L zLKdNPHu<%dJ_T1X``!xDk6=zgTR%`(zP*oskVv_CB*p9z! z#l>o?R#To@t&R|46u06DA`RDK10Gm6+}94I3@MR;Kj4YX*nz#TTYe%taoiXVw6|+y zn0%YpctJjN=-erw9wAnfh)HF zTvk)-A@gYS&Lhf69)D0sPJqIh`&w*;o~bD?%>3i6ONOPZ6G6JxMjc}&uNbJI!^~~H zsGcB8BE^E}fKbvnVXd5PGUB}XxNGr@&QOvS-swrFiy|^q<46GTwlR(6?Chug?kzyf z=_JYUj{;?xecg27kg=$x@m`pF)N`6?Ufv{wT(*c+8{V#hh&-win@pKj2PZk0exAv{ z*BImxsmm8i7-(mxOT5TxYrpBrEE#dNd5pmC(WI8e2E*k=4qCvFKek^9AFOZw^`Y4w zAx4aPPc-X60;k-j)sDu2e3uVs%`n!y(!-jrU0nbP_~$jCd9m~#ta^3t5b5pme^hSB z)5NKQJh>R+`@{WZP)D;9K&geTEKcN;io+H6uer0!+->oXkB3pya(&3FTkG-Cw<;3` z>i?hUc5p=3%Zf@GmH$2OZhCgMUq48~_mkNZV=yiF>Mwe3MArYUYf&YgKAkX*VVd8{ z2nX!mI-41!mzucU?YR%{WyD0)B^mIH6Co=a;dRX9m-tO(x_&;&* z<@ARM*QXG233s;p6M4>uk47dY_NrYQ@kBo7vfVjx< z_3(dguuRdI>FZgjb0tZ1*?t}=0>wx?;^m@wh62@NzFyZtU0yzsO1CTaSdpeLjA-iZggjlCU zv`9^dOno^br)jqI`m{<&g@d~L{77Meb*Kp6h>e7u_NSQJ%JKJppPH;^g z!mXP2&J6IuCj*Fa7H#f3vN{hjxO@!#RJ;~7LqCU6p@U{DLiXD*AM9M=fbWcZf>v62 zt=MV%F^Too7-Y&x>$McZ&><9W!T!rPkJrv-Y|x=dby`fTjI5KR>IFY0Ouss#zx(_k zvSxR1Z(pxerpLTvRQXJVy9!1T;S~&2XI5{jjt`g12hMdb=m27MVY`>z#!r=Cb%lrOsN+vSw}(@h5?;3*WpT$h9pLZ*$})T-g4*3t2EQiqjOk+phlLO<|7gS_se?MZ7FjPq{qo)z;W}z%fXeKx^qlJAO=2Q zo42|4w8M&PE*Ud>&=frPg?!WC8Z`IYD#Xrg#*R8@wcLhiMfHZ4$0Nk;la`Y`_vnN^P2C;52wbvg=azVjL8p+(SMd3H8>f zBHR?9jN1<~e>v@Mi}TZQg0JQ;a?GE^+n2>p*b@A&S|@j?F2A;4ic`|gv~w-@ZT$OY z$AplVrLyLTWvzuQ_pQ{uz+%`iKN`tkkri8-M*@GAv`F5p!A(t1xmoykn|=ljkmrb) zkE#VCPtG>JW4rgO6NE3vO=u@U(Z4-$K1xDRDo+)bN4)y;w5&Y-n&Blb)b}PT{Z%KA zu;0}0er(XXyA$ECe$52qD%l#Q%5zRPN^?%Q1-D)E&OU~vT|V;Kt6Cp3e_M~*mGhi8 zUx>gO>S9cOY`g6x6S{Oi#+9kLPZUXmnlw4+s-<0jpYofJ%YNWjm5S3r*XRNbhMd!$ z-bt0})v;_~Ev26SwZeVNW4<38u}e{lV0{^%2x!y$ zxk+hcx=I7`Thbh#&%Y0wqhgCr&7QtYpK%4}uMg{{&#AqHW%uqlzlA((#XNsfC?=lG zdNEZFSj`xCppzABp@DJyjhP4$&H+31AJ2u#)jop~A%QfjxB*E`Z!5{pA8%Tgdm%PK zU6@nPe4~a;j-|^!s1(c=W{1aTSR}(M3F_Ldkj0;tGrnGEnz$st5&ZJTjHmNpYzDn* zbsof5*S)rRgWp(^tI_xt3C9Tm??E2h1ku}8vh96&>HGJp)s@Xtunl6O@qm79g85zkX5v>h!N z$^k7IFFT#wqBYd6TV}DQgmbUW(DG*!_@ms+qp7|Qua*)sPdZ=Vl7z0{69N+ak~vso z-h%8mFi~%Am=M;n4tOD9w3I8At=Npemu?g2Y@`L^_vfeT%j_`XHCJ-b60tAA) zBoN#kLU0d|;0_DHgX=<&;O_30KyY^p4vR1DE{n6U%PyDSU*21F-w!iY^JV6#r)p;U z^f}#4*QLE(Z}{udt^+flFI{ZW5T@5*^v72vM$e(??7(@UY*Mh&FqKS0Z@Px_H_Zs_ z#(=ayHn}(L;3-hgZmaJe7|64sz0aoLfzy2Cn|yam;hi{0ij0hAWPlL9>a{R?9dY}D@3HuCQByu4lcpzI^{kzqHMC&otF?3( zu<#FW8mJ;}_%~t@S8j8KRk4T@)volf)~Rm=PLb7*9y5QYs8|DyQETw#)6jjyzcM%tk#$r(;`PR>l%h z9B_G)OoKG}PoL7k%_y_fWiZ2fGzM(KX`SPlyXv>*u9=x;>HaW&z>xy-gJ?d)kaT=~ z4tJsVUfrQ#jSINx-(81x$s4%z{#QI;K$_v*5@=*B;7o82o91aZ4Os1}rmd3-08diY zSwsJb?HY6``X7_2ryBWoi`^AGTN%=Q@=I45(k)mT6ybQruk!TQYOvPij`Vzy~YwS*_Lb*tjGG%(>Auc9#|;yR*I zG{|Th26(}8sX`Snn-;B*djt$R`63?`Ql%8rV}3(AO<9Td#Ix-XKWH&MILCup*BLj= zDcA6E`I{jcyWNsGX=h%;! zt~@!GCw>0dkH7EyRbQs?XkYls>4L9r6%V!K@K_6Qr(Q(k#9sf&5=j>W(kY3_C1EnM zfQj~r_N}UL_$z%oY1mNtkP06pWu4)FJ1!? ztV6iFj#EzIuE<%jukZ$ z4t~2i|8l%tJY(1DM5rMpP?3}QAv;{DQdkbxLj1-sdTJ5X)-LdnhW>j+9od@GKN)%w zKbxN(hb@9vlue3yaGC`sxQy#EPjdxeJv^Z(lKGj(9Jd|_cT-|eT9HXL5Wq#)RWbaL zEh=2Wzu-5T*DZVhXC;3^ntC+#&~ptGUL_c<>Mr;G*ax5yKBQIDDO@HLNAbWimfG#` zAZpj$sI*F>wnD=@d%^3#I97+RE5tbWkBj4wFhvm$i=IMbBYeO~ns%{OyIBWFCLdQd z0++ma;k^NuU_J*vI|DDM7lY4^Y85s+2j z?op3^yhoOFegLpkS-X&e^|h-#U7zai{s@<)cu);^WP`11J6FA$jz3gb;A&|oxtUiU zBvIzIVzL$)b~j-T3Rm^}+og-*yw<~klXwE6kcvGViRPz&>TsUG2om9L)pY>i^oyIi zp+G`D3z?pkg;-L!Ef1Zk~^4+4o! zOEkOx3b?VHfTDi6msj2}5v|etIwW}-aQ+w8c0lrMJ*k=d>zWtpQ{#K6zYY6^NJO31 z?==<`xZQ~`#&^!h_TKy{cBHuRoEJ>E7<5AMVXsb^Gi9v%!uw&Hq?=LQ>(OFw6`SL^ zEdiIfa)-8+`5iy&GwboPS)Rqowb|bL87`B(!7g{t$7at9G7B#!Qm2vq!B9hJZ``oK z22A^5$n!DS-aeC8PnDzn(dA)n1DNm#Iqy=Kl(6d-gCyWRdoW)~U2TSmX==JGJ9n~B zeDd?RUF;xQBoFIFZAr8Kl%FZ>@60Q2v|zcZvC!Io$&0gMneuPgt8gMBEjNhFf@0Pi zbCP#%Nxsx#33HAc!Q3A4SuWO>zS~_QU+uV}$44jv_~Z40be+PXUZemfndS_WFyHN& z;lE`X)ZU3MBN@+Cky{Y$Nzlq;P{96Q6QYDoLS5l&>O;Jf>&@2rw16EigJ<6pR#Tt( z%UTbn$$}4qUE;n|>Y{}F9Y4CCx}J?55#KERWs5lyQrH$ul3lhqG-wEbxzg$pXd$UCWp0&m9?IhMWEO@`*6;_gnuFP6#SA!9&a zRsxo?F7cfhgXbTa4K!!lx8VG0YqA_^$yZzGnrJPSU2w>oJ}uyulKTYhQBT&Ev8_L! z=2`QsDckR=ZVj6Wb6*=z^CZO~9+^6c7<23}Hb$;=GdMk+2#){d-5bB2OgTs9ei-S! zo=n2_tRAg05(PkGGftNqKeA%;D$agwQukU2`8DtQs4A1=y8c~$;9x21*?Iizo6ZP! zz@_(9pl7Eq%yHN^ITUUA%(S>*$eZKpJdsxj1?(Fo~ zs`_Z@<92%6<{e{m`<@r} zQ4wbPj8R7#m7R0BRQVN-p988aTW@-@3!}{!^HO91Ke!+yqE7~ocD7nu&}WcrZP4q; zgQM9%;6s|G_+(E&u++z<7pjgFIaD|cX`LG){bLx!`BcCr+4FV!ETt~;JOwqWz^m$ZRc5Li7!siP}+s3VxBlEBgRGG zJH2J;JlEJMB+mMa6R7QjgczfbcK=X+s{nSFXxTZ-I9XHgg3vz`Nyunbp@rG8X|b^} zU^2zBiIyP~;A#)$Y`Qmc8!tcL`8{Wx335|;%I7_p-b||PQoEx;L|LiWB45exCu7Vi zWYGj(CpW1@fi%x~yAoiJw9+=;M(X{Ko`w&e*fC7S&-I=R-sCn4>JuU{>hL3*TwPt2 zyKYL9ca_tO>Cy~cGW;%QD*ki96ad%ZOie=^o$UE`!&jaoU|OtOK(TRk@++Rj>=KG6 z^(r{Lw$jUfnI_&3f3W`_n^2Kj9`cx72AYTP|ImD?&j1T}wFL5q2ppAT1_wSaI3YvD z5M-eLsF^pT|2yir%L8CzqX==`fRCb``>G=#>l-(w1ecH@C;wq-M#2vyMt;s{Jv^p{XE`*f&E3}~yN9)ee?i?_gVI;>)qYWr)2tix zzECj}YQ(Tp>X0O?;A+arKmP`D~5AEIlosY+$$841ksl+CXif_G_XY)8D z5!j%Fl=*kKIR70@{pPdUx-J=oZDXZ6bDCJ{V(V*KRc{V%Uu*8lP#KqePjNrQ-N*s0Ka zZh?onB2BF54f%Yo1Sp&MNU&uE2bhnf`am+{fe}FuCo0qAPnM$$qxqE|*|nD5{=h4e zN;yLXfQU*yOyA0o>)xS9%JrtPb`q4#C0djc!BxPTcCZrJu z9_aCz3&hFB9Mbo{*3}AO4rhZ^oRa%B?LS=galf*_UzBypp9x>D7ama#<}jKJXAUn? z@O~1}V2@6oZ=R_m5@frGQ1xR>Tza>>2vASQ~g>c)w9CfZ)IG$ z+(};GwW30m8K-GOl&aIy z&|fk#M-~RCyImq=r_Uj(<+yo?a zoW0P9i-x2eD$NIKN2N=AO^tVkg7?X+FC&-V)IaBeNEr^B9D+J4k^YvBT30lhV7Jfo z(M&$8(w?c&^cR9{R%Ad4Soc`Th4K8y6olNQOrrxS}?U%t$1*nb$JvD!?pC> z+K&16XQ`rReq+;fmHz%@^;)i9c^CHmG@f6^`*xxWJhs)9LZ>|Su~u$zW!O?qouZ_| zpyWpQpdKPCaq@{Wq~>y@Z7nr}geh7&p;B=&i%e=EWCrqnx*8FU_+B*r(h6bM`R;}N zmD{pd8v0#6^`iMQiF?(-XQUdT_e9-0XNyJo&n6_+%#are@MXumHHLaOfd$S~grY?1 z9`|^yLUQe|Mc;4D`tK>|m2i|*Jc{y)rjL{fJFBmk=Xy_Ln#O~So5Al2_mW~j+>wCZ zw5@8-`%d_ZKZhi+*Q|ZFjo&7c(RDi~ zADJN5=xw;}J;yFI>L9@lCR1mBoL*}NG1h&jo!4R7JCxwgT=BFtWM5GpK_(BrAyCz3 z1yIHNpG>x-DaoiA+|9W?1w_c@sMcg!J+tt~s*h?B3s#A>AYqPc%&v#Wg9a^3H9 zIl&hBczuBJ;QxS=fxC*Zla>5Iqsw&Q3j1lrn%P+7AFu1jr;ka`DC+2FFC{Ra(QcSS z{ey*5gtuzVQfR6^Id{m5!~G;v1@){r?o~#aYly6=+M!!mUF4M*)AdzvE95{+&ueLS?sRg18-KY zVrK&k4S;J&M`N#RG<7xQaNEP`MlwOGaBH7!LHy9g1cK%&L7KI7J3*kR2Pdw`*i^0u zm{Bv#c*r`ruxn@ix!}D#Zd%(&Xk8iQ9o!?UqsE!lWZV%O4Vv`{4dS@h3Yq@2_=pOK z;)2IZv8pRd-Xy(bg#@5DiES~i`klFxG8)*|^RHj1pASUE`qPDmhNbK|@al#2hIXT% zyw_emY;2d<-e-sZmeVF%I?yzeIE+-wJau7)sZfRIC3RVzB$svCoTHdDO_2{=m9qR`9zJr3gaU%NsYe}Wcr(Oaa|lO^+iz~Y7TUcuwG3u-qfeDVR`6b-^hnD$uTm_sPKwahPP@C%@%?y_ z>Gk?1g_)6uXK-tzDqi=(za?HYrW=4L{jjGNAKh(8$4ftjl&X+9r`Go$gz5e$auE*4kT)@V?fS z4%h2+6*fD$g6Ow->$LmE>h=JjBwLV=b8H9z06Tm28n#lSe6pBwA2myKfZPV(A3y`h zn>W0xp~d=tL*x85X`Ih+o3{tEAU&$ht0gLwo*S-D(m~CYe&<66huxB}BPY{xjUiEW zfBs03z;lOzA+x0~ugA+vfp^g+`xMJB>qde_KmIOGRQkUs6jK+Mw`F;kypP8^-GkKu z2#QMmP8h*jw`nhG$5wID#iVB!;jIsFNARnrB2&s&%@Ky==fxoM8u9J|79{R(!qj_H zbk@S_Cp(EG;SaxB5V5{W=UIzf5nAe!m}R;CZ!#w^a^cws=Usv@(KzE=kSS>Auvcm-v%Tc{dhIfrqnNnWE; zUacZt_ddU2c=Fv>XK|zT8&3&OVF}Y_9hV_#k{|aZsrQ@-SIsp1qvI5}EdA1CgPsAmUf#U$J>hk8(R8vuU=b-+kABjtOjhvQySW4u;bB%o zM<8ckChK<1TWw)PA$2DPI8}JGt0Y3{jD%PiskF1(8x(=A2;!@#sifj~>C@cJOqy|F z{<;j*YhuG!WrW-x$2lVn&CB!i%V)mAi+=+MdRYl7f-WqRebtW_u@J)`9D)eL_AkS6 z2eLt#%$r!_h(j7m_}l5!m^?TzSp}2Wy)pq9{UudFLw50|?)w*65mzz8WLJ~rk*cV1 zkzX>9GGc`6fDzp&*9bi%T)RT_rD}1)yi9{SJWm#t!u|L3V9=sz@yDXDi2cTLmIcgD z`6}$hFTV3-$&J9DgQ^jzR7wka$MGnCzVU&!%Qqxg>MKO#uD)N>l}d(-tkZ%C^%}!* zI29$fpSAg!!a4`~SbkKs0`FvMcI}u%@e}wSm=FEDy**I(J7QH{+R}%+(Nnhzj7m0W zAVZCQ9?Av(JP@IYEL^4DE*eT<3dvgn2hxtViyNXf4w?UIxqjAxudp z&oLQ&Yet`et)l$C3{K0975r7tk=lyU!$zh(?M6qcF#*vmxrEag!@&m^t96nZrz-O`E0$Q}@Dr>4_S5Gjw z3-*4UTFW`H7<@Twj!+?1@P#=6))f$)?0hhNhn0cYW+NIrAPz2S{(F$pS8#i`4@RP0 z{dL4mj?{`K8^yD^sDcswgvr{+DrH$4p*6GVt6v=*fM_bfL&o%ke>12d;LNK6Xr6br z;#v~zK)n;Sh#>>`=$bno+^`2!AACbEGonNarCQOM@lfdXPrD1J$P!C@J&Zs9(<1dp z_wAnRDUc=|wB#Nf+=}YEKJ|DO)PozQF?{KOA7Y~bUs{9>mevWFe_G%jOZ?ZE<)$=9 zR~?sgp&}3DVgpnv_zjLT-44K`tGH;eg7~oLZ$h*spQwI*_P-PyqrLxO^0n_~}hs4+Ioor!I9iOnX&W{@%<72H)3qywvuc(L}SAwJw4*3|xj z|J~Evll?9u(N1>zcWp&(4~3t6bV+uqE&CutmC2R!)bzYF)%?6n0{scu)w~%>;nbIL zU>IFL31_cry?dXmSTR2c0(GKDN+vCQH(Z{7-f7KPgAIayv7PWW4o zO^mTqRCP(orz^=o?c+&CpsKC>4LkNw@uAq0p9lPn%EgesrldW^P=i(X>m(2NZYE~< zfPCoNOLe)hE#Vb~r7E>?1ohwy=Nxw(G+Iu&#d8$B)R7w!{~!f!EaEc&`4i-tN`^?l2BQ-2EAF`xFo;*I%LN=W@)5NxWu+M zjVVL?wk}*X!hvrI?*?ZUzk$EHActFdG~c)oxRBUY`P}kb^v;T-?!va(l zcs2GhA(QvT5@XvMhZ?+Az0Ch6A#7Xu385H0;eJXi&Eg;l4Rf`K8E@1hfPT=C=eeGe zR4dIY_3LsSy&bFM9#Z3$r`|@qnqx+vg{hSCK9T@ep>kgH4*gSzdyXa z)lAcUa@`Fzm<1U#+ogF3lX>qi9B1T4{IZeT zkL3|3m0RWG_INgPjgFEqoGp4<6t~MbogG{8hvlhd=J!evQqa+gh92QTz+`Bp=aS|* zzPytHU$x)C96Rf)oOIC7+BivZoG*=SP-&NivDB{&imNVJhFuj%rUt%zX@Ac38a-7Jf#1 z_X-r2aQNE48ZHQy-1tUzhZsks5|N2UUqHVOaw?2`ww}EIRiAgu3G1?@oo>u!XVD4; zSk?L`qH2{RlrMo&Hl!y)%Mz>&L#bwDq757u-OzN@KogTI%3fyjNd({tu4(FEFRb6ksxQ=5dLj$&QTk-+3r&eixuiU`X5^e=NUi%Zch46nNUwh}e~8s{BV!4s!yuO;luBCUYeD2t9O ze{0?1jB1{bTDK!jF&mILm-RZmn?A)9#EmNe`FY^dWGFwOlm*gut!C(Z)_6b7IoY4F zfppx;{I}v5EEsUN;{{uXKYjzIwW`#s3D4s2 zT~(J2-@H912CS6Z{EBkoJe%O~TAonM*LqU-e)KcxoY4erGDl5Gj26Q9Bk@iWE}vZW zm1SHc>B*O#%LsxK{$La4!vXZG#&&Nmta(a#=wAN#{)W9D!>;l2uuPUJyv-Rok_dS7dHK z?Sq{htwzIp`5L)zcV)bMZ!3C+zey>*bFt;7&zkNDZKWG?a9kPsZW-Yvn@*wL&s=yt zYr-$^x(i>{zQ~TOV2l~gXu(By8PsGzUjq-UfJQHcl)c+}&Mp$FBLGWmx3WT;cVDQ+ z_(Ap;X)v0TCU#ADh@HD^;g%n9l^wrVh*|_U+%9UW5DB_|B1|zd%`i3L-Dn$Gs+xXk zb;q&vDUAJcG9TYXk!byvrAI6*b)K2%rBaT!O5>Y3LYIKPa|dK|4Joc+<~2B$d-N~4_wCjI!PWGcy?x`8kmMm9#u(4Uz2W_ z^l-Xlm`!*uJ7;V|)QniqKOmf)>?2*i;zM);hhM)WlW+9ZgSf zNyCr$M*)-Q`-n?{C1SUm;n=Ab23gNd7Pr!kZQz(O;XYF}GcsLwE+nCetPN&+D`O4# z&Q@3<%gMr!d-`A`b&$j!=62! z?NF@zRw0EYCOWVxF02FrB_r}wk1zI|6q5UCB)2L?jXmBqDV#O-*N9@9O#Ya`nnIRm zG@B0Q6Mkx+Fz#!1h$S3WOw;B7^<}nbt=w6_J@aA|A26@4Frwe7m74040e~asNbcg` z`VgoLfrVjI5bb^@(cqgp&M!gVWe)ts<2{aBhLMaa?>>sHI8S0}8V*HYQ&m+d4$U)F zw%023V>B%}VrE}0to;}raT=`K!`sewMBb1g=Xy~pN&7r%5=%cGVdT@o!TX#6vG z{P(Bj>30oWw|A9y4p%?)@`5%U$Aqr*sDyRia!(-a3>RKN|0ycs)t0UdmeUHS7-`>l zdG?D#^ae2stH^Si(NOZa?P|d^*;U!jW^Q|m98_+KuUhg&fRQ-Z2B?uThIqhL45v=484OclZN(obvM~yYw{xtp8CqP zR=LhkiZeunR8Th9vB18?u3niS z`FYm7LfpLJ{K5EYyXeRAk@cwO%I4pvd%0|{K4&bM=8MyX3eL+>^7$_; zUe%8DQBhGzz9G68N4pyudAiL@=a-;xrEu&TALNU7S&vYU6k)hcn0y=)=V89z#(`8WOQ;XoeuRR*6N*U(e?)JC_g(u$pwK3!g>N;+1{H*t{? zb|E&s#HCXQZThLVVz8@HO5e9$?B-sdX@W zz&c9&1#FaKnhNb^WxAs@mcoK%%5Pge>q60QIs+yl&1>}rgkv2dzv`(HMdF2pRN-PV z+|(2pS}@OF+>V-*f%R?v87IFc>{Cy1qb%wOTpxe&OE0KaMn|>QcU>M79V~;BowHd1 zoFw~|wdBBsz7C$<PHb(-T3FBpUh#Yz7MkIh zSGvae#~YHIwVN(JJj%2qOQ8le;z1mBG^sG&2$rlLoZ{Hu1t*=Ng*rt^)wW&l<~4+0K^E;95>e^=)c z0z(HhP27(*Rt*V_K2hZNONoE!2J*64 z$IRRXg9f<^ehtF>ndR6@xbXiuFeaD2gb4?lg5 zo;mUm!3sjrIIDJ5e>%LuzW96o^k`<}J0#9pdy@|ptF#0E<-}{zMdtg~BHNXp$~V^h zhm5>dM3H0%xt=eO8)*`8ve+a<5Q9!1{`H4g&&}vr^#EOWuiF##9=PP4#;O2s>tz%T z)6xlxGWG^y`_W_@Jxs!haIW-!=~r*D;c*pg6XZVG{nSubh{sCl14i!kY1!78i5HE4 z3H@FCOBEIM=J-+;}8nAJ$xM=3pSfv#FDtma5A4*E<|{M#i3900LgX0 z^Uk`>zRY19(G}eF9lMFQ-V5iC9GBPmuwN~^_3n$2oJz;nz?6cl_v90{=SNw-unwIs zhU3?RV#W5Lcr%y7lHj8F>)ZSRtC^&;KKkc@+b9fw^$47u;C%}9x+_vWLqU)g-}gaO zDGW?9R+gxwTiI3DMEpH4q3rrI>8BGNp&wR|(@w71elqW{hWjjem;tS47m> z9<7e~*z(ybWVDKHK6Jk~j~mtlOgslEX*gg!s1V_w;2RXv#gAPo@4ul(XQEFFXQ6*V zwlwaaU`>#-q89%42SNKS^SDdSM_-JUJ&1!%-m8@$nW~{6+bO^0_aK0cgtzP;+(<(z z%G*E9Z$})bmSpG;Wty`X5_F>foT=~8^R#+|co*z5{PD4>x`FX*Fn}dhy+e4kNvUq8 z*p!&;-@-<~{remZM7aAVpxdHhj6y#j3LcEt-(W#58acEydkLBK9o=fVvH~w6rkLK6 zj?=MiDA&Iuqq1z{vZ}0_^yVSQs#jC8qJpLY6?VUh-wX-Tx4%3nl=C<^h=>^n5H-_= zJOE$qL@Vh{3dazq38zXokJYU(7ZKlTjg3F)zu1~Ul1EcddGnfXfIDN{HRft%m$E3c zEWONjJFz^;Cj(yA?*Eh9KAEg+@=HBriI|ETsgwb#>}DHvFH8FQG(ms(A4{wWy7tQ5 zrtJ|oB#degG5dbaB^apM={*r43_jjkD8SOApBc`j_whHqX=tR4S@a;*<(7HY0hz&Z zcE0xNtGM{+-LVFLd*aG-z%M+9WrOz=vVugXYBawn()o3<`%tB& zzv~Xe1{b71F&=WQKRMBG;+}df)Zg`YBUZZ2Hp(PbkEdL3M~VP*yVnU0s!6kHK1)33 zfN(a5fg;kPcl{t~hjw^MkA60EFH$66F<<3uwSt8;c(ts7+4A*|fWrLH%yAZj>Z)Al zka?AqXi4i~84@f)jI)Oz$Hwgf#^G(N_-}y$sewFH_VyBZ#=$<{@XI;Q2?*qpTMp$= zhm+67qIn(qOc{Lf&ir_iW8_o;1v4Ir^U?`0dc?fH+2|iLx7dl4oE-b&x`&Qgt0#6Y z_THy;dv^hnx+=8gm23-55}pjVB#F@BN;xYQfD9pA3AACZuBF~gm07>xj+nJm9k#Az zM;BJK^!qse%93KZsMuCD%*!&5rO@`fC{*uGw=P&lW^0rbJ@s$QU1q-c2UP3sxK}HB zQyclrbC3qZ@i2QH`MEpSou2qoYB^ zHKG>^YDAY`hY+1kGq<-LZNB@u(xf80l`?!5)o1ntw9)vuPJ6ksFN=66T6{HKW8VjH zWps_DFzqNmp^@Qkmo^l14bN-Ddx^eLgj`Iz-2B3QPXzZ(87JUqO=or^3n+92WApZG zm$p|kUF*N{u(3;+i2Z)6JNVY7$v@&dhlRMNfLw;%__X7qTZ>1yKJG6%h9d=&Bq{DQ z0DJQF&Z>9RkG_-Gp>vK-94UNSRV~AS{t2P$0}`|KJ|?df7N;bo*1Dy%qzYhvuEUE|}!vwH|07kxnoc z`=x^#MjWn8ri)1hpi2}s)?#(c6SqT9M@%+|suZVYmRB2$BQpvs8dodS8ZjjbU9?|} ziL3kbI_eXbYx^pzN7!K(uDD>gK@jtrqZ=g(7u^{}65ODo3M1Y^B(d=s10mY#W;G?% zr4xE)D3=I@kY>9K>%wwHh3DFL#T(%_&sH)tRdarIUcQt|yE+}nSXXQG2CE&Gd+)XANepZ&gF4}ghDUv?B; z0OFznPmoBOLD*>)OGc0=nZ>Wi&CBU*&F}hmLO^(s`2Ys|`X9ycM zfC+$ID1A|ss3vQyi z`0aNK*dD*5ySd7LGg+q_MYMgWf0e=Bmst@7v#`tHFlMM^x0s1dhpSAS$rsImlGMr; z0ik`bXa zxilI-abU$MoPx+iGQ#G@NXe5g`)E7?djee|cSWGb?a@t9;`Hkv*fwszijGLAE3@X4 z5U1ejk{Cl273s|aeTgaqj(I~f&r_EPF`0M}4^d|wv63YDlXv_z8S_pMBx5iDBhPs< zl8Qm1y(hc^hk9F5jh8|y>jN9Ptqzec{Wv+!S;jP1kX^4O>LfQoBy|xUJ>U6BTS3;) z4av&}gK;vw1L_lwXm%g!#_{)NR3d8TX|t2t{-r9)4bCNwVw^J=%zz~>|1`d#_}@6+ zL#aZnzji5O_a)XTlZtBIR>0lmGxP6}Y7lM-3cwOhC1{}wlMflv_rT{1Dn#PnYEXD& zq4LTL-E)Epc&W0V#x{1!tW83$Ia-L4w3@{7jpHt9o?vnvm-qbyp>&DV8z-$@G~|gQ zvU3kz=iI)ffFaUHH8hkU+MX=qCmu@A@r3)^^Qr-5V}#@R%)|9Q(eY85s#*I3&W=;9 zk7*AaKBO<;{X_nLyUhR7YNks{N)kG$tjFGvy#1~I4^#49O6))wlO79IvL^q3pkA)2 z%e`5cX`E5>(8UElNB5P;sz(@hV%uKoilFlZwdfL;aEv-|E9^e6`U%HKQPE}ToI|#I zV<@)$JXAdIR^<`)!P@kQVk3kLM^ICrDegl?=HkyhlONB~z+>N9ZebKZ`rW~16UXyt z)#A``-dfv#uOE+turDjHIR*|;Ld?R8;=DN4FBf)d!83nfL$m$w&VO8CcAW%Hqipum z_u*qimsyJm~C<;D^OzwadFY26TH(cwt5`j*wrtN{CvJXmEuS+;yJMV_ z|55*<1^6$~&86<>#Sg+TiF2}aK~Dkg>Jik5rX}0!p$?9?6#J{q*sn-zhg?8JHl0#9 zfiZ$lYUQVVNG$_w1aj}MWo85u;>@VTlhl*X$R^+By+C=t z2Pcyg$G(=bWZ1TMWDWj_*Kgm&WozJ?Iht)7D&Ue7u+?m!k(sEId%aHYu#~5=k!9u0 z77;$Oi28WgQ(j{QyJDM76R1R%CHAdKfc`>X7-WIB8=f= zmW{||`smszqca&N&-cNWnEkuPoO^X{Hq{fWeYJE0Rpb_#@x zp5V$IxMr1|EK?K`7))@M5V6S4gURei@v|nB^o}Dovv8_%l9_NnUK>9fg!1`DIUK*1 zU8@LKQawUlw8MxXB#>qIjU4}%SwqP}-iq@pw_$XJz|uF1H5lvlG#Z;0!xWRJ06}Bn zd0)B$B~1?XOa4!8GNCm!1PiIHeH%k>Te~^oCTkEXn94Mva3{f{-Pt`ceKi8>CWtvH ztd<3Xi>i2Vvd*re96lCLWOS~l@3@um0C$iXdRr6`>@!s|cneV0{akb}e<-}I zPSUM<`u9L$YV}?eUrrf!(4AELS zJrNgoYU!@6EukOQaovL|^#bVfG zbhOvxSJmsYvWCZ|srQ-a-GH~{7o!L7LJxt1P^SQ*_;((E^|&|6T)}EBMonZZ(`K)o9q&Iuw zd|izuAI2I!Zyk6^pqT!p?L2s?Rz2JL?oC`fh^O0ji%U~V2%{}Pu)~hrJTHkqoWIp> zxt3MscJ-XhbDTb_#!Cwh=GEp7@*<3;Sjyp!5SXzou3i((QTI+l-w**frhsX&E z7h^(?w=enU+qFvZZuO;H%KBuajM~p?d<7ifw=S{vueZr$aGD?<2-ggxi! zwajQuYsZe6U@W>!`Qc`)cLt@i>WB|5c}=N`9k6(d?VP%p3I}igM=x34!1T~#1$1`m zg6(Fna*TItyUoDWvr)zg8%@c<%@^g9x~`d*IEa@}kt7P>z19#;iFq7Q1?HF6pe zVnP}#F>XcQfYUj`+);ks@y4y#Y$R9Z|BileBxB){wODkkVN!M6l7+* zYfjTWQ;`MKVw;Uca{BToE6n#FR(+IazCsD-+Y7tPv|Igl8LmA&WJf6Jc6vXXwQl)* z5M3l)Xgw)mztuBPqB8Ui^HsBEF9-D@sK7(6|2{Oz;V0Q|q!waR@PK$1WCx0Mw)cxS zUi-O1NBm*t(~lZB{3k*OePb6}pd5{sgWV+KSVH3mwi|1R)~tjRdqp;U)cc|NzVC|_8X*Ql0fk5+D5 zvjhXK^;YjY)-U};63~+Bf|aA~x-rh@**sx%Jc&bI?`&97-qu{+qrGX?(7|}Goe5M# z47gEd;dek)^JC<#{j8fv%aRG&&4QWAUGm>7$MHk!ESv+=h43u2wUKtifDy{b=$Pl~ zZRgnG&Oz6*X53TUDH3O5<()hw{}?tzx6uc;s{7bew+&ps_E)Z3FM#WpU4pCPgRrHV zBk$?p&y#2O%91A`Rmv6OFQ%~+X_wS=qK+SZX>N0bv9;pVNA$c@va@aoYGPF(a7Apb z3Zu=n;m^|`)iP19!2+tc*zM}edcUElpJv?M@qWe7Z?5+NRb&#bf*`Slx==L;P`uzG zUGS7tzoq*~NWT9|rvIa5 z_s-<HKF^P+2sy(ISz4F#jzz}L4sy`3&dOVak1gaYu;(3){WZ1fOpZ}Gf z+RIpl8hxiRkVIQTd>vmZ+my%8l3G+qrW?54Bc-Y5sULH;mhtnPRrg5&C{%@)l8`k{`A5sD1FmU?wv+89#MRoj5+ zw9|TmkHP7UTDKw_v8?xbMOF(#mY?lg(l#uuPk#rPAdg$4P6q}t5{3V`r`CR~NyKa{ z3>d<4pvq-{i}N(ftV5StlOz-asP{mgmgoh>g_by?_L!i)Rv3)LE8y&rr7nW8wTow* z!l#RC{N(sNZEU{U9C)+u)cf>hZ1i#MK6>x z&Z<7J7_1%e=`}B=eIAKlQ`2tDebTsL$HRheXU`jyMbEr41z`__;{azHx5Z z|NjVSH=gj?E}7)!PqZJq3G&22GXG1|MsPGT_dSEcEnxF`7u>#ZSf$NzyP+)=eFGke-H@EQ75A3-11gS=gbrn2i# z82%{ZztrvcPB3K!5elW=07tgiW= zPXCRQX*+I8)9+P$I0 zuR2khe9iHg1N9!+M*lxtz4cdA0pIS8w9+Nr-3`*+ARs6W0yA`|q`-hQ$WYQPAtBu* zAR!GyNr&VBL&pp-47}X$dG6<|b^d|bv)2CQyRZ0Mi!Ngm5{B*!p#Q`!xa`AK4@{%* z_q>kTVvC6R!Xwk0lO3;)ZCIdeE}SYKZ>L>QV%$u3$_m8oztej=<|u^SNKUI7Z9L#~ zEI%>SnjH9^$NIUakR-g}ROcge{?l=-XVWMUZYtW~ds`k|wGNklq^9FtpBFXkeP=3P ztW(US82?v<=~g-a1B;d3mUByp@m;a)$!x}Rj!!~~(wxegX7btwKXZ9$P}MON4gJ}V zQz}9Ja04FZ@uM3 zi!zcsqpa9A+r{Vc`S!IR=oj7;OMbJ=llmJkz$YZk)d*G?;(JlT@BZ(;+PO}%fcel0Q@)nZozC$JXtMXXtbK&%m}+nQI%=PGlJ`y&v>aR1GW)wrMos> zJF$*WzGToK1t6I@Q+OI&i|8o01xv&WDKnK1;I})m)ET9_vmVO4kyIk3>Y~rM0wpo> zlKpkR?58Y35}-$iAlJU#dKg02f7Qo;yiWVU9;G$aE1=&E1ZwSasZOJPP0g1}bHt!;&)9>2HEGwfTf1h}+ z_K2zRiLA|wL*6)u^^-r9yv2UO8sVn!wC-DZ+D>I_>s#GGi>wIEyF&I`-GI@>Le!op zyFW6UjlBA{1j#>7<^MD8s!iVMsOGark60Nv9hv;%FCTW0^P_8Ues}dd zLD@Kcn0bo$EQJM2+N@#m7j>xAYq!Dlo$C`ninrNp!PYk-ffmW3bdE~Q-|E99yr#N` z*7yTmv~!H+kE)!E-DiWxm{0qsm)6DSR0wV^^T511QS^2JwnumDhqYc14m;C%4_P)Y z)N-2$dMSXgYQ&R8+L3FNW;+7)BOv=Wmtt+dhVf;p4d{3?~lh*K~4hQ-37w>CXDe&M018FVx~_aG(A zyDahjuf3Yi9=UjD+i+@XGbTja?ee#FG0Y*m*}RXy`Dm=?{I2o#66!52+_q-8@ees2 zBd9bllQ3hbveDIKUt8!w1^2J3KZ1i@h;`^1!;rA`-5uG7dzsh06B$;aDK;QF(rKfo znEk2rExD!tW+ZMu@tC7!fv5P& z==&&s7gwd>MvK&fDrIxIVlR+F!gIa4(sSYrXHl<1Xcxp0oypmAwlmjnz0zm1@(S}T zt<;7v-6f+|62|{bh!i-L>5i>ta!R8gi=X(J@t#k$A*~lNoM@|)yT?s6zlWr-_0|e> z;8NaS=~@Pze9l}}1UwD`y}ik0PP=7%m-nRxU5gM|5ku98RLmM!?~H1?1@D@UIwQ9@Hj}xkFfA;$Kt;Y8nA`@S?IY1fx+N^y)`Qf z2ZA0iFyg+}G4LlbeY@_HxPRXoX{cnDV#r6V&Js9pWD)8JA8x?C)jZSs(d3I#mVrC# zui*HtU5a)cf9+ZQvM`!&W^&b2IfimCXaKbemw64Z>QJ+Ig$k*-k+gq$H@ZF`HH50m zC^gOMIH&sNGj5d~$nFPoJ|~;$W)lT)b(`P09c;(XVV0~-mN|lZ0UGte;W18b=RjYl z+Hc3zoPjC;_&e@szF>ef7rAZkm#sjnl2G%sz2dhMj%4i-ho(X|1?sXN_lrG1qY<<$`)Co_xKsUny+`Azg3+Vb&AV&XueAd`!gqX;SmAsutfN~Ti0RrXa`ldceHyut2r8qm=44D| z@|^wH{eGOWrwUQFKLr=BM>~39eb2yTdknTX+Dbja&t<-TJ-;pKR=8mP@$a!wqVU?s z>>bwSWGwV;f|p0Pg~Z5FE}yTC`9+Z>vI$Hwle?nVewaAEvEt)ZBye3h7DxxANFM7{ zcAkC>>Xc<7OKhl4@&TYV3N7G5(}mhB@{~D#X)Y!>shlH)LV_nzg#G);d!{_q`8*=- zsj;Ui0OD|<(2JvX4!tB{-+i?cBIJvQS3j-H&dB!0FpmP6q^0J>zZHyOg~a4R=Jjc| z!k%<6!4Q;2-4?nud;pJVgHeONTj3>tCa;>0=%aFv(LODVhzl6de7P{H9+csj3#v!l zCP9y*nKYl3v`=y$tVEBTSys)KV*Hfz2>X9(LYzPac5R?%ofx}pEb4U)F;|8wR6 zW=FDp9(2ytixb+t1|{`?3>XIp*rD7c zdqj!m`X4dh?DsI7zCpU%Jr6@0>N@dxwNuysVL34vTyTFg{%QN==EUmphUeGqwcudi zEorWExS2$~W$+)Lrxuf>|3=qYO!$9JK-#69rv{RknxLL|w!wA;ewatnA{DJVIj@}u zDUj?X;C65!dITtulvEh$b;Y_IXRxXGAt2pf7{GtFc@34798kU z^144+q3WxzXbf)-udJ}GmYa?oJ_WxA$rpn;?ft(wT+do^*zl@kklCP(bX@O=j|>i| z(QaEpuRRKr7JM~>20;+HL*5lntm+=G41U3Zyhe8P&l>mo{l}-DzoM?SH>OGcQck@` z7*8s`@5;*jXNN zalWn8NX^A`J2?BVlU5E%pEok$K zylTIICEAaFZD?N^gk}}0g5~kX^G9w;*p8o7aUPL)am~rJkmNuxCdDS-K^9Q$cNN*Iq zyfVv`314DGwlMh#Eq z1f)0joMgc-(z1x1G(t+uT%!j=>qF!)-e))|> zoBX^#;F~l1Spg9Kb*?VT3?8p^>ab|iJhicsn(~!Tb2Q%`%7z`>GR~*UE6wKdTzT-2 zTuD5+i>EFSn_3=F@B&?F-h7WxduAlRTKB^^h~Ld%R8uK}$=$}pp|yFkS!I!ZcrSf^ zO=tGGQs5!AG9=YNj5@36C@a?`yGly+#pM%B@sH{rvGb3>7-|f-4zHFF+v98LnsK4M z0EzvQb@O(!W%h9_*H1AZv_)lu2?Id=j&mY_|KiuWE3=360PmCB2geZ+#U7rgUw1w`NQQM=cKriLN9^I3!QL2H zrLs)K6a7%FVK6d2*L_ccJ!=h)%)ue z(RD|L^H^;{R$B++r?)MkP10=fCL}jJx`CO1#=Yn`L(1>O60_MXGac>)*`#u}MjXFA zEU{HGt0m_^YcOC#;|Kt3dQ00X&*PG8$c)`Q6@w-Cz|!Li`eLP3T_v1et}QCT`aWyF zs41{hot~~LFQYwr==>9moviKOs#lkUXXnKH%|(U6z^~iO`uzPPvEK8i)@JZ|qv*#| zE5C#0)4&bn+0j#>prh1+3OQlMcC6b4*Y}ZGwAG~Y<#;@*v3(8StVRRyp&zY8-P2dg zM3Dh|Y#Ba_FyI;yO;^_I(j7K(;fidqTvLEdFUFUXDkaN^AqB3$*C(%zyw5d}9%zah zrf;`yA!ul;vd4Qn3=Qwn;#soPxj6U5t&~c~RIS)tJ+~MX>eRDh1_%rJ$g#6sC96Iv zVBCyGDTY2Be3lVKmH(7AltHbFUs`&S^W|H)mZ&Axv>pK8SBK7Erj)%H-L!y&ij@_reS$}q{{N7|< zF`T@GFPJiUwP$S8%ZLw{(5wiF0RH5Bv73 zXg94H$3|8YcHH4=mAqA5$S6Eki^d$4K3@JyiK;zH1Xvbzut^7L?nFf4uNwaDL5f=lFPal#ujrQqhtZ4VpRIEOe+r1BxE6Sg$QtDO z@>@k|Yj)hq$JM$+UfJt!$<_y#)ikJcRelcbEg68+w(PtwUD18z0C?K#Qvo=7^*dD^ z_1oFFY&@M+5Fp5_2u!Ce@%q+n37i9U)kyu170}aBC@0p+QD8#Q^E`K3+;g8wj#k` zgq2@dnw9S=-Q}R@Ao9tCFDoPgFt9#N8pdjESkj%7E)enGsT zlWSrWpYeBUWKIU4@(8KZPO0tfSKC*o_Urezk+7%r)SD?s`CX7?eX!h9J`(lu_(zDp zSfzPva&S*#V$X5j&G{p8^!M)sCfquNXhA3uXwRtasuXs9%lL>E-Lthz#gHTZy(wC7 zqLBI5#m&MNQHC{`V(fJAE^u;axidHv>G=xd5;`$x9e8rq_WsD9>v=!%goD>MrTpZs zmVU6v)+?RsoKPHMp^!q5Q+G=LGr}jiZTBv!{sTch#(o2v-Q2I>|%1L?lTS#$0=}Y?Rim+yx;<7l| z4X!qjdRiqPveR3;)T){+#{Tt{h+&Lk!mg4hoTy2>NMd|-ZhsM7c}D%1x``9Ly0%E~ zhqT)xcvXZYd8d9t#?v5w$@B(Ak|UMLG$H)q#w!Y8?#%;zjIoWMzGYrmaDVt1J4lzV zx$BWYuK|sSpAzqL-1ko_{G0YXJVtpkqv^}p{IEG$eVHnKI+mJYHTGXaMQ~1eKT{mSk(j+)`grcf81*+PS!`^FGdp2?;9{WC$6dom?n7dQ z&Q3Gm*r`cOwzyC8^yRODavU!ZxddKEtuDFLdxL!-K9QRZW{8by$S1TJJJ$LpY=Ykn z6-4CC#bElVG?rS#i(3ty`AXoUkbMzX*l>~l*hAD(5T8BBp#F&EwfDyMSAD*fx6f15 z491uj%I@I?FSboz5)eKjl^?Tsm_`l@7pO{}Bb&Y?{5xgG#Y>BxLjFMNYHg%7jQlI? z2~pi@l^o3yo`~v!l-MR!7ORO&tzY^af4_rcG%!8C^JP7b2^-&g_Gui;rPhL@3mjX{ z?Nwps6(VD2Fwepm`8t*5l%QG;r$8$a)>YxFFY#{Fw~%Z~$iTFP@qu;s?@Iy}m=ZBy zRJt&YOw2UI;kWtLMsG~=z#KpReZTknm&y5|$_uACU0p=B=1KiYH*l)Ki3)qi@ys3%m(i^le>Y+uYIk_b}7Ztpc z)!Yd-kO~WROSG=2>cauz6Z3+xyORvs5<@YUiOA&~WiRE@PymdAqSno(Aw9h|_v+*+ z<03#jC*+oCD2mOE08RNgRv4Rd@Qtlxd$ch4t83Xu12|?nt2IZWnji<)e5|6rSidRU<1-Ap7Q!rqlHr>KmJYx`J&T$(#(F>6vSZScd^=xCl z7gIvefzF3{{nxX|r`~k9K3xc@H7eh4Yd9pB@+nglktWr_;Q<5sBFu*#HK zECZ+lL#mEDBV{#+FGA~>v6!c0`{r0sb7{gE=Ke)3iZ28&x%)CqFI{nBH<9Uwl z;$oaWt{)tl?U$IyCNMyf-LMEBEEW4Vtw(%RGYTAcPDp0wq48?#Gzb0I_X?qqjLNq*0mmbI<8iZ z1c1l=UGq{k{1P);3X^KFRsTcMXUfl+;3 z;cm`-`6_{ld|0=Rb83`al~r|GNug1?##s!EehYEkr??oJYd3j0R~b*H|Ba!>W?31~ z^*qkzfyVkSO44qO`EgvQOD_Fe*6)egj-v&8)dfUky^8mg>c6W$d<~T`$I}pDjMbG( zGl9iu{?8JK7uUR{-HfJBkiABeo*t>4SV5;39(?LE`69+8^>s+x{>n*`C5(NdBKMvu z6%JEQ*5s8k8gY}B6Yh7OTwi-qZo^3%YTMhJd}h-=LdUV46YKI~tW@@BfNxUjnP;bn zA+CTUr?uy3PM#m&Rx_u2#QWVLitf)|xO0hoj0bl@o*nDvi@v)>Q1{H`a*Yb3+Rt? zM;iMRoQqq8HbN8VQ7))`Bn13P+-YWZ#8uyT?et7TR^L$Jq8pAX6@berpGYn{(%~)7 zMinY& z-YDSJzbvMUg6fcF4>JpwzN`5bzK$bNpj*11hb%Aous2TQ3-Y_P;Ixtync`anPbD5Q z6`3qz6G!g?kv($#5PXSXF>-o8IX%x%*1h$LTZL_7J^3yJrdn76Ha6w>T8-+0gZaHa%|WIkKPC`gDC z=+^Ctg8@p2y2gx>=tmoTi*qZAb-f9mELxYV-X*Bj7ARlFQQ~#bL|o9u>#Pz7x6$E|{{!*)Z~0rYVIvs%xj-K^ohY7X{XfaZ`>v60D#5 zYDcA8eAuf{IPVU;gxSP8Qgyyhx`z$Phtd09R1`i5>#YiKZaQQ(S)FY^U?eZ?-jiL= zDCnW|bQ}%b{1Ba;JR@A=h}hH>HwxBj4C_%A?X`(?4y|ZtR1seIGGy))%_F zV_l5?=Z~eyW;Zw-M<710=hlDYm3+b=U*@eti-|%A)it7ZYJ#_hIUG;QnN{f`BJVHQ z7j=OZ!!}0ah0qQA=?>LVu~c^z7C7~DPD4EaWS={Ls5~dgle$xRKkd)nxF+URkF4O{vS^=AFkt=w8&l%bqL6 z07g4&GK;5B70x|+%ysSc_;v+e+QldWUhwE3mCO;8No8r}Q??j8FVx~o)IwS?ZNebhfftLnk(zwN!%`4tL;HXu z;I0dzVV**j` zXq6h>uO-YSgr?_ITT^$pbjapnN(=^K?@NpZYfQ+@YVW>B94HkZc%gpm2K?mx9B%!;5^82S@iK)jLQORi$Sa~N6WM^?Si^50ul*69B7TfTXWlfl-k!B#8YV~nrGgRaH{$!Y zEx5SKAoas*rp~aT24lRD{)9ELCFt+lw*99R(iaS(akowM%JJ`?w-@5L#19RM zY;JbI6yv8c=oaZO%<%4;V}dJ%S`Y6loy5~^FW_GV3Q^91Z?1dx(Dxo}W&@q@B*v{3 zt;(em%{Jo4nV0WD%?cG>pI{^V+u&WN4;ztBxu#S&?ONMi#JA+xr8%_GHc3<}M>HaSKSZgrf}efEPf?;VYJp={@<9Kf zE0!rVR{jk(9(N_LoB>hHq!)t3P|Mpy?5L*9YDUsgez|?3u4cPhLgtuYFC_!cMz3^% zw`{#cN~7FcTLomtrM(52=Zk{7tyf%S$k`3ev`nTfnawZ&pg}^=gqK7?32gawh&^;( zgDx)A-r~$hTXx4(v_q6|MC%Ip^T-qXfXj{cqkBy%Q-BS%wxxwcQ>4~wc>HuZcGIfp zn+C_>Rddc5pLwvvab~V$T2s=Y7IFH!+Jo%02W6NCCjCN&D|&`&o*So^eLbTiT|=V` zdyFqNbnpq0L+`5@qABIPFcGatr9Ir&C)IkK35&MQ2Ar9Fn;Wso2T6FxcGP?ZPEk&g zq(?3l=C&@Z@uQf!^V(}Xx(f52p^+@udFC~AT`dznAG3u0YL%C@3HC#HN7gH;4a&&i z5w<9dBo#ty9#WUBvK3@}Asw3>1$wrSAD#X!Y>-`9M@f~&z+q7uJ66uY*0*?l;|H9; zr$-J;aes>0c)c@NAt3+dyHBf$q!o@x?ZS)Yv?Jl{PV2o7g2$WBl&YRU6^w|P&rg-i z?PE^`GpH2EqW{R;)}y^uF5W*#VzTWF{nvK9=YPBl=-&fJ#|JvUH6)rmJcaSQlxzey zw<20>_B=_jStp~NRp98KBG571A& zxk}uly+7|x)z(1iMc|t|NuSL>E~B#2A?=G5F9_S}pf-t?b2!FA%hV(t5D88e5*o(? zbq08^6P{ewA;*L>3)-Z8C0F-O7(-|t4&+Yz%{FcsF5XlQ-9T%DNN{9N+m}5Wq^l_z zSGS^O&l{ng@G9~>!Y8R7S0<_fMNG}-_WW|7lm%noykeVXiPF!hqeHSUkeH`4l($oS zbI;vzFil)c=jz*dxX0K5EPwp;daU+(xz!)4vU=S&uKW&LZYbKL!1V_RY=ramWV^wb zk@Z@vmX+seEk(8ff9!Otz_64t{}~oDPdT!F@a3pSfAcyiN1m#8Z=Fzl3BRrN;e>z4 z_J_i`&?AhK?WQUtpjj*n>OQrr;A@$}WmenK3>bco+_>RbN6v4r;Z%%mmBX&BMtyl$ zV}m5{0slUCu{NF7SF(cAl%PuEFW>Qwaa74u)juEBRFf#Ej$UgcQ0pd&hUL2%T5`^9 zr}QZ|3el*`B1YbXC<)+=sX5?vTuaMbETM+8_2x^s`^7T<#=e+JYt=MGCy56-nwqhG z+^Al_>U*fy{(dj3KaI`iVnOX;>UUtj+}C6;ZR8Px{|6g}FyD_OP(;o=yiNTz{-fzc zG$2VX3Z_W?sofVT{)iA$`t21hG*f3aw{X-yM;GvUeShr6(QWhfE5RFiCozWW<(bnK zVw#)+*QgfdRdNZoCFEl3y$*=gY-)2d9rgKxeNRIizHl)fn23@QPE%MaK>YSjqJ}ez)o7XRPADv?>3zg6ZKu+g;}KV;u~|Cy*0T)$+0Gm7Ym3qk$HAOeZuTr}y6AWeuq(7_DGj7zD_<^bP#a>^w z7kN@6^VD$a_^+aM+rwA6P=%0(&@-;6iZ8XfHKsvW{FW#d0z!B7jhA-D=?5~+=b8t% z@ZVE&(Y{1>6{P8+Q)2K zQD5+=K5KKl;Cy*I1bJCqKF)r&B(_Z2H7}eT-pn)=bD5u-%@#g=(ITKq$UfST?C;|; zvSF62KtbKL`1%`uwSCeZxCzK6Wu_?I9(W0<$@;(xCmRFy>|NG*(Xr5TGLCqVboGr{F{l=TYl zGfAvJ2ah^S?q?;1QQ~z8Njs&TKoQ?XDO1Icv0Nhu+_5*w--R@)7CRPWRzDbkP>tzLfDIU}A?~9HubgS>MvlV#Y|IE>ohePqApH?G;-j z1dsIL1*Mxexic3Os}0<7m4!-~h*LVu-aD)~9~OAyJX*Ju;oFgL^U74NsyxakQLsKz z?ma|*e8pp{|GZDy#W1EdKS*TVca%He)-xF8y7~!uya)`8?gL*LIt05~cqNizdh-ON zR|T%(Rq2iCkIos`-B}?2=2U9Ap6PWxukFH0r?nq4BUiLbatt5THH@{RCE^VmwUH55 z3NoLUk*RG*OyxQXxFt@Hi}+^J)%_9QDHdN8kQ>URskaf?A%)yM z7bGfQQ$<^tSFBjKi}rp^E=fiD-by6V7YsM-d>{g@0fOhSnjM!!m6yDVoNz-IvA-&l zMTlOylj;4$C!iBl(;e9z*Wj}k=Q%#oL2~|+Z?u`^n!j_|Aa&;pp}1xn9#WjnNk9HM zKEV{AG^vZg%MQF86jHyF=c#^2Ip=*e|JieyIX(6r9&9pH^sir@J~<~62u5*u`2qh; z#p7YdGvcnO(m+(0q$binDtYU;c~^;KnyRuNd$RBlATt`Os5-??YK&2D7(edMmF!Jr ztq~^QH#N4|bV`YGYeKto#~f+DlV6&opggMgN2E51XAU|oZ3qNEK~e+#9}M29QHKkd zsYSTkNq_KikMNCS(l&{#A&@Ow08CC$sGk3{#<_Iq))VX8-18aRuN=P7m4 z|K^qXws9a>^x>GxqPVCQHbb=Kmt1cqzdh(yS=#AgRa@zpCp-yDuj|L{lcD+qyRNC} z^zhY#FKZ#bb#G+#X~-j4gC2Ao{eAF?acl2yqU#ovaEb~_7lVc;Ce&;h$-?fBM}Jv! z;j;m-vJ+6grO8YiKz<+}b&cRG&Gk*WA0yJWtLhXheu89tRp8neR=2YJ{diahG@pLL zc(pWP`Hab8qy7rd#i6TlQsjYlXUX{mF#lgR6jws`1gmhyz7YlWzw*r)iG&{$=%8rMI>r1yIq7jE$7|cD=?$ z#zW1lj^~z5Jtn#F=L{5u%)86k<}EFRONHoHEBF(qgxl&(iL(~zas=5h!AqC>Txc~r zoyaa5aX~lS?olOggC}=Bn=gLgD~=X9;f4)=c*pVOJE+!%a!fI^R&uHJRS174KqyA; zWx^(@6eHo#TV&x4TpRYDn$%X1ew>GPKI$G%hsx$}8Mplyidhed3%vgd#u&u01Z`newfO zG8ep?Di-sF==XKQFE%ZC&g;!WXb@BJx3qGPh5Rcwbo>|u?9PR=2X_4>wXJ*LMDn{n zxfw^DXP+L>fhrZ0C$y%m@gfUE>D$&mY{5Z;k$1N(GADojWZXZ;p+CD?eGBZ~{pM)) zj@-iJ+Q&os0sOVS_`i3||8X64cis-sg`Z2wTb*7nSetzE`5%2t_Tz3+IMJ2-h8G0n z4_jFe_h_~FG_-BBd@M29{tt1Js1L$ZxY_huvVD3KiAj0n5tu1E~`{L$-r0mw2JvlUvNDrx#xtOWYvc*Jj+V%xp0$y3;7Ov zj>G1UIoD6AztXKEzzlygm5VrZCzm_~er7`KFojo!dVXbaQKr*pbr|qvC-+<~eQuJg z1>#$+sj_h+qiuPZw=q_#4I!$#y^bmG=iDVjgY%R3@AG5p7=-y=fJ8m+SB;G0HXc}D zF$ZOIf$9%Dh@4m!UMBRs;zIW18`ZM~3ZTb@qcD-*x7}`sM+C*ezG%8oD}R7$lCb$F zp=~8}g}!*N^nHi{%SAsE7n2-+oTe2taI~Lxx)U0xJGS6X zS&(Qar4n~x`5=mEHH%!8jG3;gp$80KI{lLazkphfW#ZrCs;eDPToe-B*KmObEUude42_%59yVBxSEMoOuw%OMVLf(5JII7H#xXB`1INzMLLV zQs#KU$P6q<%j^n6X?SB^+w%{tDuWu8_6qmDQ$`FpRkL{(sySxkzGlh49ZxvIv2;yw z1{)TA8y@ewUlC&2U&${T1ieerg)Wv`B-AF(`?!(noi2?UD093_cybw4(P<-;Z24X! zAB7k7O7;=fovVI%5Op)Ir^XYx-a)X{Yz#Buie<13{jEb0oAq~zHRw_}DHSYX1` zT4-6=V)i9iJr zOKt;Fh1^rWo4lBQxa*2tZFQ{{I>m{tsI|?dp?0x@{0RK_O((c=W6c*|yVn5;F3xwb1byBR?aJgrMtF8ohjEX7?eeOWRFq1v+k$OFW?zC9wPsP-D=U z=rb}!p1f?E6rdC6EJFn#Sa7s6y*r$*OM}6fNr3WrmfiG`Qigrw-^YoT^}`{|fFn(C z^&erkA0YanzAocDc0S>GY@RB$!+#=qDHg8T2zf4x=%{AxtTr}5VFVqDcVB+VOR98v zu&x4#848a6E;hUDR!jI5yiJpWBnRmV$4E6$9Jb5&CAezmBfj%<>DWrwY4Q|FsDukl zQ1QlWT^hz^_sgF{Hv3UNPPJXnF(UV6rH~&A?Buts4~?Du$Dx#XNZeHlpd?2g15{_w?E^(3cp?7Jy0uPz7f(I~kI)^z$}yAPQv zmmo)tkI0qy6KQ-~OZ1v*80qSiNAq%OG)xu^9kOfm)mQ2K`*17&aAcb>X87JXGCg5go)_itdc+wRq{C`_e9X27 zlXV51*Xom2*L)X9y7Cls%u1-2++F$@`aZ9_KgakY6hxlv(16`3;8eI^&bu4aZ?uQw zK5~cFB6yf*Z+)vA1nX27+zsi_7L>Z?ROpxU+lV_2zU--6k_hbRc%yL@A*V3~hs!e1 z(ZfsLRTfetXi?Xk?%Nc3d|#W)Jps71ef~s`_P+HgnQvPggcS_0K6PquwY;Tt zOSX_a_GxX0K4(dlHa5#ENB$6~>Q7p<1t^yPiCa6Z=&aS3NK^HW-EYSthz9r6&Q8Ju zY>SvKI)z)(WM>^G?KWzYVCpOjh~hb^u$Dw%?ZL%8kF>W>1|8b(Ez_WvqLLkgfQRJ2 zdlI%SPH{D>FJC+AR(0Y$UdP+~voAnk$UIq`GV(|*KG`#Bv@cTErLr2{hGAc-m~?SB7IY1e7^3>6g5T1P9_FPn5ao{@;6&mly!)Dy#6Q%*2r5Q9o#sso-jLE zQdLZ;D1Q%o0~rezGWLT77Kw(ZJ}}!7Rs4BfU`gRq-Y0S;H(m9+!Jd0uTWLHN!h*?Q!w zn$0x9kmvt_-P(`rJyE2qT(jtxxH?)igsd+gN6@pW0rDv|0g8J? zd7(isv!crFZ{$Gh00HJ%3`B_smAyKgy+9v3=J1We9oGmx$J2(@@Wh|wB;UBjim0`O zZoGQUCW2$JX}RzGch0_gi=O5Q$%WK178+TYYTnkfiNv=B7+DsF7)*F{?xPMdZBm42 z`+k2ySbQ`wlWSYPFA}=fZ#ctnMx~{X*Gv{uXlJ6nrwfCJ?@I)RsRFDD3^Vr4hJ;E! z%6$;1NNiQ_soZy(#z0LP7h1T*;Y<#5fk{fjNqO+>Q6cu?RY;$ZLGlOKC@J9g9CTr+PYZ~4$Em$G!}Gg*5eC)(7F zzj2y+dt#+<^(X3ZH#0BQR>@7Sp$*f##WtJUUiy8Wzr*{q0r?4Uk9mk}B`gpvKLg zXvV0i*i5(Wtk5YNe>jf8Btx(3*rCIZ5A?|H4_1=2l#B9$=e90Cf}+@EdK#k@BJ}I# z7@geEVB6Y(n%F`j3w>ysr0zXHo=uxEiETOaJ84AeMy<+b)F&Ig2LzMmdDKD|u4f>m z?7Hkfq!h%emqN?3`Q!=~?E5&x^-kqf6!k}x85@!^r2HS+3DNTo=OP>(-tZ1f8xrdN zq?}i$&vluwBL{P^9fqO#!ry%sZ&+k-4RkP0%^6DF{wbNzH`b6d%)1%Hyk@?13uc5t zzbiWGvI2F>CsUP|F);vyGNs`2g6i4nE3&cEQmsPJvdP(>^yD7}A5A2(97@?J5(s!Q zpNu89{_cc>f1;2#(ID;Rr*D)C$mq;e+r{BvGUJZ*lo>R{1e}#g9y4#CjmmiLUisl^ z;^W%`Mv&ozbYuXPYtEdI_lG$n#|UTXm&tv2+BAON46a||Qx3Wx8<_FK=)_wD;cBXu zd;eN!<69Pp2FwJbSs7fO_Q(563>;oCsg#wg)652xU#1F{qtFO=see>cO?}2#V{p5D z6XU(M=5x|e65nfwucrg7fsE9!{sq`cq=G%hXcD?p3Kdg42wqDk%xUqX{uWI6mOr(- zFEZYAW>L!cHqK@YEd^nJ2~IjFX>NF};W{}2`Hf2ePx_qK<=-kei#=aO9MdmsY?a~w zs#c=(UUg=Q)n_ypW5Z{cQv(-AO|c>}9drm=jkJi0WlOTZv&k7#qXx-MJ8s;PFl`iP zGwnNA?BDzKYc5!&T&HKCMH*8FylRx{8M8)L0RJP@W?myiwjCk9QF^w>ZVSPnaKS@D zxwCKioo6Cbj0?fcd7+<6vAss5Aut8nGdpWI#Y~c$Saa0pmTb=;lAHp{;76PQDn`0h z#o}L7tDCjkZb267(A0{kFs=p82f2CXzJ5J;L+afq-LiY94w8H1T1m@5WeLwYFtgsf zm{x}*l_N`@CzZLt{cD-BjN9+k_tKN0&nRf|w)_X|#%wn-41NSa1h`!ld2*KQ?bp+e z51julcrz`LzyF-`anRs&#^ZoGCp7Sx0`FT-E%}#ve;vCQey7w~uP@9g=vJmVJnK|E z59-}pY+|?i95C=DuBjNLL7R!pkmdqz>~+`l3O00`NjPs6$4`jY#yi}jH%?Zo3~93U z5asyvb-zQVI`e#b_D$NFgWmywP<7=s%usEj-o^6KdQs~pt+_^AAy)_uoB1cwGIscK zTUDHJO%YDGJd@l1F=?XS0R5R}dllsUSBb*V1#em(gow~l2jx%id>r-Y3%Bo~btShN zNmB8<)ZW3%zrV)+IN@1nJ`uD@uR|$$x~oIAHS!j9gL>)C)EY|(H>JkAbpM%se3|E~ zyTm0(yT7<$=kI7zQ~Jy_Cl8O$M?{T&#vwj4HPU+@o74dP$6=ay756FN@kAf}Lir@L zrV<14^Ri8SfLoS1(R}G69N&3GDxnFwGD|2|Eyx4NucG`4_jY2~x+P6zevNlWHnnZ` z1!fc`;?SP4l2PR&NH#{;MHobug*bJ~)DAS~15BKlHwomQ+XbuaoL;oMu^n7^28{Bw2ggqe2z6KUt;H z13jTm`|`qSf#Z0iZn01<%j>8%kj;SWQlsr5C;3yW!;7JoO0wCmeLP`BpJ?ngXP!3* zzpBmUA<9udU1`RlAT4OUeY~T5naoML@w+)cohT6IZdhCF`@NT^)CW^7XkP-={HA)E zwv#&j9F|Ve&^8`61<#TpdCzz~9~MLYnF-30N>=$eGdYZye(dRX4nXgmKS1+2&PsCB z&7l-*(#W)v#5*Ac{myu=tHJ^U>fWVuD^m?*s5hc5v4&4dqnQOTfhwvWShU7i1tr=X`G^)(kaI=VR+T<1@B$NyV&C<^TAJ{n4;o+LYI_Uil+9H0w zb{h-QMFEyBY!j!4?SDq>vMhR=u1sEgBI1vAXun8@yAF51A{@^)R?yPo2`yrjh{8%( zeh*HSx5O(FuJ|aHw1yd0dF%@BWpakm73zNr?n@|n#`a#Qr5iecAOaFJ2`T~cF^9_q z{rlA?gD-4?SDLmXDQC#>0B`=Ghr`&*Rl@w786ynk0=4o)x+ec+TxF&HdTak7A5%%K zo&ZA>`d3UT{8?Z}#?Bzd63y65isl;W9Uq&M{_Je(-|NkoES1OrB(P#bDO@qL`%iO0 zprgAfIyXDbhE*_W78{KHW*kQ@odsS2zY9%N zl$eeAfq0&Qlwx~I##?JnBi$6^u_g#KZF1i#55C3d9J^P1xG8mJG&Bt|*>F6|r^idl z=g>oDy$&kj)?pwuMHrB7r3p%5o<1fDx=3fSX`StQ<|icHugKgtRC|`;Cz)rbV+Bj8 z`Fgi_u^6~2R31P2JSu%hvnyU2Ckwyj@mTRhku zL33T2z5jO6_-^jIT|y#q?`!Z*SXr9?+d3_+w`9e^h9GM2m;BnHtj@vW*VHwgkzb_*{g`y2Li-?aW73;o8Vf0|NI*p<!ryX)KClw}?Sw2Q1A~~U6A(o(M;F_BHb($56tgvQ$N1KD% zIydHQ?gxtsufVF?$r+_&)ia#==lm% zc!jTm7!`!7We=1_vkrXMola{UsF-bb7|Ag*A+C!vPkkpolvt?YU-U;6IV`<=N@0_7 z;oT5_=k@$Y0`xp43J+ahHbNHbDY!mO^k}tl!J_}{s0jKQ&^3$&{zTagUc7$ zvAYaDL~nu6#YAAajD3b=Q>WdL1G_M7kwW#Am~&}(M*N&+lh25p6BRp6gRJ#cpb{kQ zNRE9KTj0%n`x}Mt*wMdQu~AWs6<>I~Rf`|0ou_f0C*35(z_=Rn*6ja&oT@C5W=qEd z=RDM9NGdNMdZ^#=bgg%dFcxsBDSa;oDH)jTeRah>MlRPxe?s(H%6c-ClF*6%B)J;E z5>lgYDuqiq&5Wc}%XmOb(cTk`{o~m_BrEkn!{6S|^1n7@hKNdS+?XDo^c^|#FX(Bn zoGfI_UQ^kE+vspy^}_nvCYFTAwYQPez5~EY#amd`hD~6}Smmo+k^#d8*C%4v?pdwg zhMT4JJ?VREfzjty_LBg+tLAu`jJB;DTjNAA4()*PcLWDBxX+?aMjLvZzvL-mKdfLT zm=zQmIb{P>KczOOeW!kEKb)4Hl`4ON$Pa-j-T-PIEV2?kCTYe?6=Ad!&JBFx8s!16 zPfbG9o20}fwpUxk9%}@*YQh*zR69AbnSESH1`j{u8NCCwopi+KEWwm16#0hEdbVzL zdCE+M(ZvQ3%-M!HyjXH4@pNm(N&FV;F+zUo`;)hF#hk3A_srsa=u={0Rus7_2jjZ* z_OFYQ@SFqC7IVG5!as!gnVO>_^8R0MSZ`pYrE?iT)`8a(x^}X#!t0wR$K|%pe#&;+ z6I6q?2Yk6d&UK#9yhh)f9XgwP@G0)KQR&grG(n^O^gfS2W zFe%k5&{T0!jpO?osg2s*T7U~4q@n$V8jJC#%e$HY{KE(4en9og(^B!&Pbubc+2Qbb z8k=MKcQUl@DzHRJr>OGyA=Gvd{`d79)0CnMY|Kn_&De6sqpbIsme7ym0L#yRjl+rx zKuZHP&ZorW+-8Mz{)1TO6Ibnnd?P&=zT1E#-?6_8ZZhTEO}+2lw)|m26qo)JQu(;Q}1+c#HNz zvsvdRkT{(ak?*sO~YyHowa71KF9i z&Dz(c>gLmuBk20RRI~$%2KLSjNDTvRxeP}&@)f(+#rz`4-iy4|Z)`T`CBoyk*9ND` zI-8{Z%*$kAYQ!7|_;!+Mcu8I+yLlA~*(oAoqpJtW+qZS?N%@45t*rGwyo0RtnQ{@$pfD!kx*y9m(L) zfz0#nwvOUKEnoEToLvFxm?&w9W`f~UKC3h`z{8+PQ@GP3Y>wmW6~Z42Hh~V))pyY5 zYnG$w*P6z(=XnuoO1wxS=)H=0q&6?A4!ggba;rd*gU|n<)o)L=MKpMqOgT?UVJxZy z;!K{=u)T)vbgN^2e%w`JwkcpBxP!N9MB+HZ(X!mK#+VCz!9dX@3Y#f6+IiM8 zFLhC=?q}KXmm25cdSFy~>?XCPsZ*}&18hG&V_VB)Ly#Tl#r2~skJhXF^R^M#HTBJR zX-RhEXN!D2{>)(w@q(YbK}9`nR03vXQ_fKPU&3uk!D(;D`7)8wt$Uv_j2JkX-eubm zZNJnUR%rxg~zk~l{TR3+AUQ>zc}KX|cIpm{=8%&Foxr*7Vxi2cGjx-(K* zujvFs42Jza>xLig+jQ`9?xXilKI1p#j*j4|MxLl?lb2 zATKy=_Vm{6`QYm*s|Xf)mHcwLVxnd$+%W{TKWJv4={}qy-SWp>qnm`{lM0}(=fmn^87f`0u*`K ze`k7sqSg)73|?RVItPD2`4(s?B_NfMwteNQF7yQfk#S-cJpxws@pRI~mH(;{W9rNYm*sfW|8Ib5dS85^_um5N7Zw5UuV$+KQ zr|X>R`VK-Kz;lt`QRB$x=0-jQ9GCYEqL-^)o7htT#8Vm>5z)c=KV@E2>i>(iXh4L~ zgWH|&zd)LHGjbdBsWK7z3(A*A*zS2FC|Ir6sa?YFYF#E?jpyOjmsVDN(LokBjc9xw z{4YUqisE|F5G^FH4_U(FrQf~@S9aIMd}R3-PLQvVrw$7or|hG|#`wqn0d={k3Y#`` zTExHAr5lf_(FqkX-^8;F=ga1dES(_VpIRd1Q?Vl~p7*C)RUpT4DIB8?!XSKq^8Ncw z{SRIVZSuzLHUe#>bPXnv@{Gj@Y_6PvjcX}*vQB^g+gyqJ-vZtX$%zu4nW}q%_{GEM zCx4R83K&EAZ;X&BzwbFPA*Xut{B(ILs}LI`VW9p}X9Y7O_2-!d#!?Cs)G8D@B?f?r zGsO9t1kRUHGF3%RIXr%(#AtLz>A)Kq**5HlEu5~q=ua`@Q730v_rE4iV>WlM=g=9J zU{T;AvX5bTX@RYDn4Pz8(P(DX`0dP&3Po#PPu^zm_ouT-%CLG2OYaS3=oSFtx9|F=s0JG&L_a{wpm%MAS!SNBl!6caPu=j0lN5!@c~h-QTg<-L9p| zvS^0yoJrVvmQLZ{s>h&BvRQmC>8d3|pO1emZgdC2S*; z1QyTI-{|o8u>tTs5pkZZSm$>CV<9y|M18xcIbd;pqki*t+|?#a^P_r;OAgp;bJ$R8 zl3EW&rj#T=)>3oTCOhmIjOHj4=dfekkGlV>6xBX2YkdJ-x)0yW2+>!vpB(jptHkWM~v;_?) z-0RYSt61uA&zYNHJ@<@@T2W*X7YT@x`8W#R|Ab=Ri<~}+z*C>h4{TP0ldFAKJ4lbI ztX414E{;Ht6~3z}%Cs>Naik=$kc>jf@Y9+ao2K?Oz677D#XuojOJ47?Gj_|cU2$j` z#`Qv~%gmMlKNzFX|E)DxrH5aDD&)L*o_>yc1fq5QPuHf2Us6zo zH-^~{7yVJz)Pe>64*uaNH`8q0&pVH^NkMO2OSC-+ndyG|#bc0AZ zfuWN%@^3509-={!?;WwbV6$L`*Y73wW|lIZxT4Fb(|ils^eI8?(h6XuzmN0*zSA3T zDheLt5za=LKexc!*QO6}8Me#G5AEwiC}gJJu(&&rbiDXs>(Fe`hf#U=K?0sXc30tj zY?QL^ZWaV2v$<@y);kA#Zl-bSpwE^;Md|wwK!%B19+n2{=VC>_BleeQTYifSLFER& zHYc)dUsvP`xRav>Y(y89}Gr8i4knIeCV>cvV5Xg6eL^Y7`?cx%G&0MwZAQyPhIYe ztQ_g6kU+N3ZIS$<&VnPLmuf*L^Pxt4rCk$2wSDl4)A5B4_SC&()aL!!ysf{;@wv=R zS`N7~ZHwTAeSNavadIjpwO6@@og#f}oGml&S*5it{pKc+mR*xOy2S<$)~cgpq595i zvG7|n>ZUxiDwvrt7b2|#5m+O2@fWL~1;!i%c!##`4#_YWSo#nn_q!5d9ESi8jckdV zJ)%$aA1rJB!d^9* z>BuBTzM`BPxt#z?5{%~jQnm2hShPDo(3T>xXy#uV?T8C5>CJmaeK;GSe{TR50f}z7 zv4;QfR$u)wB(rpj*rLAiosedZ|3aY`ddMPWPsC6tVSlpr6=30nu4b~={M()O%Ct#8 zVZku&pAYrXuB45f)`AkyriQ#JDMxI_I?gP~i=1^}2 zc;(8;wZYya;dk4E`CEw=nn96~Tbc#EAZ1xtFE_=q&7|n9AzFT%$1_Be=@(pD!8U#` zd-^V?wa1DGX2P*CCvzJ5(5oFOX*My+*|4YK&yr>x~8H%+xB80v$s~XXEj9ulZ%xGD)KE_+JpF;dmXm(m#+gF))RbSm{akbGK^Uh5 z#i%;0!OyN;_YxT8y%jVj$Y|D|t0(b+4@P2hpR~c7@~J|yZ3*W zeo!Up%ULa}fk;(om)_PhOygtje{b-AR3(q}yFoy-?tAaVTzmK51^tzg`5uP5LZ&0Q z@*NxLDVVPJehLAIl-Q1=@eSrT_um*Z?6Es7F$Pt0->lM_pU;t5+bkWHJgARrh-`(o zmIL5YyN*d?&4msgn^Smxg(LQ$egPO@oaL7W^NWG)QjJc~Wymv@yD7|SA=A7)jwkqX z-tWk>AW8!I`&OsU?EeI-RBW4vq&arVGA%4T_IK3~h2DY81vdmZ;j?v12fsEzBs#-H zbZTj7{u8gVwMwy59MnYo!RIX$f!-I%En#m}I|$1}?L=Eb=#e94%TG%!KI2%fYA6v; zEbHL@Jphu0CtH@!P^U2;wn3ghGU-s-VnkdQqviU_PrRS}0Oq0V(9wfz*%~A%N z;v*}QpOCIpri4jJ*nV~qg~11lx)T)V!bwGN150duVO+s$e3iixeiZyoS_PD090_^d zxKcfv@98+;M&g#v;!eOG+r(5-ZLxaU^W@l6PzL1~X=>G=;*=(z$XhsheDw8+=Ft2? zxAmDP6Ly?4I?G5_v0T=GA)}9r<^pnYmNSQNyFYg(fJORC)~StP;OBq;njGw7C~XT| zl&GAfJNnqpHqu(OFb@;uT!o}_)$y4pabwrs@<#}%Ow2BoZ<2Z&j~YxI8Y>ng1T3#_ zF%+=!CW~k~j`@Cq#1f6(6y0d>HOK|Cs}~{St_(9O04wYkC!3SO3JJSjoCHdQ4&9A!9m_Rp9{NQG_tzHBycO z2d33Kr+N9JyZc;QoWHF$XXm%EHFiMF@s&@HY`Y5(A`IQP8-fTVFWZ7faTLz4S@TS` zoR}*WSWZy75J3aw`zZ%={%UmbC}EUa+lh~Z7jG$t!Nx5q8(~&HE(J>W>&^aqTt$k< z%B6ao53Ccalw=1L)AAu90fVzMD8snmqUn03tI5ITr?S~I(roR|zeW$LC&$&~vw2h{ zMji$OSEl>oQ4!1c`aJ2!F|xY+Xu=u2bLIve&kV%}6Imr6tVTrCH4%L9yuSe7$;J_c zBz|myJm2X#M_tivX1J0;J2zjKY}DSynYO9r2oYd$IbN5`&+9pDuXG4BCdI%^>1j#$ zLBP`xFy>^N*i*arAgIVD9tKmgF&vAQYTj|pP*h42mnemn=#KG@{7&g(kG7LHyH<#- zsx?m)SOP~cj*9}81OT;=cODcVrF_9L*w=x`ximfmb`U3Pz}Qfc*vujnm3<+_DQ&dr z<89iX1~fx2{fodgw+x+$`qzbuffg6u7p7kRZ9nWyN|*CPV_C0O*zA%s{Tzx+q1qvm zSn4JwYN!6ifRor^^@wc7Xq-6%@5&%t^=*s~3HH5_^gNvhgUxLo?PE`;l6slN^OTot z%OgUvZLX_dy-zrOkKP?$-J&K>F)+S3szHR0@qbF5q+Ns_Ts*9_8Oe~psb~YOG~}yB z;nxWT!J;+yvIq#-167vPochhQK4z^sjcYH@i}5qFt(UL(+>VP{v=0{%wCC@yH8*gx zNfq4u#s#dWO8!ISTsC4{&EHa5wf&^yF=;2&>wl2z5jO#+7Tbx{mlepT!UMvC2`Sze zXOb=MtFuigu8KvkeCZo zLNd~+;Tq_qFSOrVvdj*PJHXi;+%*s1P29q9T5|bwPi7Gz0P1MV-%Yow(4E*3?M^V@_zIWnTW5JSl)SDk=o60s=KLM z-#-ep)(AvJyoT=P8Jj&ZEZmzGb|rQR$=gNYm{s)taI9$7e3)Bw*=mqqxlu4FfOT#A zgSNg@b2SdZ0y~9JkY+27-3x4$^cZ~s9~;kTVzDC^RxxyLuFa+x#?xSQv(+1{PR-tu zW5OK;@3;Q6x)FQOrrmeTJp{$H6ps2Ht2}hHrEISv0GAknCA_3kA>97VO+MQqr^89? z?xaaw_YH0P$yp-XKN$F5eManCGDIs~Pi~}o9J6#d48=RSv5q*;<$QJO;rpk%_jS8G zF^g#Cz;3qkjN%F#{>k9kXHU|AEZoojpC%G+ghnJnwX)(Fnpj=0Hz&RNI(#uU{=;yv zDxxa^vetE$(I8md?8cTKVdKvmH?t)FBaM#0vP12C{c;2k9k7MKnb2D*D z0vYIM7F8h`I{G`(2VF^WiC?4z-pibWb`ehGfw6Sy?>S9nhvsa6ZupkgfIaZ}Q}->G z+t5NJj-RNF=?6TRE@`{)&doC5&6Jz~a$w)QD+Pvvm_ayD0>l1|t z?5_Z9i{{|vSj+gI7U*6|`jV%9sN3JQo(%ybmRVi4g|I->fTZvEr=lg*l?(&5OtCH^ zw0fw}XfB5PL1d4Yx;3V)sLFJ~_)1D1P|Weiew1PRT{x%Xt~CRR=l{=KRXI)}BZZ}> zmtDdY%NVp1Mz;|z^y|L^*cSM-twNX89lXvTGLekgTI~2=wx8l{eZYk${)mybYV~Fx9_a3-yn2QyJ!IuT^=_uOV<*w2ZB4yb=3(D$ z_+dShW~E`_IptyX@N0g8lYoelvydFUw$JoABTQJ_Vf^-ORR_4ID1Lm@2($)|6WX zOjHFLIB5FPyCOpJz>KIbozGw8jmL*J96#Ge>i$-_fx04&wl!Jn=h*!08 zCL2{2to8)_c#+Mnf-x@{aGcM@b zR7e=TLdA=dRq(mrE#oR$3p}hRmVocmK~;{v7yQVO4I?b7YTrnkxMXoSJb_sVCPo);p%| zRK?6h7p>R^JQVt-O@-J6YZg^3lB~(<@YyQ4*Eq7*j?+5I$KvyK_NH|*z)p+fmq*Fu z8`8h0&2jjxg^di^>pjm6p!34ANs~NgYE~|J!?2coLJhYW0+hExNE2tXE?xd=r9Z$~ zZ`W!Cb2g&Ka#IHfLaB(?2?AanHRe!zS_pI!V<|H!QES`5(V3 zw)9XJoiv)T`|;ydSE9>8H_b>>r}#Gp`DFo_t0=yRSmW0VE3@_#pPv@(6teMiw1rg5v&1&`7Q?g0m{({nQ2%I;m1y#g1?Y+!COQISg*DFhGE8nUFkhM$wYgep%Pu&5IcN^qQ%^3< zKGpji!8d7zk}u{bQ~so}Bw-sT05HWuo~quQYL?hLJ0hZ(@^xGLc37j^ z9v9Q*$%*fx=&6uZnE6%};00^J%1SV;?={E&| zLCkfyHdx1fY2 zKw+g*U|qAH!!);CW53;UpG8wFI@~b=)7X<5RF@a#Vl)^dlwd-3@$8WCu z{TVPtb1Brm$VEyS*SGI_s!r6>61{{xpM81RW-S`Udz~q5B-}|PTO@isDQb9ApdzEo zr?wXlX(q?Zvu2s_O-qAPsSRpEwslwHaAhu)4P=L!@xeTsY*ayS1%%G*^;F`dsh~6U z&I91)0&^=q!GZ7nrlrUpZFqa2k*${D?&1vd@mp0(>{qi0<}9zU|N1;+urnzD8swRl zSXK-1SwHptI}=uz)Epcv40h}JVKB_u_&31Al|IZyZ(7MDA@Mbmah@kXRHRzOeG^CI z&v>z7+)T~TcswP_E8$V8cFhMKSCgh?4ew2;4P=Fr_^kjz;q|O)$Rvr>($QVz5_7K0 znX?c@^-WJflks_tNHz5Mb4pRV>&|VIZBRbZy^=nw_wopTNV+Nm`l9m4&2QOjqTBu7 zhu*li#a`g ziDEQnWVD$C`B_I9MC#35E{y8pgd@UNHf6lEBC zokqdP7!namYaXD_|D6*Lss5X_H_nA(C+yXW4IEL}tY!muHP6a7-xbBDwWVyPNv=PQ zb?+G^7f<3aLUzTCY!xzmHw5f$rlyA|)iF-g0E0JX?*LMSoxnBAF}sz&gG68VoDyFl zab`UER<9(?E*iX0ph-c|Cnr0AXSYOoS8>}rqMgz)2qN8yLpN#3k1T=sm8xqd_FYE^ zjH3x>l5kxsyV}Gz0EBLC_Q)!oy$y#G# zF=n@VGC4*DB!)NFQjbk@(Ev)F&GrF(^wFOXHxiwAkj84I@H^9H*_hm>)pi@}`F_Dr zVYSPlmu1;7ZHSHrZ$Z8po9m^vU9QG)ex!9~1{?(_HQ2wuxR23HEbX}_OIZyqel9hB zjZ*$)!qkLGEwUvHUh9bGf;P={+*5GKInhrTPVkxhVc<8fPdMKG!Fa1luz?-Ar5C$x za_O7aJnjYvhBTJScb9*bm=u!8wnRL@K*~e4$&kX-?^{ii52s9?v=l})Y;p4Bq*>Jb zOP^;!Yp%Clbs!*!+u6vM1^sJ}t3 zhty0j@u)Oz+ZVD5tN+b8aUDo#--yX|>!i{${!J9@K3lfpJSOMdIz;H)x^K7Q3zceg zNP%dz#Hd?vn+oD7Q(t1|xx<(r7Tg|pg1V2H(JZI$I2WV7TP9)jSN`d+q8P+{BHX{8 z9Y}8S=x#!KoM#`gbap096oALWFfCA;{7(8~G$Dc6 zGTWe6z%QRv-!E>z7thE2(n^W{Ljv+rKc3S{^%V-3=z+ciCw`!H@t-RQ-0=rV;oIi} z>Vkb?Ji+ySH2*_kj4@@x&qDAE@1f8glkFF=wNp;~2Rhg)QAo!lC3MZ0t6wKd3XQer z%+?P)@5_Vn1K>g;`&WCrC2DO{{&VeIodDp))k&sBav*JiN zmJe@xUv@3d;WE#PGA{@v=|5{aaTGY@CS-&(7@=jt?-{S0f6tV*39WG;4v$zNw{_KS z`9Z`l<~6!uS$q*4Tbb?PuoSUeA*Mj96A>ee)Kt?X{x+vwuh>bi7*+~EHM58w!bFZB z;x|s!sU0t72%%&lV> zO11D|L(k{EVYQag_@qUYKOD4kSbJ44ba;LwD9`))4_nD_jNm&q|0#=usTI^x3&exr-W<+ zExRCjpWie?EY5H1wQrpmfmON9J&YlABjabaXAI0@*B|3u`Pd*ileMEiLuTw10<+fN zLav@3II83LF)6${XB7#S^CmwjQu2YE68l0cBc@wCeb&(f7%hberOt0IhD(!08sinm z7iW4SLr5HT+6wP)D{|r+Vff2xtEi6sKn?FC0fJ=~Jm0oV{7(L?J|>(f%+AiZ!+1i} z)CAS6S_^|Z%Y$=zbm8Md@3pKd3w@DhX-L3Dfjx!l9_eu+0r@5?ZF4a3A(Xx4`Ex&~EzSM$zyGx`V3N0(p58Z~ z698CeM}FifQ{r(CY3}^=sR_W%2hx3-uutXFa(PLBFxYjEfitB>c>R&G6L}h@aPVBC z7?IFGzP3;fcg8{4%2Asp#)i_-1(CVm7;`2Bwk?g1KxRzssA-6OEB!Pisi_lG$8K4# z+X$e2Xp^nC}3`L3v zJ4JqORX1jax3*e$z&^&i5a6Crf>xwap)|4YGR2m1&h>t=354>~ME7u?nszLZ{Zh7v@!>u#m)cEbEtEtPj(7v7QM0%oxY-o!>Ni8ad&S8>=%*|y1pwwHZC#)~ey z<<<3lc+$`AkC&Of7sod>VTA{$Dc^F#@WyF@>g2Yg$b39LH{kAV-Q=gfg<7O<1@ZND zii}nJlidzmS+_L^-8%`oTxlpiwA6*O&>bKu*x#I+_tIIhu<;w5bv;k4nz{pas_Ppf zdm`+8xpDh?Qrm8If4P6RqAdN5CuE;$<;4j8Z?Rqs7cLE;t2yrSSUHOb#nzf5#xVL; z#&a~0E!ernYJ1uUYIpd}-?3x}b7l)=WPWUmvVvFS6uGM6Y zE(!0n6S#lZj<3PfR<%~i?4Ri_2xfRA<3BMUPW#%Q(89(QzCH-0FJI3iu7OOIJ!j=5 zbCbOxtm&qo{KucEFvBeM>q%Bn!BJY(z@pn^!~ViC6Uv^e)`nAtjtI(S`e5LJZZ9OwU zYbdexi@J09O4FY=(p2MY*;a}O*#JgLE?SEi*$1kzxs-CYhK8xM?NMajGIc8cLPz;z zk||zc?OWnUjN#rn<>-4|eG2Ox!I5vA4E?mJd@=((>no>uCGY&fWCM3Oe4`6ZP9C>k zHOh*uoi2MGa`Xn|@N=XDof&tGc`Osa4=n3r$d*T#;Z-G4f0x#-e+PEMzx0zXk-NjG zmg!yM>7zZcD{8{-MwCVstX32_;Z{@aLSEY?7OTv1l-##`vCl()pA48?>9|Li%CT5gh_5XW^eI0@vjB-1w0 zJxhDakEe0D_)d~V(_yE$4C|_$WZ5xpcP3jRT>w%;Ub8nWP?}X+E103!Jp4KUTGJVk zqQkQT@}G*tmGAJm^yEG-HLC;#yE>6(7nV_A4#)A>1F`(XzVjgWdDqSA@KBMq$cKxS{R56~o zx5G-Mho+oc3}oRQ2V47h3uM4B=;JxHSC=BpoNDD2Qja-ZMWH`~rp%Sxe@2vkPZvD> z6Nv$&b_oyS{=I-qs{@+0!Krpz`_vem`MY+lV6=m$ORc+A-xrzzrbrCtXnvSf%G`xO zFImvk3&+L5#VgPnRYie&@X_LbYANcwP@;>3mwS5i=YIzW!K+z3zVm$klEI??Q+EEh z`^TDojfngnqRE^C16MiBzXv?kmyp%|_rnk4wio|~L|1@h-{~FTJOdjWd=+%1DJfa-B~4HR?&8LU!LhY_EOavoLBiv9TATAosS#-%RP5X zz9FGu1-_z*63=fryI#I0kUfiLLSOCt-81 zBWAgEa{nzjFUddjzKkOIgzZgt(f!HYzHo;$LMP5|pm}1v4ZD9wZ$vh~xAz>(uN+m? z|B-mU!RojGcU`yBqo2>-=6_S_aiBBz*-y9w@#Mpn^bwT6%vxFg6zjs@&I^{NJ8LS9=uqtBxe zn)$XM`d4#c-<}I(|3UOc1Xr#{If|tn5rL7T;XAOpe57k18?1U$F07{EgwnPvM zaWH7~SwE+ktMhGc*{E``&hY0yCmQLTP^N&C*?mXW<$^vj6ea;T5g3G1%Tx6+PhlVR z{zlfTY#OJKgo%oWie?i36RFM2T2)fZymy*%&KF!2u$Qk+PEQ@3&XhZ4hG{W6U^v^S zcQBeBnX71P^!U@VyZdg@)hak+!w@Jhu!ZfMK%9_kHZJv9J(($9P#@vEH*wAk0s*N~ z`CccFIljc7@8!0`huRQlDWIPkXS zBqHh1QjS3cXz))-6!@%~sL{vEzMW@CPh^Q@VWkNpr6~K<-@s$wuWt*K$n9=ZI|u7% z226jlA#p8QSTDpe5uR$9rP{yJ)kj;*j$BmarXt+a&f^9By2eYf@j%SnVlH*Ao9JUN zQ=kg0D=PO*8%Y86V{WSKAXP^Sx=Vh)AgmB30n%}8OR(=}8I!%BREC>Qi!pJ1VPZvg zcbik%U6B2gvG*6%mLTnU(=8OcoPMA!{8woqX0+w{vm{o!BCgRYx_p8yq@j>&d}K@J z&`8034C!hASMTS0kHSm1PaI&sJk z-(QKS2)AkOJkiqEqfMIY7#1oeQRF+U)9h0$T&6b5HeWCjPHzfuf>zoX9ujJ7Ja`V? z9ypMT2G$ul1$ZafOecf`X|coT<+VOYMBBXW*SKX>bi`fq{myI2Ywwj1k1_Y$iDU2S z5quljrsS!fT*Ijc;V90EZmSWZHWv7?ah5AW{XvLSiudY|#fH7CwRu+`1Ge&;#ZE(* zW?i7y68nIHh9QBKEFA->0?(;utFqiPhg z;7^^|LnbbGnGrKz$Zjg}(mmIs9ekuO(b*J}PUoKZE2RLg{>IPqOcx3F2nWdoC+WDx zoTG-p6NP=m^D>J*Yea54Mw$4&0WlZ_2Z%FodNLvX6#9ESc|UNW1*f@Q(7d=2h8Or? zP)IRm_u9N@+vFF|?3#FRw$HBX?}lecB3bWY#NBRTP|KDD z_Y0A^|Dokd&;*zJC_$9c6Qf=h=lqwsAO`ASFT7dM;Z&0}nH8cpSB|+>{ zuaWNkO;97HoLy6F-=*XDSM_-|CK_z0hEbw!eFJ z2f&}Wptpcmtx#d!zLbvw%Q`u$)*2&UR&GGHzT^}!$BIBiFpE3H$dDSW%t8yF9u3T; zaD)Hq`0WrzW3PV$5_nr);aefP8fjd zv*ri++FDk1lz_J8NoyNEgy;SI=|FaZ!DpZ{@gaKNUe#KlDyb`qGcdzD4XFNM21w@_ z`OTE~SLGxQ6KFo2Ruzn=;kXTO5dmH&oe2-Rv+jO}Q^#oLa|I%2qs&Ln zR}~$Caf;R}#)<`aV5_0u@RPl(fC~2#%HY(d*yW9_dV4=huOiwL4IWQ#-h=6mzXH-< zfy$~_LhN0xFv5b|rE&3}B)@`aJT^;fY84<$QrVe$Rn@P39LV^Aqjx>8Dr`}l}Mg8~TH}MJa zptmN1fQ6b~sSt``JX>#~43Rp%wef5o4k{|!C|)0jU(bC0OE?0#Z)J~hc)+Z{eAH1sW>*b z=YzpciZHW^`JrHt3HhT-4Ycx3@r*?KJj=}LV*AC%PV(~}_d>-PlJPSqfTa!hz>I17aJOj}hf=|Ua zf)+m+Hd@&_-eZw+{;808VuxIHA4um+k+YpFizx$fMDM{Oy%8sMU9`$QdqY_TQnCJV zWJSPJShN%0^LdxWPG}r=pJML=mwv7JZExMcH-RhBEogDS zs{_0Zo1ZeeUna3mX8Z(iIdC-GU zUGEXDX~;ay9V8?K4@tXz1Yv>^1}7$2o#pXO<_l0=ffFsaHpH?JzuAm74PKpLSl5|R zqKs*?p{3jeDPMMe``fhw|>O$+4FYbs<5jN)xO1q3-Yiq(D2yzDPR+& z_fej7<3cn8(Vr$@R3Hwg#F^@i$8C$p2b9JJ>DpoD(ut3>ydpLdCLi12CHRcciE!x7 z*wY~OBd<+@&!-Fi#(xCI+H9RK{Ji~LMR%bmVBaShsPqLtG)30y3!ItJ;7X_BTv{(n zBGbi6u%$LEubj-rkDA-gcC(fC;U4Lh2VBBHMp|^ z?^#+Q1x3C%E4%$iCV!#Nwv3k@!D@Uki~@PwkX84M%3i^!($JU0ncpu)ZnVi2yxXWW z7PqWTDJzay9Jwi@q48-K%lDi*#g8>L_=MgcA}Q)?OBl;`u zGf*3X14i3P+}|hUsD;S<+7VYOw6@>8&E7;ae~^igw zG6;gTUZz->`&1w9f;#(ki92qv{r*cUkVUIXdfwiZjUUBu@N0Bx%fxHvSL2_}jBHx? z_Djn{W+u+qkE-ixwq1NUQ9Xjr0nnilnHwXd$ZST|1tA-mUr`F9A`>ZZBh5lpz#%*pWiteaK=ubJVh2FLE)}`OS)n>1|_D6_L(0YEVq%Xp2S@)%I7~ z071HWx^Rt3ma)OAh>I-2eue%2ko8tUaYbFXb`pZSySp~-5`w!!a1RhHxHbd`?(Xg$ zoJJEMxVw{Jjd!52rjeibJLf%r)j2ooVpr{2wQtrQYmPae@rM3}kt81+M8B&uva;$N z!%+6b=J7RuKM@S7V>)syAs%CEVId%xn>4+EuTwZ3!970ro?WV(OnXBbe4-#ZMP8Ld zROM!`hOq48RgR2`Gp+I28JV+#T_w{l!D!YlYdX9LVA;B z2sz|D=PXGARpeM>d8sdV;7ZR*vDo+1l6D)m47;6lNXB?f2frTcVK3og7o7-UHdEv2 zoqrT6u^y+!^F%!UEqC-&HUJ8+ET7ukggkTrusWJeI1?3ueZTU4zX?iL{s{_+g>&-|Ii<*s`r$wS6Zjfa0+S{AX=u9oyI$ zgtD-((0Pq*unR4Gp{_ISnC`wU5Q7Zln)_i)3*TD=p6yYOxNHD)DTN6*|23HM>>WDl zkYF7OnFe$?b@sPL+apX8Gs&$}tTgzj+cl#*0XWF)(H;NHW^^q}+>V-LM@78q@jlo& zb%2-H(dZyYZw<)rbMq|g?L+e@M>^@-%S&72N+Jx%V2z1=7%F0NSV zgpXt{ffO&-KAz^M9eRuynfLT95^qFNl_Xa>jaUP_aq^D19crv1v*%643|$tjmCo72 zYWx<(+KatM(wLjK2{_RTKd2|qgr81tJI2VUrslyb29mVZV z;?DAhtQkk_^#aA$I)5fUeR>?6ZCuN)gE-=h(bvcs54I@s+*(J)G(Npbt3$mYjV1Xy zFBI1-drl99u*#GjHGu|Ji$A`gp9@@uEJbW?o~Vm`rfgWLrMU6sdPYE8W%wdE5pglpH=3>rN;7Tb91A3BT}9!m<8V%3RXc7 zc%70NM@^|!#Ujcs7SSf1aB|geTO+PBkp8<*oh&gWiZ!mw)Y4j$F|Q{zx)-W9LD9iLBP66q&hG0c8R zmGmjJ%{ymp;Xx?CrGNSDN%DLAz3r3nm&E30o~J`ENOtcn(!%s4K)C8+jkG14hIBHb zNo(13f=(K0x{UrBM(uhc$e1g=J^^wbQ?%NN&4Q`kQm}F+H8ndn#Gz(6lkF?m^nTKp zl^EIMdm4=x>;0_ciJ=m=Z22L0-I1>XxzKHBnsp;#5^`d|WUUkJ-nU5d0$nQHo%8^( zE+W4X&F9MCG!M6(Fu;5STF*NzyA9MP4sGnXV+b0HztA@3^GQYoiyssEz9w5m#0Cc- z_FYXaxJ|?QH&{|lyRDz9)Tg_T+*Q@5ESBxt0W9cPJJFfmT=h zmGY$#e`yQ7z@f2#{}eF~200+b;*p6B|4-tUhW(mLbSAx#7e{VM`96`|njK6&Ty)_pT*B!Aln;6K%rTP2wO zC^2p*)jtXs_qm`rYlTk#=h^=>80cOdO}t7)^8O>-@qe25%>9?PozKnGyQweFiR74P zqbej3!uBt~e|xULcS6s{o;~n1>n4jJrKEis#T154=!GqL|35djSMt}FO}ETKUN_j4 z`|ZF;>VLX5-=jW&6!)6%Y2@jd9cmPXgGOe?uZpV#eE{@1r00`I=2PaZs>E@4%mK1$ zbl>C|-Pqtm0viQxK7=?mS)UbflN8d$zkr(t#K3? za}3t+PRb`k#P5wuh#yI!GoN#;;13;q=sVZ8rXSfNMay3`{$h`UbyWVGW4{V;wl(=Ud9-Lw zBXOlmF!;crlIG}E#(6p|eh^)hQyk+;)Fh`|9Wv6f^C~LGAc7%ti8whr{UoAD3Lb6p z>Zq}lRX=N2=@>;-D2EN!(mOcHqeMpnw~VNgc?tNGQY_JNyg@pA1V&fKO2!E(rE0c2 zTqTW~6napd!~V=A7mBkG<$1{1=ERfXMGFWarLG2mF-CO2MTddJi(z#99AYu9?OtkJ zb5+Z_Hns8z>|yG=K#gInT;w@^$Es4ac0$tCQLYb9YVvf6$2HtRVQY-{ z7>9+4XwVNy&!HbHgQ6zaO7gGyzLT${wLQhhD@stY8BNf{Upe9a{PrYahkyrUpV6-n zD^Wl2cl5zWoxB}FTt=mPw65+BG_~j)yKd%vm%*K>8F@S97yogia3$m@GtYL!?vppc z#GhZFj}=2^yBnDT(q2f-8tmio?HLV*w<;1Fd<(zMTz;rZBvT5V%dH??#8*H>GMscm z3HYuk?@YJoJ6$IN)oc?J&suENm*j*lRVSZm0_YtThsiwki(XH113+r`YDT>xX%pi{ zh~F5y7b6*$6Ixh^i4@&KJjz^l5Vm7^pU3+#s@8Bi{X;u;ceq+;&Oart>T(GTC(U zfV3w$!IYo0sye*l80S$&yMx*j##)+b>hu>+&WaO-Ffy-M}A*n@n$=!t9+PL!;&`XHZPQuK(w1cI)MX zb0n|6{I{>k<_?F2%xng$Uhm|F52rtbHgm1ghVC<2kL-`a_9IiPxNo={0))T~0{6&whPHfmOr3Co3hlVZ-fgLiIfaE`uVW)q-xHj5 zKvHM(4pueieG6K)2v~DybHRX5p@8R=;62PnvA2~yP|D7$=80b3?k%ooDAaraJNWUn zEeyZ#=Y;u{)ryOuhYqhs=IgZiB;hLS&D?lGZY=$Lq#Eh4n30QmLrT9!F64S+v+q49 zsvz$Jek#xa={#}GO9%qOuDp@E-`3!|hFgL2n^yYZuf?f4LQ{~<=+xf1jL5=7P-opK zoRoOdET;4L+G)PUDX-625sA(AvdkrVbA9)baSx&6g)mfpxi?Kv28T7YpXMs=+o{oX zv5l6d_sHVWr9J+wtv*DM5gq4^l8SEQZOu-_=d`^Y z(`V|7D`Fb33U2-&S5lqVgcIOS^26_&!ol9?_VY> zJQBgp$)=`jHeF4V*dTSamcE2Bzhe#;S<#oxEG$9xW8tCr9*|U1yapZQqi{jQx;C;^9Rp z4_SR54jPkacQ~;JB8_fo0W9@o0X_9i7vd5r_2q+*YZ+akFl_y8jE8wV=z}a%qS37xtJKJ{+N0O6{#?$2M9kEMRqkwj6>hr7J`N6#^W&ygyze{(IdL9M%m9dNZM4_v=J}99tO#y+CdP zUx=O9*`E1k#gVONRwSZ#HJ@=f1a80r#N`*TJ>pji{eM27kB z=SPxpChUu<|C;juL#!$2Y~G;yPoJe23bEO}%2UHmuy16~f5*7@U74kEED|Mdf0BEz zlDYccb$?xWg-3OO^AiJHx=8D@NmbR<_T-rRh#JNB9ekgq;a1(}K5ySVe)0FEXOt3r zW$jRXY9D~R>;Uh6M>7{hK?1>IomVmJU+)mG|NOF@XbN28^;~zR@HngeSFI6DG!uBp z=k^m?5Q|V4fRo|E9Co@yycc$3ec zbbtg#twH19iGbsOiq-n{M&SaEE7nUV0-stwzx&U4*IxUj$*|JTzAQm1W;WhiSy@9EDk8D?WpX-0mSybH>p|5+fADv+%hExKN2wI zZICl)>18r)?|8?jU6GSd1QxKpl`ln`Rk_VswX{W<9GbRs1~-aS`O+%z_GS-Sp zpP0RPoLC)lZV)45JrZnC{lIewg>2>>g)8>;7q@NSs3%5JI>3J8?n+sL?cAPGXc z4m~sJF!&;uf5#!dEZYDV%hY7FFuk}TW~jN}3WlJSN`Y zps)8g&RN3W7fTHL8SGEIhAX+VlQBT2y&bT=+EtJ<7ho=|=TadXu zqW~VvvKL7=8ik}PXj6rW|CDRw8Dm>T%dRaQ$xF7BxVRK$5zmGhlRP|LMu%i3`Gs0! z>!)NJ2e}}iVk;hxPVfX2r43U-!nQ4;Nf}i@UlbYP*WRK@;J@0m(~F9#Lu=XW9RE}7 zF+REQ-3c#(vN*2k_qCnPjV#XL!)npj=tH{Dk6NlW?MV+UU*!a{H>+q;{C~!N;fu72 z?lfTL)pBHuSo_ty&E3%^+lg;Dg>t~+^2Menez#+8il|tV=xH*_JGWAM;?7-1aF)zLXX+UKZZJ*CRbZq(&b~;Ik8gz|HC)c2XI!RRfJ9!Jb~lNqK_;vHvnw-1pxruYNgb{BqEskhgmVr211B{VEQ zWS+brM0Jc8!790|3(|pEjbQ;5TeAIzhbS~Hs16hEfH*=uhby)gllQUoPKGEBYOV~7s@ou$vadkF}ib50)!~1u?K7M`o-tH@PDBp*#lzAbEzqU+~ zIzA}=_=T_d!*@Pe-KcTLTJPqmX~#$JT(5FIB!pj^U87*5Qhll6+Y-3ztWE8H;*LuHXZa} zzl06;^Q<_?QvIq1x~nqldG5k^PvLSCE|VLz$Gh!09szZKjdkGN*MC6JOiJ6mB8SQ! zJ?>)?{eV%*OfHd!p`6<+3O=NpVzvODzAxKkDY|F7%6LZ@i+9r0!bbVV*|##|?}ni& z0kFK7077dDdTxcN5lTH9@1ud7x11qQ(%eBmqM}s4esn#w<+zIu^^ZMw#vIT-F`KD| zbhi+9`;=075(j}(20bdPKaPOY*J|Zt%LI-W1G3A*8U4r|Kj4bBDl3r8$T3%!#`nj& z8B{h$=v1g(_%IXS`BnN}06@*9AB6?K{M-%D1&#|)Oe7v8`YRz?eizM5eGgh*3x|L1 zh3D>Z0@U!-2-a&S$W6_6^V!iQ$FnY-)z1#hT}i{sr*%y7Dy%av{F2@e=a63)wBOgv zIT{o-oA%4Q+#N-Z(b}_jYXTsvqU*owy^^|J*OE;>?VRUWG`dygi+mo&ch_`U#$Ami zDe4pQRGYqGPU}&gb)PB3wvuB$8)YOmVTn}<%H4E@wDIz-r7?@2J6yeYy9p9aV4UDx z1D*3a2$EgRR_3v}QGf&ZvLQ*uQ1>MA2hu&1HrAtX+Ql)IwY1FxY3wAi?LDfih8O>q zz%vO*`mdknwGwxr7h0dr8(iM@#M8`-*#v{ObwzB4R$qY!f)sU<$l6TPm!Q<821=(d zIwCt)yFKMAOKxe)GX8c|Y?x$ttGy{>Jg{Fx9UzINua+(`b0Z(x3NV#WzPhan{`BwR zNw1ZaF(^#;5A$utrsM+od^N>wH+f3vZIYVF%pYz11U8A{UdFI=G%Y|`Ai6ruDx^!y z+CV)ii5LS(jA8dQe3v7hegiFx=1w|T+~S`)5ZIlKp9`j~6ZWtvkPKodHZmO1&v9U8 zC>|yu2ASt6SG>kLUUQ1B$WeT{Fh=f~3FkrRLSe-(nW0ziq8jO=t;|@3pCDP!kKC8O z7a0;o3oP$0w`0%eG~+Zd^NY{Cj)$LDUQ3c~d@;$=;Uuvdk(@%SY?=4Zfn!UGw1&I4 z1vGD(pIZWRKWAU3{W-5}4EG~-pu-H5!tH{L#o9ikH_Uq8^h+oWpg&;C7)BfGgtt-L zTHJsshQd#6H!Bx-DpbHxQS=Y%NQV?pL*Xr<{7I}t$PUkNq{HU+d|p?jDUwhwG6CRt zRZcmcW?SNIvaOOjpP${^HTV z9i&6y&02mBtBl$PXZmZ#Ea>NAshzoAsS9T!I9E61x7Q$!9~z#D#qokNkO!)7itx~C zx>cIa{qiKUa!rxB_B!vx?dc$ciHfIF*koqiPyAhybza5Jp>a+FdC&&b9G6o3lBZ2i zr_C%MtK4i;=2782$X#%BT2ElmV3&~UNnpdxkY}uOA zXb`+E_33WM*u^K;dOsvFDE#r>4(xwd=5ScwQKQHwqBLAtdbaG{F(V#x=0-Ww^OuvF z;&|2V(QRZ@VF&spy2O4+Cp{U?cky((USJ)usBi4tw)EfS1+U$!_Jv-#cB!VbrG5QN zZT){1=T_h?J@t(wq|Gh=tp6p59)cPAVY$wXF*Ow*Z((k~(Fs2l-+i6koU8wSE%9L5 zr!VzxYhWOy=Q`c>A-Cra(8e)g<1qG8?74DF8Nedg_Ec+R74!$nyqZCrhBR zQT4grKTkHYAli7)mo;4q{+%=M271&;#oGc;p$pqcD*_VWgw zQN(V*U2F}e3_zmfW@xQ51%QG3eT4B!Tw(0mr2En2l{x;vu~Bus_B(ugK9C;T6q#AU zk`mz`6v?QU&*|D%>#CdK=O38Pyi^mIFacBYT@gem5ZKHlN_$(jj)7mODPtuwI3#8; zoS>gE{|S&IoSEY#G>Uga#93%(yR=w}iXsl!uECe7T2u5!RZ2kN)gy)WSQO|eJC0}~ zyuSujJ~SM^)JNsA4Lj#Ww)j2NF?)s6&2MKGip4_kYc;tW7rQdYzo(EZh9CU}?AAjV z7()uG|6K^3TVr6KfOgC9t3=zX+ADp8pwlzR5)&pfZ|)>=oPjLb(lHfZY+3L^ZIqP3 zlM%zI>o{C^3qrHH(Ri>E6usV0jOufB>jlSHzAmiO1bVN1@o=+u&n#aE2k%Ac*S=H1 z1|Ll)Ri4`}%W4MMvTN7N%F#Z#!kwyOg!INq50s*ZijM`4tqt|^9Suq_zvoQ9_1YH? zPPdfCl@l-JxLn_N z-E+0cffClia*W{)GO+kQtKWE8JuTYKB!sdN;#-F%`lo00HP}$~OZfhr{z0-iPen$- zo}D?NzS_HL3?(vY7UK?=bN~=myM6bHUoHABUBuQ~oE>?1jUonSyZv2J&&r*ECKagv zJDaa&t7H}eMwSoBvaw||BL}`gb&O+PYCr4$UBiL_3k3qeoYhJl^Kt{4OPM9d+>1D- zr|-=koXw2r4P(-3lNZxsyz^)Hp6tlw4~h?%pL>?H&##?)zVl6Lb7BLT$NQAJDB=41 z4&NmiK9fcR&3b(JF!7yg5%uW7|0obVorH9Y9szLI!ir?89;T@p>MocX{^?RgHhaB| zTQ-4wiS9%)YZh47PAK9~K_m}x8(C|zVS__DxonJqB30gQSfcxEU~=g<{zGd()Cs3% zU(tB(NJ#~Ptq=VHY{Po{N&3YIuw{s&9Mv)|LTx8n*N zB5Lb#I%db9Q%xKlr7R2s2B%K(&pQf?T{YEnmG5byj{VjuT$0;)&bs~=xD8a`jPK?Z~cDH z`yw%juF!V}-V3?#2XCk()koRTS+le_8yeBsaNbqdfPLdGQM@X6#v~EPT`mUyY*D6( zO{DK00Fx8lM}w{o0D&eYgbQBLoul?$|Q=l*Zko{&;3+af>1EYyYqK_RM=n}BEAl$gP zs9nSXFojSCrD$?`4kRQG(Tk*@q4q+lSWivV9r-8u-mDnYfI~AxRFubwu5u_vgZV3m z2xw$wX0w6D`+m=bprsp z8TPRvfz=Vz+dm46-pA)_7`X>uNA3>hN^YB^o zJ7+8113wx!dzf^aH}}o`yC7af*n*=zN`esS7D_Ij4m~>p^Z&-dA1LgW+wwiOR^xIT zIh?#kB+FklE=qA38w+>IhrK^k5qd!_u5*4U1(t;a5E1OZREmz1OV;gk%lCajCaXxu z8|iN{*e{zpj@nN`d9-2h5A`Ous;-9Hr==J}qyy^jn&ax_D? zu~N3BO6qn(l{PC_$HsVrM-i5y)>b<`{1vfS}3%1FgaCX+Mz(p-M*XMAHXh zu&Nh??YeLxC2-5>#L?_I$dmz*zVOt$2W%*0y=5mblu*YlRm$py3_bg0f1(!s_2)G& zyV9z)i5hj?p7konxgJ&k%~zJT=_$ZK=fTK^<~QL;HZ8JFg*RV5X%|8G*ZHC%HGUk5 z>TWdNQW6HtKCWq;rf9fS(X|smQ0ZQ_InWihU6m*^#E%N5_KCrHfSrd6je{%phIHo* zy92?Fu2)bsj0m=ED3zI>UckZG=sUA{Q-(hd-=lzEmXgEg#S=2VU3ORtarc&CLG$?* zC}Dd5N%|I#Soiy4nV~N=dxmD4XpNRwwQ~6v}{f~b7@A53-?BcCY1^W{@`qf2#^490?8J@R!3BfzG68El3$%wN_b-rvvs7W6Z(xW6dHF0)f#WwU=LL|8JPQ(i(l`< zM;r3RdQY_o#_BJ=2Eax{S$o@qJ#+JN(kR2b?m>ycAJzNzC>bBgpB44~x6liAHvVLi z@e*_g-d`|(EO6?(GJNF{9DhAWurVCB( z$@W&(pA;NS{oA}#b|yJQdl!&6B}OiEI!0C6(2Ox3xBZ8~`a@GlAUm53e83$#(`F;# zeCZ^e8_7g{1nDgKTeb~6{X0fwgP>>4eP+vst!wxicX2ne=vd+LyNF1Mn^sP`_bjse zD);)#x>6YxuQW;bBQjw@UnKp8(86zCYl-5IA!dsRs5YcjU^2*1;vHQ<_761H8sRFM zVm1Kpb}@|&15HU4v(Jw@aFz1UmO|2Yh?c7Z9na?rrotIkF;hZk$x8WUTa}e? z@XlybqTO$}yAg>6#y%gm;(MhX4q-h~d5nXLc)f79lIRdI>JSnTrt7z#aWmp4gUQC?3=_AP8`mHzr zZfb-XP2dbv);@LQ2h{!9JyD09M}A$rd-8p{r8k)?TEbw9&1m!Y!NXxgeAa6z$THJPuM$*U=k)J- z@K7XAU3G3C@GuQt1aBjZGjc1rt>&u3!DrXeQX2bT7R@2iuo~`|G)*bU#->=Nr zHmNwq-aTHqi7lZ#h>ikRDnqL0CRMiYTIz)!5Im-Ga8lcN>mqSnmbyPm@4{fEXZPD6 zMf;b?70MTrv>AR}aD=D=7{WFo2gH=;rK_vNkUyR>glrN%SHG-jkP@e+t)VAJ1lQIF8S zh}x6pMlw)zeia=0WQ?hk{oN?zJde%s*A362owvtAXCK*@M~gpKoUD@Rbs9)P3gIF3pr?u3&GMsQp8)8)g=BwwcS#-UqE?nF7X_a32 z=6^uz^-8mv!d$6U72~!In90}`9ZKB92~3-h{4U+ijtpN}IBK+&a5aBtu`}y6r+kU} zu}3swJHM^i;Q>^u0ef`|YfUNX0pP2a8A`@GsE8aVKCW|1;3iUo{=vi6%!|CwcGIQE z2b%GY0~p|T>;AvHL2Rt+;4h2$0`}I9nvtE}p!U-O)1@oalsu?=p$+4|J4O;0b#|{6 za7xInP*>#KKX8|b^HN;3W>3nuAC4~i1$Uz(x@VAz))D!_ z{YnGusQqN;T@OaX-PNV>cYV{7Wutm_J{S#Jbf2w)R4!irFpo9{-8cfOXi)YIH3H&Z z36Q3qH}liZ!*kS^5WB{nSzCmQ?k&+uURSKS$k2pVbjL$SxQMLE#rdT#oLZj4|RqQ%IqmosX7 zn|-$8O=nuh-G~NtQ??teK|X zpu^vpl6N8^mnpIPO^HEedA<<%*klc9eqmzrW_m@l4LB7yy5|B~QXE{~)d(rbO_!Vx z11`o0SWG-};UI;yV@A~L+Q{S_v-`yz`&cH+4#198Rd*@ktTBg6=;ovy-l$3Cb0CPx zAL*tHVb7<}1s{ukK6-estx|buVzBC3k+Yoz-;LnvMJH#ht#COR_$l1pdVa@+1`NiI z^FU6>&a-A1yjuUs_D{se-h%o&Fq$#CjYor)lH&uMt3KTed*KE@>|%OeG6oMNt>>oz zfE7h^&115OU}an{1q47*aa?=z#66Lz8~2)(aI3ET{+Nt#5r@&zU2@1vG3mre?=+%Z zb%X({_(eWQw#X-$MkwG0M3to~QPt0^XQ@FB6!kQKiijt+15 z%vtMU)JJZa+=|yx@#LArmH%L?Fz-sJX2tMp5NC2QrWeK=|LchsGj>=w+(f1<`c{bH z!66XI6hcZxIxOCLplWUId7fYkdZ9OG>4)zz(k~bvtTy>Cwe~ed9B(Sn9wtC!yVESDCGi1pCC-| zvtBb<*#uFm)I8V}_V}Cv)m2A=l`|nxMDgn|b|S!3>b%@FJyks@B@Z@et}OV`c2-sd=Aq#g)xsIjR^)9>f@uPMbK*7jfd zto*jPq^6nx(@qyD6UguccOQQrMWu?6|3|^svib}6KPlsJC5y#~(3P({yGw!SzL#)!4Wi#*ztCNLLElaBX)L~nxK}tGP4%8$knRV9Obdmo=1B&piB?M z@431SWJANwxnvZEo^ zzQTw6D8#`k&lcm=RffE2G37~YQJN6v_d)&(iSYU)gKob|$_=g*6m= zCgrj>8G4+C8&ih=+MTXiSgWm6^|UObDb@5!n@b)Fy5fAezA=&h@@?G({Vr}y$%?l7 zPJ3dQb#kBvG28yTfIwm=(a3jY?B5`Gx3?T-WRX-M;>NU}9epTnbR!NnH(&bMms+z} zg#Ip;vsTNt%ucGC+dsOIqS!t2smI&HzmG8;_0Y+Ktk2-ZsldTpcU51@0z;2sD$q6} z#qPVE=7SxRVB8lqb7sS_y{z-~`J8v{m>$cG%gE)4o&6qFT{7?aux6eF#RJ2BC7AsF z1r;N$KJ=0A;Q3Zo(mDt8S(B!SZvhi=N8c0xUtBY8Y=eymdPJLTSq zM-gpSr>BX&!O4I*GlmI0HIQCaQ`#4sJ|R2PNJQ=MyH30}4`dPY{biporCK0=e24mE zP)$RmI`%g;7ml|Kv<$c_zW);5h0J*UE}KX(xDIMy^~6%kQ7)k8AZ8O{gf59F|K2rE z7!fyL*9h@U>VOGLSp98aC>Gouyar7qa~kl07r@2y=3|?mO3_s26)@JS34L0(Idj|A zWrsbjHp6-SaX@}xW!|nW;~O@xYu5!GBe#kTo%e|-o2Qqg6`Vwm_^x*bs@yZwDJQ@e zP02w2&Azzi{F%ssUV_)tgPIMwnrLUu$I{u*dxu)!V9^h_FOt5EKPQ^0p60>qIkV-} zh#<#M=%_9oEr2V~I<;@-0j;|og-`M&k`9xMv}_ZVjTEZf&Ph(M9WRb`n09yE0ohe; zoDulnl=^-aiO+^HraxIz6e1-&ey~C&bVDWZo#++}a43x-B$kt9;8U*oS+(SwArTL* z)IlWPIQ3UFsae!S4-W1KAD{YFzhnyoevdHKB4M0qeBDwGqx>Oce*BRx|R z_$fCd)luMfLutKF^^Yy6ZM;6nkJIZESWAC9@Hw7N2c4$xJ`23-$O=1ube_ zrTFydG>~+P`g{^SEnx1YF*E(t?GfhzS^se39Qq`icj=N|N!O6z(Ky|M5ud9MmkobX8O`T zX(m1q?-c_azAZS-jPd9VbM-^_TdpP1BzT3D+m~`Q%wU@6W_%$WK-aPhaEYOj%dtLeYaJ zF8vb%R_g+?DIuy`^Suq9Hn3RrF>f7NM+6EjN&}=A=16KxqYA^UQpouUxi;IAB07vd z@})AtIQc!`j!lN3c8|`esJKQS+r_Pev{QW^R|hn?^$&4w0s=D_=~sUnu=N$;B?DV+ zzdDakU}&Q@TB>;DK`+1T=227r62;R){$@4o@l|&zGg(F4@3rKVb1w)d7`r!rKd|$b zL1W-#sa-di1!l}S+HM)B=wFH8-0eCC+pZKUbGUnbwOuKvqvJh-c`#nc9B=w}?gFWg zA1gJPYreHd%nm#!dNDI^#svh|-nPr<(E1(O@)>Z|lctI8HrQ1M1Iw;<7AaK}SU3lj zekdv0OZO)wY{Su2$)WG~1Aa`oUBf044hKbmqdf~Ey)P8eOm3kPAw!kNvJt*`@3pos zkQ%1t)Kt<1;T9Qv&KrogxB_{2bAeUI@x~3VmMWIpYdsy8T*Kx=Z6Le;dsFwN4CnFy ztgY6<&8M9|@+iG!|k6V4!VoY0ZNH~j$e zc>?!~YFykD*^OR~P|^c@r}y@`n5h6EYkO=9HFp$J)`?<1|CjODnPHnPerhl^=H=q?@&73uKV<<4Kp)cSA|PBteAB7UUk0&twbJl+dV4E z%1XXOYmEQ(=_}oQKuyF0X$8#`?PSIZn$zj<@ijw+d)YodOq@f_@2O$GU&&Xd0S^u{ zsd|ntJTpp-=Y=CwZYQY+|4I`xR$ejK?gs~%2HTmo*?}iA)X98(9$h`~T=QfBa>31S zO?CfEY6|Xf^__izJtt0IHaLsgQ^RJ(%vglP4!KePM&ruIH*0ndtktxWFPE^S%miIe zT{Ro@%WU5~&!qLetN~`yb*LUVL~cJTj60e?dxEx{UHXu%Kmi+j)09dXALY@9cJ z;dkoVMf{p=DYfyB9^!m^OR83pY%t8Z(CrTWsTDpuJSM}8LPHK+_r0}AgO4}y$hGAf z4DzgkClqeU;!-92i6(?&14eoV#Vhge=KY1C5|%jo`{A>9Lltu>{zWGQ=Cs56TBvLM zR9pIgr%&r?IFgmJe6EwN#Qh3N3vhE_Zq`pf7BR_s8^C|1JM%CO$XJ*}g)_Z@3w~)X zPTJB3k(MfMRer|-z5+Kqd6dzMnuyC-bPi}U-3TGy5W)&hQFf6C`6pX8>!*7s^6E!! zQ^2w27lyNGKN*Jb(CaYUR0GjMoE`b~q};!i8U*6hhFZz(ee+-`HnTFh-t^TYmsvua zk@O!lCUl{S>;bl%ZO{@1T>xg7m2DH9 z*lc~LtAdA{^xSm)V)?k9K2kewxH$|IE5G*-e?W8bKS2ii3^!>r@%L2Rnb2B15ih)| z#A3GbM@cJ@a(ALdoLYC3NwH%!bph99z|e^A7}*yL&gIS`$ujZGg)(ADjPD_n#P(8) z0Ry*LX*EDa|Tj<p`}X1x1B`+8EqQ{qZ@CgZv`5t$0uo7egS_Pn_QtZw_AQb^t13NVkn%{p{RZbc3O$s8$#A%m2xE z0bal0nVeU&J$cwtlN>lMPs2c(By1xAT^mVJwn$@@{uDK8uW|XPdOBAY#tSP6wc&}0 z(klPaG;=oXxIOR6wj>qanYn9vnXpyf;kjeq@?H?vz`W3B9m-BAmeBi~b6;e}FJTjA zc9%wja@B7CAa&bTCassxN{%G$w{22V_VxLo;Z9A;f|aIcTk`?qpi8oRpwYbjrTM_l zp8In{(LUZnVmT?R>&5No<6gS*zPWahtm$+NEp`NueblWJxEco3Qpc+KV$85tUePfda>E*$l1{{{*p>J`Zu7cO_*6%M{n?qlxv3x9 z=0&Rtkns-uC>pPz6*Y}-|61T4Xe_~uSYE=m%gvsv&~RV`7~TgfYUVk#XIzKGd&T7N z3LHUj9L0>1$=NQnX4O?9<6*R`gUL9_Vy;@5*vBkpUwRAipsJul{BKb50rvQw#GUox zRfCGGtc}z8+=bsPefvi@QS9_*hJiIb&G+C^gTvqEE50{=JvJpc zm-L_MR_QLu*SZ`xcu?kZ(z|Gq<@h(~%1p8oLMndJyqd(2VhGM4C zJK(kNFf*2qLHCtNWPz^Qs+8&IapF<2Q|t1wdhh@0Hr8P5^U!LeHIVb6tj#_u?`+@s1-rdC>d zjj2wj2n_?yOM!K8vX(#Z)V5zVruUFwarChCyb0qK>Sy?mncpi+@#EOS@6R3;y@8kM z;~Jzx3wt#YnFsu6q9NvW>;7k$YRMBDEJyY^U-9JkZ1rJk-vl}1-H-aC&iE!A+kqal zEt(K?50n(F*@S^kM$+M0IaB1_?c!6Fl!K|~ZZGUiINOEoM#KfQYd8+Q60$qpi`{L2 zSH>^zT44FBmrTE77i8{oj`<|dL>3sbjU4aKs z^CarLXH(K^1)YG#-jgq|2L&nCm*>z0PikN0hjLx;!fv^_)Tv}iKeedKgVY9q2z;F9 z%IzeXi$DMV*vBW1^rH}bPyka#(Wz5((m(ZEA1><{+u}nm?KVDb+6+xy^5mQXaM`e~ z_}kDN!|qu@;y)Ah(A9{4CV3@wwXOCUwewau!3_?I+!on&gT6Zjd4#yhzhdCzWO6;_ z9$)+D=;n{=$Y7?fYd$rnfb$P&9{{It-O>~`YG@JDnA!k?9IU`S7wc6j@Yjs z2)w{1;YHqYxSJWs+84O)E#k1>T)l`y-wxoxU zdrqCE3U?-0Ja{(PoRByKJwCmeG6#bUPcB^5To%DdD#pFn@|YLM>pM7Xw-@I-vy0Z< z*r}Mmwfpy$`6cNZA0!dO7x9;ARqpSH!2Qf}#sYk-JMej9oAdQ06rmkg%$g|t^4^Y^ zUSUr9N3)Msq$_B8`>}Jys8n4It|}D-@uxJFQTJW~%wir??^G0|tdD76UgX(Za_yZ` zfm`rGYwNu$^G0CbCcJ=oEVU5k4hn=Wl?Ma?r>XY~uzhm^pRHZfx_1uRqc)G8IwULu zHbJHTr_%Nx)dU}D;h5NG1o&0=z_%b=K%Wt@ZLI=$-b>QP)}<_J=m-Dc!*|X9=M_#2 z8N*(ee5dY1vR>~s81bm($foaqOwqA1C6SwIjpUX7!?sr)-s0b;s^PhRHDwUrBSng_ z58jb9l>z_`q&_|$X7;BjY=eW`3+*K3E-oR7(1P6D{PczX2QS#PP(N+(!(QS4N7h^a zMIHZZ-YQBcEl7t*cXxM4cQ;6P!yuq^j&yf-3PUK}qjV!V)G#o_07ER_bMCYEK6d|w z&&&_6_Z82JRuw;MIjF?MecH|TYNf3?5Nc%D0mvf{8VvJ<1#K8!XB(!ViJeO9jkDCB z^#`1?n6XdUY1{lOId`gYQ8%oa2ZA;s8=l)^h}7@50Yb=*zBCtox0JXi@7~}?6spuy zPc!|{8|dubrn&nHWxUIOr|zCc(`jR92a0$}LvO*`=JzW&J>aM7uObuK^RrKjmGkXU zC)gdJvitoC^Az>q{Tg_A2S9`0d`l#NR*5Dl^oV7*!amsKdK1>5zKrZ!dbXSoRG52M z>n@winj=GvEp>z!!OR7Rs>;=y_sqn>K%ID-wqzbKUDu5D43oIvc7P+zOl zDxdYMBIWO^U(uY=xmAGsZ(HrY;YUV6iBm&spIH3uEM}B+t?cHiF3l4XDgs{-N(uI_ z$6F3EYm?-4&-qs$ks$Ub{d#3ulzLbWJM;D(4GmIqcV-|#Gad7@KVdy_v9ysRtxLV- zioKby9&9mVIZ3osF~E*8!WuYs{+{rAE1+;{eG#-jU00(JM2w>=^W$Ggy3H*1J%83L z-+?Yhl1sZ%dmAGVBR7c^V?N`Nych@PK?zVHbnk4c0K1VJa}E@?#?>?4W8xrC7hkH0`Xq=LCC~!- z5SlMBe?IL%?X3N|p<$tT#PbUy_ia{g;rD;RT7d@)1GI>hjf5&?YT~+A{}6P9-#gWd zUQ6lt%YTZOU|%2m)kaeNVs4_qI!re)RrG?T7d+Ds3f@z%dYWDmNc^nVnP4dQG-`jt zeN5O3YOaLS+mlzP{&fy&(!pb?=jq)5VcGWYzw4*s>rCA}uMqx1djfB5P5*RpY+ z53A&a@caCWIUNW&y9DXqZ~4{v5WLlNO){1#94F~Xk;+V0hL#)BLG0-)p=l3xxiSy0 zh-r=yt59+JpK3=#3Hm~G2d~B5qG(RzYdGHHrN~s6ZgBXm-8-RfrO->SNcQ^1m-X$Y zR+DFB@2J%|jLZcvE>soUJst#X=gB-Dksq4dUiNM}InTr$Gd5F0x^j`8(<14@K6}K? z)-T@l@h*TQ@(eTHioILykRrN#Q%OB*Q+QrRnlOkB*cY1BX|LXl%P9; zd}}d+MOgfjXj|QOjf4)K9)=jEr0Ipz&IHjl$C6KmvOS<;*i6wNmTWM}LFw;)Av5uB zO(lw$fBafC(L>fV6uZw9n^yB(@f3^?vC?HjFHzJF10%T6yyTTb$Qu3rg)cGd!$N7U!dz zDjjB1`@0KI=!Fpd0U(*>$yHwKJw~-1K$(tfKG#x&L9vLPlR^rdz_aq?p39a(Xlf#; z=Zn9HGwEKZ4on*+1T(M{f2uhxxw&wkPxhizFv5)1aroBrwl%4+V@S*uoytXK$5ajV z_NK@it(0u{yXBW?w8#UlNb$@?r7=f^HHJ%vjQ3v{;>I``v|pR3hg(dwQI9Io#x_Z8 zo6X_()5bVr5!|n$hGZGE$-9QEt~z$B|JV zWp_+M$uBfciISCj-Ufa3lBwr-xVHj}lkn{tUFyekzz#7+s6t^!Mu{JX)Bi&1E$VSm zPtksR-2YjUGbJA};eKnygvTi)uxk?2iZh&J%r8u7oe;slLZig?%fG|33F$lsr760; z^$q3J>@5r;ff(na9GWEY=6MJ_N9b4~d%wdoB*xt3wwE@E#t%y}-K+rI2VM3ev&esj z`tm(;xC6M@^S5gP4qqpSJ-QXu3a$FO1-^I5S>g04P*pjhmWACObF63ly2%e8Rp^(( zl@-Q$XWdevSgkp5X&%$J(cGtbAZqt77Yl9TbUh+pMJ8zGL6L-icw+7~zVK3A5j1(} zcW=~1R#O&=tZZrD_C8%qk=t1wFYpC$EB4fhi$hQyOFEh2bI6}yt*&r&!^RK9#R~FA z^69%UE$*oGh@Hni|HC#*k0dZqm{nCaLKT%;(ku;~(k@@E=B)~h1y?C{>y0$?n>)ns za3BlZQ@zLUwVj(h1|7MVHx7|)*rO=#PHTD0?M$@KdXZ26sn=g-^iCrp7C^)?PNe6a z?&9eFRF3Thp|Q;cq5kMTOMN;^eYYNa-!|!g{rjO41?;ffe~`U}4pF1ifQ;4Am<~|q zXSMjfR*ZVk(4>91M%Q}231FygLxjqIb5#V$J+e*nT@Ru{xSKNGLNxgpQB=Nbb9U^$ zP`6gob=t$E5?2+UBd)Q5#Kepdu&}qn_(V9k!R(np+^qU2X&LH6aOJPl{cgdLZ|^)Q zpi=Z`3ny7U^ppiY{DgSfxjPe_*z4pgMR`6*G=b>7GjnJ^X_yb1zW(W@@PG6j+UBp5 ztch|%d%JJsIDr3Y&~99DV(!UM-G5z!0V;HUTKsR}vF#ziw?X^#Sjg`lUQ0FJf0Elu z&rrWpMK(>E5P9F%(Wm;jxw=UY^%j59d@q42dS1nmy4Z8-1Aj#HnQICDTsv6kNR@cF zBucXcKv>MvV8MGei?~G>TvYordu5I#kscLch z?)uPkNn33_qUpaKZWs0C`}=SckG&6_+p#Qpv#3vI0d3B`+m4<4D=t9YmkZS7J>6%7Za)P2PH%%aBYUA9kVM`IQQZ$QM3?od^G*wf z}@k8rh6~NGZafPs!hy|$o3UjNO)Z6PhbS7mG(Ot~x# zRxRiy+_4BFttc(^%XL7P){gEF^8L3Qg4Dgv>ik2*rmh^}KN5(l7}b-K` z@|Puyl^-s%?`C+f%7IQLZ51UXkvA^G*hF06%AG1M`o1E2x+I_SDByE2M;Go<;?do| z5zVm|It#yfe3>Z52}JezoNzZi@a5x~9SS!}#hdyACHd#QC?M;kx}Q^Wi{ARma6exa z_8$8kYzy<;bZB5gu!0QbAS1`dgz$oDNV8N3Q2D z#h7Z#X}^hMno5k)NR3G&$DFYJ1Tmugjh51|#s}{808^U{o>(1xq{n0oL;WM~K%r#* ziyqW~{3wTM2b)}t#X!YP=1L20G4l-unM!+sI`<9J0bC!g^W(P{_PCAWafluwyDg6S z_d2w7MdAF=0Ll(CT+yWDRALkTw212WP?AQ%R4nUy0H@o;RU`2xSFScImngIfTLTtQ z`SM>Cl@#HhFnbB4(L#>r)5ZG?VxX?Qa&cF0}|<{IkfWYz>~F4_H- zhTaAJIGCK8b&p1P)S+k3T~SD(xjh_L3B+Y~ws4Uddn0`!>-h$_kn@GLfkn^7t%_+u zC+wwe6Z5-e)CUJL!e&)QwXQg0I*R(tAkK6hf0>MF7_ZqQ>D4-}XfrFxmL`@Q9vG)y z7`ZT0SDCB3mps+C)VKKvx^a&2w&>nW=x1jTx$)tzfsbNudj_1QXImy%A_vRwkN-l{ zRB$1Pm1`5Zq)KiT18$!L7bs5OQ$+WzsvGd>cAH4$-TrfOss|R*A$k^;xb(Xz+^yZF z$#Ng8Lb{S~cv`aBx5$6hZ7Gxe_TKxoR6Qx8ztIO=Ij>`>OHFH|IRqbzRBCz1)qLeC zYTsv9MGEjQ3a#S}Z3icc`?cdGTYcGwj9%F0XXTW2Y6)!-C!LSYO#s8^cF{%>UqHA8 z{D%b~Y#6$dLeTN+E1?kZ^q!K2mBh#`+8udvmhx@U75M{b`YEt;2G6Cow71Qg;gswv z&9CdflZ8e*I3Zc)Biy+P^&U!~y~nYcI!r@F!a`yZy$B6F>h*|L!|onQ*r$F}Z!8R( z?~Z??EjgL(PQQ8oNL~1C%WUx-!fHI!GHq`Vx(4dN32dIq?VN^hbnNZ(&wgWm&7Ktc zb6`u$t`M>r#ZMfP;p{PyB{6LxK&F*po7ZRr$pGFxHI8C7!Z|XVF9W&#Zb$ zk%BrV@-lv5uoh&#+J*#BzZ&6#Z%$<0Vxo>-SiaRmxj-a93hm{nWN>94l9P5v3?eG+Du&{2xw zS-%Zg%lZ@S7Jd%ovFg$Kjv zv!i;1@Itvvz^b1}`?mv6d3$Sk8dgYUP9Z>dAVT--eGYjo{+gOl>@isMg_%Y1tyv81 zsf+Fq^pqMW3f#B#(;)E9rmrd$lm*AQJ3l}JU+E-&I66$D_9N%UQl9=tgK%DTNsO{H zKl_aklFNUJw^wR_Y>Sg)?jr09*aUjlF`jisADj^EVnIRyZ&BMkr{oz~ANHfSW1h^> z^ITNBOw;C-)XtrPp7~zX1PjleG_tv|#HDQ2+YvtT!!Tfp#fLCi(uLOC6LO(K*kD_%~ya$Opsl8f`;c_#bZJRBGq~5+fz<*Cc6lGgm zA$0AfHpIv3pm(8B07IjBr9@AjlS)r0$369k!P{KeL<+vKv3tIcgyS#NS{Da$G8^iC zwzqWkjDa|&xH0-QT>Tb7ql@AXhWm-ZunF@N?SeZ3ysPfWf7Te;uC}q|N7hCJ>QI= ze+QCvT)Sk^48ZG&Aogp-JJTq=6V+b^{F6>}X_n^WT1>ILZ;gq(59?*$;cp3>5$l%2 zw%^blc-9rbexxZ95_-Q-S!_%XU+`B7tReT;>d!h}D7E>`cozIeK>-6*=w{6>e@Aay2FFm_T}H2O zI^twyewLdrrpI4JA48f6DrIU;wK`*#_`p^TeUs6&NiW9&KdyJvMdoZBFs5HWM;NT$ zTtw<_o>J*U4IvG(&w=EcmCDzaMN~B_K^?Z19yCpZ2?iSvn+?eqfY2fn?=icFqjjpA z++6qjsl7);k4nI%9nOEGH}r^K4@qDw&{(C!BMAJ^kH_9E&sx2`cxFg}rlDmL?|H%X zJK^4ARhWKP!eS4bn6-MX>bJ!K;imXql!)c?cNd#r`+JH=)z7}cS1krXcDPRG3c8|T1OZINMO--Dt%)1zfx)hbg! zW&h^;eU!sR#CItUzB8Hf1u4;v-168J8?iYm5HAHF@G8px8-)Tu*hN3PgNkZv4Sl6c z(&9r@IlC%c5PK*gi)w9|QaO`lGgjf10|nZ(fxs!W~D?UM@>ZiOC@DMZ#Ucj_-lkpy44I5c`If>-I#!xEFk(;5nqC%4Xe6^ zsH9gac37A+($%XXi!hgVGU@dit@5A*e_Y%FHar(LnRLBXJg<;ZUIoSZx*1;~hyOG} zF@<6S!S*ftk#+JzMr3*$ z6Aj!9Joxnzh3Oxp1dXjOkNQRum1F~<1WA+APp97;7E<|JP{j0Zm7ztP zRwdSs)uo&^CbYqIGV9LBCpU)O>R>lB0O3V@PGgpBrZ1STL-VTcN0d)Dqo{po=|Wl_ zMMF)lkuOi^Z$zjd!VexP(5u<|oLf!me%90jkfD8sl-GaIQhO(o-~+@FoOL_){iA4) z!+v;G9O2h)$MLR`&0f6InIE`JJPXeRcqgE}2hk3B+^WR9L2J0k&e%_VuYFpc)$xk^ z6s~b(1v8|-x=UwIqx|+OC_(13@ZVaB+hvyMkWV^Qr~ziAEFOp}!F_uqXHclLN6PK?we4arxTd?=gF7L5BZ^`-lBLT4{{iD7$`n*64x1bFM?8kuq>`s zSxz*U$#k20vII9yvBx`0xXr7VKF{cK}r zd?m1lZ0FA6S10Tix{2QE~>Rcade)R8#u>(XYJ%-kE z+D&fXPl<2%N>mTk{IdvaUJ-Y{3VHg#-#jn0yvG+R%KYKV@kIB)=AnG}njs zej)w$1}kOOrkH5>!F}<)ZGYdEM!=7zL89^JpI+sOzLq$$i*-?Wl8Y)DnmFr5;@ZyM?CIe~pC2ix02s6m%86Prq zQDUOH_B7(x4S`HI$hlCXs_TzMn#+;rfv-Z?@^Z_EjXZZ#6vl-hxh1eX&>g8E%1EjT z#MGQnwW;q52GUsg5EErlP2^-G$dz>qhv?|5Medmal`kn;tIv_50>pN%!CyX|D`Pdb zH(X&Q*Yh5_o4VsxflY9XH+&?oVs_a*YcudMXZQOPpqiC~ZR`$tCx zj=rAmJD-NM7@6FGx!}$DtPuLMosV*#vS5m1C4|F1&bvuWy2#JBe8;I8clnD^#MU<` zdYuZ46N?*=*aev6WVz~-mE|RaGeBs&wB2xETmwzz>CS%`$D3Hhvy~3YO%W(18E&9)LcS~rlT9<>@(Sa0M z)@EJ`%nSO$E$$nRi1y)0A1R{@+L8!{VYc-e{ve95`n@R+Ug3hG_xr59zeLsZK4^l` z)`QLULx~lHBOjCQ@6SOoO?>%ZBN&rJ3qUh(A{vn@^=_o*&^sAnj6Xn2{o?&ps;BW1 z;CYsp86|Ag+XL(Co!G%lXT+hOyh0+UulLcPGfubJ zknBeH;LSgw0!gl#JjWB9*gZ%gux1pw;Gf;Z!98n5aEP19Ii_Jr~q_Ca%v z@)RMi9ukgz*fh6)=>fuZUWux4?6VCrHrhU=p7SZhofq#yMO+de#m^C4#As%S#408V zm3aHlpxu_kLKcVY@hk~pV{v$u+xTMd!+R3U$Qg%@-O5kcz|LE`tQ>@#>62pnUN+RT z11V-|b_J}M;86?tCYvGwxqZY5WornzKrbGt9?o(qYB9+4;-a8xh3-bzniO%Cnqi7^u@ld1bBj5a0@)Y z&1rF5LHr&dbe`A&dTISX0Fkx_mgQ_dr*5#m+lGk;M52Cu>(nm|oO>&FJ1MfshNN=; z8jJ*OrnwlmkE$m1m90S^5kIu4E^)K+^V2C%I)1Aky^_CXw z&f>?5Kq4o)o_>1D}qxlW=tJiOl3hul|fkBM3Tc)#QM3lKC0K-OnGAl4a?Fp$*srW?ny+hzid zA$6Z967h_k4rP_NI~3w3-=D@%0*=O+-|9VIx4rBQ!DHHc*BzbT?=K91go-0YrX66KpJy$X|frf{)SNgngYk{yKIjl?t-iEJ%MtYcY)}4 z9xlI-*|u{L8SB#A8nRvO6k`316al8175jlUKy^NGV-G;CIv{VPiPJP7$a}HM&Z$`w z>yQFllvDF0-RAdBBqlR=s!5NgRv;5S(Gj1@$ybC3lklJ=qBYbVR$OgGI8S@otR4vr zdVeR2qEc+RF^;QD5LYK~RjReF_u2T({I{b>(MOF;sUabHJt@*p)2FalDZl21)52+0 zg(eydK7f2@4Kq=-v{t$Z<0nNwA{cq`#$}{9mV8nX6XkI+v310zzVZ+CQ}jwkZf`UvmJj3im)0&nLSud>FE-aj_~>#hBN#-G%GU_yUvhYx^|43 zv9YzLfLvwXSG~Th`eY=*A%rm>c@=gUx1^6bC_5AezpTZ1SoH1HYjS00p3^tSh8+KP z)wpDx2v!5Cp9Pd^4&E?+NQ171kQZ*ctlp2-!Xqh{0qfc(;=|{|{3-0UF^Q4@XzPT9QALv-|uv0?6>w& zR(L2pUGmeRe6YOJU`}QhU5j8%sg-@r<3deQ7sCht33Jr=L$%c#lrVQw1#vFLM1_@#Ghsq4SAaZ) zPW>g;!%s)C%*!34N`_&!X{Uoer+rYUmHC>GuR==gaLhJ$Hb6al#&9YwXMWy>LwW_Ds8eJ$Q)|`n_&vczV7C_3T@XkOXKPG z^V;=$Paq{SK*o8-cS?NevggpV-lSbwsl9Hpp@BkoI3i!_ioWS;lB)I%_a7btGP8pg zc!`spEUkWfbJ9P}+)@F}!)k>yE-Lp_XQI&6?eT(dNzc;rrgabziQz?Mg+f~jwxT@p}`er!PJxDzQZ7QJsY7=-*5WaKHk#N<8 z;FKzDS$~p5?xuJJPNI&t6r2;Y#ruhh$7{Y;l~JOkHNNU#VoLe&#!f=t~>0*}w=(P`$ZH(B@&=!PUij5Xzd5FbVydDlm;lDepfMs6P~ zC=Ca@z6JjJl;H!&zgu+ZBC<=gE=v1SUbT?}e35=T$kA`34QVC<+hZ6QHBskK7i`Y~;KZ?r{J5ZJ+R2{0eR*TWoIn8n zU4epz8ZH@!dEmRmHMv;%d@AA&&JS2f6?|)}@)0NMhX`AJ0spxeI)mt?Ucw-M!QPCK z#>@1FPun-WrJz!4ODb5iLPUH+n6&BQYk+C2vBnu$_1lC31qvtWSL7HJ#RSXc2+Sw! zDUZHM6=RN*d|jJFvrh4H%gj#eLq&9a57noFAK?KXpYmO3_>O58E9V>05l#(!j==S8BIN?+LqoKHyOAFWYRx;0+Fe&WSA(uM{Yt!+ zmh2-=`e5?^xk;Lz0Ml94fq}!&D*p$Q{9faY?JogGKPYJzUDuh^{Z~TANhEH*6FtJ7 z{8L2Fw!pp9f@=^nhHU^8YkEf;Q~zA-i)G;@Z7;ZH`(a zlasBYS4`^Z+6DKrhHZ}W?;@yxQTe{>65app+FpmYGz4Faz~KVH-EgE{ z%|}Eh62r5`Iw@HO$AoTTLd?*}D19$2-y1E9l>=cDP{uOBB4&7qUAhXk3Fu^*=#kY+ z2Q|0Or0wky8k!xB!Qrvb?3ht51qym1)4=a19bkD#YtyD#Vt3Pjey4)A=1p<3()IE$ zgOoi|3g6!9rZiz4>rZ4zT<8U?=Sa{t1YMx1j8y0^Kfr?YZ~uvyGfCl|{4Th50u`Oq zx%r3P@2^RWKUbWK%#f!f5-G;(TT<|5BJq%-@x6M{7%HqUs|6%}R;leiC_^t39JQc@e={S8B`nyJY*G zdN^(4UsDl_26X7`?#KmQS~1z-KHS;+&bf<58=!1e#4Wz#1u&_cRK#6;UQXAM;(12L zd(R0F5bTfRgWV(iwq@tw+nnFf1gbv$NJwXvbiSK&GMXRIc$xX^1f_{zOd;^0v)1#u zZYvliYXd9zBxjw3a8||KJf`QM;2UtyeS*AY^;V`A)sG_9()&ag=Cjk7wB4;AhDOoW zsn7YfY5@;B;W;f2c|+eb1YS62z`&)?%Gr8*uW-O7QjV$pf$2Rky1H>K zmkGmD&D`6)K~bvenN6|h-I^jz5=c;tmcu|GkK%oId3E`FeqAAHmDIJHhV=hRJ7h1wzLBe5GHhjja%iQ8jrAre=Fl|@X z_3?6ES-09V@TS@kRBFYaKc%;KlvZb+h!sp^-0(0N*h4m-k?(<>_HE(a+;acV`m4oV zwpHYdz47+;c<*>khc|?nX`3XoNZ` zGh)$C3o{Yp5E*O*b40E3ki{KvCLn{*4yHXm>^>;APm#9qumHdBIw+2NCb$AJP29J> ze}b9xIHrj{IH;$GhR*ER24*uuESX@6-!)=0G%3Fo)?!Wtn{|>sD?(=j0w~+ z1pVlP?S5ov67^mD$`9wgng|LgjNAv&^k9&mz%1c5uN@bY*`K3Gvoawx;oM2{lE- zZw$34LqGQo3%$Sa<~|!K3XXyGXZ7#o{8hjy94_}X!O%18ldOuU#{D=ol!%&K*iE0Y zMzWu99Z#eUyj(h!sdvF;sMoG6UH|IAVfa&VJ5jq;y)8HVDM{ZPS{64Krvk~N@orG6 zSa^5{M+T4nL%nJ!eTD0~N0_B_LZ zJ!_6kU@_rXAT1_3I_abk@Gnm%2wO zZ3p25D9X8)$ocp#PM|S|Wl89e#}RyCuoF+w7BLmBv|&#%K3g-{KKhcgi7>tW(aQsF z;u~e6!XP+ul63t?>^>js^p0n$J%bXcxV_&?18h+7?!)vbFD^LdI%SZPVkDFLgI5~# z3|aFtMR9zMI8emMR7VBU#)v*Vzf<%jkU~w@sgMGC0g-LRkVWUsJryD-03ev`z~h#k z7*Z_@W(;ks*YI&$U=xDA_W?Rd%^B`rOiQUFd=%fV_^o~Kj2B(0IUKhT{$+&i!tr|k z5U0fz8n{NP<*^nbf{*_?&&9Fxtl`&2r>E7pSgPO$8Wo=GeYRsat3FtKR{p_%E$k4h9o0O>}(nymb6#B ztRA{s0{>?(DmP3|pNcQ7l~QPyyh|E!w)&gYFV_ZrZs$#DPHRIJ36cGD1oBX_iXYe| z{P*|s>>nN1>?O=M^!JGe=DUzGhLrAf7ETONBdqiQERn}CB+pAoM;@uL*gV$iYDKJj`- z@*b*;w-0%wwzBLx*o?m6kFN~#Zr9rhXnj{cgUhKk_xll!9mCZg1;qm78^Eci@Xakt zUEEIx{jj+dEX2!D03wk$G#miCoYnr6`=omAdVdHOV${<-@bSKlao=Fm+}1c zgSWGp{n18RxF&I~)Fiq$Y?}bR6vc6;x7SCtHMD^*t0JcrSJ6w#wZyE}bEE_4OJ;v$ z_Ea)&E0BmM`!BLB4}?U8aIfcFGBMNsNbQfET=_$aD0!Qjc>h5E8K53mF(V-j3nWx6 zy%fEuk`(+4OKnZMj`Fld<`v{*5Os4`&=W$VWe{m2_p4&NzY!vudJVqT?2G#CR2CuV zC6V~v%GRY1ra!!bfsCk+-E0+WwhFwx1!#RRL~6yT!CvHFTOWoRhuVrDH@^x+XF{aKCF5!L)JKHln86NLz5H>cvM;eJa!L)qv?^C7|Q zsn#T93LCb8mLJ?5d_2qXgDUuz557CHp`Zu;yz=kS68fxyJ_~~JN#kf6=bl0qPd&R( zx4?XIugfC*kreXKH71L$>YqsYeQHXy}&VIhlrQ<0v2XimT z0UbxDaaJMja1XTeM<3Pb-CNtzx%b^A4*jHz4ui{oa7Ez4dqI(0r}1PMFRD2)Updh9 zK*PJn-w4OuY_VyeWAPM7@=X4vJZ+!kKq;$~(*@_(AJpv3VE>2+Lr?Rk2SR#)Cc>O9+Ik}Y4VBpR4Q$8ek8&mZdYv#zKMNp z*$T~Spld7nXWCosfj{0!ZKzfD`FXPHxr(vuC6x6hBL$Zk&%j(q(;ghl@RBzAt)pdu zNI~7lIQJEz=r4YXTt<%;~lSVu(%CXA}$JCAn|%Bn~=?Pz^YU_@GpzK{PRKnJ#g8#YICbbXsp)vL2>kWZ@rj<#c>YhwO^#;@k{2D<+nzQjy$P>^i_ZaT)>I#N zc6$4znp<FggTb^PMkO6hGB6a=2{X5NGucWQ(cj z9Ls6iYRa9Q4HUj@QdD4!(nXJQ7_Ss$n(Vn^4Z3iC^ZWZlQ@C{22xD%(rmA~B_>nd3 zGpY$urqBD4P2WN9V%VRHT?KGM&PRf2TJ0?%<}Yc%sgwGZobW#34PvrVTAKYqPP`Oi zpXBO5uGr4XJ)JT91j0LyOuFJ_fNCF!7=JpUfL;9ee#Upk)rH$e)cTgqQ#U{8>Qj_o z3v1DL1DyF(p=U5rP)Be*pkGocK^;cOM}GXL-}x!kusF%kDY?_Z@88SVJEc?nFK z;Pt-vq@ABhZ>>ITDQAyD@V4K1*Of>;2MMJ#*__;eu@2iN~c11AoQJM-cJW!Eu18at?_B5 zXo8B_R0!bp_?NgsXM-V7@yjrxl~bVn)yfRO;#g_%0OSXZ{jgQ*$qYu-C|ThF2NdLE zQSf=2$*Z8&uN6nK=tE_ET-}Bl=%{~owPpScZ2Bm>`SLBc%iFgXtj-gBrW%faG!qm| z5Fo#Jf$R;RvfqWLd>;GAjY8E%33GW7`p$@ln?S}`B48LR9iTvv5TtlQ{1g7qpi$f% zwtXLnEMzc$JklRr5rYAM-AD?zkE_2qx`m_FvvK3LwBe+ zr6)M?n7Ey7QC^6>6))p)Ch=9k3oo%sFqyTJ%DK0|-SL#XY+#i{lumWt`g>`L{PU4JC=cc?$jVH zeu)GP*Z7G?a=Ns!@o}uYYrHS-*IM?;iw^HZPKlDR z{I~dWIzKrS)Y&_!w zFQt68ApsuSNlm)QPgFlChk~c>7l7Jp!A6SmG_gf4HcIB9NsSi_?~2-X1yA=59+dBg z?&FYG`IExFz|U&GC7AY1 z13o@4*+$T_6i6LSJHdVXXQYqD{{Ejn{0wbfj`EUNflBZ``C<1RRbb2{2m1s_{8AT2 z;rDY&6F$8tp%hMyo5I^*9a$_B@7*g99jtgKTw$L>-T6Hz2Du!#^jeOn7FqC3;BN1wz$D9ZnlJMMNTfEG};tz z_o?jZ)1>J`xlSXeED9DkIs3H&RcPNirIlKNHh240fkw@&Os)FBVO0^^(Hk=+A<)N} z)E(iYhUfid@5w2SQ3Z)xLLr17L95$8!vj)?k zDLzm5fEhc?e?WV0lhSKKjQGC~%}~r$J`gq7|Iv6?#r!EOI{N-mJ>$p^V($H2bbC%f zk>ER!_V!!Na`fI62jDfi$m10;Z4H!ac7LB}7uzo=)x`a;?Z#EdGx*T6 zFQ|Z4DSK3o$Spn1Ro$+fepejP^+R-j3z25G%JGDCyf~gAfusyTg2b6Qg075hIUD=y z$fh4%b46I2KJg4DYB~v&QR;Xzr++D$ds&i;JroM41pf|Ffg`?MlTd;j6hEauTJ8w) zjtk|u_WszyYN<`7bh_NGfN%{oL+oolDqnus_tFV11O6bdEO(Ao2V30TDTB|_q?&*) z<2a zP^*eI##b6G8HRsZS<3Sb1z)5%99Ub5%^)}Mhl_EeaHJ_eZ29_$W*UVKFRU8>&QWkI zk}m0U{*|aL!3WXRvMBKDUhxp| z6IwZsv=8rDqovJs>a)!^(She*9FgnlBFF9Phy=mDS+XcsmsdL$PE(ad%v7Pl{W2RH zA+c&GuxXunf$VsTo&!^J+f^N#B2y(tIka&f1i^W*$%T_DjwG2!StGvV3}Ru~JO zxO{2osWrRGK6Gy>7Z1+cN$lkULKGEm7pjYnsuvcgxg zEpF4?HoKw}qT#R~lmU651WNL0Z><@ZnWTySec+nQPjj4A_%1RLzm?APu3G|#-}L+X zSQi3cxnNV6`S^CLbl@){E;{uKZaGDcz3) zU2m?I(Ea;mb8a+lYTwjhdXxvmMmWMic>Aed=Y#|-iL zq4gqV`ut@|SEag6W_1}i4jn(?ZH;vwdx9$NZ>iqPF`!+E_q* z6LAw1!c{ZvsMLM*8iriu^OHD}Oc87eWm#E%>6fAN&15JX%Ug40s~~pQsLx%39xlOx zbHh;`l5(qn{OIUQ_YZ-7kEB@!AdGWQOOCIkGhYLPb5rFQ^B2EVP=40UlngHilu~t&e{G0@QTlc!!wkvM18(}h213Nc{xD{Ch29M2~;h8 zTJX8Dr~%nudm{c;I#to`E^TIQjDXbIDAW8LBM14CC9|o?;1qRC%u-Uqzt&uqyly24 z3-U*t7d8)nt!ET;KpU7+zZxBYuX6oc_IK_a@Zif?=OrFwE7)*;+12K^($j1?P(FXU zN*0vF(A#@v>mc5}@&_{~s{cjSTL!fisL|S`v;~S&DDF_)-Jv)Xx8kL^J2YsKV#VEE zg1bwKJE6GK0>L48fFNIb&i&@ho%2-;3oO#=>yo{f>^1A8 zv;Y>3s_PbzGg#Gzd3{+OwAJ}8c!l%f=>2{7hsA&qklfpOudRz1I+TS=K~!yWu;)AF z0{cTM`Fed(5rJc65EO>?eDg<(4wV~sGW*-dM{$&H0bQ4GaWvm3(mZ|`eh2e*>{lzpGbyie6r35v7~12b`eO8xpATOz zZDB~Ah@-PbX$FZ2nUn-G0slanlKY3SH~m6@antIxqd=<5aW>dU?4Su7A2%WF*8bHg z$92ltpF_J$%f#>aMLc9X#PiSP2c*CS%&(mRO|QiAyh=i|`bg@LGGEN!1d#{grU_Ef zBOe+x!QSt`!?mR$v!C?K_Rx)WR+PG{u`pzuCG2IULd8tx z&4-9+)yF|LARe>xdpT52)j8rL>)`>ed^SudpE|hd2^2EnV?9}=J6fA}siIE>*eTU% zrUlEtPcV1Tz&@3J@NmM>B8qb=ecvMyMSR(Y`faVx;Z_`p*I$hL%TN-u`$3gkVXTEY zj`CfKNJ6OoK{b})$1JRX!Ia;xc0W}m`7!L7(7hB86o?(7>+RtBcvrHNWRFQkPg84{ z4i9=7Y~jsw1ud-$#0IDjpHHlB8hTtJ=?rvscGV?gM2K@c-5r$>xxb!56WmCelGK## z1JUU-4t~k^#Z!v1hr8?Mj3@aZReYp$@0@CS$M{O=Q$Gd$9!6JA;D8-@-pZneZn}&_6RTU@H&~9D|~PR7ITxF*q6?r@vf;MRgIgyR_)<@W=f4! zh-&lQ0ivlXBD&i1x}EFI{roNSv%dPqOZC7DF=h$Yo6DvF9LwhDMw#cyuD?)%RL+mH zw=8HcWMbFj**-P|s-|R@l7s0+r*lAAOzSn8tb&oewv3U6bE1VHum9 ze&?YU4|@JW2H!MADDr;0SvygcEw)R9CVnNbn$4%D#2I+r(*S(LESzkoTbe1bw0fDF zxwHV()W7pOf@jIzA^3BW8D*qpzCSt|CF}6p;0iqZC|{7$@tLg^pnZJ*sKlS^sb8J8 zO+bC=QsX{Bll{T_Xyrp)lcPQD&64a}Ur{Rw8s<~N%uQq^_;c7uK|*C=1Qn$UB~OKM z@`5r(M`5>tT`bK*$U#fMjn5E_)>-aXNBy$k(!EVYS=VpHoaR=L!8?896~n(45O~z z_ulPrWVK5&k(xO2I;hWGoEG$1Aj%ejZFW2%1c|Gu0`_@!cua41^B#|16B@HKtRHb* zR5k_*T)@qpy_(3pkGY-~84)M)@F;nt1@}Y)-f|xvhvsz!n-*vCoccMIFIJhqDihwl z>bGwM5#$dhj~N)YMPuJ6=OO*yl>)!1Sr^S3d!>xAX_JHjbitM$f&1&9O31_#-77+c z#@D{t67(-xPiAyy!ghJU?hwTw?Ne-?8L6)WK^xCK3@NGJI5u~@#{g@&3^kpi9&~`% zv>TT40dp9Sx{gIIOcc9=622GHdL8XtnI7H6=_`l8#f!@19#}EGmX+;unj$VM`!GBB zfaV%NJ5SnZLu~&?r`DPyHMyj?vdKbLZjnp5-S&`2XCU_XlQ{;1_0A52d|PAzEuah% zJ(iM!pFd@w{#wSbxquVcmt%4*J0Hqil%K8%Rm{u$ipGYyA%M7ho*yOfh}pWcPSwV_ z-(PuDk%f`Zpm*a%^2wz@`ayh!N-gM^?zg~#r zYWe%ut)NC-N2w98yD{0)`YLXN9?$4gj=6BTy1hluTUEMwQH=fWPkf@IEED$3w<$|O z?kOqE(ude7HZ}qZWr={dlOH(16Y%WUf{O`zTr}OwqbjhrS>oE6-;yjjDl1rn&+j^)%!PlP}YUNGa{$XeFl~>=w4pAFq z2?k|n|Jvu2zeNpK|1A$R*M@7jN(RdxkPUBjx|5gs0)r{OR_~ZO)vR{t`!Uq6a#rL@ ziTgcQQT`j%Y%+3ku6vxC4K8ZD5m5g7e66!CKuL+#x5lsHh1C;}LBebdmKm4x&8C<0 z;x~Td=`yGj-CyHIX3uhe19UR>b^@z3rM2ga3NKVI{j_rkBMb5~nbE0v{-JWvt%1zfRSe?j+fC zIc8JmjfV;k@2$P?WZ??MQ@)vYj>%-RYp>@i6i&NaH7YB|tDt`u_&}rb{e9~v5rqF6 zmuY&XSQ59uV6`GXWGRm!D&ow@;|5ywG4(>1%v;t1N0^~{2q*M~vOG=q3oxvNHqgKW z+Tr$ly%QR$MeTejsnXS-!?K$FYxyK%j4ZyJCl!BKbNXpBj&_vLJ*L~@En3z`zu1-i zjq^r{c1o)NuNbZU4OhxCBKt2?S>KtxUzD&eO3F*SesfuURPlNk@dv#aq`v6+otu0! zQNZqB(CO`Y+^4RYt&!u}qj-q9*QURZAMSvVIil&_gyyr4EMy6Kz|<-WYvQp60tMmnX&X7Qi60?0H4dIIzTteJj6YTu>ALmY9;fG3 z!c*cZr_-NoPXqHd>HBe!F8h)vqB}`XlR$;*!!|-fvA_+@WNlAVqsxd_ifRPMciAsI zDebT9^OGUDZxr!e|C~~yNI473k+A_j5uBq^-r@b2>@ubepi9)A1SJQA2sti%hKOx` z^y%z-S{VeqZOWidhLrCN{9s9Q4@NAuPV_o2(3i2@IbNCp@8gRFi`@~FYf?oIwyI9I z2CW`YpC`R7g^_^MN#~P~sk-!1YRexVk!VXBoM8?8y;rj8VqVwAPb%Mk3C0N`Dp+9e zN(J?Z?#)H#ORJy;0ZM07fY?{qz;Wro$#GAn$VSpEa(a=(sOiP)LY^BfefSIS0_faD zC8sw9)r93-FYS=z^1hRoM$o)9N9+u4%m>(;R!a*UODGEltK-4oJIU%6%+AsYQC zzABkD(*H*k^O5iV!AvGI58hFIG~MG3nqn)^T)YQG+~CP69o8LQWTRRQ$qaCf0ACD? zaCuXWmX`UFGor^+D{zyRM)Ib}*^M+QfS7Z67CDmA6_hYww7b*%zWGK@FFbeSnFLJu z3aG4ej1F%s(aigKcRd_HO0PeP#d3M?nOKsVZwWL0S@v4^u06MTqXnDt^mbd8`-r{r zJHK1%Ytvo#LUmf2-_GMjw?MbC_(d;vRC6_gFBaEz?Rb&U+M|LccqS?2F)QUZQW|3M zERB_>lEpttjd(nvkWrZ(!wNnj!-2-%U>^J&SDO+u`|P}Tf=sK z@75C0RTPH*Su^k8D*5}l;SVD8riQ^wT#A6voq< z?^}&a{T1I`GL(h`2KYWPVTEbqkl2MyawM{A4hThO&Sbh=4$2J=YDHGR)~r<6s8xQg z6LK()p&|zA1((sth7`Z=NqQSVnmR)*nZEUy`o8y}@d~w9q>**BRoBD04&6}KnD48rA7k_a6eRoS5oP^-Y- z$I@12ML3iJ;mtYA_8du*mE7R?>~D$4N50gK%T9;lN73|EudH%&Ho@O(hn?fv_ExGz zb~f|Qm4qIn^NhSLyv$depZ)J((v=3*B=SX*BjZ*3 z?Rzo&EW}#~oXM7)^v3P;291F51u9_hrzz{Y(;bQ;Zo)W6=O=d1G56gDv3N6Qta82G#U1;=gCwn8%YMSj{AbQ>o{k@$f{%`=*-SAyCB&UZ4%+6Zmb!Q{P zL`^b8#~VKN+)wgAGQhso4(lyN z7d@nup6S^h!`mgB(9yNELSB*tGOv**ys!elhgs9K&1WWwTpL?kSykUrQs;>MXZ?SP z4ZKmhgiqYxhTkBN=?CrWLb4&bJCPcpB}48E+-3)!q4Qzrg!9OLt@K|k?t9kc$8xm{ z03?Zt@@v*UXWO4OufrIq3oMgav`Yj+s3gXhiSis}#0x?eX{AlSw465#MOynF0p_RN zE3!)IKca2VFIxM*2E`bDmf9SNq@Xj77R-v&MsXM8vXn%h6rrUy4t{&^;(+@QDK$mi zlY=HqM)F!UYY(hR{K`D{L-{CnuCdc0(RS*OD{=O3w<&YmyIpdb8bO?DzGt@<*`#N9 zdBH2P1ad=#RMTBV>8w`w3XAzGmBbfSzU-wH_>B=P-C+q_uDe>6QF4dUYO`ojZ^6^_ z1SkNK{%xKa_u=w~QUk}KIQx~&YP%Mh*WU@L$1_|>S(vhjS#{{>dhojytM!UCD(pj= zP4abr9J8pu2_u>PeW`{nv6eDr{pk@b>Wv3j4ADFJ#TZCRtjJ1d@VVCRjj6h&mf2Ua zctw9nak`J!Z|Bk(y>AE{_C(W?qyBK^N~jE0@4EAVS;Uf78Jl&4ks4kHvPR-1|ty!i^*jsZsSL7}ZF5&?2|XnE7hB=IOc z^dMi7w=ODRcGxDb;uYJJfPz2L!4DaXvF7Uga18_qS?_?3+-0p-zx|k(H>V0EEVl2G z64O}W$W72xQzRK5&#u&YUQpw=^RUQ*?7*_PX|Y1G0{5|&JQ%Ho`kN$&d@dZ*OQN@ho9oYQzH<-qjh)$7m+ZlOmo*>$v+B9{8S8(G+e&MIwk@tx=_v2tjIqpuU!>W2eGLe|MqaE<34TPrOnXTt#x`b8qn zyTPax+jylrBYXa)-(sJn?@MwsOE94X+ zBJXcL3^|w5Qbp?V94xu4uw2xu3LA5We!0@X615h!i?x6Jsjl}S|E=>!TsYiPuk0r5 zYchUxn5}!7hjHK_-vw@tE62Lo4!`Ou5-JLwO*>ZSY3{eLNu7k&OV$bFT2lgqfAa-! z%~i@S1vM?F7dQnDQHs^uz^*v^b=+befJRZ@c4dh}&sjVHmmANe0pcYabE!GlHc@An zbIZc5D``AMBO}sfEVS+tn*>P31tzqn488%^Y0Y)l^O z!d^Tq>#p<6$^ZV6uusDliS^5OUSL z1i>Axb%nm=h|!o$F;$?E3@n3**zClfIk2WE{@lmbZ6`SIKEnPCyz{ zkK2|FR`c5BMtrBmgH+DMs;Tz}>@zQBgF3?@Nm`y#hcEcP0X;EyNa7<3)03~G{8MMd zs}v4kSR1&la%+Zdl&SBOCN_T1Q>NVn&-Qiez>A{2b$ljF7Ef=2qy8>!zHb)`1w>4Z zrF4w$O{@v*sbqnM+CbVs4#*8jOpnPuW+}78@ejW|J{7IIu|;tFBIwTFZ3D_dboZ@u zJy8#IoPnrK6Z0FOFrip5rvwB7mbOi(th28hf1lrq)Qg?2{nrNff4X9uAFmx3kF!O$ zxYW`R);mxM^wc-ccO1U?OhHV!>5kg_zsawVzQ3UBV36awrrNMo6-)3-AhEWn7UFXr z?N>WI?IBEMPo1I=MeHc8bZr~-|CYU3-TVgU zGJzxl-4E_Zy~Ru@+{PRsJupENC%>Xc2(TY^9jni@@{SVV?YY}Fs)6!TVcE8R@+HqwZ|S6ufbe-0gwWPTx{N!3-DTUnTLp;90b)%;8t3D)Wc4+fhI1D?rN~@6>IX|#zgTTf{suXg1hw>CL_4(BzE$d?<$VmAkq>)a-f~R`OL3#~+DWED zGN!b7PSgK8c+_;qeg#um%_0+ zOM>sG_La-ll|HVxSI%n5KiPYlu4?x3 zZpZ+mKTR3Uh&sM8QT0a4hAkCX;)nXM<>hS=M+*pbkN74#WWU3mGP&*lG8R$v2 zqYrRSakDyZgx2_!`EPn=@7bnI-NEF{G!CJjC0zG889R^mRy|sk>YBwZCUB@t@*Mdw zRLX^=%J+@1wQLqg0+RgR0gkKTFCt}QqZ%wTUvMNj4b3`h!W$+Nqkb0?J2U_ZO6h7g zxpE0>B!PP6)OPe0)0BDYGZ38Mlu%Ghbh!Mhr;l0P^X(WV?`xgY#z4|Uqd$kPgv>_l z6)RQG{f5?@s{?N5sK*5hv{qUz2haC&QxQ#*q0&4Z|W)I zp4$Tz<~S(8Sx)OE(z(!0woX66j%^!K?E^QmvP}CW7t2h1)%{~V$bsaX3TxTY`EH2(QNq*@W5m?2EU8@0*-Zx7A|3h zox7ZjOe9uDBBqOz*YUg04L3-)gnO>aCPwg|hT191L?LbjmufRAO+7_V;nKipK#TiU zrEodwf{yj@Jf&kuw#bK{0K-3`bkY)`9xBuuuCw!jH$g^6>MGT8W7PeYKJ7hbrerD# zaxbx;6Mg!;BCTKIc%`?BgheBp9@a*BJTrOPMru&DVe=92HL=CNtPgy5sHpy0!9-t< zof!%Dh=Kl;s&<**n`X^3fBU;0onrhu)|V5@ z96&9)W3C5e!{Xd_-YZKZ80NBk-D&(BF6l?)BY3k_z!+R`_-hUwy$vQ1y|SPokNnEL zFygi)`uykPGS)8feMD*ox_;vD0s(9aDfuc}I$FLy7JO`7%C#9geDiT5-LpP+wg@J9 zagJfNXugr%KT)edNy{d{Hbp5*0$g<6hYm6!5xcLyvNvXaUiP+pJ?J5+BdW8Dq# zhMmn*7dJUO`DEMo^OE}_`;mWJM)>_K?{8EkI+L5(V`;ZUqLunYc5iC(@rW!^5o<2O zKc=UR$3I#+m_6=hV!zVs@+y}0ii1;Z42%0^9BbD7Gcgo->=norj6|GNPEVYq{g+Uh zSWj_H=1o)S8GF*y!7voAvL66LS`#+;_2ZU{3tHz796k`1ovzENq)9?k8kQ^s1D3t# zGkeee%l6wXWy?iYTcvi#%^u_2bHqvv;Z{c!o)%Fug;V+`A7|#IR3pF~53+J zmrKd^iqv}meni-n~*a80}rKM5@5B@w3G7l(PNIb8mwG z$OSvhTnwH$b=4E`Q~xrv*(9_}Zzg0NIf#IXOdEV;nw&)MqWznXwOeQY)`%Ok9xP0! z!I{ixWoZ3;q{We(S#TAno#zSt)V)WH;%hwMY={WUN~0cT#*SlzT&KQ5B+QO#6Im2I zEsS|Rb^E0SZD%-eg>5jBAVUwoJ<9;mt{FMdv7?PAdT1vtmWX7)#fi5fnS5rdMVls# zgH5ispzGOss@>?-@|i<7;{wZ!*>U$LXnbhZn@f^&xs4Xd@wd$zJQoD7_NiEpdvDK~ zPQ-L^#IFKYcdvSWzcPc}b-*$&S`fU<$@(pU=CSThJj-oiR0{re;aYAGAAol`*p>Y2 z5X*^uAu!R++?L}G(+a!QLPsV}J>dRwb7U0TuK^>x=;+t_sb6px^rSY~(|$1;Yh@<% zynPu1@(8_E6S~XwPHgQu>HjxU#xq9!W7wo@$96{^H^O}G{;CBqZQh+?vur)Iw*J3f zzGo-v>^oY(oOvF5qx;j4GomsBhH&#<{6R5prTO2E_#~_a+;i0JwyW2(YrOjW9eGDl z&F>h|#`{kSjI)c8laB2=P=SnOZD_|Sz7w22UE*Ufeb~~2Sl0UGu(IA3rnm>f4TWG5 z0iCO7UH=)*jjCJ#1xggZ_x{_CWa5k_2O!bAbpqq2Fqcd79`&B$)DG$@R7XW$ZHV zQ4ny3eEK)kF)--06jfQapS2KVMZHw%Fa>*j+B4j!~Fnu3;Ox&tV*x_@0so@f6$$%tYM8@#$IXtQC~390b4u2+<1OfWg> zTaAW%3`Uv9bd-MlVr);EDVNdl@=!*%2KtHN(!`d(O0+bV%Sm^Ai5I#9!xd|+w$#r> zK}yLwV;$TJD<}gJ%x`aaezPP=;fO(4ZK;sOUPN#H?UL@&Lc^l--oX_xgYzcv&x~KP zmghzm&ClpMdllWc9{N65QmiaYhZDMJr6lYE+_9=E{pDgR_q1m!H$SH^R8+JoOiJqi zE~4Dnlj`jU75@?lyu8?I?+Tzu3zMbiYx8qhJ&Tt>wgu9(NO5S0C9Q+!)#YI04$7jJ zq9hK3oq%0-QOSfow5~(F8JUm3-v)w>8IDq;_*XYpkNjhcax%@SQ2L239X^>c_Yw3% zSh4T zv6~NIH)}R^&Ndjtix!ZqO;?}0TSRaun${*ool(IZLt~Q1b1>hAeF3Il2?w+k^YdOK zp=sbnmN4xvAEch)q+YP2zXU+x;#3HGt!yG)z;lCHzt7!TnZ$?Iw!umHX!@dY0fx@o zZLbef;_rdnsTGET$?+3k#_u}t2?pT~{!FoiI%dgTcaH5kID!svb`9^S>;4w|Fs}RK z)%C>83sM!bi#CgWT2G8tv($^0Dl5jG&Y&<#O~qpSSFz})JqL{1Ke+dLw5<>Qv?B?4 z0`B7}yF-g#&Vmx$25&I7UN54C@TR<;xJZ7`llm5&4wE~lEbeKD*aTaiLRA^f%Dzsx zzqtdM_Y;#+n}wHzm0So}0fMkGc~IabF1$-@`Ii#I23rVV%$tO#6Lx zNHlWCQbX!##c%YhQcC|1$%{adbP-iPg9c12vtsG&x(z1e@>g-Bs>@3U!fUn-`^l%j zcc>A>-uVWW%@7e)f00<%EbX@yfYLHfYdK512@(V<2mGOTf0}_ccMex=>u3lS zkJU*4EF+9RE&GQ{f62WAofmRCl?fPzfurYq5Ft|h>Epe&8*1|J{PaLeMmw!byQ08H0RsoOTv-6P#UEqOy) zc*E{z-u$MXybwz)CAJ3U7nP4S{Y`dluRwQ~iq280__F!i4U!8UHYU3l3EX?Cnb@`K zF4dZBFd1Hc|4N6-5kH#QhndD5QdwI(K~QM5P7tfec||bJJENK&%T=R@~|=HB9}heo$93+?eTG(x@@?g6g2LYeE$EkOQt5j!4{ z>>K`H9PXV<<_pM^o?gy&zbN@rK#8L7PEBlczqF{Az}b4!If4y~rN2s0x*8PuT!UdP zL@hf$=(+boJC=1`oRiz0M>FQ5(VoeBxXBPFH~cI5zG24_uW20RsCB$^!7J_{J+=?0 z#0!f$2$O8R2TzIcZ-spBqqAS9DzWL(2?6~8##K#=MRS|QpG>e_$-+PWbll$^QF^4&H?Q`Jtq2^^=F9{a6DY~$$M^B(8gZcOFNryPQr<^WQ8N%)k2c_I`{EhFmuVA9^qW~g*DUSbp2(Fyjtk)j7^ zamh(gPw%_9z3$IFH`mVNdG_6>HgAKQode+_Zk5|=2nFOIvcc<7_-@NuoCWl707f{w z*7Rj$0`3~~Mo&~lHBP_cu4#k>p2Abuxw!(3h! zT$~m31|NUXXidu^y?EZ$G$>ThxUkT954HxKK&7N!aA^Uz!VXuQ_iJA( z<|h22#4e8SNpNL5er=z-#9em|Ss8AM`)`nAU=Mti+xgrLWo!kVxRQuBQoof(`z*lA z;WrkUDE|^f@flA!`G9xy8fA7wG|sK|D^$f1iW?0fQ*rC_e_ zAL!k19;eK6_Rh*moApFqM&OJQv(EqIy_F|tej<#WcCdeq-=7dlc7N~LEDG8@_?5gx zp8w4{G8ZCe@qdo)bn5&g07`NEw*`hnVcmL8?(o%<0-}FVmgnOZ*oN<@wfG(N-JxC& z9Y??&=}w}ufbS->aWlYeogE8!8Ym)S%X@{LW;ip78Dl<=8-@d8ei#8=DBo=o_oTLA3qwlmN&%A z+KIN_lz=OMb^z$Yr8vtzSJ9Xp^tbS9OVopxJ`5LwbBm>DNl>c|Y%LJ``^%q@88t`J z^GG_(5{KK32mO8Dss>}&eoTZO51FHYt?2>Qf&oD>0iFTPc{!K1Qn?2C3%<{qBXQKW zguj~zTPouJVyawg+O0I>u{B)~2P^(z83z6$w<;f~7=kPV2`D0!*g5Tt4dN@$jn)cS zLp8N+@qRSW%uCuA>C{__su6ldLM07}8O>15W->D0&h!b*o^pr^(kD@`;ExxW%>#+& z6{xDxuj(c8XG%TTYjf40b1#18>!@EdCRWCk0hJ`lcqNpqr|x*uQ6EhyZ6lQ`91Q+; z6v!v9s`kT;4%n2yrmJBM8s695kCB` zplH_H)7%@)$&6DPw{cZ)!$sQ_r#ujJ)G{$)$enb`Z7kOXzl8BC?;WI`vH?O!vE4P} zSdj`mrnVWuo+gdOZz?~!J&KCWA{Tjp#g;I|hU0w{0*EstQkOIoN53y$y;gbdNHoe< z*dfZoY{oi9+|I=L4bbkONZd*>3ByDn7654Hv*FP2aES~q?rbb3UrEP9iC?PNa8{6g zEirzm)PPWsqX>cP7m7-|*(R%(-4)uw?crz}0l?77 zbQD8hT3^J-U$gfA@Yh`Ob=4+^+mzS{cO>R{eCZrc#o6_QPo7{3TFUe3Wp*1};dtrA zpl_XDE_!TK6Og*#c28N0srb=|0Dyj-V}Zv^SsvXwbdw0q`G{8jYN0(3&aRC|G^6;M z{xZn(onyi)9KLDBCG0SDsJyys4V^UG@E`%~bo1B)0cGZDoIfLK#2NY44Jo%r-D5c+GFjjW7)={R^gllOw!+@^24Sl*}GQnOKL#0g&TGM=u*{| zRSSN79{`L(VNuOlH0@jZSV#rtMM0A}Fe4@??xE>nsrT^c!7PzjlwW!JLZ@B;{z5n> zLx!@S<-vhGb})gX8hi>ly>#JcNUWoj->kUAyfUxw<9~#yCOh}MzY!TiVtmKK$dc&r zq3|;-U4ix*7@RnZ)b=4=Sutf$9B)I(O{%i}Z9=;1-@-iS4DJZ}=@#zkN1Q-0MWX>f z5W}LwwT?FpjR0sh#}Zx#)yR>Nd9SGQQ7}As`W?m@tpoqW@X!*&tP6*|j`>wrlKnLv zKy`fa(F~32IrB3J!!rMIrkN`0Y@B05WBqNKbtuhV4UTA+IUZy+s%;a?LyTB8rV0%@;A()N3gKx1zJa;n7n13_k z=0Q)kA(sn#dx?u|uTQCASaU3(GsXCnAYhtLCb* z_4eK~+UnS%6`w_F$&}X5!H-R$5DGP--)z{wpy= zaOfI;QIJi(&Ti+MoZqJOy>y0MhnvvbIf~PF#(XYj4#P`z6z*M*$fu3%9*z{8YQ_Pc zZ=9;yq3h1n44V+1ix}wBM{xw$ZFCv*m^3jtIm)Ki7V~hk2udxA71WFs^-EgYmkS@A zD+nJC%Hmi2UZl;RsE|^^Eg`_M>gAA@_q-DbiFv-WTjnn`Et))C(dkIF%*cMKb{^HY z&ZbriylLFYKooh8d}c9aKzTAsgIk+tzdP)qj8sl9%g4lenHaGAhHGQF8!I$TmfW9V zakBputBcehWf4=FP`A`quqM|%VDdbV4%;AbGzDpI$<^KHKmgY@E1GiDAcAslLg_T! z?PHk3R1Apji^rbVmUy(Yd3qiXrm*$CT`tfkm-!Zn$%80xB9n@)S!K~_f0&cdMBgQ} zAw@_)kF3y{UXhD9nJK;G3tEh>yxxasd?N4+is#5GlJ*?hVn<I&TSvDG*ebFAPbXGLo-|FS^c)~uH(E{BeK+iTOz2l%@U7yfQzxk?(t5`{(HWQ zbL=jYtuUtHF@HDGR$O=>?Y3q@so0nQw~BY%!OTobwP&$=XU!eV<_`;=&1K^V_9PwP=eI9L55xg3B3=sV zMv#|pVhgeQ)x|G17DvLai)8tptN!`)KDtKDH%ac|jCPkRcH}clV2p|L4c5sKA0Q_r z_-pX}+5@6uvl$2V6|&CXT10S&l@ULi9coEw|FoT=Z{2z0V}Z^6{7E$|$s%J$`J>L; zc$z=q>m}X=iKMse-hryOzpS@gQr>AaJe1Qc6r5!MbpK*xR7!s;umxnb$%lQ%dsDFM z`c!ob-r`AlKxU*w2|MnCr%d497NgJ?{duBd%qz=evHU|&wbxpmp4{4{2FNuLxcl)J zTOK}nZzX!JIY&Gc;~Cp7pE4d5ebeAzKoBF?5UWtLOVwLIe8wuqoMWt)n6FH|96X_2Pph0lsnqK^Y`(|yhaOB}^19$pHvdx}S7n*wm2H4?!3-u5> zwPir3g=)R3t#OG`D7b$%VJEIwQU)zTkY@_#(9zi?#21a2u-2iuF+~-c8 z5~os%>1sU-Z!Jp-E1-YbX!KiF?Z~fbN})isMWDXWz}c}Vm?#VUbjGSuR2r8rXI`^Y z{8nq~Q0(xj;S<2*K$BZL1+U+x7?HpxQkz!7btW%c;i4gUCtsrJ;s^BT6c)gW z7|{|0w+4FA-sL6OjE;Sek+`jNXK;*@&L&E1-tPK?xG*|AVp${A1u5V6KDb3}8bLNA zsPr%8xYi@7)wO}7O|i1OpESZ0Q(96%t2U$E;GeE-P9;6eWlgPDDY=l2!Mec#vsbcT zy*Ijqv3r-S;YfHL6U612&WHY@2KBD%{05Vj+o_AJS#c0vUBG!-pEiNyYfq^Qu3ld~ zrqe;Mzu_gkp^4$`fw6TJnLxi*UE!Zeq3DdBQU3Pjr-on0PfOQ}R()U0#|P}LF&`;& zE9|(&`5KMZ7p*&u`)(ql-hYr)rKSny%BG<_^GdJK6U(Nt6;k7P=r|>X6n=OG^i*KF z3!HpxT+5|4%7$sr<;)Nls(-Ki+Og$u%#5d|_&0v{P)TgHi>8*90H`fLnei)IBt@IM zU}jf4W~`3prXN*$m|P3?*%Y@G>t5G54g;{kpYmBd9vVL}=dbf>%N-el-w+^;1aA8z z*{w}y-Hrm>x;V0`_j1Xd6k;20@j5&tT6KK$^2MSSru&mrjj;r%~F}EVvhbuB=kU`7KzA9zP!{NWP zHGXknB#*n8dazxAKq(oSnvHM>o8gG{n|=Ic@w za#8KuQV}7VJn=n~>f2ECL%9yOdSBDbv58XOPG9{zQbvJPJ-ZPfwILUsX{EwYA}vdfg<(GoCs3%o$om507loo;iXl!BkXB-WI1`?$)PQgv|8P^0hl z1%qgqXMz^(6X1a2y-xP$e%j|YudnNn6ifBR>Vxc$NwBNxiOEcG&rq=)p8ZnucZ|%gy z(ZZa_XOM&(MLk`YC~sR|TnbGEDJGfaEn9&k)*_qo^W@uRSw%+2%Yp!xZx2^^3zZMC z6%BY0-u!U!N5#9L#U6H3_)OZ4F2lNFK;V?~PCRW~ycWfsOZ)o2?)hb*yKU?My3OaQ zgNtn*aAJvUo>k-B#W;e;`6ST3w+>}>_IYI4`4xigdOmp1f(#j(p%bKFDpy)0zg3YY zI4NdW@~qG(Q8sga$bFR1SA_}R1SZI+DB$#)p-{Z|beHCNglW*i! zJGP#ZMlP0ziQ+9_OB5vwI{-0dkM!ah9Xe7hLIguGt01^(i2b-WJ7}it)%r>CDt>@z zuW(i<%jnTdCR{(1S)Iajy9>X<7`BkEz+Y{a`y#HQ`b#2K8-KhFU;3;NEyi0bIy3Zm zUnd`3Q7mAP`L=H~H4%*BsW4o!tED}6raqnk6aVDXiA&d^PQs)%afEPxjxO2ZuK#ow^Eu+qZcrpCZ)vjF#raQS}APH^%AJ@f2NC;}M5QI*|bU*Li=Y^=f-3e23x`pV&zkDRg|w zpR+Xyzr~tJrH~ViNvrOk#ca+NwHM@mK@IP7-CpLKyV|?^vN_MW@wK8r2O4Oa@vBXi zAQ|WA@7&zwFPWi~s6QaBP1#`N@gyaicmuCq`%KFL5T=gM^3|SY#@N1_?GN!s8I^M4 z0NeOjP3_-t+-ylzXGppMVH=*Q3etz8`&|?kEv16?1Q$k;tUzyVEE^v-*%!GEIu<0P z-Yw;L1$3l`BEtA=CqwXRu9GhiL!!7D~LA2 z8u91PoP2v&s&PYK$Jv{fb5ywekax4|vQjoF_M6h%+_l!akJ3QfdRS9E0szm86EQYU zQHvrueUZvZ>9B%se~GqMSQXdfwlu%c1pa?yy;E={ZTv18%*2>zVq;>P6MJG!Y}?kv zwr$&-*eh8p_QckTx%2J)Kl_}jbJG{y7hToe)$jBE9yyh}80B$~JnrDOk}jeyMtu>B zqrT}Wtf1Az-Lq0f(CcHKr_%@v2Snikg~*m*(123~J+ z0fwrx7Q1pzoM}1Z5i04RDl;U}pltkgmDf~q#=AKYJY$6^jU*S2_)hqobtCvxR~p}9 z3j;9=YZ37Em8j7*yi?bhHTzSDjz{>`)j2*TAF2_4waf4X0nzb|&|#g<)%rk*S`4q;cjTT;Q95Dz(7$Li~jc-l-3A_-SM5rNf>j-o z`j}W(dpyfKu03?d_xe-0+1w|In=emg@qs*ki0~JJonRx%u!}QNla0zv_`9`=p;QUi zR*9#jFt-<0QWi-_v-{c&`!UmkXHs&e5|w{`!pzV?r|8Bs>GtKs*{ zMn`Yd5(gb4VCg4iPwb)NgHo(2PGjyi#<2n#kg)KcbLt(ljll@b@>8eoSkRo1wthG< zK15O1erc{qMXwK(KFnn7;jx^@U2@fls}+lInZr(Jkigv@-6^2iqMPeOor)WmtbwY4 zcg)<`?Em)k$EzjX3{PFJ-nazN!vYk*DrC3B!bj$Uo-5B_x;al?o9w_kQMjPI_0qnhe`k{Prnr)0} zc5wwkI13X*3zI0}3WU=rilV;e`#P5vREmKH;0`yn;PoN z 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%0D5I#(%QPjv%3Fv#LrKo8hjDr$HaR3%zi ziAL9PP65K?L~xtBZ{HJeg$cWK-J3si?Ei`q+ExKS6ZPFqSy;Lil2qinF~gD0JHmfU z>3PH#3rtql^2a(IGzs#oGqQWRA-$Goqf;T~nY15a{h>axGI*AZ7g5gzpu7eF(MnU&A_CSZ?JwJDPCaE$Z(@M zN2$8XC02cIE>!ZXKO*pX3+m%&=(v|gMF*8jlZx4eC7{CVTK*-8=>r{>WH>QrTV*Ft zpzIEwRYxX+OcN@9ICbmtW%@8(Ky4m#jjgWwG|Mi$j<8`QZD~_REIOejJUCV44A6gE2&N zwOqBiXj(T-x7T-f@U$~lX{Bx(g<^#qDT=q(-Gl_0vZ|~f9XuUp1|1wQq)82>v z%kzOS$zYTg1e%#)eR~^HeJrrJ2QS|5szO%t|9Z$Yv`pjzzic#aT#_SGP6z12*U&eU zoKCbc-|XoD)@ipbM?vfAtz&P+Z~YpEUAeyQFaCiOb*u}ZROTah zd;Z5{Ja-th57k^on!#MCy42Y1sq{Sy10J=CcpFH`nebWsE3|eZ&FrJZkIX9S`Xl5h zYds;7ddrZ~Dn#S|Qcmvjv0^eyg1i@WSVz#?s!Zzm6!G1viW?i~UP?J(B!BY!dCE*m z;Y4;g+Z$^olEDMJKoP{pIn#?VE@{HNqT3MPz2<$-2V<3%v6}{9+8SgcA;`;x01>0S zoqdGk7)!a*cQT+^O~8(ojCGrN!T`p*d(sha?H?NQrBWUBLj0#+KR(4`vf7+Qmq`>o zjhP1d9(#%c;_ev8VwArx^e&p%e5Z>W?FzR=Sa&BFpKY}InZCp&D!Zn!inCa?cv*Bz zGAAH0u*$nkC}#1LG07plJ{?#!Oi~j(`+Mb(q5;G?ox>G z9$Yn_vddT}I|bCwW=>yU@Y(k3Iu&K)ez)G!C<=*StDs}2wZYpJjY(C43BeYdu6aDu z4BI0BkV)P(0})~yC+;%~i5n)ezIMs>3B%a@mR%N?7pPw37{R9Kc5a)q3m)O`?;PA(uOSu%DJRly> z&VP*{O-~w@D=Ah%2hMph%{}f}HZq^xk*diY)iimi*E(Ri$fFCCCxq69XGskx#0q^p z=)J*2_g>!tp^clqtCYbU=;v%h9O6}*cf~_4x!_x4R@Uof%m`Rr`7ZZ!0u1SWbqd@s zDHv7-xtL`XTMu!H6)ibfFCpPr&q(;d1C@7v>_%;3jM|pZYU`w>TPY$%wY2M4@(SbP z6&eoyHO*pUpFe{2H<%`EA|E4frOAZNxD?ke%pdG^Jxyq#2C ze{IlimuYNLe?57zdncp!!M2>6lO70bX&jWN`tL6rPQe z!CtQzT)rZsOYxz%?ryYwOqAFZNUqiId(AxMBg`wVB^ipe_{WQ!K>4*xE3Ek#OJ2o7Coq`f&T{7z(IJ+{V$ zV-_;rZ)7o%|1a8zKZUHmRr1gIztpx7#wLz_hiO|tpzbC}%FD8RJTY^bv&8Gjx37X@ z=!cZApuS|Ial?8KfbHg-FCYv+V7WAjvEvos{HRlE(*1%Bh(i1S476FfzFRogot~0# z6u#9^ETwLalto*j+IBDXBWvIDeSzJ##&mAXMyF%(v#vlyAG1E(?mEMGGhg#)qKSi& z{)Sk%=e)^pLRc^<5nkeg!&F6MI7(7i#YJOUPZiw9(%UTqi^W9$lZ#3HJ7)OzOM^?g zig4%0AY^b)BY==9h_G?{bB9P8?mIOD>)Hjo~EURE!G* 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¬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®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 !di*Be9ZU#<*o4QdTS-bCw+E z^G2&z7oTPcnM3Io5u|7dEWw+5_AoR7WSjR`CU`XFTSkUTU1iofXt~ zz<8}OS(AjYT(2xkC;s^|u}i}>jI(+6Irq^0`sas}h_dhsen~B9q9ZePf(u<7rXPfL zUey{;SP~rj-~#APXcYG&5SzK|kqy^`;cGL(k%B^j$Sskth`o_AzlK%GGJB;1gFJ#P ze6Sc1ZnT2pEX`HFp6V#VdDg;Qw?RW>sX|a?k%v>sn+p<5yAd2%F2B8IdO$=fWYzh^o{8gxrDYe*lvzqE+^u^w~#_Z&7s5()_b@M<(LCLo`0Sa zAW=|N!bEXBIVUFD{oN^+K|S{Q%We004~lh<`n0rZbXmxeC-O^)s0D6XrLYPa`5H{e zRE`h{hEyS_t^dA5rHUc#&U)-I4ies!MIZj!8S}b3JeJ(EXmB1<#JnK+8mxXo*zdV6 zla+i%v-&#bpPoS;=+=$zf^z4GD~xm-Ji%`Nz`5j^0wunyp?C>CY|PdAQn-HJ^fBlc z4wu_mU_kKJbwv3LN`gNhyxuVelQikXYfzb##w<*w;?e>4o4;mC{H|c7HFoJ!68e@E zkx>sK78ZPUPD13=ZS?=$i%pX>WgtlM2?3&v#8b4 z8zsWO_&&WXp8g4naw!zJ(e|1kmOb#tBa;vX(a$|&^!ku8VUA$Q++GW)tXn|8O4t+y&O>B@gzvK(-o8; z<+HV?l?3r!FmaLb96xiQD=tXu2$OO!G9j}=@?13kb9w13@Uz$O$TQvGz|Z{=+PXhyhuj|Pg%z?2rY8KvHe?9!yje;o)@ zExgNd7AEJ0%y1*?o>HQPze+$XtmS!6$Ul(_OCRE6H&qsAE}+aCf6e&u#Xpe%H%;yu z=WC08ecbRj1Z&f|+MS-~T+nPJ=#gbNEPbF%Ck;0wjNF^S@6G!`*&}J9SJ#kS^caDc+Jprt3B29 z`~N`u!ig={=nBlezAKU*rJ&dC&709s56@a)v#;AOqiAt?_p_VcyiB<9JD1pAo4dV#?!}lL5qYhs!7P4saIsD6c_{{Mg2DNL3wdsnG}Pa<|7o< zIH+=46p6;^rVvhf5wC_t%)V+7hL~8<5p|Y*5!&Mnb4oCsEu~@(NxV=>u{gZi-$0ji z-lW49;UyC?Wiwf$F?c!Ox=`-9P#nRxAwe;kg1%DRAVpYr|LqC^*>(Wl50PX>Vp4qE zMl=8^rKx7^@7>@!p>B!XI{zL84WH_wzA3~+%&el1{{lUj#=ojFrKjMLBOP#0*v7?! zm$dgaC&5N)<3{e)1^xa57ir&}`|c@C#iAE4_?0pjeZne#t@tSC1h~}hi71WL|06|a zj`sa2mDCf_Vq-AFxx5*eiPith_ZmXQQn=mo_x<|64Q~jR|dpw$~&zlwMsBj zbFOlQ6tI`E;5j*7p5R|rWdP!>Q6J@-dDu zTB@9B2&HEF^*`c2im2ItLSt-QX6KMME-c>1p{q9WKdcw=BpDabrqXeAg{Q+>7%A%C z?N!YBb20!EAL~tzs{@c6?d>~-w125I#^hpPR*fq-a>pz1t^l$x&-Rw98aK|B@N~o^ zJB&}~^TZNg&LEJD+Uei^bQ~r2Hs{5QERue91k7UwPAlvy0Gk5$DNdx9%p^j9@^5*J zl67PLYFnuRSr;ssLOCY4?1y5>)t?;eeF*k6L-S)(kbis#MLI-QJZlr=a&OM2Z6dT~ z$%*{MujW!EhYu!NGm>NspShpD$T739$0f1(0p0qgNO75I`iGA+@jlL_b3N{p>m%gP zi%Zsuy{1Jg$eZ5%DsD|R(*-fed-N)8tnT35FMO59%qN7?T3G6vdDGbM$MjDwkJTg` z>F|5uqzs2u$avZqnSqMdU|D?4A(y&S7Uw}H7mnb?hOGU#CHp6v_=)S<_~_-kP|F>% z<~HMOE~(7BeM7n)gPtP@mm1rpP{fluK(e4nET1VRu;HLQmAb=8>Ir@o&Y^TYa~r(b zsOew&ni!COnoNPAYB#yhu|UMx!+_`LX_@Q&wfy^VOs&FQ&~bo%f~W3UjgYo>x$*aW z^GOri2(4@DA?4#UCpHE(4mSfAt$uB$U!3Vb)TKp&so$HC9OQ^-Uuu&YuJ| zULO1^zteB_7>jrk925=HFK`f@uJ%t(wMuNi)W!PbA{Uu9Q&q|$l*r*(6Tn2VeiM(n zTDGOyPtZh#@=juNDAds72_yXD#yvs%-j#u`R%Ut2rF1cKikJ>d#f%si+*9QB7?V}B z_0L;e_g5wAwdG{7S%4se%EG|If+%T1G(9I915=;oYX~De8v|TsN%nmg zx4U?1C#@jvv@6(M{k%#@?Rs2@&~0Fb#Ey2$G2E2UyrWBI@HFJzC_0KbiIhsM_gz~4 zxJH#Hy=BplX;EQ6R7hV9|0;;&Yl_^ixLS;M<{dd4Otw1;^Yd#8YIdtFE1NsRZWXI| zWAwAQKM0#iIl4NOM`HcO;5LV|yH&RfLj#5C?RTXl-eA1CzZJ@2Kr!u)6_4K&A}Vjf z(Uq4nvScw~)qlxlW6x~TWa*_OMkzK3`(0W%){I{W-3s z7T!I;J5{bo)@jwmbVQaa1{|E*jMfNE_guTHlwY%TWR;aIl2Y9q@6q=pA|N6ht>^m- z3%}i#&*#^+jkSYMPS4ACUVtz3LptD##1jGr+@7KctyPIi*GvUF>UDffv6~Hd z1rX9e-n6PYsbT-h8JTs6jHj&x1G(ziJ?>x0kMk7y0npTpy^HjjLk9hqz zMIqaT1Nf%)W#GB+K!Ljw&pS~ydUD3X;NgbkE7^o&?xyd%s_?&s4Ut+xPtvq-=?|!;8 zmoVL(A>wwT%gv?V3^cQU>-H?b$hjbePH29ey9Ykvykl8D#AJ@M*+vgP1o!GG z0=9emJ&1%ZWas{VrLi4!2@C=ymiTkM_#{!`S~1@7Pc^<%To`*DXOX6Cdb7lTMv~mD zkh$fgn{bQd-$W!%5SO|hP+c0U6&3@e|Nc>4dK%;utcl9LF-D2wVW*M_&BGep_dWMY zWj2jI%im`tdute(;RhR1Y2>m@9G83#ln1>%+gUhN$t5j1u;6n>-ektOl-!%g?4#kx zwB{=>Zi&n>9vBj4R~^k@6QCjNd!vpPUXg@Os%Q{Pc-H9i(1~KUEav_{lh@j-u70yE zyrKKluPbkNrE|c4Uk=}lSEdD7N>#ewq;JM?dUg#0Hx=TGy6+97Q%bq{W=a}G?ga#n z7xHH^MSBL@A#BY8)#vuhv~Ys& zLMWuwi_QFfEjQrdFe~X{KWFh3ID2c?(G2KVj}ovr9MdfqU5or_VX72wO7`SGPRc zXS<%y45E86c@E7BvSIRa8tekkf+x3RTtD)(aPZG(K|<$h|B_3pu6}h<0tWT7GtP2| z`3CKR)Ts=vneyxyfnrKmABe^#enmEop`;5@Z=F_tNBAQ7{p)*7`6f*9h`JU}^z+O4oGX;PRIy#z_!`JZf}9O_3$fC0hL^p_$JGlD^J>ZjqRz4{;AD z$oY#~0XTE< zxBMkVB|*zgcaBv1_WK!Ah&qg&zW>o zYzG12b0@mvNOS94$iS_*OjH@T*oO!h`YnmGPYaLNC%*iR@q2{45&vF}h8I?Ya&0`R zSu0Hsj!$eBdAqDu8`n=ez-`}@uD|}9ifR3s0E}$~W(l1(8=6LH#x%MpR%qVx2VUTx zbL7H84+DO38|f(`9X#_(WSbKstrew%wJKsg zMw?P{#>g^I1{d}qf3vq++5f`H%X|Hez4xqysseQdWn1s<&c3NuAn@EozTdVsWh*J^^2KyyA zFX?5zg(~Fcn#(y(Cx}q%8JTZgld85+QQNYR>?`ZR^Em|hWD`1%QG@(9*7$;-V9woF z6a;voHO-^P1ygCAWJWEAc#2{ANkK7{LWF#O0Xy8EdtZ{v?*Yv)hm@lPlfPOL^f?{eF67#B7aUt(~H{%Q?Un7s9c*XZH za!d!8ewN!km*Hw}@)sS3y&R{ppL$BaI1<-8fiLL*(-arjy%aiao!`+_zb`{|u0Gp$ z$h?etcSASKwR|c4_0(cGhVo9&esj!`r|^m&cj@*v<~P3diX>VGnyk`bX!lEi*fhiOLn4 zX09(eErv7^y^qWa9Z?-#t~$DQYKFN|M8|82`)j#p8k>;06%I9nDzhIg2wRgd?fWje z3=g@w-W`qKdI<@jfu%q1ITOkLJ~f z+v)_1l)PRJSZdo0nWVa<&;ZJPMaxr3;c`dOz*MO-Z05|D!)w<@~KOm$m%bU&D1yVh)05#nWas_=*gyN&!RaJCOMnf{$dz!t4bOCLxrZFR1|Kr zGG?;xy0vegfFW%TS-y!hJ0X=Dw?-%3)7fy>EtNrFAKUM2D{-@mE07D@o&OF)-kex7 z?|LrI{={?IE z-?S_XhdxvO)@ag0gFd|Zk_-PsdYU6q7K&YR{KFm77~sM9uXV)5XXRCJmg3a~M@hN0 zVqVD_XqYy;N70P!JTC{C@!!FNbK2mD|7#}z=QL!3zR0DYka(yYLc>4S@;tVV?^juG^ zYr9@Ca*T%Ru=zj@Gb9Uos$Q;1_Yu*3gTP(lLbA&S{!jS)9++7EC!uV`mqTT*$G1h# z2T+dYwf70BC1ZUw><6Y>raI9jF~#jk7syzNEnuKRqC%(f92q(luC3aLH9IRqEZNCjShv~mx|a!gT!@PzZw7x5wpH=XnN)kixOQz9iUs}q z!2v1f&TPVpsLcJw`jV~I-sxPmWk81_%Vim?gfrl zD}ASnE}~msqcH#uuG9b9sQsQ=T%x1f_!XX1Utfch^goQfL)D5P=mOPe*W_uwY>LZm zh33BSo58mO94z6hg3b4@jR1JE>v7<$!TKxUZR>l$*8Qs`O^KE8W8r^-li?AO`u>o2 z)ohJTUBF^2VAgt$Wb2vSnRB!uPQ(?Oe)Qf%ZC^ELlb+kUUi3u%1n@t`Lj3p07LPVd z(DR7moXDWpIbTeX>x=aA`n&3=)7Xod>|xvRpmRjT6f*bncAG~qH-4~p#s7|x&1Hi5 z?}c^?ge$OQQam8xp+~(*&7F*!?OX0$ATx_Mid(z&JBN4hAEW1=j?bcC57qMFnxW4? zG^jk^|CVRL{)R%qZr!J4iqF%0*IT4_LgAZqNU%V;YQ@=5r(rVl13%!-^xzATckR7W z|9EFnK4wUS9-AgNOxUcs3YAi&Y-%LUf=^U(8P1J@wu*0^>ql{F@n@dZ4}-7@$v?XX zP0~!oaAV7F(%L`Ucm%P=a3T$8!b-l#W_A;JhqCVs)ylETek;;=d_W~0kHWYWz7*UP z07R1yJ(N@8GC+d@c+-oV_liz)YA1B_CWJG+Rus}Q_yCGSnFR$%UU{efOB0(NX_bu{ zVC3<64lQgd^a7J0>qMK>vdCV8Z;EAN>r~O5k0YqeCHgS9x-DCKtChSVfBZJfZv5@B z#?wVZRpC;l*(OSIW?Hv58Jra6E^|*+`3sb87do-X0XTq0hGL;sf9Yk{ZcFi_zmJuY z1>ekVC3Fg7P2Me)?-UhNjL5tJNI-X1#X2tHm4g z6<13;-I1w6)`jl_7HweLl#|K?Xe-=O@k7P=1-oZLUCAHt&R+|lpXZjdtOUpPHKNr0 zmexLj39L!){H*TO&?8FF&=}ma81fG`ZkaTR1NX0)uaTuAC>Vw+Kuxw^`0aq2X?-Yd z%e8#H8o1MrWML!B8q0ZB0q+G$eol_bn!E$^8E+nzOM)iSes@)L9#JPkKZSqqWNzkW zQwH=s_Izzz^%4GRz*CR@y~`giQE;OX?_fhz>EcJ|Z$70%mWtvES+enUL3_dk_XSt} z$nLNIHV8&nlj!1eZI)4bcpNpJhGR2?y+%HdiI)?X8hw?=8^sQTM0 z79<|rjf2sBIjzKeWim4%o!-i7T^rHqfLi`nLdmXa+n$=<_mA1y&(uYkGdVm0`_4U+ zn_^N@NXZ|l0c4$a3^>g!zK>k(g{$lk1MJL)@h+xZc9X|Y(zGg%&!9wkvHm`y%c{%| z4U%w`J$bi(z*p#Aob0)mZSyOc^FT6prMiiM9jKbMs(i1eJ?BARbF&$TX&}O@n&UVVf8lP4z?+# zD8wU*5NiO$Ukd&fZdiidvGA9V#Pjbk5hfN_r`3IvrfiJLn{MgZ9R} zn(}YPHoO^Wn)m(2p$%uRnCF5N7q?Kc`OIfwx(#quo+4mPp z)?av8=&(4ND6R8M{gIe#xOfUULUTs?3w)nHO5LLx)c7^o(%l;(O+V9?W>u8frp#wn zJw`3ihShvx!H#EWD`jPZ0_&PoftiM5Rx5vTHaP%6|vP%Jw# z@pEi*E%N%o5eS)vfhK7~{qgat2~tw0HBZzsJ44yaQtAj3*q4yMgk5u4cL38DR>M8z z2E}uB9~BekQIInd85eXRUae)7qe2Dj>G(0QBBA63hoE=xGtwsyti_nn&Jb!!F6cal zQ)svk&;y`wZdD4+dDB{8c7W`z;87p3xD`x7J|{E7s=#mz90LkiQ$BriB#{*TU22Fu zo3~#onBI6|qNzOBqFU>NR*g3l#ms<`c}AimAO&9`nK?sPk|4f;MUaaTleQcFnH$$T zJ&Qy^IwPN2P5s-1(WloAR{#spf(_0M!_N^~DSYzCa3X^)Q48)0Z5m%Z*4Ommp{1Yq zE>~h+bq|~hv@ok6Bp)}b%Xingu43_T&etk~d7P%8Hon_XwG!%P{Ph54czbm~_3c0_ zJh*tNp!2}B<}{KPU>=pqh?hAkXBXB^FkI{NVOLI$!t3cfdwy;WoB_d-fcBCFodF|Z2G{5 zYzJtZUo}#2!kv2jjY-*-4O+?;z-s8biRK0c_p&ZeKiu6PZ9+tX)7bR&KCPsv9Z5Ho zHTN!4_ORA>qV4JpL4>ZJ2<}|wS>NrZvyJfqxGxXjz>KeCPF|0*mR~fASgtx0Yn=OG z8a*7GxJQ~l|I_uhx~!`X_EK-~+MwmK)$euwZFCc%GFLY(l$XZJft!@uN>cPL@tEC& zFZz2w{VFUB-SJh}V0|6{DabUC`Yr@4C)EhdxyIVhC|4#|jUM8+2)JK%$n>{%&x+UQc& z?0_@Uy%{VozM$bUPM!)NEtkl1HN>yCO%{(g+u<9sg@K=>R()>Ev+!ngy5T@yVTs!x z@gYcHoTfaH*`4%sj_uLod(z0GEM(n5M7uicmS%3j!|`+xiOkEU!zBvdjPgzCU6Agok7cAr9lXg`enXlbJ^E`#n`*XC{@Kb+7|Jr zoX=6|c{@w%-_nIC<>}})=>-SQu2{)Ww@_Ou##l1VZ}ZrQ$c8PzeZhvNo zGWH%~4Eq{zVMZdzVy!z`U7_O#y3MIB(K~^J$q3|}ra`_y0iv~Vx%&@-34@Y3bRHYW&0p2I+hUSq zJHjXm6hW8`h^Lsq#;%22pwwB(?$mScUf-UHIq%&eXR9#*u7BQ)qB*_`T=c5>FZJuq z;f&+_YEP_$j#5yzmITciKJNRR-E|}k6hZ>o=$FoHwG=%9L*uB zmzIfA*e2#DhcQhW4Ytj@yjK~YC85BcZm?(MQ#B8v(1 z{vuOT+ct8c9<~y;3X*R9WPeXYAE*wWehgfYOW?!%s+if-#$Q7Aop<+VUxfrkHj@nh zT${P*ACjhqiy=!`ApBy+hw^5C1*y{Zhn0gVFXxm!zEL%%2BeW`i7jp{ilI;?SpPGG z-MZY{kB!l0vR+)p!WO*6rN$!PNkbu8<>~kwvXn_7*k*^w}#O(4@(t%SJeW7|8x2{3c^772{;CD3m#~ zZcU9N5@}Y9r4<6-06+{LWr4-6Zxk{#Bqkx+nmc$2n9TeNr&H?9R0)*oe92pTUHaM5 z+i7HI7c}JYX*gE8lVqmzWfCH%zCk0-zbGTVE7TPkdJQSn;_3#qCn|dkIsd?m&pV*# z=84H+11}|gBNfNe>Km7-M%$j89l-EzB-K7>GpxsoZ;!Z!1Gs3k-n)>^9%2?Mz>Hq%5cSp{i_cNy&{YoMI}sgt$lI^ht;g()0p)84*b{>(atL>i@9y|Kh7z zN&!ep82?-5<_DiTU%GrwYejyh`=>Zggg0&K{NMJ7)0qmq2e5*2iMqjcOZMKAR)$(6 zPx?yhg)(6sDlq!xXG!Xb;C@)5I!NEd|2{S;{gZ+q*_6|0r`SK|F|2SOkhF-^A_Ty9 zH7s&UUcua74WFBZY@zKwscQ2~4d%)bkXSYD4?wxY+H70>$?=9}fziBwq)~~uUnWU9 zsqt1<3SBtk^9uca=c@s(RQ7;Kl*+u zsg3T7_1)C$%)hWYluksdgp9uIPuA1EJ20WzbLl-Ubhloj#q-BA;NA-!Xy4Nt#j*Uc znqzpQ?M71Z*1?Lmr?Cd4d0otTGHa|Z@~ks$xTOant|^`E z(|ai=-_JO{c!R*VV3vu4O!j^MSD41~dh77Q>W`I%{WDu8U$=M;{ERs8XWw}g$8<7< z?6S!$NlPQ-5ws$a7hZ_qCNye+gcjaAC0@d`_Xj#CEBP(_13;-QG zrMiYKsF7;J%A}kt9yr%jV8c^(FNsgP@BVRiCbNJ3^wR8Wu9H`ua!WaAD{z@C>_Pik zszdaqmSCn-drRW2?UL0S(h7X#>fSvPw(%>WbwGpVW^NGgzI>{VT-eYi0b%^b+Kqc2 zusSO$e9&8~9i$-t1aMPD5FmSDxd-1D4TH+wjh zjHmK^$^vxN>$WU*b+{~2^ZDy6GplRtE9!rJ<2g4gBXR{33&bJ6-kxiwC+}bre(u}M zu_Jgsri0FTvLyjyM-X!%+AL)5BA%TV^7|ksofn{#dv#-Q!pf?4@{@Gp1Fd28Ykz0A zq8BB74z+ObXbcnV)kg=05;SQ*UuQv>jE?H-9+{z+UyLXBcOpLJ0*3wHO9m-^Q3MRc zpUjl81h)-W0T&O|u;IFMlB;{`bD!f1&#(`WN*S>V&?$}gKVdRBBpUJO4d~FTlo}+Z zGxWY6z@u$PZi#QDkB@!wkvY|Tj}+FrLK>F+Q_F&Ha1G%i_$KyZ_LDSZRsDd;4NlSH z8)2IVyfE(d3l*^5p`)tKG5O6Mk`%wpkiG_&z>TlyYPG@oC3Fy~zIDLkeOQw`X+Op* z#E#-UYZpx~;nk(!9U;Y&2v#W| zKs)|!2UuWMQhh$Ia~!khJCg=mY9t{n?mNQqWs0&T3>nbs2bkgyke8dgDpdA(vT6d? zTFKL+JNuzs!P~aM{_S^tii$WHamyALEkzOp*rDN(htn{jw_r?Z80wEyHvAoBxUScb zwQ}N_4cD&sx>FWh-bq@o>I3Sg8dJM}%xflAYQq%KDBdd|jc8@5h2vlwk7&}r@)_=S zo&az+M|KO#i&doJVihCvW1N-`N7xSd;5TZwdFixnKpq(q!xyNBE_k9p(jBM=+v8v> zAWV9`C9Pqz8c0N>D`HuE<3YFY1jwyU5Xn%X#*v&v?$>|Q|(%7t&AHk$QcT*>rh zYjve^{9Mh7p=fwEwe=2YBUPnJU+SqJwA;GfxE>LdCx_uX2QI`7=X%10DoeXlC2c2j z5WQLvR?X?@p%q)g*=VY#s9jBYry-M&{f}^%!GA1s>WI9gLpqg>Gubj zU`Hyz{yHml%4HCP<}JKBEo&+z=d_V@XPE~m9)8BtPWCfUWH9%tCWk_J_zQGj4KSuB zc6^=0Mxu^m88h4HFp5A3qKRidqWbKLLLQ1|2;T%?$SyX~`Z${8`cEmPj8RFca~y23 zcc_ylc6fSD7~+TN&Uz0j;tLYc#eK`HVK#_F;QYRzv`=))=0@{O{bm*w@Ov01I%)_z7**A zyvnakoI}_C{J#X8Q-#*WS}hV~%XMA~RysXL43vQ=tlWy8abh zo3|Mkp+cIqrKIu=)iRjqLGB&>G7MxMH@{x+?-`GNd{d-X~mo)Wqh zd2Gf;=kZsV6DBmLmhm;fG6R4`OMLXPEq&DJ(0Ek*2JN($lB7P#Tq5ew4v6i<6SDS# zAXe~UG^Yx@=W+byyqLxQmAcY1Q?vf$OW7s@+NgD1OP65zcpfKLecW1UTCo3dvUyYK z_tJ`gmXWjig)2%m^$X7h-qEg&4r-p_W#e9Bw{Z8PhR$D7Jf)P``#OC? z$inOSS%e@fBO|xUdt1)qSa-b#ZSgJ&L~u8)iVTbMnRY2&bJp%8>(0Hh8dNF?@wsd} zobJ*)<`?ARl>?v0pL3g?C0SHD2d<-C2d+Zp29&TrE&h}@i@mt;6xrYbN*KNjAsvR| zWr!SeFN0nOJKY6d?>`Wy{SlG}_rC4xAd`^9-r93qPKkpCW*_KG&-L92z=EDL-)V=n z)Y0>aN1eSQZ=J~Uq6&;(ujZ>j;ul#5nDtokB}s*Y>(rvq(krrK`$ z2977rcJE1drTZjjUIuNBNpJ4E`Z&OaM$Z3RPrEpoL9Ng@RCrmyN-QX9|GX)Nuj&5W z8`dEPr_A9u<_swmyU5vBE?uW}{O7qV$F{2vzss9&0wDYRo2%#C=$wH^7i&EY7EL-+ zG8E=aI+448GOqb>+U!#?-Zw(LKF(2qG&A_)pu{O2O64WtN!T3j)=t0k!-CDkzx1y` zLN>m8XS^G^F7X8>&erFI>>p+P&ew~Y;h`Y1Y)h|+VOk?2IqA5}!>qt0b!!6w>Wz069?;fE(qr-)9; zi~cu*;ic2cw={W?Y)M7PwIbzWq8L?1_u8{qYg8DTR)eMO>?5bob1n7D<>B*D5pzs! z_4r;nF{8)W`SHuoJZj!@Asz2Om-29KJy=+<3UTR8#2*>=Q+uxC6XU&~x}@N-?OWWO z*W4n>=Gpa_^YvN`xF7VPLh)ZHmSHW~ebAN5)p<|mQt4q)Z2zFa7>+oZ?Z7N|DJ8mV zU3+h%5bnR28eV4Tc6BWm!#wm~8dz72)w`cD03$J~J zmjmfEoCSjLJkxc=%foDd!8nlIY7XMIE%A&LkIkUo+>|Mcxd>oe+)XY6zxPjuZ|iGV z=9YWGno8b3j~MvzXA3D6&F*q>V7bdu8ukU&aX2M2faLAfQ*`~hYNR7|UqQC&Lw`EI z;Xq*{Q>MZ9FcvR0h~Vv-%;n=CY86v6P61y-EpOfGkiVW zE^Jv`qOYgv?ZnVipLS4P?cIBrmJi-p6-C>kTf7;5KJ}PEJH^(JYoop@Vd)DlWc~~W zqW!qc+Cy!@VcG&Z=^lTDeNY;DOmJSYIWq$X)cRf9<+mUyf1H4*dG5(_k-zwn6FN_y z-_E!AZsx3S27O%swtFJ%ize7^ad6V^JgBg3wwq>9H?Ujk5=u_SPBQy3kx@_=Oe4X} z!~`5rI@@gAoziDVJ0Q<+%%Frfx-aIDW@ay%&cP)!4@3&fJBU8-)n_y0aE!$x$PCTS zwDD4*K3}HJ`s^#j`!IE(LQH?UzqAIOZxk3$l-*$g`Tzb1xiq5Sc;x;XELQSpuTpHA zp-JOw74zq+mF{#&zVIBqUt+Yt?xWXJ97s7cc$d&4mcXi0F|cu8I-#Ff?O1u7;Y_mi z1ydsFt{gL1o?b40BgtnCxMhaJb%vk!?5$FYU*(T%(uw?-sTAHE9$SqtjSK08=B^4# zoBPV{_>Y5(q_$qN1--#T0UsetzjEd~HVS97Uvy9l#Q}LLIp9V$P-9nrSV;b2_93F> zQ;e?&hO|DLSnP|6Hx<(Y;0+m;Uy6#wyRie^P)+|fuuoNKU9DtjDG<8634D)^L*rdJqBh??M1LllTPS-!O=nw0&Tml}R1>$4^Hgs0LH}cfWb*xX z3IbVWTM?LeheD-<{CfO78BlGTzTFWT)U)?vn2_N541dEJFf5vrr-QhXfQ3g?zoTFH zj0jFXxP#_}-3wh$d6X0L;}~kZUcl0cHbnNvUC%8qjyQT=jTp)Udn)2g<>Z2q4#|O5 zFr-;KIM300vabqnS_M70oQ(5V@GF^B%dcM)sMXeUM;bi8v1lJ7HP%%#QPJJ|stmG_ za68MCQ2?#n)hLFgJ+N;@XMAM5E1&Vs|91ML^U0Xw%XFzzIUTS~s-YB6B%=Ha{}J~D ze!1e4Rm-N^r5=CoC|cNkEtOY)ojIf@xPm(T82BQeExyQbls#QjD{-15R(EWgSXDe? zpLaadVLNV+wQ;v=A!i?Y6W;S-I>-2(Vx$pp?tY7PX#9LGmi%_g#rqQLIZcMP6vn?% z^l?T~_Mo1wUbO#7w$sMqiP+S5-g)`xhSvR{j0Fbc6}vmyI~ z(5m{=?n}R*+MdUJKrm)ng8LrB?@cbob09AM}1n#;0PQKAnTD|5FG z^Ld5I20L&tPN={CP2iu;I?L2X><&T<&+lo2^d1RO>}P|2ccS1UUB*p38wx7^>z8jI z5U2C%yQznsm2)lCaL5x%I<2hd%}_{r?n2jvvddGd@4+{R#Q-;5i5Tw(`&zf#_TN5^ zZv_*86=ss0HBD5d>9g}s!QMn#<)}$!XM(*$EJ%d`w8mPCuuQZwX{Xegi(I$xT<1Y@iYWwKc=XMQh}QfAY%@| zO<$OM)hlMT&f}G#fDpKocS4OYMW58+@{v33x-S-}mI}{=WG`*qd+ZetcEm%HY?cRWa zUB`w`XCF!VDS*v_vUGzNN%ue zhZS2z!tmj(-~$^;^cyz@Wtw`!6Indxx%ZNUscZPrt1s`KqUy>r)7Q7{>}QkhH)SC{ zhU4>WrIoI#@rhQGHp*=n9N_$a@IG z)nn$RpeZr$ilF@JMa6c7GWTnR>G7lFO6jV(TI{=K2a(^v?~F&P04;qz?WBB%44QIw z0(4`@ojO$F`QtrOYeasgK!g_zy47d#*X%hDVej!qSyWGTnu3&WC0V@hvhQYQI53ES zBhxAyQ?#^pUi(C0|CM-tBV zpke(xKGNPAWzIP=SVvzxySL>&8W5>N+k|2j27%6p%AGel!#%x6ZP^# z(zH*|IMU#Pf}1dl#;c*m4VwmQ-#HE1jtei;bWA~a(c}d21Lu6Z=8+%u09kR?LqfgU zCTRztf3Gs)KcnB%mC7R=+2xzC3b@vdJCW&bE{eTG%c z3LX5onyrGBpG$SQ172j`T^-X8YQo3j|0;)vaZ8D%C1i4msmeu*g*pj2?sVgVSd#v{ z#ZOrBqT-z9!)D%o=s8|{L1=lIKBIUy&xKj|Hq1=*yPh2@*`W8&r;lrcmGYeD%mlkB z8U13a)($SGbggky*C`V9+)cXc7u1iXzYCNJx_C15biO$Sh#Ot3^a61cJ8CFJ8; z>eQJS9{_+?g8!_mO*q@}oD022H5)Pbn+&QZ#8$I(hAm^C#xjQeR7<(jTQbpNi!Na| zbJZ(DwvYkqA@rr+zNehm1&(@A@$dOV z#XpjI$+=W&SC$Jn;Z`}~et!o@kgf@GVsD?yXit}JUzo_WEIB(>=!ai%C_XVWj9*rW zd~+ss1sIj$4IrOD)1jdaWu@IlQV;ni=KCIp32h7-E><3zJ(4 zTHUyt|86`{r?owNg5)Z%`dJ2%$7wrz{Ms&?&0;$S%w%z|EH8jVUWm?5H@h%_f7FPg zcq?j-8DI^mJ>Qv7HG2=m!YgSmg0%@)(i(}iCHEUuh_BeqKIejQMbA;Zr zKelo9%y$OFkxzSKXvO1dx?o0X3%w7z(u~MNg<(@A#u@oXMqf3;lKfl-&b+|}4o5Ur zS3yTM9px`f^25b!Yxgos#f`~_Z^S!abqAdq9MsYzb5YG(PycvK=!=fPn^MPR#k3)#Mq&q@0_1-{^fz3N8he@>TrPci2$$M9Nz*f21k!&beuTYrP>LvT!8 zdThWlSr0vs%zeSzxsAz@n$T!~35mxp>+|GP$eCEz>qffOwM)p>&xkK+7^fYON|%c_ zK9v+8&9Q3`SV)Y6=kV;E-~F73<{zi`h$nfr@`G|f&t#_L7mQ`xJ6R>>7G|+jBj(@d z*EL0lno7Yie-0mj0aN1G9OGTW5%_wYBdd8dfhEpBcYaD1Z6^!Q8S5-0qzud~x z?qfc#^Sw^i@^%?vKlQZfTe9zWlECS4tH`t|B6d`$88!{j}87obK-KaO(eU z@-O2ycD=p2Jv!pt?P(H!z$8DK{JQpVL@wy3HG^j^y(rux4GKk~Ic_o?F-e8AfcHg< z>30LKVcfN@6$Se9kr|N7GV|Y<7aO=&cfHnans#+*sT$U@8r{~Yc&|OXve`S|ZCW4a z=L*g>nMw{BR^sej_oEpgM34u}8d2JQ00bPsG-450;Kgx?tY-goBAr5In1BFPZZgj9 ziA+0j5^Qa7=4u6hF?VL;@Dkuuc->mS22RXew%T5lQ@fnx)Hae?GP8sCuvo+a4AJ5 z;}l?gJPEHovER!-Y4=9LF~fS!KQ?T0KLf|Q&%w$D5EOB3sX0E;%Mr@UBXORbjPIA2 zRNVTdr(!f%on76JLABj&7pr>Uq=eZ9_?cz%?wFu-!TcAk6udHIwrXsvM+wF`0w zxHZgnR5K>om5TdgeExfg)Tr5^pQ9+%CY$3;FPaso z)?7p%M?lAp#2!9#sRu`)D3QvY%&+pgvqUnq)Fa(Y?+Xdv_SjQ&Cs<Q=5BWx|&vPZCxJN2evrSXf0D(sl>h%2et5r z@N2La7On=NqB}ojGfBRWITrjHTO~0jKN-nEmpT(~e8h^SmW5zA$V&{^pU{ zD{9@RC%xh*WaawPzN4m^#|tMswO)wu6}*uDVMVeN;g%b6I8J~2kh!|s2TA%p@e-4q zTnFYUFwOL)*p2_hjY z7-snUAny^=BBf4~rpTvp;Ef(lH^HzT@JcI|aQ3AkLm@g|MP%Pc%%C|mG&Gm&Otg7J zhI1I^yeB0&#p|enoA>Z~3WzO*v$y6UfUfH3YHhz&#irrq=nK~xh|XNUY!J&=emFcX{?-ANrUl?<^TvxQ8Dyl)`Z0#u)4+WxSt9B^o6)Z~>=SRGm&vIdn85`5wnix%>d^eMhFc zB)Z$aS^brCHk*OtRWyXse9o81zXNK=^~7_xK`+sx^C%PNGgeqx?$0sFjN0v#w{B%x zV_A!}GJzH`C*_+|e)=NCcwG+10x>Md>l`(~6t?(!Zk~Hs<2o_JnZiLUIk{aA*iF%N zDuD-KxwaFvw@s(DVA^HR7*L)X56P0r*FoIXCiI#-GaQNtpTL;XhtVggSbmnOND!K- z{o+_bD|;b-o9fSYYX`S|e{DaNPpu*9wM`ii;c`ojP&D0=VK;;x*a>|6wKl%AbjB3+s z*QU8*J`IJ(JGC#J@#?RYFFIX5>?mxoFgNg7vs>;(2mF10wAK(=rI3p3 z8A=~G15n^6Za)Weuyl@q3{(r^&ydaihU@+ul4U=^cL&Twx!nAe%gPB$PV@QbgN*ba ztDp^?O_`jXsiT99Yv)xd;N|zazX~#}EznQpD;GhZB@1y&^EoRlE+SQXQ=E?nIQ89+ zb|R^o38mN~Gsb%hR1KbgnMz+kIw-0Md5b!Ki(7vJE2>m2WJTWp187c87nrKN$2Ki; zC%wq;HYZL;`J>*|er2mZtL`ES*8`tJXFldPFgY1d`a4#{ko zYqVS17r2K#a?u;CidXR1c*T8-D;Tx3wYBYj9vGB3MC*DRdHonm={4|J&6%}sC2t*l z^SSp%v>9%G?tRm@p*pjvKLZ|95!itbRodcJ(k7UZ7ggG9Zawy|iQ8C*FKWn zhKErO@9qVrgE?^LB8CtD*ohaV<{Go@yq(}$e14=YLdghM&PLedGf~3q(WePX(r)Fn z22Qi7fTNs`%6B;+GHGfwH+(YPStaMfKSeUKM~>+zX=Z(;faR93;z$6^DSUd~PggM3 zYD7&^{FC}lpJ+wIL%kQLXI;C#v43`TG0p}p7_J5q4C+}gQ(O0b;ctu6B+$W;H?CGl^UsJ z(*vVX2HGS$fs1v=w?RApeEx;iqOf2!L&iLYElz+IItzzip9j8(>JD5au!vD%h(^T z3FZbyz%buopIhSdK1L}@S;bNUGSo~jV}djm1SxGK5Axq`eXVoQtN@^0bS&YZh59M;d6#M2hgf{aFgxy78~ zz`4*`hxv3dQM67?Jg9R#UB<89AlC=6A4Nt=!v@u{)gzuey=!%z!y9Jy`%OzO%jTU5 ze-W-LtT&XJhBz#;GABqHJ0PQ}q?nINZdTZ3SP#)}A9iO@sfp$G^J`s3>c}g%p=3LT zWR967Z*Vb-J|*V*O!|u1V8!0A4bPnyePWA0MNax9ZbE2>6Df{>!`4A~oWz_DlLc`o zVLOht)nDLQSFI%D%Pb5C?!Lh<*WgikhJah1Kh-C$brw$^P@={hUYdIC*DEUUw`Z?f zUHfuFYQhOPXi-rEyVa1>p6&xVGi)bv!wZY(lxG^ggsQD=ecjJ`+>g_C>qV&tm7 zmqi9v{S3p|1p&~_hV^y8sX?VSsf{6^-{#XAvT4%MnCv237T;=N(3rNhEyENr zhORvpfvb{GMrMRw(*wVfJ7VU-te4D-<$76Q5?%dZ!^YiFL7nsQWAnS@(Bwc?Qohk% zP>Gh~!-7U@alB+()f_S<=exfU`GzK6O;h0^K2tvZjW2oh7HT`2ylqGOam_kPM~1oB zC2d|py>MC7cw*vLxxa5p?UWqLqaNz}j}`*^Vj{Ot{Jm zms1h3rL`!VB_!wy~tx)fe75XgXphE#v zQ-*W(u2>9!#u0nsoPBxEITfS;|HR< zc2vp=Z1o+A3$WdyIg5`O_P8tUsZxd$ND+zdbajx%DwtSrHTsyad*6EE@oWH2 zLt!jGHcFHd)hD861ypbVrUGT;u4+MK8=A^ubW*tBb7rYxbly)83pVg~V}Sr~%yNV_ z(VwXJC~2Gu4g)vJ*{W z1Tyv%YG2oM?NC%R!#%HzMSkU0^T#X7o4Tw_(XN-ds)HUR=G=@>fr}ipsTr^%GDhOz zy?I=l_h4Lq#KGvs6II8@k45$p0Wj(zVk1LQnQ-cKVNFPq5JbU9u(#3C1D$FiK#q-} zJhAN({a7{-NvorJa#hkTOf9md#KwOK~)MB10K8P+TJiUC0m}R_4Az>d-8+d@y65X z+D(PwnwT>Ymce(_!_dJK$#c(9cFX-;K3-5@C#fYzCd7cme^75_JidRl2@mu89l=R! z=ECK0Mj!$?+FQ@B&kB8>5D|}r2K&7ota)RRrAe2gV(ku1KY-QAX2}tw=O^oP(fgWE zta+PI(Hg;6m*1q=&96)&n(*T75^#2Nl?cxZ-|)BoSP(qc7;pn)S>8-LvobRaxI165 zk1RaTJLA^xrX?qh;m+lJ;7fDTeO}ziC=#lrJI5rS#*Bw5&FL00k!~Fb!3EYU9q96p zB2P6+554KgP%=; zy5s#7scQVc)y@YXD3nH8#bbjSQs9vFO1k)O$rA)S*XKy&BFj?UG(?{wp zvjkA%LEm5A81x^T>*1kGH}Xga z368S`nk|qvwe@k+g5&loB~`QGN#W5PQHQ>7IB!?J?U~Jnk?lN;c;)%G&>i68}=`E*`bQvE!&L!uc3 zG>FLFT-E36kl@zcf>N+2ta@lh*fE`$2RJn}SHS;BjdySXWeJnd3|Yk6X+^sC`V@aK zb$Q1nGH}3@bI*q%2s2@>aVrdw^K(LdSlL+3{`VfFIghfWITWu-5-Fk!28~3Fh3^@2 zMQXz>D&wKQi-fwa5}!1-0tLhq{+3IO>Ep1;m1%Bvm*doFfG`r&`rV6mP>KvECd7RH zjd{9Kk%e)yc!KMmaJ;QdEmOx$KiD)KxsAX?XB95ev$PzI@3lcH=Lu4M^eDv8i)^5+OT@)7>!iOH7zMZodMd?0~^5Rd%O zf>218P15TkWXPcP`a7S0!mj*WW*Yd za#G;K>nQwo{U2tbGbK}}BsbYzJ@Mt=Da5n%6+i0{IBrR{wUZ&+;;G#)^;Ux<1ui2$ zIw|KWR91(s!!fB2O+9eW;Kb=*u8wktkVgKxsYvOYgV`{kj0&T$;fhbQu^v6DDo>7eJFfUN^lDSMpooCnw zX&)`&g2sWW*%C9f_?qaeU*YfKhooiZJ#GDa9_5WTPhM;KLFHgs9tSeNZWuDTBWKu; zjT8+GRQIz3oQ1N)a3LV2LBjy!hF}*ZNCIO-xeObGik22GiDRR${u5lvEMhlaBd`z1 zSv}dnz**T80d4r|+pUyVLRfY{CfEA$rupNvu7yaD_QC=>nb(l zgstD#s9S#xt5yX2RSCmj%PYi#^yBE2MB@<}N!BjeZ$qlY$O^2^ulYMY=J9%%<1vwU zFC;Up(udk1@H)&~Me`q;4d4vJa|)GykOrbsW~)ij5Cl);q%X)Fvu4wP4N?eG$w}oXf;WjhY&vvy&2M^r={hl=1Us*9T1gu z0Ne0R2Pj9KWoDfCo-glxg$&=0G|jU(wmKr)e{7VHyZxPAB%@%XQeSNOav;b=ri^?` zbP5=yfv9>s`0?>Iy>2I=aI7g(SV*oY#OiWX-*~PcA9FikJb(VeXxNzncIx{cTD;L& z(K`?+_p+1{JS3b-von~877RhSE5$rvIF%`4$9MI}(Y#@v_wHw$suIv~^c zQt-(@aIYAKiJe@Oxw$wPYQ9{vk=I5y(;Oeu*M2F>OdctF;qG$ljL_^rP>PI^H z?)+z!=V3ezymgbGFWkz}PW0k^$tGOxui!eb7@=_%u%9!m!+5DZ#`=1|4;w^-_iUkR zNwnFd;#{R};X^b;VI?Qfn5>!~`06aV`NHrOK!fe%)2cEAOq4WYm<`iQ_~Q;l41(H- z=u+nhQ?i6Q!Ks)IImh63x9|Wnh7i!KCKkm(>tU~_@)`F;VH1PRIY0BY{6EHEvcaTuT_Gtqt?#fl2)(gTYm+R?1Z+>4@NYYM&ooLur zaQ&k`AfmYWAhJukFBPU)@ysO8v@|B@^t&>4w`GSYw`7=(hMNTMM+XA`w;-3lhe1bC z*d?$V_g0c|_Tqch*q@y@&^>iEVms`hK_S$;bE9iqL0q}V8tUzd%*Mm$c=Z*5 zY%&yz!_rlh@y!l%YVh?PN8P)5~Tz zoVc(fw98dB>mEtapbu&IiQIA%=;tSE_B$4|bcz1S-_{v8U?u(DZ0t-^asIP-_B+Ip zcsj*xA&~H>Ve!FIJogUJN+ra-C%DpM3G9g&-w{%OsA^ z_~Kp;D_t>u3aYhfpNA8jB=}muqhDlVTfNOZguGgqoni76JeUy6|Ia1(`^0PAkLQ+^ z9OLMH_xjKozMp5ib)_?Zy(UD)?6O0HLU?d@a=XdMb2&>|96+CNvT5_8g6e*Hvzdi4 zK%M5F267PMi+8&*#z#}BEnPY}!&s>PvHbw&xUH-qhXxAtoCY0Rg9}u++lgmB|8g*U zn$YYx5u}O!cJtKKo>klPa^7a)oyBFlC`H2e>Y~%=)$)gDpoLgcPf_T;SF1I4mO8x0)Zl zdb3+{i%N9P$`{vd{-LpQs}_hk$fqs&GEI(``35GDnx(U=9STbch!G3uP_xlH>&yuV z$xOmNb=e;KZ?wBkIJarxFgtqTh$=0!3u@4j>L8f}ARV}ye_DNRbd|clW%*}$?2h$q zTD0gTzjY-$SmkCvmu*HYUZg#&SWG8@cej$#ye1v5ck1SM`MaSs+WRnTQ!U{aXQKzgZ<*PsWm7-m0 z-7T^Kk!iFmuNo5+UgX!FgW^nvnM6(|&u3>lRMrFTwkw%OuHEEcsyB^LvYhd2mV5t# zZ7G}$%KZv?c&sgA?>{-KuTH-k+sE!>t6{jyP~fhSlr>Tm4ii~I&hE9rs!CJ)qXc)& z49|?D>}2*Ss*WTKX2hDAbguP3l8mbGuhB~&)`p6zVlp=rs38j_!;z25n9g66*KrdD z$si=G6S%Z`;xo>OTFT;G_S+npzEchLw!`p%J>xc|K-S-58T1h6))R-O*hWpjxf1VQ z*z-~cE~3mmc17Ha(&ejnji>Ah4dA@x`tGeSno$qC_z`Xo8#-d^QE$lF<N zAfEKc^ou9D9tR4$!geL!T;WuKYIDJAIOEZI47C*_aJ%EQ?1XosMy?mmFwt3q44p1c zjp)>LSg6n2^?PAi)!XxnqlxE$BZM|K!}t#P$u!OO#FO~6<@wAzOMWK7UrAZIkBx4j zqV&~}3v#M@RSIBUMSaR6kJtfyjHW7oM?Co5(NrX82x{}XPd=WXF-2Rh5feXZ!z6hAI-d&;V3$G=nT1AUo(J9{Qea`iA9xa`Zr=EZ^#4Uw$v*)pgJ6af~GNv zyxY61NYc#0)q=Gs?o=ZY7zyGMBY{!cDiZnTHIeX{s{T+{w1G*H%T`P!J^# zQVPn-K9!To7^8iHiGw`+zx*{a*>lxOHUTJV%Vggeb&<-Y zu1@{RTrgu6l_Jsm!rv5nF_Un#&zSjbV__%ye=buMjnDlCSN^7l>4vWyK{;)gZ)JvA zk}=+kWH)Q<6EsBTn*W>b*=XR09ENl~AQ8Q-Do(Wt+_8H+u0Zm8qxqtIu14uIpLy2; z=N+7C9`}9k$6dD4!E}RAA0@6vdtDvPZ&6%Qj+2V_!Rk9;&caJ${*J;Kco^ra!tHDd8HChF1R|c z)m+h%L>|YpzE`OIs|{O<$2x_(g`rcDCAcWg;fKUB_2s~!=~ww?{32q+zRgD2&0pBq z=4R#~U4`Lvjq@)wnr816&y~h2W|rcfhWk1RU-rq=$1_9)1Xo%kFs^hMwv$=?8BzK+ zgG*n~3qqg49O2UJ@~z!kqQsSwDBK@L=S%o!P_&nj0Lqt~NrP;GDKngUSa~#QKyo2W zmwe92FCSz$`!vFL1^3)Kld;h?$QFuTiG=eUzfzQtc}z13ojAv1P4U*Sse_M(7ZOo% z{%?Q8DA|6+2`e%-b6&fxZ;dMRMr!4Qu}k+CBLO4`OQ3Y+m8UXQ6~SqCh)jv_`0b$naz zTC-AhQ@4<}-gTMItg$#!cgLsJuQOKVMN*~R0PrkK6-I;(;iY}{B*Vk4#J_f3KWzPc zOADS|pAFKwygov9xwMV0xZ*V5_4qNeQPP-~=ol(^058{bp?gue_v4<6_r8eex`n$* zdISv-W=CCjh)vFNvt1M*!3V)+Ec7Do5V+11WS`2-8eXbgo!EU*Cs<%5PVLGT&H*^| z7#8Z~=sO%a=^{fP6)>&ATu=0SB~k5_nXWnUq{C#J-TqA*zn`&~Eg z)=hSA_UrAb3hvwK2alrV-PL`g7m-d^UaF0GmlclqG$3O{)QBx< z=C3WdI%jw=QiRFu;*XFH?@^+F{Aj04`D?v_U$e4*Oxu}M(SJsnNT~X9v_S0%vlf+L z9ocF_kZArjg6V7jzStag|MLg*; zTaaK=)&Efllt~Gf3#|vA;fDy2fDBv<`Q$8>OMRK67gEU`rNBpZ9qQo2T8swF`x5*n z<#l{d);G^iW>;ua;t{w7c2^%eWsEsUz5fWf>ttSC&^jr>B=-+83zZNQ37;`l_M4PP z3j=>K-#9+930%St5t~prJJ2t_s4sH(0bY>Ry|xVgfW$q{gNT%7j=%e(ri2XR;B;XswrUr@MFSt=Tf4H_KF6f%iuLPfI^Wq$W0Vt|4X*jg&uhr>osI12ow({X`g&YC z)zZibv9~)u9nOF^BVSk!<62a3=xaAxZh5Q3Hb)*wwZ1E1Hyd0p#sGVSW@sx0*=V9- z*Z*+Tkips2i)H2Yu&b%~tTb!xej8~N3My9-N}jm+0{dOl7qAbq!Ep)NBC^gByYqJU zLAjDa^$1i7VO?#kMvy5xG_wN7#gOmmBiX_>gGCi$q`8vyD9* zDYsGiT)5T8`y&gg3~phdcV*m_fdq{>Z$dIbWZqk{U5=F%27J;EfJf^3(df~GY`AkV z@HyU)@cfswU7s$FeRXH!76+(s2`dK+V%^ZcHRv^>kcBcs4v1F6?Xw{vejJ99k8%&hym);d=? zuO|0t)5ihRX9G_mu4U(nn84DSn1I>({2Im&(fY(GRCx195sD@YbOcow5aL zD#HE#1YTF>)4i|!2Gl=mE<%t_vhy(MW>y>G7DRMmZbLSwo$x12ZQIP#%_ro2>dU}t+_i>} zBf5HKIT9PJPl^PMF^Tndc1yVH*hK{&CGl@@@tOLUhTVked;N{WmODw{sl3!(zSIbm z9t>CVKZO&Pn)w)y!T2q$>oriO?zqMFO94_{hpi+XjnP*a5MosiiwU>;sz9w;HZ8$! zmv5Ua6EJ#b5k|t4snitu5>86a7zpm_Q-o{dg0_5Pt@zRiC`-|DM=%5nh+PB`Efo z5=~2g_nH%g?4vaMpb0`)B1-2Z<$BD6piTcJmrge~AFdO+4pJ=c&GwL+QcDldz+d+0 z-9if7^8mlu4K(NB@W!d~`%9t2Q*My_CX9sz zMQ;K>Oz3@(>~XxkEdBo~XuaIkVHI^9YAXfKetCSJR6=#XMEL;Z7^-OHY$Wqo~9b1=H@~rtYuUWC_1>-^hyc zNxnUfwk=VU54Q2sD1@=2I5|THcKcSW&9(^}RnB@b9Qw6p$j(9aiA+SIt}f(Lub+L3 zD-I19==xiqiSP7NUwPXj8=9;Up2oJV{5tAW08;M?}LaeoV!+p5*$Nx8I=H%WvJk zK{ihIJ8FkVI(2ef?cZlr>4sb)6wZozi`NhQ%_o+$Hf$t|xs~U+`r{>eGWOut7*mYnN-c)QZmLs#5Pg)ILQz?Flk#26k z5_jEt4WLXIi0Wa~ru0{}WP%x3{uF-`&|X4a(J4cPmNi_Q3$Q7rPt`-4Qa4bIN)A=5 z{$$Fm9;-qtk(Q8SdevYL3qp}C1ctdXln80qX;3MMpClQi{XGz zX$Dr^iV7;vZLEjb)BE+4pT9(&$W)NJwdVTLhDlBfrXZV_xbkoT^6_g|%@Io87~mDV z`lDRA)5EGX!1?rbm@*kf7Qj0Vk9sHZlI5GBtL>~AQ$L4!sxXn%-Xb6M=1zcq#bE9B zf_@>T#3)oP!$3#vo~=r_@ZhNOGIY;4ZTN`XAjl4anDyWR)wOIg%^u~o)~VGPy)X^Rj*JufB1rR!cWM>S96XTZTs%Crnhs-@t;__`wd~>fKZ27eO^wX zfCpC{B{EJZ`yYYfd!gZ&LpOwsgwGs<%h~Q=%ZCy^H>#I!qn8-U3HA8Y3O%6jsmiG> zhqN?2IyB19{*?@a+~ay<$mfZPZ*$`na%~Te-%`Ek;PmQnE_`q|JJz6P{fXqX2NL?iBucnpgq%$mhS$g1Mn+34@t*`9H{hQCCK8{?sBUS}-i+j_Cc{veiz(LD3=mnVbHm4C2w z2d_=!`{LnE6JsUzo$^#4NOQwy6a`}=2y4nB!X_HH&#^+5D$GEGLo6GmXV6hpNoTMw z4XT~@F#t3WMe02EU-iQ=#)uzf^PL}x=zUZuj8^(AhBMJf6*Law9g7ApLeUG~x`T;V zA}WNlH9A}LwT1Z)i!)ien_|8dj_YTe{YS7hL$b|1g`uQ}MS zkLcS3k+Gimg)vf37xtdF;2tWpo3yhYslz?bE~rE`Hbiuq=Y};ku)gd9!&2>N5{>;-VZcq zv*0Iwb|g;jd>mBQYtWmHpHChwg@Ss}H+a z->Y*6*C=C79#a44J!F?#8ThM8Ra zGnPDel20^ z^oS&edV4vGr!B!l=xnpG-<=fYzr2Uzbx0u~imkkHrws<&6Cv6*dN0x|6-~SM0~+M5 zwplv2d+notkDyqg*o5>w@(-Vy+lZ&Rn zsaN;1*=oh5o*c2{c$N;+e0(5BB$6f+{WOrLn`4nqd*<}UW<~Z@&c79pj6KhO$?Ajp zr8Ty{{|3ZW_=(_Q9Rg$3 zOJ!Y#_VGSW?Zcoa;MQo}SFNAvyB#dh?$^D?l@0~CL3nxBrTJq%kN2z9|A7s~(;-KC zP>D=t{v^u$%5r20V-j=6F7Dd`KW=39vB=$?3VyD{%OTz2YhFIgXmLmk|3>isZ50Y+ zkz#K7`<_vV012Xbw5|0Psr@g>^Pssp%e;eV;SS24dYGYV9w4ZGstwzzsy-Ca6i=4C z?nTa>eEswOX+~(=a)wY4ON%e@?ww6bpHLx$e93J#A|0zc*xyZ8~wR+pw?J{(%h3cFW_>NpNB`wZfv zS@&~CMnRQWtgVB8h-1tR9`yW{jUpgm2aki{F=GxR$&M6JCd1# z!nu3XSy-U#XAeuuLz5;Cy)Gb_xc^4AWsQ5qNcE==Ao6C=hA1jScU7rX&v?zjs2C!DBfq5A6u?HtDToYWfl-_2d zAqr}kC%H;w|1VR)zns8NxTHE<&VEOpcF9;T_M)z1dofj)%W`uRXO#{v*^?9oIXWMa zi;>e#))7QEeI09+D*QKflNY&rt>_Mz+fmy}W^p+DWCj|^YDbAuzuDQL++p&+=tjn@ z$@f@nu{i4OcMYrTH~`fm8UxRc^I+eVF%p(~)IdtBJu)lE>t zECNQj2cn?hUQr?4pYukN zQoqJlHj&NyX|Xxq&@9Gk{#R>0%11JTP;a|}dZwQ-o{`L>PIX*v?dji2!K?c6=!;xF zdc7+`K$0@F%(9;maw?b6l>H1OurI;a!*we9b!D$a$D$^W&rg^n$8&8IvrurU0b}UJ zkULfe?)Ku9I=xNhTN>6t)_sw@cPS0;KBb*Lt0t18o3m~~i*E2@*x{9>-+3C4&k!wz zCBhwP%k8^{u34T15)#kgOm&`RtIuwHqn;E?#qRa(vL@)S5?{woo2qVo(~iVSs&JS0 zqUGEJ3TOZOJaYSnENMej^r+!()+R_iLDKb-^#I=j^=&6J$%YDakATc*_Q7fWw@C?# zd+3{m)?Mc&pX&LXi*lUXL(0lc$@#wKgRTn=`wqV&;%ud2Nv7b>sywxYvPHw+zn8Np z8FJhe8EX11qquP*uy1VDo3qmz9!LeMJ`?kLth-~9`iB1U<8${jHK0?WJkA2n#kHw> zgdp26jRQ_bQE=lNB+?=74dc;!ES$sSALL20QwwGRx*`K8%-Q)x({a@f7anwU4^{sQ#0LJf+C`CPpi?S=vaC z$>Jk>xa8cQ?`;zGOSQVQF8F2xla%Tup}&UUAhpDwVhf_bvR7H9wf8BZwDQcaXU2GfI8QeIo-04*R10x5|+Qc%x#|!&Y^{ z2!wWc3iA^51cn5 zzNmFnQ&H*X^~(Tb#wV^g59C*OtiRn7fKT1|@3m>NPKNsR@Aib>-7inkP76E=+tjkX6nQeam$^bgd3wN~zJBswihZiZqYBFxJH3jPW!n3Qv`!w3F@3twNC<%FX*- zI9M>-3bamt4jww8Wc3?H-$fq`iB9M-dRWc!bj|?bWMy=Ca7J6Flh6P1o>J5+@WAa+ zuchw}?u9o!t&7sRUKdf)-nojxih@R36lrD;Tw@!X)p{hyPzBTVBWqAec5m3r2J1qJ zsM$5Q5FaPMT>QWK*Y}U;>pa)+tnesM69X^kOV}~R5J?TWPK)yl(3n{kw}J;tpcAsA zb=GlJ-`s`&*V|r0tB`AHFk$u~LbKGeME;xsNu^FU2jXh;IH9(c(WsYrP)F?A(yDFV zUHcrg^+_gh^&xa-y)UpRpR&-N?l%?*X+IZSi2lF6-RL0S=aoG&XGj&-eJt zUiM-8O%T%SSNndpW7K7CKxGPVwBBLyIu{q(uHYz`cE=5+1w&85 za(#YU98^Mp-1l3KtTQn-u_t2lD7i)H4MpSLWi#tKqozfY%*zMI%?bfy#_Rp=@-x0l z+49wI3sPKYi>EPT+Dt%(`m5eikU-D%5{n~LJWb+7x7gQqOwI)4nSYZmjo@OtGfHMy z(VUTHH~$QJ&%i!k8q;(5OY&w~S{@d#u6t-ke;*Rw;5|Urb3Y(eP#45T$(pqW=}y2JP4p9a~Q&%yz26u-;AE2J>f?W6cT%kuOX3zAD-nZveje zCoyJp>Nd7!MZNXq>;0?$lZKa&7Ubtw&cCTg&fWIibDlOqBuimJ&e;X@{|;zS{iF27 zFz}UJ=OuSgrRNC)Z;s(`-+l`s>YlKmKL5cl8(h=d*80B(P{6IPR_^J&K4%uWj!IIN z7M%!}Hj$<46|V>ybsiCYg9fBnYeDPE!}`~*)3)-qUN|iRzL~4U+I?pRf_2=MCj)HnOB5dm}QzK)})n34SI-q*hKuQMRQo?m z2^f{h7|Dg&&ZSaZ6cv_>>s^Uy^QWVS@mkKt5^%wOMmw(?*y?a?o&pq@{&Z}qG*YTWrE|iA8aIQPol&Z zEJ{qJkXDT|CCS77y|MOD$fp$6%REZg05w&TKm$^-RCg{Yv`fU!;O4oi z`y54*i}oDXNr7i1$#c4W0HodK2n4+35_+M1!eLV*A2auSMZV#FHEyoVT*m zhF^CDqkJ40wP&{D?t;%=r@Som^P$PUv};PHwq``JxeV=sW(7}$Y1V#<(+lMVP=&@| zvMVO?bSI+MWi&Z*^92d5Ysl+~mcP29`%H;=gR0S*w*^#|x{MY?*)nSTeDlxJeo(+r z6bZYf8j7dro4a0ec(K=d9Ti3|n_-GDK^>G!s+EKOk0(NoDA(=2!A-c_xH14JE8g{bP0;Zvx&$XL8TN@;m zO!cBA41W@O>9FYG2+@pM6ZDC6&*BO}bh8W(-fJsX_tVO$Rp|@tj~E^Ji$fa+GHJqV zb@D3sOOIywiFWa4Ls1MMBB-vLBojOMg_q*@aj;nAkDY9tTYx~>!^V{Ec?Lku)Fw|~ zOU7vaQQRsE1F%pu;ds^lz1_yl!BxZx{LUjZ3Np30`(`(mky4p{+ynQ}qeQa(bK^)t z^z5atte)2Q48cj6W%ngZb^RqBd-}5kH~YdooHTW9r)lr-k26Xar;4LaKw~rq+|QYi zLD0T$t9UMtjB`acDJqJuo^=Ldd9%$fe$ovx{g@sSUuO{)j`vG*2p5ro}ahyAR z)NM)(59s5`U``e^)OlMim`*yBSk~ERX7DV1rRC1>+FAVTt4tA+sOU=#BTYk6+Nx{D zUAlMM3aCj{RSss)4I@UOihnpZu>QJgn|lrq(3zvV#F93iYmeDS(K}P*OrVTJ-A?@m zT8d=&o$Q~mc}FI4Fp*OX^2+e%cp!bq^f-|2Apb%F^oy{{CWard(oL2D9_&timF9Wecr?}=~j zPEV%;xjD;SOrDt-RvaAUakln%E`{P`AMk7jrj@Df$6Ws{beU&ji>`hhR;^5DekIYt zMX!FMWzBL%iRWQYxcOj?K-h{u?adB?(q=*bs5d`$K0o#pMLcfY(`Dyml`i$3dGYh| z$|Bc1G!J{>Q(_6=e++ zNmUd+51oT={&QLp-$2vP@P+KjvM09Vy-x?I^L0Lb&|DbIl<&00X!~y+ zk&v`haL6(dPZq{qK_GpR61U3X0!%8*pg!+iY3W!pQ>l&G&>_?o6bHVGLCy`SU;hz1 zGgnfQyrV_*w)JJ22dpLW^GrDQOs_khepr{=!8mH0$BwmXh9 zuhKFcwi$Qh|9ZkLvx<=9GL~9Wx#Gzd6-7&;$r9qQ?U>$rND2e7&|Y+#G{>&K-3DHUGdZx?fldbMAH; z=!STA;`W@YTd%A>5=yxy-cI$h27s}TI;s8-fQ#W(pIPO|9oWQ?nD~Q5^|q<7FSPU|UPxCe+p>sH?Hx&Q7c-wi%{ zXuMpN_(6-2C>j~O;6ppo$T)(TuOFjtz}9+u0-pZVu-W}}ho(d)u!DPU5L1os2Qoz2 z621(gpa=tNyOkNU7>#2R5^ysKZN_Y5WtUjV9cVH(fKlfJpY>UR{a2~Ko?qEuvM)^d zSL{jVQ!#r+Rr$Q8-bECDuqp*as4S||z8j_oBb;(O6tuK+cnYQa?|$^O<690umQp{XH{mRRhlx(cJCV)FBtl2NCLtEd!wwtMJ3F=@{Tp@gmI=V z{|N`K1c?R@5wX_C+nzT;37yfuGRJis8bnzYyS|X8vRNt^1Ef+zM-n<}9$%5A zAC+~uWb8B4TDurDM_&bK>;RBE@29T9tCXsj3(j_$nB2;$49t9N<6<_!?;Y;wd?&9v z6=(1g_Ob+g+&{9!4J|W5@19WqP`@XxfV;DwOay+I(2r~Hx<+r~t<% zS^Z35w;{4|Ff!BqErPK0@);JUgIUK^>PucP&uodbNa<~>BSjBS$_{$)Qtiy?ZJz#^ z7wPpADBvbgu8ydsQW8(WyRz`}lI^;u+3^y=2Q6S>JaYgs{6eN@i*CjC9x09vqQR@Hlm9Gy{n3ryc@C|Rc@Z;nXS*@xfVV;my8Okqh21sQ2uplKmt0 zsk+qNE#z=Y?5rRbaz}o@FVt5&nWEc#T-P;A2Ru~(p0!Y(A`2w!J@>qy+LF|n%$^8r zbr0)U{~`L$FlYP=Y4+-p@L%ncl-=2hJ?P-}Egc2ZsH((altF%GcJcDv&g69e34h;I zC=ZmjKm}%qL$07#ICv3I^eHYX zDto6)y>Jt}bAbbjH~3o|STGfBtVyad-yY1T3l}FZ%m8TYirZwEk^tBZCa%bA-jV^P zF_8%WQEw4dDxscQ-N ze+fwGz}g7$!vrtvN{W)z~ej<7j-fxtZ7@@gwbvbE=z2alAcy z>I6p91pBj&Gsh#fCm%9dnDAZR;p@)4@%v2DyNHKc`L_eN=PWMMUw9TrX|U}I*;g<( zU2v5?Le5b`aFth44ltP`fozio zWn}fMx1Y_2JxWMAilsdh{cIv;vpA>quoQk1hvpKIta%Q^%o7+@8`jJes#2(;9}0xT z$F5v$31hFNgD!W?8hu{7S|oh7<}xW#whL9lk{CSRR1z_$VnWfd$lh5=y5;wH(2Zva!SYb8vepR&iwGXin3#zRO`}C^dc1Jj&)~qXiBjDc!e8Xdi z%66K=;ucTzpdf6_LiF|^o*evItU3@nQ?|tIKWPh9ZTmNfIkx924o6QFioa_%ZpU`U{ zme{;5`a$@ZQKgf_{~3AD)N{GsHL;7`1os@2LV|cD{%wIOmeihj3I?;cQV*E-!yHn| z84nBJk$FD#Mukd#1$qAcnB{U9U9xakfcWkd5KQD7# zvqfc8CCD6Hzns}hgj0FG$ULc@a>`vE=Z!y`py5nn}dS` zIr6-w_Ab{q>v~ea7ggM|N}E?-MP2jvraUfw99qXZ0`q)W0}gw6oeNIZanU4@uADk>DONQybCC|RE92?$?gg{~J0 z5CO1!2o7ds05GV?U}B{LNSJ9{-CTA`&K~ZyVRu!>SBNt@8k$dich)Zcoz>M<9A1-m zfg8+y;>q5A$VJ-v9ymDi@KlUml~5evd)D>cqURg@_ACerVLL6gx44^#Z=T9-%C$yH z+>o7iKiVVy6myl>QfO$zsU(N@qVi%7HVLQ7yI}d6Z4Yp5)Fi!gaUN1Ck1k-hQl*~T z$=&2|H$L@ICJl$YtL!}!7XSAXpOu2fnsXLMT2<-*?)Q|spUZY6wkK@qW26<G^KY^V*Rg<_Y|263Yn2cJ zP%`4mX_WS@-SYCiuxMjRYSQ?TS#8(44YlE7Vu(f|TRUsfnanpk-0ww<8=s;pzDz=2 zr&8pS9bxn1&Rgn>Lni&SaBpH-pCw})RcEnKB^XbWnCe%tI?Zqzs#O;5iVrHpra3%A zkOi`YKymHS*aRZvF&x5EbVr2qCkUiEg-b{OR|{*d6wPyNtOb8+Ww%=rB`))V_Rc zLRs1cgZpQC1gfj^15UK|1+@&CP1*tE@bxfO2hp=sYIcp@R&3=deve#MLPN<^TVe&l^^5k&%OSzk43_M6mzHSNXSr>@~#2 zjp?z)RWBPDWS*VBUW1Td$pdfYjF=l{nj?VHBk{}B4xE7J3qQmXuxP(rpmFMuQu8EY}-Gx;U< z`eqvJIiM~yvjYE;V}f0`VjG;VZ9UVY;TQHpjf}`ZyC07W27U1+2(VE0F*~th%x^8)-_Ylh+iUQ1dI9gJ zF*Cl;-&E%U2t{8!9A&~p_H0N=itXN-zWHSR+dB|Z|o4`-}PJfuXR0})hTnjZ)H<%N-B}%!BOAhS z(rUr7oRpLj2rR7aKM{^thSbS!uwyn1L2Zjr(amomiyiM4{T{ILUVdRy;wl`*xRqTD zKFik5P`Nx1D76^B&IfEDEa)PCB{og&^dz+GGA%}b4e@#ZYh^7CBB@K`H39D*?hG5| zYc5ntoqeCd`7CROy6_)Y3Onl&!Jxs_L+NToZbq>~K^+|U*@(m?GIzO!_JiJ^eez<9k)Cw!#~V*xqKx1^Y0ZsFgUWS~9Kxe{WWxQ7?A zr%4u1gjtUX5~q36n?>L52nC%QdyX#nc>N=w7{>K8m&T`fIYS8Po4GwRb6L$43|M%r z5r|*;MVDWlGUV{$$>d|*(EG(tz22Hd_~H+f`T6grH-R^_@lTQCHsHb%!Y;0(m9K#n zsiq)9hX<(9m4pQ5zx6tBr_kz_4w>ys*c<*C3Iio*h&}z1oT;$R7THov>l>Oqd5X>* zQO6c8T!6XDEZAN1)uM}WJcns$q~IMj#)i!e?K7At!*dQs6V~x7p)2584b}Y=lzDt; z6d(OEc?x8kGr3gU3x54FVMQWw1MG|^Tcay&MgXYf(GSLbd0N%Qaw30pfbc7*mW2jA zYOU-P=pMEPN$i%q2k!HuHiQU^jQGlC-X?GtY+88X3uHTWo+lq4K43qnEv@@voB8ZW z9p149#!rdBaaLeutB1=ekn+9_|Adq&QaI%-HuCPdSe2_*2I6*XG__@VFL zsYT_V#+~EP-vgfgP>tehh}18g!kLBLyy<%hdF3$PILlt2CC-wo3YV>wVSBF776a@c~XSYNvR0w$YK((7wZmw~q z7%O?-VllJa?nQC|O6YanBH*ZbQJQvb)#A{i>R8)CLF?L&=6SVXI0r(mtARkE2xxul zin#xrfyr!*QC(-yejHWv&*#+B-m6YeyyG4rGygv}&A%y3#5ey0nSUJ$X7qWX@dQ|2 z*MABu_=kC%gl=xC8b112K^vhCpdK^CF?~S1dEbTT)I~&W#>_^(c)a(@&Uo{&xkYI` zE!-Nl=o6%flsdN%ffg!3=Lld@x$&qeh#g$rLOmlN$%$Hr^vN98^;D%Sh9yjt7xw|{ zJihKM)$9fI-fe15>*7B*{Q6hb#Qyro%dpfU{}$IcC{N#glhc$}H4=13;?O1r>9fzC zHG-PT$nOe!_n-AX;-ZH8=*%0a9_X9XG|tX%=--i}dUBSr~%5)3?I10aK`8_ADpQ&oqC1?k3jBt|`cRDjKRyiz@_5}8m zl6%Eo;@!&<(|*dvM3dWieNwcUoLR`lS)Q~EHPd#&*Hp-;F9l;&5X(9F@@DUJ^o}jr z9;;MPkTfmjijC3D7cd9MFxsnHxZ`qnRJ8a$A0gdSzr%4vMVz&T^xC4$uku0r% zBfY;jTlbb#fw_+)x4<+%BHhP5?sDT~vY@K+^Yjd`XkWi!Rkt_ynr!Gv7A3*m1)!ZQ z<`A`OX(@YGVANc=1Nlz05t>*dM$qPju`B^39A+#oiq8 zePra^jro^`c;CTo3CNK=;0oK<`q`;z;~@F`7RnK|xYP6-NoVMgN`=#{96;khPkU!L zP2-#^bb+~V%kFme@%{1#5voeh(D*B|#r{G5McB}FQ`>S50TwI3CP<56Hg&jx1?($kD%vwwDL$);? zPz+d>Py|(Q0BaiWcRtr8F9LVSzmd9&c|+Qq@p>V+SwNe%YiDH|U5-7tt(#dSQ;<3a zTN1!Fm&>cQLc#d^qn#i{r?xX-yxklnr^`L?GG!4k{E=o4nBe{h#>L7rSmu#ncA#c9 zUVs(7wkU=k;w8|oe>C*xn#6{SM@hym!+U=YFV(t%zI$wvDzLS6%mC#T)Xf}o#ECKz z^c`Q7S92D>P!dz_S`>f+_vy&BLZq=Q$v@=O53f)Ko`G%0QyTFo`Ha&lSw@qwCt9=y zIa7r4ZrGFv@dpC! zQ}cxu9r=v1W(_VRitEs{!v;#$%nGQp+;Kq#(rH{JqyRe}a~yI-Hk(n%gwnrWtyAyW zApy1oW|aYEAOp+UXNukJ3<{H?3jLyFLk?l0*F|+@9&`u1vNbX7HXNPGtu!4Tu^%QO zukr`7JLo*o67nM|Y82XttZ6uGErpaz9voXvWL;D)JfYg_9(x) zGjfSwpa6Uhm863uvqZ_Zr&VuX%4H~jhW}FSDpB~;v3kp~w=-CnjxB*cNXQ~NpOK}0 zn9EDEQ(U#G;gttiXm1wL+|#BSW7|_-Z_RR4QsGkyk?Cl5H{lq&ynp8SK}gY)RiPx4 zODUy>_6mJ>J_XHu#l549lCH2C;8rzCu)m*658P-_@SQxxNs4fG3u#rlZ$sc6y=BBBfOe3{6%d_J{ z<1OTISjOU%x($o3{UlnrzxMgu>HPJ}&OF?uzON773Tb9}C%u7yDK;8vg1cA8*D_n} zCk6IrW|TmA1ovx{xyMhOa1G>!N7v5Hj9s(c;TD5>zGQ6GLKSVw$=9TD^@a{dBYFf{ z;!hsuaXP^S-NHXPHRs^ZVVk#~{J)>x2G#|Q`2;|+=HQk_;!;GN;bmGwr*5cS^VMmW z$8*BhQ_}>!=e6)d?miIt%LyJod_a@)*A8nNXZ2X*IT1q9{4Q8<+_9g^jQD;(w|>T% z{$6lB5E|Ajp3a1u=R1Uj$jE^e#RmDkb3!iG9%%Nh25%sf-X2T=-@V*D$$t#lxSy(h z+ClPrZg6h4zqz{do;i;KS@dkLh}l0TWJ*Z*nT@BaY)syK?2`Nyr2e{MHIuDN8_8cT z`GY&IFYU}IfLmdX$RVm$O#M%jyrzQZ$o>8S$&)RYWdGdH^isK(g z+qV$tK%c)^YrlFU5zb~*+xgGp==B}GD7WlC-DZ8M0(HTUv2~J2HP5MtpfcFQ7NJI0 z?>(6H5Sp>3lD`3af_KdeIC*i&$!C~ev-)rSz2#53-;Xgl_+D?ZIohVxBBRBmW$R~Q zIufJI+?PHi=2vvhBb}4p<5!7&-$ig8IPog-x;Q#aG95%Z9RhaY7^N*uGcdOQpMzui z7f(r3;5i2CkWckO(UbKI$h=?mzx3d&xv`+=VnGk#ZOG1YQVX>UD0riH*yo_WP0BcP zh%(_3RA`O;LCkw1kMDc0_XsdUtnW$Fxi=X0y7#O#{InT&AFf(ppER=}-&wxkZOocm zr=OLIt&N_M=-kc~^64ht`p~(k_1v=bD$wdyjM5hPtynZ_3wh z5#Mqi@4KP*d(IqrYe|<;neUs5U(KQi2qL;0z6=e_RS}D3Y$l6~v#N_oEnwm-S({b7c@hkF{F5a3 zsY^f>A9*U~(DFX7(dXyiusP8O2?ndN(-8*^oAQ2z8c>%dvA&86AELwfQ!wTrr^V~aZ6Ogiw6;|RxcNl}UpG(^Y zO9k#lbntTRYrfD7@X2+%FRaea>qu$YW^ZYe;qzbp%^LH=El{M}8c*f@-WN$C8EqnX zR&t3>PcQi1O)eg0Xj5;SOBwrmK_4@gJtyJ#<9V;GME)vG{&W@oTh^Day1&VlzDnW>0!pfW^c4wqPcc^o&AFXbH27!4p5r7D#N0#3j4`5 zH+DzPqL1_a*OegUZ~N?(-e0MN7Y^4i6(QEMVXX4_LgpOGgtG10%{*GnD4>NCm&tW_ zho-nHS$j-iblY;9pjY!X1F@0*<$37c#NSTsy`l4E=l3(%5RJ-=9^a2gWjSfz1+PV+ zDs21Hk4mfiH*e(>Z^c|%O`dBnV$S7?%gN+wec$)YdPx8DZWk(?F`Q~iN6NFY)FBc4 zp*e1KJZ|J`xTl=n=12@ zoXnDZqH39cq%X#*{YGT!nxJ%CrrBFGTwyBx`)Np9A%c2AIY6r>8`q~r;%bJ^tir}$ z(3|e2mBhXK=kqWYtrT6^{BOFE=eb|P^mB@M&=k;^Fo@pqWCYs?}f>yA0R}^uYc|sG&BhO#DQBScUBcLImgWER~Xie z$$%LNl&Nvn$MSTYHca*P#&nFdlVv*wji!=cekQ;sjJ>>;uR@++ndmgrpQ*k ziWaBBuai$(m~10CSJuW-tfREH`Y&9g91M3~74R%R z>+lYTn)c*R?=Ni#Y~QSwa2B;BZe@N|yZ8h~M@xU!Zlje7YL7lIRV3G*{5=otCMW%? zXh+48a?QonB(2Ry-{ZwxSsOgtC12JL1gnA;B4f_^Mxj%QA9;N?YF-MgHfJg=3@wmx z@Dj^jyDZJrJCxtibEicDr(i;$gE?fa%a-lmQgBRFm8TxsMLgCRe%D2dA?>Ue3d{QT z{J^EvwI0}_!=Ub0RiGHho5-LnXT%FMvdQ6ofKgtog|K%WQ%xl<4fJ2TTpvAeF%(L6 z=AHIwYtsN^W2Y}d;NhRId(6pYMJ_;e&ro*de_1?!!hg=P2stQb7yd49xeO$Fz8-42 zkHWS}mmI&pX+S516~Fu4O8j5vzYtUT-+t)|g~9 z4HRle7SpMMlcGd10tPam77!y7A}&oF2Rqf`sw=Tns`B!L+fw5tqT#?$$ zsry^AR_L%=Ixtf1xOK)e(o2QG0r?!p@B5p+vFIZ2OQPu2y_E0YpTo8rZ3oj>U>Jpm zm%d%*-I+q4sOg>^F1nekReT=JoJb&3OXQbT1>>`m(uVKO!AH+1rzR(Cb&&^|eppa3(thA6BCzLtf~JDeR_3klC{$13bI4yllI1-uVX;SsmG zolw||t;-$Q3$c(93$_FiqMzR38JHJN1kl0`}c@Y&J$-{;9G^fW;Q>UH3kSJw~risGQkSK#-Rz|z8jX< zicRzjhSf>?E%>me!14udgKN~fTu{4n3h(Od(7XP4uI4|&%FZK!{M7?+8vs}|>>@w~}5a1ghv_|W$pOdQx!_o;85o{S7w2=fX~%dTPv<82wv1AG< zuoNqqTHBRYEBHhg;g-~qKz|;gxW0{?&+B<2X|8=Q9S#wOddDTnucc1*sjRiqu({_P z%Y(u~Mw~P}J&n>?x6PI%ey@{VYc2gCof0BNXQ`n0;3iIMvD^JQmSGo#E$Um_&~W17 zM(?J~2(zK~k7(^*x?x(FspNO)W-P|L?L^lK6h=1Mp6x#YvEOY^=V!>dfN-3zhoL(Y zKgo8BkBRa>Jjc%i=!)-lrnbZCpowTCA061|g&Kc1(L{OurKTm*`@J#u_NnKfX{46h z-a!z`AIS(eRe^ugJ*ZdOiv!EpA8Yq3oQiCvQK5uc%yx<7aWehMKUCP%JxY017 zlhe}<_AZBRSsJ^9TyCVUHdG^%rHAm`D*Ja|m_hAG9r!xi1lp;IcXJ4&v%#Mt&<6lGdrJr;LuMGj4#Se#oYu={WnS6|GrQakH2_- zG=0r9-hYI$cjvsgRCdfwU92Q*_TDT`wYla^mwYK{Tgp-_#YXtSB+_mjq8+&r8~*Vz zN}KYL{X)2fT1IftVv?&xf61QR+5xl09TyKcw@i(5FlML4OPqw~W5D47xZ5>xL zp82D{4k&ksj%r`=7hQG8oP3+69g@IeBWWGLb8I>3I6U3GE61Da%_3wqlG}QHt2_IH zxK-Tm%r1TlkkWTdF~;-sM*iWar=wefx^IOQ8ieRaJ}b{dOTF}6j`??U+W#mi<$aEC z-;gsRZUI86B+~C84e1e7JVGBfmlS{9 zICEqD;J^AWy;cQQ(9`e0vkG}ORu8bSEAN|d8oEBBNKA~2o8<`bc|p(1CE8@xR4{fr zh^QbTt)82yd`aHyy32N-RJ|iI>p7BhfFy`=1aY_g*wmKm(NB8#BjBG(yM+Q^@qa@ zr_%iwsc$HF&XWu>^?0oxD2Yx;VpH044hp*!zZbzDW&wZMDYtslT5u|dQpwX08L?xe z8W2bt{}@!gAKdbw_xzzPN@yMPD5@0}LnafZkKSySFomdP(E%8#0BZF7uV% zM}bo$6@N|i0)q2Q4f}9aW?rPR_sPr+h~e+RWZ)u-vXwfcS;?4htPMn7nN)kf+$A_Z zqO{8W93k8ajTunX0p< zJ8FrF=};5Ud5Mnlc=dQE`XU1hjEf&Fv0A|3gy`{UTW@*_Z%LXcMj!QPS8{SMMNI-Z zd0K*mkGtgXKHJ|)9Q})IJYToEk?w~EhN)bU`LBiI_&*eYLBG?MHJIv{VebuZ3Le7WRc+(ur)q*%7Mb=%;Au*Bx zp*)p%X+)G1!&;A`hdCt{D-xBy&_ zNZ-$3d63yO9Y5l8Y6bY(681kc)rJe@1uuPVqfPrP@y&reAQ>0g9gdN?xL4USmdmC^ z>09P^~ppu5C697b8aY z?QATpuikU~!^DSE!~3YNqp=2f`bUZICZ)! zM7ESiQ1znhb4R z^RX*mClVr`Vf34{6q~Jb`uj`v87;39GSl4`!FkCWx$xrwewXc+OB_pibeJI?ZK_b< zB(wMhe`L|;6oL_|xl*4Bv#+1HG5NBsxR!q3hqqC&Y?iJ#i++y#xf}}10YeR(KI~|* z$#znmV-=`Zi?3CaVT(ATd;~g&KZ@%tc8f0;Q zfQ>bzK4Qcr|6ufG%H~?Tf+Y_ka{Ald0sQ;B}uP2{fFO2?Tz; zX_hLVV0*v?pQ64T&KlL@zLq6>xFs~jAV*thxBY|EF4 zBFJ*=C&1>j8A4($EIFqepxl}A;jCJfT}<~E=Rdw#&+m(16Nf7#Ymr|>|Agf=&#nR6 zh??8$ODW%t&jqP!grxA4!8;9y1J!d^${ZdQTqN9<>b9=N^W`G+iJptY>I5Ze%UWK z6iHOeB(p*}o_pUk?KY-?+%ib;7z6%0jmL@3w~n~QDC(-8ZU`Rc6->}=PD6{0 zE-db12p=?pSYC(rQHE)8?Q6kr?{FRX4WhXU(@c7l%wgcWsys@3kJ_Bp_l_yO)TuH4 z)ChR^nYsli6jF)-FU-`$BMnD{2LB#yY0Cd7(RoU!>QHKN(rLr zv{YJQr3BKFlX_~MB&gaSry8e+T>6^*v>zoY90rB&xEdiJ+Bv7KKar9+O#d@Ttx!KH zr6TXaqIfxj9xV+FYzRRP=NgOK^l~L#HY`l9LV^;+u1&A^@z^V00nOicL7y89U3A|z z0>mT=Ap5^)RIE~QL@G!V%IAdpl3KY8b5i#MXR-CQ1-)c(MdMv#%RW zu}WlPkw3+gIX{Vjz7>LJ!Uq89{a)*the7Hf-G1X%@1y|WQClnQE7{y()@tbozp7at z<3o}g8tDyHBULoPC#9Jl^B<3_)F!Ps{bfsRi4_!Mk9n);iY%fTdvSfpo0bb-eqPyi zKmMdnJnVy`*hJKs@&xkUk%t?UY>KevxBbnRJF|w|vfOWadyPeWp7C>kSS^{BklH2a zj`6`Vb}$k*eW)!xO!_!gR#@Q1c-DKTc|Ed=UX6>`x@#;@w&c>uT;H?kxVpChts3Z% zlVMYWng0EK0s5H#*kjE2CfNX6_HTc)G(L;u``-S!UV---O)HcZ8WTyWDEBDBA_ylw z;AQSRc!s~oFMFUo#wZRy6Zv?n``y#T=Zm3|oBV$Psy9@%iq`r^Ib)XMwRcVEb_E<5 z@M*u|e+ZRt`wiLBBt@;v`-K|TG*o}#^Vo}W_*-x7^WNVBKp{3Gi#&Dyjc^|1?0XZYJUHr9^vuF ztCM7-QDxQ>5XR@_0SMSoy%Q|#>0!RExzRH&_AyRw-*#9m-7CK+r;LWE9kL4z5)4Lj<>l^7!RnxYpfV)!lxtiJ!?kcaR zh31mgm;?q!Q+mQ(mfXto(({GUQT`hEX+~m&gfE%Gkl4UxB{HQ8x?L{f8i%5L!aoBS zzKXO1yD_A=%}f4#ysjDSHVXL{@7;p0!ij#t>+DG$FQ#fp4dTsyDjLkb zS`&61{ZzF!nEb&YmLM74{=F%oc=49M(xToVEz_BgMZ|=^6n9dT ziwa*;^$VlRO~lJeAzUN z`Mqy(-TX&7>6IMGPIy=T_nj5~gG#8Ts6~5!ZVrU!oBH)>K{G>r7`m(76z#gXAuE@$ zb763*jPmTJ@XshW{J0&m(Qy|=?DBqbGs6b1R=dZ|@+DUZb(~B^k(C*m3z*YlfGexs zl&d6VM%1joGvoFS>hs1Li;NbLx2JkduDkQ?c*Tp~NEroI7Y*VrhfP9-V-5b$;v+J8 zBnjMx+`?QqdK2~$%f7i&wab{?Q5O0#;!YB7(qE%O1`Z~u?3It|HeX&#~MWF0e z&D{;!5W`Eq=dg_`~(k73z@bq87!(V)0a@bX|vQ5ZxbI|pz z&rA-^)9VqN@cAlBrz0LSUo1M1+9{jfKw0U(b2inMC@ekkhgvG{+Y*nVpYWS`cx~v9 z3ioCo`C9&X`7Wzpoza3T1xS@U$&+ViUfb+X*0Z=DZc8`ebCZ6(cGEYAq?Z zJ}H@^%CkEnyo?O~{37kZC7phRRve8(Q*B0urx_nd=aR_GI-Y~ zRSp@cA6AWSDVch^B(XFjZYH`x6(FpRNnjzNlwF2yFK*k(&?CiJudof<8{D`(U0)>1 zZo=JdNN^Gh9aWK*LZy8fzos_)nC;nOw2(yR+ojUC&OXE$siR+-RS3k#!o^A6Z=rsz zXehAl1cNYBO={{ZqoWwDujFH^xp59kDqx$I>h9Q_fPoB6=G-ScW`qaPhb#?-&VXqK zu8J_eR=;aYY}&LKFqAEt-gQ_->xAGJV9<)xE|j`S;?IgTe|%y`> zR$AaM3q4kFl)+E6uO=6}kf$G^iIBG_O=AncEFiK1kN`dvxX$fIOSS8KRS;KS^I1@f z68y&~kXqV3u}mU>JPA7%CaI*0Yc%fA&@tBaZHiG=sm2c<)R0I#$%mbv97z1Wm%a*)@;$d1`@j)wAeP2EmH1p7D@#iP%>qU;>dM_!N0{!X$;1DlG_@k>|uW3q-| z27l_Nhlxw^we)DBPP|14yWAU}MC-Zbb$-{VFGG&C_$=B8ukg2JGbQJK%lM?VTl>TP zgm1wOzZ ztwAijF)FXaZ`WhNOe!a3mY?u#dNO{Dw4PBV%%x?q3&Ld~FEO_b7xCQ?zd>%4a>6DKoOApMxJ$=%1iWT#FU152JU?&?%x|CYo21$=Ft$)CAC4EU7j*Z) z3GY`1CPUwNPH7*OcwvCs6J_f$Igk1aY%y}=PE?-nDfJ>CZ4;J(0kpi>Fis!Qh}bt@N`qI;?ClWhXi^??KNi#~r=-A_9P)GmC$YRCQ^Ce2tifezcg zKkpQC2oKB~!vP+N>n>r`8eh(eH=kLMCBN~vM?3sFUKi8Nemsv8K&* zI5UUd%A03aiQIKFTh4(aLKket&I+cc-mj<4mLID+tCg>knlwDtp8PU}B`FFW<3?BM z$?DZJO}&x&{5nEyzh$$XEG(_d_?-e7`(8n7bA=7hwdAi*)z^_TAM%In#F^{t2!7); z{zuIl_LfFo{@b!o`a1Y4Jf4NdJUf)hk&AmeHfpf6$1dEKx5DkT zP#*_P zC7Fx$)zeD82tqC3a&PSz@SxjXps8>}`4RNl!FLPOrA(;h;pjWOXA$+a-9i`8E>S&? z&QAPh0=N4uc6R8I+dDQ{TSu{AV|+OAx{#yM=pzX?xJmZfUV>_M|F?N(ra1k}(bsG~;rwgfs@-P| z!+Xnwx&5?W1#iW&|HRg&O_89TkLX8KUZUqF`q6N! z4;Uw+fryOtt(l!S*Y0y3f!%S0>_#drU#F-WM@V+99UG0jbi2<(IA%p(v(;bkmp;1x zx_hPGYTKQnjrv*41%VDV8lLz}?#48$nm>|upO)|(iS|&)j7K z4X%G6cux({F3gtrYlZ$c4sJifCup8uIZ9A+dWYkZt4KtWBAcJshBoh@`nI5i|6TP~ zBeDj|@^cZroaQ`qTQ5f;fJ{n=6REIPK@*wP6oD>w5cZ46T3pB1*{t~LMie|$#BgV% ztW3?ocIJVaUi!3eB5fFb)t)YI!=kzeTZSBL!Usyyq3V#uq$!!7|Mam#$)`Qw{s*m; z0dys+r*)v4g~uqAj7N9N6xiE8hhG1iNK*TFLx)lQ@nuc#u1?8JP*t}1r_ULJ)3krV zloPglh$GpD{nsL^tac4me+G`LJlH0mm{?Ay8E^zh_e|??wHf1-OPHb*0@MfqHljh8 zZcTKf^sP$<&`Z%BcN2Emn}G`^E~>BHNx*l;b5fZ>Hh;L zDY68r{yP)l=ZR1EOOP^b5) zo}cNlByhee!r1cb3?|~Yo-bny$CekXH75igI7|lD*oDoGvJ1w|EwihUz5hv}ZisI5 zo?0n>SaCzaDcY%V-4^}evdUkXS>YPNyK|t-!ic`Gw^KYGqp+uHvD$M+@0?)tM^vZc zlwy-q0L+56y)pTmp{p(g_G`s+wk!z<<4&<$tE_V?Yh_dF%vx|~lNCzgud>Xp?a~GJ zu~9lq%NIK4*fh&6>@|rvt-a`PX9``b>yap%2McGaK6r%i^W;WF(cVTle-pXL?iJt9oQi!wYO3qWD|5Q?&VOJO4S(BYi6{F>3KcJqma;ar z`;(GHesTKel9GKsEvO>IVVbrTRacS;ug;CBrC<)tESk(5nAl=X@KGiMt;F(G3bjyw z5&`x)?taQ_b-k`H6OaUE#-gwUOX&{nvRf>b^d0-5G)cd;XWt$48y;mJkq-lw8nA5e z#T!CCK(i|u8HCTjr_~18BhICv(UwTC44kgyG3ZG&)yQfSl;w3N%peud>RYSq(3hW0 zA>dYhSD2A#EupVuhqzKbliOIT+3{r@C{^#Cpgl|0*S1P6ewEa8TXr{2<^Gy3cc=|$ z_@eMV=3{2_JBvtNV3)&inxYCO8+?VXN;Cbz6}5TSWoEQ7;JnlWedXN}YB1)J)k83? zr$SHtXI59py-b^zGUK*qk&Xj5)(%N{Mh)#5E(m`9LmTEDe88qH7BRRp!8%{w*97?3 zhkO(8)qi5r9k2lH3@VhRxs)g0SgHixonMkh{$d+F85%B>1blt=0oYN2-_}jVFYM1&YGCh zBz!+*v@M&{GEZ$bkiX>X+-?r;woQX%R9Cww70!-EVI#TDrFN;S7^Me8-3Sq0KvCqO z$tkObQ3cBHavzIDE`Uqz#=Xv_SG_?1u<9HXApD|yQT)30I(&UCRVy6$bp|Eh@92zT zPTaP!L(7FDH5x67B0udS2VdVx)^30P7H@@aFk$pOU*uwCmXE%F1x;!0B8hO3Unpg-`?o{mSG!kjvDOGAiiokoASnw z*HDw$?%w>=4AHNfF2DS2w~67E-XmVEX}G3JQathkhu-yBkH7cze<{ixAM7t2d*E}{ zD&3yiHr?33M3s6de@K4sS7SIvt=Ufn86iiUk4~t7$j!4Y-F$5X@tIzo%pJaZzT#sf z8Ze|Y6!_C;&L2bdiWv^pb}I{Scvii@rlU|mO!s8Lr%jOY?s(NC0;YimmjkTxNQyDX9K$*!C7^dx4yRL@rMK`(Y1cwkyrWQw*8^gX

    x8^djw2nLZ(;SJHq2$JX!v^6!>>@(<-w_g)DSYs(xk=WE>j102 zINiV(d7lg@x=8w}^}2VvBwZ9%Y^L*OEW&C5W-6}w?0a$Phltv$8$k5BN#|? z1kxNloD-TCJh&8M)_euLNdR)z2W=p}YCT?PY3+VZkszY=4ra_>as#JgXPIKmL(>5} z#D(j=dr=?P-rTDD*h76D>TV_TA#}4hg~vWuj}!zNq5KTLB$4e)Xb`obuFNI70lgVZ zwkF{}q6qoq=&WP4U|2IqX-RP07Pvg-OdX}#Hj%g0n{$2QL5o_e3QhGX(%gbCKWkH{ z#Y3`S|MS)Qks0&~$+SY!_*JG5SEVT(-^9Z1d#~dYl)MnCsT)k8X8c+o(H>H8y?ES} z;W=obyL#g*7=~W^L;3_;!rSBs)9u(I*!?e~sxK)l2c)?t{NDQeKgrBIqud38Fzjv> zPonng)hL4PCNPu(F*jh8a=m?y`d$H^6auxkILLS#KBR`+S=m{rfTXL!fYvR(@qskD zG(a=YHm8Ob`DClKkQO!rZ|m@lqh=_$wwi;=YNjveKgkE=wZR?1@-M0=W!VBwm)*Ii zA4Q`TF+dO~@#7)7w-I=xso3El{N<4gOb`9$D6^br*;O>K-wA-f|G!A%SAP(4;}of3 ze!um9f;@c_1jlpk(;v;vA8TR8_il8mx2?e2ytA&Q-Va4zwi3v4=l@4wZ;R8Pj*IcL zD!&tLNuxSs0-BrcZTFvVxDX~~&C`?0^~&ouOWrz^fAYXLVs zC7H=6`rM+GWyX7`Abylx6E~z(VnYY~QrV1omb)ZaM3P3{k^DAJN<)5i#&P!z(|4Du zi3l$h^q4OSl^YQ(jDi-4>(v1dUP0{26pFc3mP|ghrK}>~FUP%N?gR(!@@&nNy3tqc z9Yt-v&Fl{h&fSqH)zB_(WZ5*v7PNmv3J;Cdiud84U@c}qaCRt|J#4-n*+DH8TGe|U%9MC+t5ZwR9+hn8(B4|PIwz}xOK7s8jQ zp0CA@-ZqL&cFU4_XEz_|-I89eaW6YWuaZUlAX1qC zu}hS*&GzvUnktKMw>DFzf2bQ1Cfee=cOjxB*F3I-HcYYi*moB1F z0ogk4*_7p_8d!LAnIt@ z(zK0tr>pU|t6APF6HWX8W0p)$1aC;DV6_p*QKW;#-cM3oBFwF6Zkm|$GpkrCcnk9? z`MqC?8r?vK$nBBtNeHrkrzB_xlR`k9hs6adgepGwMM=YfDh)V3_A6>58fgpYYzpR4}-cUKomwF{x z!0hk)+UGQp$6giHqvi_TJWCwcybH3qsc7C9MU86s62b8^KD1LJq(?dv9V~w0i?7Oc z6ECZSh}Sp*QMW#$Q`Q=J?clc>M&ECLtvtL2cyEfSNC=m#rIKQ3&auf}>dGF3290g= zn=M}H=qvpEsy--2z%34=U~XgimLpf|=7i{faot8Sx5>%k^|NiJBk&D3F%Na6R@6p? zBT#5`MP~Q9edPip*ZrQZ$oj%nlx1v64Ane8d}WgCfRUB6OxH@DzZbf}1E245>(9s? zx}YAq8IbsH;yrO;GSLALaR=8?p%8{&T{hCDd>_%hM+Uh;T0jT5CR4AUv!$lqpTZN+ zW}o#AlO+9aMBel3=32p# zZoHkyYRw8@Nx;n=gjINQqlxjy`&>||al_(|9H&pxQr2=inF7sSvAe(E*H4WccF7sT z_(Lwx5n?__Upg!43Z`6d1H~-08j2bGS}xweI{lABV>0utES1~XzUHX!vDgxBdsvoq zX=}S8p1%1@_j=|SE86f*wtN^b76sj9UKQ(t{F)Vydy#UC&PYxE6FYi2fBMvh-Ex(7 znh2C5=Wk6=Z$oNrHa5B`2Op*oqPX@9YYonBb(vN@W;B~=wgR!|dwBU;vg7Qx7MKN< z_o*HH1RJp%c6fECrKoxkwL`TN=siKL^q^=I0 zpr9(`Ux75%Cs$N;Pmwfc-*4ZkGPM(G6M|fw9d6JT_sAPMZ?|emrK9Ro9347zwN(W{>?}HK#;zv&bzy= z|2TZ>$J+iO+ZWkn;;+osD=aAe!6D3>Bit%^k!@K!nfJSuKe7CHK%%O3TIFM>rHy?Y zGbl>4t#N6XU=ghk;C7&4bDz<5A|ENemD*pYJvC;pMdh)sdw)^ zQK=WAhoLUD$45W;+1=LMpLp^ZA)R833%`qS8-?rCF8!qjLzf&<6g3p3{Js8fyH1_Z z9bS+03You|$P*ixy;6_=qk19p%-7;v^sLnawQ$;QMYZG7cjdC{A3Q**_N8CX;lGPh zIOu7d9AVQTB73^PjC^2^1{W|tyF6D@yZZU^)^2}d*Tx|rGkc$YLg!7Sna!w|^Q{BA zgtrE3K45#pP+8>cYkD!4cw5@DRqAhM-c`iRieFehIwja{sRiL2c9q6|QUowLI~70m z;`B)l88Q(}i^q}|ih_ig@dfMXQpB`};I8R<8l4WxB5rr$XI=Df#yv2vAy%FH;~Jk;*?t<=cZ<0?8$6!>@KYd!nr;(5PYesU=ieFj+^gT{9v0sqpdDeFF75U z&=~2JNx8*(`b2sE)1~o*6Qk$C?)ct7_-8ZzTRXD;t(?v}XQNVC0Wiod?jht%&8bEL zX^TaY=tK9*F@!8u7=_)a^w~Y=NH^s*mZUDc^5;8hZ0K*D7Pr34)~1;7k^b*&IyvmT zY^pQucblwHbC>yeA0xG0ewyfFhQwSd&i4G~oO>|~ulf)r$ouSx1WvW83&4<8YEW1p z^)k%o3M&E1@}0qQb??9BVd{YLh)B{&)vQ!rSqy#y;Ez-P$|YHwruO59zI+{}&CgqA zD{|4N%46ohDDb-arduB1W0uReXxd*EG<=*hpjTAlMwH0&%a|sfa1@|W8srzzP|E2^ z`{?TSYW|~iWUfvN0R2aeFMS%5hrrsB(U4aJ=WiiAEQUhZ(6Ut+UoM}cEI|}1=$jS) z9gYvqaZZXG(fF#$t)@gIqYj)y?kv}w?sX*(2)my>cNA<^-DlHA?!r+L-nPw4s!n=* zPDj}2+V3uaMGCh4PS??V*!kqM_OKlyjo#DglO@+QYWBb`dOs?gj}<-bSZnei6AdV- z3+e55$6!y5K0Jdup9*$Bm!IDt;_zmV?*6&&c3EIIp?xa8{`hd^i1dC<3&*xzRQGo* zRJo?!PafpNBNw)Fh+T6&N3-&*yBbM6^w>wT>&7J>${xpkp}3MJxPDD=MO$ieT9@5Q zV&CKoNv&dK*IntG;_*)YKpFga9R@-oi5n2kk9z7t;M+gv68Et1UibMn;k ztF;YzAM{F})3w%T%KV`(!BR`dXRTo@xBrxPfuE$rHjKiLVhtV|refxAxiYpm@(nJy z$BUr0B%nKW?7rW0yms(*;vyDhp=WK3ysJ>n*ce_d$zr9sVkpbz?v^86tw?==xGm$Z zH)Iq$ED|m)Ae4jg0jJR#NL7rX$6BK+0%u6(WPJ$rx5S4E83#7268LZNrhoH(s}hr} zQE`yA$h7AU-hUxfTzSma0X@BczqIFCc!M#DfP1A)urVqO8V0! zZ-02MpenLDAsH3Z{YBaDgBuQBuMeTO8{fM5%^zoi5izN&;N{>Yr1ObU3hK>j$Kpwm zV)EF?sCSfh>pDX|<2>KuKyq13GMQ!$d1Zi1^GvVfl5l2quN{yLOgvv(lO|~RwRSZA z^M3VEOSx}bi2RAr!cj%Ee(yI9|I-N|+K$j+Se4Am;0~};@ajX0$7f&y^D|V-%`c+C zy!DMfQzfNe4Fckx9) zlObI)-Qd+9-k(WsvprCAXD=>kpYM`<69fmzVo_aL3`JKm(;DbQ31g+J%e9?7p3`J!2>mgIa45W3M9lBDr zIpMp!ak+&Z_$BD@D9n}E%1;;)^&47`F-e)Psxxb@_0+rj07ax~lkZVA=T4R-<$W5? zdjGJo_%O)xx2K+xz}6mq>zgjiy8oZ-CuXCE#cwPlt1(n2 z>Tjr-2{wf3*NJnZLPcjse4|QbS~OH}EH*ktdP3sUeJ@gj*XE?iiHB4&^qK=2im{ja z70X2F>?;Qr{#D0$2Q@3t`%zmUKP=_sG`{}yb@LI1E4nrRV9;dpy&H!JZRobsY~?_)i6g%@u;8wyk2tp7fu@1?-xzm-2&|F<*+JpcGQ z=+*^xo3r5oydMJw{~svNS7W=oKY}lpLR+6q!4Ioj9-fAycW5G&H_~HuO7o>ByCgQ)k`G4FxN#??maEAs`8e#-ab_GS=fAo- z;$e~e#*$duTl6oy6nDlVN~OAlFFRjki2POZs79hTF90Jwm+t}|-E<`(oOe4k4e;t< zzcbNs{07I{c7*W7ez{J@&drbI{CL5ltz?j~>Ue2K5rbvzJ{!c^SVn&P?Gr)CkWLk~ zP=#D<_$1bLbA>59T0fQw@AF=lZuFfGNbb(ate;F?;z}vo*Q#MU4-8rP2jtU4!Rb$!yisInvhmFLxJUDyHH_3W?@ zH7S$7^WIYxx-VHZy@%iB6o?L~?zQbCk^rhn9evWs`rnwy$+C{XTVN?|r&86p2!rLN z#d7aMh*V8f_%CHlka7o>`nD5~;Ka3t&Bb;M+@FS+qK8HMdSgtTliVez$^O%SYUJjR)dIPb=*TG1*R~B0 z^1wjy`AQzi!QZ}LmCx2>h?HhaY&A&g{jr<1XdJQRyK}TuZp(r-gI*O0rE4;LRx!ZEt?K&m+OTWCQMabKdT{%Xne3ftfEua|YlO=r$@Gxugn@6Gg zqu!9+*4K4)RNVR3A5S&JECl zc?X!@iN1tDt@7N98~@M|i_CV^g;qD{5e~eoviDB|lPu5bLJ|joQ1pcnq#(2EkWYi^ zHtlTQGL7K90rgMzljio8kkCgfzx*;*MIG)Itr(v_?+Wr9~EE#dd7*OgPBV z8<=2)7n?fu&|I=A+SMtz+ajw5#5J#>hGI|r8thrZ7MwaI;j&GK0*Up;O{FRkTI%O+lU{$I^E`$49PU&n;+6!^@?sI)ybCICNz}N8hDWil^_34W0r_glb95#tgnC zv)^ok@N!r9zZyfIu6NR}V1Gp|wnKxqo=m)tm#a&g+A04Z;N?Cz$G`6gzC(=qBg+?t z@(8uKL|8o7g(-wyQ$9?=+x}k&He9bre1M*pjgTb*Q0kPbqmp8|!BFYoVY0nO(qZhAfC9L+a;tx)j0DN*ssccEID2``?@S zbXGCk$+*g`_i`KAZR^00vI6esm01^2*%BQf4{~a^8bLY43M%?zo7(ZG2VUkHws&$q zbk}61FG^LlpqcsgS;8GDZ{A6hIM!O-4_+jjYv3qkem^1l6&A}oev~<7dM|!++^xpnN8C@c&#H-HJ*l~;R$u_UFT#mCDv$ zc?o9n6hpQHZ93G|&*rOc{K5m+qHEHcBqLLb7a7up?&ha^w@*||&PS~d2W{dT!8hec z)On3$Q%hrzmp!UA1hFd6rv2&PGQ5;hrJZri%H6av@92ju9VsBnpGHfiY1@$x=w#>P zS0u%(*ej7G%Dx<{34YtFx)6sc3tYh#*!TuN`9aVq%Z-*Qw8y7vW-@5)JXqucOl2nW zD~GE6v-J0pMh}w@H=3Xyrg@fcM>X6_JGJ*Z+a8N`hT>Bh0#5C`n-jo0>mbV%v~wJi z`#@TsQyeVp;~(*Q>>{Um1mM#bPF0q@Dv&C4vqMmtssD@muV)f*LApflObUmt`gVM_ zy^P5mJ#i|1{IwpCbl%cMA5PbGmJ=vAqvREJ^%_e>%_FsyFhNXVx>v{IHH=qKEL;?4 z`*pc)KgAAdOLh4EEx;>GGSii>Az-=O5cB9Ek>%n95v?LU&__X>FpPQG4B`!uI`pq* z&FbBf#!40+u&}79=!`UH9r&93zIdOqZy=5N`y>7ppdo!|br44qBu0hXBSf!cV+Y}x zfGfEQJV7rW9Y0veel$`8L0A%#8X^oY-4$fqyjBa`5c z?{o-#+l!;#+{V6m+d#lEy_#tK9e5+7ZkPsLYOL8wK$i_}VSfQ*E*EAzTZ0p1vpCgB zwXl={czmVs8^k@?yAYuKDYI_LU7Y4tX_s4>M3`{QYSbSO=6W*SNLkjXSKwW44mL(s z*la*mDxHnWP^5bL)z42{X~3+aBsuj5H3^QiOR`{G$OoxlM1H z1pIBZTTKs_!F;GdR{cat#Fj5;^va@DGK}A&a7+I#p(eNF~mBAkar+sdmg_WOF#CArJ!byynDh_9o z;Q~LOc03ZS&(dk)LJqik(aN#n`@gTWBt(d)Lb4Me9+GI&vkv0Gy z*Glbt>@-3?vi7-D*RT{C?!6|Nz0Tf5vKfcX>g)8{pRmfp+Rnifk?0^rN@6nCO`*30 zIgRYJ0G)Y#N@t8Ihh;^td}li(j`Y_{Qi+v0;qbqgGW0hyGg>)5n^M%^p)-X$oRDE2 zgJC*W`NA*tA{*b{zdv@1!?W3^0}2G)fEiyu7k(Lxc93)2pd$0> zr21nV4`V{Fpnkp0W7tE{@S!R>BpKp>B^V5Um2edC4BiX{EKm~=K?DZR$(J?^@~K@8 zd<^f4SY0iz#@-Ds@kWhm15MW3d69|K_)Nizf0_qFi;~XC5NLm;Ur~JVU^SybKHD@3 zcnox&I71#dI~{FUai?*l3avX zT2y-@bLt8(&-=oG0fxQs13;1F5X$(NB1|;@)Yj}p{fO^rXczJN-F1Fp~fbiwDx3vHqY5b5oB-0~xj%QM*WCC3zYHBXg?ceVm4oZua z20u;DC&|oIig6@1ab}8P7Ws$`DE+7s{NqFD*Z*+F{{LY?cjJc~@gDg;L^o&+b)^nE zG#IPDFVpHOq4E8HD;jQZd0>~~eOjSM@JI0lP{^+lka6FiqX*UhFnLL#p0)p##iuqT zhV@1w#n_+kajNrJeCzjwJ`$y?>0U6KbHOA}Kkd{LD{1KjkpGD3*r6|3DW~GiNI8GM z^)!~z|B(z`{PsPYB*d@S(GhILb0U{nwrEhQM4k23IeK5{(Y07R-5}w7g!nG6)JZ!w zf=aT$6vf0%`yEN1Wg#_SVw2Fft+R_q(GBwskq0n2$o(Jd4>9gyA0Q()B3)W60!rmm#iw3T{Q7J%V+jQk& zp`)?ir0fMYGk2e!JeM~S!>cBb!rvYFMP~iAP;b=$F9F`ncds z$(wkX#tHLr=QNDdpC0STb}@iNX^(O-%L~Ep#i|!5h@*EJKve=6@S%;C ziW18?Z0Zj1SZA$i{nI5SW{8P%h%76q1Uf&3H~orJcZnat%&KjEw?MKvQNjEa?3X(W zfh@+W_6ctFf0Ku>xio{a9~wjQM?ShGoOB#hcE6#t%kLfNnmaRXnk;stFaACHph5_F z_{51i`NK1btHP>WU%!O09ICWN9W48|K$soV!(^b&B2M0xeFH{jZ|dC0CUX2N^ok1X zIw?#xz2*{YK97(KC|mF3oD=?pCJ35qhJGr*FH99B7N8|j0Z*5NbR2)Y;h5(*z@ZXQSTVLTRBBYi`ttNqfn@uje%c5(<|t?AesT0$|3<$CoTiYwI5 z7B|<;jNTbJL%}Aj?F7*{32_QMwn?qx)7dYG4H)Cce8>8ICB>;^-aXM{4Cl9hW>z*` zDu3aqXxbHge2VIVew!CL^Xe@nPL+r@$FAeGZ3<#P5#w`{-N^5}WJ|ZMyl1x|df~8@ z`tU?85o|5o%VCM{OM;ge%}IM#TeCF#To(HHKSrQC&2BOamaNNXuaGWUSEKsAU^xlk zoH!5lq|pEfB*FkEDC`j0qPhF41YIX7KS4k|@< zCMK7-r#?S3*=fQzn$IH?3QXvAr^Z)dcE?3CXix#9O)Pu9-j}xTZu&XxoBVkfm3j_Q zq-u@#x;ihI-n$z}#vF;BsSrDi2Jlc3Te}J+wk+el(8cZJw+_{APAGmm;e}V&f8tau zz>M7mH1Y}GTGu<4F^|JIV6J9e+2yH~fzr(M09W|Pl3-_6-jxZq^{nwWrBDMafoyK` z+G8yRSP9W|*c*Q0^oP{mxWkSy*THJmeWxOLuA(Iamm%9Ik#vdhf@F7r(eo?kv=7eh zQY`_E6kRvPlQ|;~0u8{G{;z3GxxsY4g@gX4=`qk zKMaab5I+pIkAfcZ+9{zSPyI!s2Ei+g*-+#pkaw^neZ3-oD!itha)5z4z-*0#NEH>z zdMoJ)lWsaa(Nyd5$((NCr#j$oac6c7D_aDF49bvl&HR1oy^_!v5RqUKBH|CWBXUQ- z3X`nBzjV=;0dnW&m0gA=t&L>_Uk0|#qe3Vhf+~h;I+iUy7D{fa$#JtPLvO$&;bWI+I^;$t%1fk$e8nT&Ie>&YGNyhe3@Q2N4hvmtiSSNOP)N z&s6*wHwI^2_28A763NSecce@U>QfTk#zD2BaJo{MtP0W?adK2z--^MO{~{3SP%8na z3b|-%T|_#8j|N|FL8W(>`qbOB0R{%=D;*IyqFLw2A;2J`%>@3 z^K*#&iwRVgHz}&_mZ3czqj_?D$pg#o&Y%D9IjI9f1ib* zU0uID66{`d?NW?lyp5=iOd8+?>(RY!m35oEN9-oHickLcwV>qP5JYdsG6BJaumyfb zl*X|P(Qi}i(#IdRCVncUF~;Xg-&qKS8&3XLL#S=|J63ZpPH z(RTX;J>a+L_X@iCsXpDmXa14Ur>wc%)a>qm3kOUWh8aQSwD9R+h`7PL83_L{3kk&WbL4l5-m;u`NPa;-d~v{2gZ@ zy6te+0JAxot&q&SZ%R&7*3Q~KRe_MkCV%{~Ic~X#v+($nZc!(i)7eZ!q%ua9ixDFL zy#S~lpdRU&FHWOkM~c^GCqMxgE74mj`Q^(AP$FolebzS$C9x6Eomf*ig4R|8aR8YH zu*bj-Kby1C4`c#Qu2aR4e7IF4v5IWjN!5a+RtQZHqBL)SKdBsDGr}O}Y2~Lg*YHgGtoKt*`jT@M)i%ytAz`Xw1vLeQj&VNa z`ojTdCYoA64PWkIj*#X{ni~Mtr_jm)+N(Lxuwao>tdR?rXUm@mG3r@0pJ#BiF-L^4 zH}AgI)x%ZB7^A_`HCeETz-7mbky~=pWWQ;q%yJc>R8~BiZRaIpf7^&7B!~|T{=WNa zyN?=ax5z^ngIjv*(hC&(o~R<`ZtHk@I+}$q+>=+WnXmI$rDU z`@wTMKqVrFWKN$=C;w#mt{=p`PqZG={wYfCf$k#_&*;swKA@{Y9C3+IgfRp69OE;x zgO4aJF;vF4B?uvdI9de2;rT;a!o2#*b7Ez^nw(qZpB{&@LJ-_Sgup%#1-~kE6baH# zZ2>MDJ&t+_JFB``_0#1FPvAfhT=h2FzzA7{JGFzSJYfQ)qMtFQ z1;j;Iz`tI&MT00H+&?5iZyw4= zImymK1nZ!l5%mXjn&PmY@7NY?F8oBjn4dJ`my~GrH=*>U?pvUW4790zZ}ZE4f$mp2 zz3uh-{&#VwBx(5^I>EiNz+5-+vdg|d!3)LvT%3JEXRB#gQWLw$MITo#2uIT-2^j`Y zjhI&J)&xs=)_$oSKIPqkh2za2nMUEmJ;$>+l@2h8!$?Cfk|HwL`ZfE*2y+7Cd*-j< z-xE$zr{V>dvtz)vghzV$HEfBP!?Slw{;(YtZr$G=c2YgNM`+soc{S<{@xYch@79g1 zrva>1%ins}w>|5tR$TBzpSuf*-P*W!o>Rgf=N<1K8|3UDJe?+9^2iG>6_|#P1h(F-Yx;FTw5>@$2w&)zHpomB^+e0B+YS43I(776zV3h6kIeye4Ui4cdqd|#!88RH* zb7J8xwCDEvm5J^vZPHy}r-?OPux0l6Nv6+->hWsurl!IPJa7(*Z|%w%;oPXv5>(pM z9`*7E9e|f%z)w(yN1^>QzQ^$$8AgEjc&NRW)XT#WY`N51nFam2u1u))%yJ@QIXBnq z=f6_g;M@>eavd*Bth$RXzaGFmbhrVx>h%07Dyxgis*T8wl<_zFn}EG7X)id|m^om) z5fF{S&asn3EIe~+m!b2-s%=Z*m7}P42BT=gB!`e#d#5n@1u=@^$~A!5@Yr>)573a^EV3z zvcKST?BvZWR0i;7fY{k~O0Es72?}WYkLpFY3}>!|7fY%Dd&~H0c%YnUy=qOlw zbarazgTA7N!QsX(wR`c==Wj3B{|nG&i$}xWz4GKt7{s38#16atwxA%O|J}h`4Weez z|6-rZStI0PZ|8O0B#GilEa+y|fuh~jX!HIjTLSno8%X&MfNP!b_DlbTlaIMyFogeD z5R?h%S7NhFst-xUiIzh6LkVWMBZDPVW=+BQNM6yM!!MDj`d{B&wI7My$cyoG#c*pZ zA3Z~iiw`xXPX5fw7k1(;GxtLI`He7sVl_!eim{_6s&J6f^|hjjrxs5en412a-( z1+Ov>$YpbeMpJM85%bd9&1haL6wE{mOAHEw3hbHR9F`eFQA!Ge(3{Gu3DJ#2#w*`p_!B_vbZ_c7oK9c#+ljT4i(JNPMgGg(x*SBXK`&*wkxG0;EH< zW)#ozf(&j9KSEB;-|N|*wD&Rv{X&SZT2WE?ibL;tXj*EPiiNH{~X~KWH(@*kALxc-@b`-H34O~&X05ZO# z%_8Zk|~rV};NoJU(sIy8_V{iInh zv9Wk)I)D3w{L;*}?D9_jJ>^S8^;A}-L5yIP*-#_6sgP?fRI!UYn_EW|lBGo!ebm}s zjk@dSt?u!)WY)1QYbBFfB7&v_{IW4W)5tTIl|^Toj3tHqeX0^KLoh+ioSJQ3J=b^k zlj6dHBRZZczx{UbK_G7$M^}TeE*v-i@Lu$Y(q#Yn-s1}CG1a#^%DuM^S|F4V=f9kE zPSsS`mau8#_47u0fj}I?j%=z-otKFnll1Wk&(^Xf#KR~x6A3u)0)s3oc+~NG^SKKz z=qGUU%TDNu+?fZBr6Uq8an^Hm78ofAx0~FN+AR{+F0ze)E5N4vgSh^0`X13R&$q}& z`zPHK$rFO>gXpK|hgXl)HXc^0#?rfu+~;SuXWT=n7k|~4%M-E{4y&~lD|xh=!H7Fv zYvAKF#RBbNRuov*7 zmNMZ^gV-c-U|>;ci2JXB*5_j1JL1SFdJf+Y47z4c+_{{V?i*q_*SrNkkOCsQ!Z@@g zFb<6~JeUUvjU{#R@oFdlu6C>>fdo$(GbTJAwIy*UlkW5zqMx&D&+;uCzuI9<^w13o zemAl4R7%}@`5xkb(HXmv3=DNgTs|Y4B&gnE4VUrKO#H2Sd}O>_V)XBDA^gy|)~^^0 z<+sWrfBpH#0XsPl{3$n~@YT@FV5V&!%>VJn_p`k&az~+}Zw}0uOoC#H;5@7Ecd?0U z_e{ILO!|s#%(bTSoIngXgcmU@#@?q;4U)=gY64QO*-bBYvK=P`-I}Q=tm#TwpwC+a zH`~Jm3kM#v(&*pqpZEAmcR=+6ubP7B^!s2QGqQGWKBAe8nR`R}&r2G)&jB)z1oaW( zKb+>5RbeN08s$O2U9KrwhJ=*?{N4i)zlgs^LxxHaj*vmhp6k~q@Ef@kxv6qzZRoDw zp}W5KP@$gLSBdvPS=>qx`6+D2%$mOKh4E|~Y?HtO<_kh_v!JgS?j5Qp&x`7{6>Y(o z^aSs~Dc{}DGBm`s-m6wqQ#tek_tEZdz-PNiOSj1HKMCTH@_jj1z88EV_XX^=WRrdfWe=XIdS8 zpE|l<6V`dy2+Qr*j(@d!b4pcduChX8q~>5`S(@BL3{l7TJD}yYHz&L}WtKHG`kQ|B z>acxl+M)%Pjl>#QCAR1-o_hH(6sz&o$fxwcY}gmQr9V^Um-}K{_>S4@izCP#VsW-j zCS&Y^&fnV{uWF!?x$hJc{u20Y3+r!#JNk35$N;XTLeuKtiE87aUo@k!4Ztn z=D(~NXJSYERaeK>#)gx#W}}9CU!k31wW(sA;{Bd>i&GRfpdKf}B7M0qn?>4Xe~Wz} zYvcKpYn1Q5QHtcCxbE`l=LpsvuAR}^_Z7nYuMRA1d!G&5i@Q=MpAvPY>7siuL@^#m zi+5@gyL3E7Sr#djIR)wY&0STWoSMG1M2ckS`wRIIGf72#Jzy|l&^4}BMVjn6JcV`o zy2KDog&n3Ud0EE$i`@jJ%N+?9<*ET6l{;|rVq}PrF_F5lEL^c>&sycNq&G==CVxP* z$2Z4{Au`2XENrhxoX8;nBl4Cms#PReM(X|%%nF-R)Em=12O6p$6bSjc(Wf?fnDMLNWbycclUTmeMjPYHe$QG`7Qg% z{PTR#?ax-#sKKa24e=sBZn@rP7;d%#U;3yawZ20sS-8*N-Xe9YypEOf%Fm@iJ&3;z zQ@y3@>-@4E>|tS%(=bVG+1JhJIXcae{;5^JCI z#^6yyT*k|JxZe~n4)Fay9HKuE1ES4<0&-z}2h9Sqcj@=Cr_k&`s`=UXaEy=EX=={N z1F#Zd`n`z8h`aTRUIwDKRzTOH)6Zm!rPA*{U9YP#Z;kH;Zgt+^r)=O`>n_~eKSX@_ zhC&sB-d`o_B=v#A?}-&$Cwjj-4s4*DIMJU1D@-ilo8&@1%UtHcGlc8PY!r4!h?3S$ z${cpX2BPin>aUO$t8&m`4~>iII8s5O4eyz%Hh4VU?G&!966Lr(NNa z$vL|BdKI^216)_+;tfos7c|2}w7r|o-*Yg!$Os}PJsaf8apdlOe2=*&J{`h6lris> zB6;^a7`x4@5wWIZBIFnhYUyhq1X&sT7$5AB-){%}>*FHI4ajsdlJ2<{fW5(|G^}8i zm?a*q`$copcmb7vFV2jHe)pn=wUoVUbEmFS>|*F0_J<7|Y|n3af2Ez6+PZI)?G2c< z&Fi6z;SgF^i<3=_o=ax5{W@{hYT-&ADKfmE#*_V+?L@0dkGvmH-4OM2>JID2H?$+e zWT+X{=-|+oq2KyWr!!?PS%07MM6Z3WlKy4x_SmG(i?&=FRI;3s-LxOMWMkaAtDdr5 z0z&QRwD;C)X0?%ua_ASvbpanbAUgq z08kf&x`U?S30-(e9`i1q@O>cnk8tz$L3Pk^<|fZ`G82019orui ztR^;fYzZ`F*x<-?mXmR6IAx!HS3Qk)%{Q^^S_t@9)a+Nabkr778{=@&7Vxjji>wAG zU|Ryz=p9&3G;Q(cJ{aQiS0w$6&9WhYY4um0Y9J)3$)no7n*)k|8f0uPxE!&1uUwGx zroAGz$gN;*dv#Ygy`X306VG)eS^KaTC7^nS^N7IH(&4}GkEp%DPwV-2=uD^iUx#WY zfg-Nmw7h!`rK6)w;Am411(B`jMNw<&$+n1n`@Q`A%Z~%MMvb(1WAJut0kTa>ch|Il zjUez$Z*}~j1(6lpq%**6?hrTi3vLp{`aZ{2C9XwofV_g~c=D8IS*`&%w$n-H)t&oe zA17_~z75w=m1kr0UTc3d%<}5bh@9j92k|x;_ez59FUCO71`2n`%N9XEy*9jZ#hH!aHPjaNXJ%6Blhq@-< z*caw02KWYf;dMA^^@z=k3yTy~sv%s3Y)Y#td7Wr-nU8A%9HON5w7m~zUkGD8B~{#~ zOx6X4u=6whrEiBazHoyY3mat7;7J%l!D?){MhK(SmDr(rK@)`w*LV0MA&MztMewJY zk^g4PtNR1B04HsG#rOAx6bW@KYMLy%8jJW^Wiwtf19#6*ujq&$15E%!$|oE1xbG>J zlJJl6R-c0s4ct{NvZ-;VjV)ZG$YY~K$#7d%<6Cdrk-zaixP@mJMAWM!?!ya~VK<=d z0xMH&!?a_PYoE#QKW$Bh+Bn2bn2zX~e?P>E9Zfum=qLeej8^x~oQwN=x3@%h@4e1k z;Yvie>ys;!Y*yDyO!Rn`zw>N^x?cBeSKOuUr|yl8>MU)2P3{!cuSYsWDD{(uI4Gqm z&?J?~26a6df&{7mq3{v_Its^GE(Gy2<&+JeDD(EbLj!S*uv|(s+9|LCtvNecSVEs{ zKXxRj8G5P0Qx2&2e~N9dR;s__m-P9hH{ov1N%Hr0D(b_dK)P$qJ@XfUKKDSX(O>Gq zGC~v@!&iev#wH#>%6?9fzZmt5+NNN(Lxl3;7f3E*IG&Xo1vc-{a#t`qyKMHf-hAUS z53QsCTTJG_gKs|MoAZxak|(RwQ`D6s#wJbmi1;4XtIv`ynEFi@aT8$qZ~gcWw%+xF zxa!jp8M6XNyx1hvMjadag&ZG!a@F%yJtw8jJ3l}p0N(xr)&~1F@zQp`k#psHueLIE z|DFEM>pJUy3Kl%HB~O=M%Mr2P8IxDWw{IQtyHUoz0f*Z#pO{~dbLf;+i@@iEC3a5i z)86>BIRmOAa#WLZa6*yY%2Cn9S#Cn|dYRSk*p0Uyc8943jE>4OFv7btgG`vhbnBb{ ztgC9|T-aEb(GK*AGZQB`RQa&<>~G8gFBtlNHaHxT7wBmJ+}K@$Rv5=qZg>PpDpmlt zi4^mhP|`a7LM^5R+9*jvM5M>MveY!hS(}{2AvvbPLe_1)gl6jva)Jvd`@GK&@vndU z2O&^5ox7&Z{gta!$OBqLhJx21btl3q39Z*B0NRSsWuJwu-w>;fZ3w6kQ~{1NnAtJ? z$Dn4vtSU}1ua-HID ztnH?4LTASV?Rv}ek28`l3bpBb`{MTOWlS*C*Lv-cd=l(q25kXs!TClnA;i>-|DB1| z_48{iVOo8sNXVGkXif+oi43h5d7b5{wD$qwlHpVN@dJ%@9;m(c)g;){JZ~Zupm_oI zvhooLl>8z^Ci!K{uP|-d1P*j9UZ=0bJ2EM=y!cn8TeZmAeW817kPGI=ck_3jcqz{< z5$A=pZQul$G6?yVflG6l7&{2&pq9;TKqsNlNE^jVLvZ%QKVrC&4hW*64Nb{hP0NEP z((^vPpP24N(lw?yVy<)K1Jf*$Y0HWek-x{t;o7kcyv$ycOU$;1i|%lmon+X_O%Pzd zC>>aMAhge}fp;|PDcvE9)1{qDYaYfnl1WZhWU1$nZ!gnxfRAX!t@cZ)s{dwxsj-V0 zN30L&3(;%KIj5hU`AKqB#s46uUs)x(+Dz>Qu;83!7p~~WFIr`fCS|D4<%rn;3)l{d zVnFAD@3~4d7@gVy=Z*vcU?hIEOTy z^j|Vsf;X4kf0hVj_zNrp^jh$SyiV)TnNVdr%Mz;nJAZ9+RZlIab~#r!(9+P!h^Eip zOQBr2MSDwn@N#VF`x00v19O)Y{&~(|Br|r-Ip<;Ql>1e7^KFEHPUE@4?6M@Mp__Fz z4Pu4BeDDxqtU&j^9DY+fe|G1Opl@>ykH1a327PlO0*fB>K;YtX(pBRFSCbZ<&}qQa z?7p@m+i}Jt>axvEE}Mm1cj(H=W}V0c${L*O+qC{9Fo^TX+wR0!&t;`^fzuUOiS4s# zEqc<{1@s)z(Elq{^_a62+u_bcp}L&3Ap`A+bGHy#qs=+X(ST;8Ugildw6-3czZ$CE z@r$vW5D4}_S(5MYxpLUKXo@X=Uj{81>2yLx7P#a(E&g(z8b%&xaQ%v+G3sysLLlsC z&=#SmY7f7b{1<>qEnKk);5YW9_dx zzGCrEB5XLUnlVS1fuD;}nP}L)L*9Q$dsQ5$eHTAE)a@l%7EjYAu4y|*mES%h$P^aD zHIE!&-=X49!P(tgA<@OrAa3J4^7B-R)p9ptDdRGS+hXiQy#Yu^k+R|&s=Wtz$gT2L zdv6cAfj<+ub1XbljD4j@-i?&my@HV??FR*ajTiip$PN?O{(?D6H;A8*e6ZFYd!$7@mO7fgO!D zfkf2!BTB+j!IYZakk5093~o?a5O?xj5w4!yVVA@;NKn?fS_x!dy73c+o4HQ&V|x;a zV&(IwT=C0C_(s-~GB0(2)!sfbs)7@W1&RPs3L&g9D)E;~GcL0~NE*k736g=enwi}gnGyo=PH(zC=w zMREh^mfxqvv_6C^o{68_AxvH)Z`>X#>JeDdb3QeHfS3Vv!Uc8#(=K90Po7ZNsj*;bj<7nhWQy$1o5v-cuP zHKr!G{&n(98tLc?_!Cl~)I9W`q9YFWZnT!e9Y&X_R4!{}L};Jsk7&)+bc-zqMfZxjy+-ykn$y|5 z%T?V>FRvk};#}+q56uj|W^H0$<&3%?w+{_~wxdvuZ6=?hA62IIb_iJiDC`ySw+_Cp>!He4IQ5sNd+zzBkVe$BRh((Bg z`gH}rWdMcCvKVU7JMD05+41$jm~iQO%&@OjJc4b{1zde#XpQDn(`PfuBQy-~^%Bd= z4VZKX5NY{lSq#N}BN`^2h3q>nt?Q6HH!u@&9F zEbikJ1_h%cDD^es2AY)8Bj07OuC_L5$D@`N*&p2sk7oV45V8&%zpudA*g3eZ?EhhQ zk$z-><+DQq`4V)gA*vC0I{cPfb%hu-4IO>EQKz6A4CHlCea!R>np_G?Sse{ohK-09V03+Z!@#xZq}bZO`H9a-NLZO#7&G$IUPek(ETD%t?BEU|Nn~I+v zz5xaY{)88wc71ER_b*Xwkhh8G3Z?}P=L^#QDM#dhi_hN@V$i*lSPersE{l$GUZ542mPMD)tWHB3|j6-@9!`Js? zUtG~^jEtc3bS5k~3;S0i|4)-o^br7|yvdg#O(_8_n*Ba4two2K4`DMDsPNN<*}b=j z;zDbthRhm^u|(=Z<%k~wA)@;&?R11BOv;4Oc;sa^h`R66){ertLl`f zWk~z`H#Dd(Aue;7l+8N)o+)Pw>q#fr%5TMndmB9GEa`W%w|qSG>=)$p>d5fjAEf&M zc`lSG)d$)1=vzBXpg=f?41RC9iSZ_p{~zZ|nFABEliN+J5_;|FLz1yFfg}U2I1hwc z-g*qylV5E@6ifm$XOpJ?xA;mK;?jctZ=0yXl^rZ}5!uEV42yNkvY%jr_~X({P$uZ7 zD3Ec3=9_cl^u3qvkVB2zAJW)7R}URM(_9pC-CDKGEXT*6=pNLF3T$W8Lai`BtyD{55%9*U0sZtIN27$nN<7%<#F~yx>f{ zJc&xik|SSR!nRo^_m z_7fHV0`hEq2shsU2IcSS|7;8@TY4YQ{;-P-+#$H8(#y};IJy;}kaYcQw#79&Zuv3S z!ZBicrTY^b-vcc~G+uZ+h@rqGeILpyn>vC9@=|`YI&F0h@Wx0@VA7y>yF(6Qi2>eabU!+Kk@N zuISH*5SLET9Q5sUT93dX->Pzp5*6lptu%eUj#{La(#P@2B3|H(y+M*8f0w>x7wbj zR_uQ?vUp+FX&>6|%*gg>8EEkHyPcX`;(1cH&DbN^*~{nBg942X){>+m0@d^oGKZZ} ziA?{^gdtvdq%4zJ=snDle$dcFU*NrbJCY3|obh+BOxEV~yMLE9WIt5w1Q*GU)|W?_ z0*_O#qJmyE_IT<7x~jlUz!&AmVUsru8jr4&_Pa)$-BV&EIn*BFDIB5%Ple33uZ+(w z#B$gDOXHC2?~3e!w(WUggoM+k3znv|3zHEP77}MekCFpQ@|nnQjdVG_E~l9Pgb3)N{+ZvAh2>vYrpg`BT!Wi}u)yNK@;6^a%|W4Fk9TYB^@1hd zRixym2fhB}k|9V3Y5I$G0_c*HVft7DaQP1lV;v!@@3YD#=jwmX*h}8}W$M5|Ufvq4 z36AH8?x0=MKt$KV;A|oJxk9j{cg}m>aad1<`&#@|v*6Th-d~}dT=NZMvi)XQ3jBj7 z_z^O{ATkyeaA@FP@3F^b(&~ZUro?Z5%yJ$-&cz*54;d_dGV=MOueTyZ=3V?jl9Aw9 z1ShBLiIb9g1wSVSQ9rraw7xZb9XR;kI9v=n@XUZY`GKz6t(zp*iu@P`6;&IP7SWzzkMN#@_ z+G!Uc60hrsi8ifu_;SOTU9 z7_yNB^IhF94J3*Y=*Jxcp#>+iPq-F48ohsA>ISQsw++-=uM3h%RdkiMLoKn zcgq=NDx&R?eQAmvP>A+mFzt_&rwop?ml3S1or0WS{5h%eNHOJS=bqea07Rb0;lnGo ze89>|^A(4#5()>0PZ}g_00ZCxoIRqa4Pc%OsC`&|Vbs(puE6I_J8HTh1ObdyB{GXs zu*e60Hs0g*I#l#hfiIGfxl6eJo$dK@%FupHJcuaFk1&?b9JQ?`HV4c#1t)dNUO40K zJ_*EQ+x%9Kf1H|-z$}(V&TGVWILepYAd5bH@<97y-5d7-xhF~}d@-TmC`21umgo47 zxrhx|X5e;exx*TUc8h1ur2BweOsrqcft?J7V#~$c*fiIc13%N{M{rx`v>vi(k>-Nm^e^jHLbKs7&62H8fk!cS zO1uP&6Z-J|aEnY_bNO*kh}~>6k?2IJ^nzAZ*SwUUUw+WvNz}dT1^?RLndE5_8!aC> zFd;i5RsHPl7xzu+&sya0552nm_!d_KkKOj2Mv7{3mp#i-* zfX=&p!1w-O?jXyF4AA})i5rF0(qZiuN)iU?jk_e=6olT-s9jQ_og7p6DIuukz?C z0m?m)6kgEJcJ5AXfg^UNM88|hc>zE~bVciA69^9?0#9(FB91V&o|CQgyJ~0) zA_@vx<52r)f4D9lU9x5X?BJhHI<$&Zj_&Uii^n|`dRqqUOibBA)jOWkp7kIWFS*3Z zkrRLRmrPu*$V?)}j%RZdL4`H~hEA`yR#O3*HT88luZc&tg}KPR3kPUv-;~x}yMPqZ zng_0o1N=3K!_S%JeH=*;7MVxll+_kr6Cd73%5YC6E;I>do<;cT=A5U| z3QgZxt;d!0vz6+XZnBD8M!*m)bb>+iYJTR?vyJAiRBv(q{$SpwI^Bm2eyd?^sA8JI z|26w~zU?HVEgb8jxvQuS!=IogKHsP}z(n8$+klYUdXPF@tGXi%95YV8MM~&NMPk>D za8ELo81hOM!!5s0WD)4qDO$l=JnZ!HE%zVMiS1aRg%{JEDyzj$hT(9qrHzEI%%`gD zQ{*7mS6~L~3*ma3A0ykV5|9pZ-St-b$(@c)#fMnCG%JL75JI<%qnRy-=w>K&_ z926|2+`6aKsJ0E)y+gW*FQKXi@U3P1HhyV(2vBhL1AK}mTr>rOPdEBcnQ|*A+85i6 zqy*EDJ6;PoFnkPfECf_=8sJFp$7BVK{qsV7LYjoFOAaGzCb#_^BnW#Em`#PQc2ME!>%3cG4=$MXQ_yL)QtmdY|AA?eJX7R-wa z?Ed$#^W+bX5tVrXGo2bwY}=jU6eGrXh+4NwuZhv_S0RagJo@qMhoyv7-2m#-5Nm|pbgxUXeH-_6R+=L#(OxYS?0h; zrg!68)MGqkMNZyM{ZhnV9)z)Bh04N9aio{PiD0USZGfq#V868kvHaEl2E#0FABPmeqUT4@zjOjP zA<;uPlP`4-GwI_{5QM__1CPUh@7gx@-gj-v+tl`0Siej7bA{W73(GM_<$A5)IJj^H zMsC>LR;cA>C}ju^5=W`rKdwm6#3oWrqCGBBBPIuVMZeNh7Smi&QxZF*=zi|_Ce1Ou z-#rpQYa4le>Gn~SY7_gImM&bBl9WFBNgB9trR}DoNV23DK4&N=?P*VQ5b5hXPgg9i z?0%C!$s$wdu)9xwO7sM64wMHl0(ejN56`$JmS&3l`ArNZqespC`X$2;`bTtr9rj!f zEEFWCfE;RL3cXUl_9blJVS?5zO2EG`izg=0ZVFni8`ryYOx6pn9k33G#>yf|%j7j< ziDS3EzxoO!!M%Don?7!}w|0N)qXlmV=d_Y+7$&7#>G? z0FjV6V+iiG^e`k4Tz4N}AO(+$DoyG-F~Q4ds&iyV_Ax5zEYk6lrHhx%DD z%P&*04cK2Qsbt6hqNEJlo?2wJHeEBzuRCW8S#v2OWVOWs;^c34972zx{bE2;svO&} z$}ogFQkHx2x<~~=e@Cr++16S|5Mi+a1l>0f0s>xxXI>p-kE$&(EV z2xmf4=wHJ3k$hU)x@4f`aR4s{HK%v0-Wt^miOPLyJ@&I}Y4lzAS^ zcf9xiq%UwRUg1PSPn>Ud-339d<}O-4f?$i~O@R%4;^9z~#V9-aDY2AdlFXM-Jyf|a z=ecCG1)s}%SZU@PycyHRC3xq0?EU+?5jtQyfCM4%AbPp+@7HJwr z*aSXC#U|imv8JR+#nmr*;=VcLD>U+PC2 zY~y=#iY982`iQTLgnR^!IE#E-j(Dib+6k~0g0>8#?H zu%#1#KWR}N*GYcZvDM93{32?;u;M#Jh_Ew*J(3~yC_81>H)tja~ zu2dnISFQAY!>Qi#XDVr?$-dtwzczB{1;^RT>t5@t!{ciZG_3X%Gt~dw<_+!f8T^8J z1QpFy$M4Epp*clyMb~WF(XysZ4V_H97#2f;eiilWG)XJV+F(4+JF9T4oLe!XiKs!| z(M+WU5KruM$0t8B>f%fU_ClHc=$LR!`j1!p-or=K82w(+9Rd5>>~Oyxnrg;hc&g$2 z&#{#ZFvOI`ty`FGW6{y~K|@I@4%o0{7LJcM&_`~oZsy#D5Tj{Fm`6dnO$rp8i#^x8 z@PODn`q<=HL>rjp**`!PQ@4N2Da~D&9@FQr_{7%EXmbpKp5T#-%<)prhNZa%vmusx z#yy)pGfi#B1u=dwkG7Ubf3&Z4Iw9=K$_G&PP8;9!kBe;@MA>w7y6I*IV6M?D)MWF7 zMw|Y^o$T(l>wr*3+v6_$IsvC9ELXtd!r`UhK-uHGk@oFe`Q%S*UV$}%x7)Qp`O?~0 zmaoPN*XHA~RPCm1P_aN*GyEHhjp@VlfY&Bh?)gEBu-A9nB{P3xchUHRkSUXtsDOC} zj(Un!Avu$Do}<+pmG*T|SP4@5*&UL>k&{qhT~~-jK=TqKk%R1o=N3^kjbReHL_k-H! zGqxw}`;C4KF&fB?YPYuNsgxK$yvBd?Dzl#H{zEl4a2Q66Gwc1TvHKIy5Sdr?iTWdp z;aECY%Pq@;z*jBxkT8Tw)RSiS0hlvWz!5Y~p zb#q!8N+U^=z2^jvy~x<=btWU$F0Ug!$)NPHXhnrfn?Q#^x^sJlw+yxRc-8O2fFW?& z0nRPuBCpUFte#r+efKz4um%gJLKzXC7KE87oE|24g5exiP!a|&p}tM2x%fE)8A-ig zeJ-TXz9K}kG*Elj;$sdP_;~-YzaF%6^6sy{h!89IH4)SpJU=;|ymId#eF&@md&&RQ z6a$8_P)baE(0|R>S}Pqf*CV6zLW>ygCsqoq=atAHNBg$kVf8oy7r;{ zC7T?x+d#|SU9P~>ZM z{zM|;w-Zdalx(V%KVRZrTMu78eQ}KEZ}c(a?LWx)3COIi*#ldp6@}+e-bfuRaj2=; zjtfvmU)$hFwgFZ584UMm7udB0zea8c|#D=GFEkB9w4z;afH{2HUYb%Hahf zb7>n572n|J1<*RA$^u;nnqItz+=CR=-;Fv6QeSd@1DDfk>_ZWgAY z^mK>jUe*ehW?$-1EnA2X<#W{Ux=U2$FfNul>Bp4A+TU{b0>KW-8G!JJ;IHKf_ItvU zCkevj)&dqWS#CNffh$i*j?|z0&8vIEl~E#Y%%j0*XKa+Z2cLe5wl{kB7PWiLZ5ly- znSNnCH?YQ$4~(qRcYPtBAbk>9{lt7PlvCIbh|YYf7j-MSE;96kc{5Lsj8U0c7#1)1*^t5- zQrzf6{_hXh8oN^5l+DJ-&QI`To*|8#mqV^ReG}_B3a~we_((1`GmJJD?covZ6rFky zS$qfcIHe56Y_iA*`{&?*bPw=DX>r<6mzkiLYvL!?k>{AA@uzqOY7kkK*eJEM?ui6H zJ%H}2Kr~`kJ7NsLCnBIu%G<5U+wGMZ#j9NT6wgN1t!e{Uk;QEZ-|ABI23ya3>C2ru zk2X^a?fw18V1S-yo$Ns`ky5RE<4N+olO(I-nUI3xC66`H1cZ1sr*7Ey!qIu*7#sN| zhB(yoZ|4;{9mmq45P}fS3>RyAVARnU2u5Ua^9R4MsZg|DKla}^f#n>g3FsWWo(5CP z8aE4`cGW6(-LRgNEhO0x-5CFfnkBXeHm~lAcv;|HNp5^?w7fn}Cm)$3%amt(#R}|qy4dz@ z!Om=+v_$j+#bhL1)yTGRpl3EGc6}T_y-={*cYF`?z-Q)@X(FZ}qN5s>m2YrRi&MDS zj)A0&Ip>^lz8Bt=4g~AKe&5Yy!{gs^_cqiV_to;#H8B3)$vPg1;WjqW2Ww&ZsY zYZFgM*G&mqBIMd{^QBj_{Qp5miG99*yBxoQp}4_vyV%SWescm+*F81sthrFUZc(sO zTw+mBB8~kI+4HgHKcE;%G0qe#B*g~Fr{JnnDY9@)i^_?T6c5~Bn??Y*Ixm)2pm17I zh)AiF7N-1BKJ>4?NuKU=-~m_;ATMFmh~yIYnVjL2UPM4)J+yXr_- zfEQ%wY3$%@?VfQZ^}yq{iPxMV``z6g}6Ubt=TWk~hc{CJ7Y zI5LNYtoWmqLFhtnr{=8hrfOTZwx@Gp4H{@wwJ^h5zlhb+;yUzya$d4C7e_65KwET~KK4D8QK z-*fuL9H&vuU)IFqPr3i2MZmggXzcr}NLo_8FUqxHcMU9LB%&4f$;66XxvyfL&7ex^ zJyc5(P*Ob%Rv2Ep5{3Qga%e%^@9e}(Ze=W6H_SG!VVF$3YKIr*QOy;_z|dg&ASl7^ zmzfFoUNb7giNGhm$g6ipFzd?3Wu1Sv%*ce}^8OJihfY1~cPMBfn>5uSJOnEd5UUEv zYxOJ)GKIWb8zG#6 zr#4}_@|hJra}xa#;A~X$9?gD?vFnA#OjpiEHDmRb(!qyKoOR3t=7K#&?zq`MpGM!LI8Y6g^!p}SOK=pko>Vc_9+KleYL^}IW4owd%Z?^*lo z&)NIBMr^O0>Kf4W?O;!%)^22$2y)>!n4 zpqMcYhz1NXO}_=wzlQfmF{i228ElBTS_(mA8n+$clv{FpZNwJ#${4pW`&jO< z&%WkdOk~fLI`N2z>>Y|7y2po!Z(e{^-xs)Qx>2k0x|(KrU=+Ag&U`oIky?@|@afYm zaoq7_9<6uDo4Idu9}UE|=l1teRL+?^?y1|Ay-J`HZg7J(s`*hg-pG&XO>A$YDL5Eg z@rv%HDg(%B<}S>{5WIX;#{69N-D9I`s)A)W=& z){n1x<{`tq$8)N?Y-jnEs~g)7av-f#%V6mek@w9Bv;HTtGUtYQ;NMeW{b#TJfK9!8 zlk|<;Amon4soI}RhbHrbPlh%7497;H2e4Kz)K9_N_nmGaM9rsbhlY$OmM#G;pvT+D z&q;z0%@9#|zf*yZCBIdkEZ&h(J+kcHu4!xNnkVYo_lsZ;+-%?OSWV`(*WN7?nf>;d zeaNuyq$@n4sij3aXhr##{ZZeoFsHte?{FXNg?K=MRJ(wGU#y@V94E96uU2gn^qGl9 z!{y3l-=$sm)l0XHrZ{SMnj=ihxMsi~*@u1;XE^mp!O7^}vJ}?I&Em$GVP}?;x9RaC z5ok=qa>Cwz$!ws1{sUN(gM|zqDv+>8MA73Q-QA-ib-OIag;I_@a z-zTH)>;Gxk^>9BJp$37(V}D*w&Dh^m`Gsc*AMIEm&ta# z_+hF$M;mXXd?d@%Jxjv)K){);9`Zy37b8e;${=B> znbB<(%ACB>ox>Ayy8nK_rUBPE)?|%zHATlXS9JB3#HT4Kak*GXv{Z;qU$<3kIx754 zIPK~(tVfVm;AJgi)0($fYc!|$ZnE`I@v|LCS|;hjj2G3EwS@g>=^%r`MpCtB%PtoqsWSg{5qzC^mTp4Y<2!`>x5J!!moBh~v&CRcb;ymKMO)%U-HJ*rZ6i(M%nAA2PT{TG3xbjFr5r} z*TPzEo4Z6JU3!Mi>|Mdi6W!+3CAM)o9y=q})r>U)se8Wt!*b!aFv4ZpqzvLY_D{AgD$>b?Y1r=!K(wBmed z^)(^$ClVK+y(L3d@WfgBv)irm(x0G3SxL7v_D52UIEGZwNjYNYW*36o74))}y*m4K zanJHEL^O=SFqu#%W0K)tWt;ei@z0>$V!3<|yIT;&Y zh7v0;wO#j{EWSa%SO&|;GtcCc1SGsXovi1F7wL~6bEzzkeRH=NqgTGHsP=8PXoWB2 zlY0C1-cLG>$yX}`$Smn6vhn%#L2(oHq=u%+rH}k6ZWBbw%_#VCgw|R0@5j{)yg4dyOKV zU@65MmZhAhQZt;K``$AjC-fhsWC^Pj;F->JJ z_%Zh88y)ZNJMwqK70r8)S8^7;*4CFRnfFY#Qy5Z{Q0}=+O=PyaXG};7n&RO3c)i_xGz6gxxL4&+3SI;yC(=85B8 z9xN35TWh#mwpOPtd^xg^m}+nM<&olG`QDZRav@z*zqu;)w(g9q(fMRw+)&NaJa%Tt zON8t1P4>Z^nzQOPZy-adxjG+M&Babg+u0`B&_ad!1)U&kZP-6keQ?E&Na5QrCor(z zUw@nN&Zhhm$zwz$=}CKLmc3u;Y{6QR(|)DnreM4$cnb6?e^Lw)b?=o&=f0g@QFBv@~G+-SjOy!W7pf`E+&lE6(dM8H>ah@7oVSRwfo~ zq&ob%i-R$nQ6yghSDR%?EpY-nE}c&jDV(-UO;fc&IMT=joGEBiUbz^_wUIeNe;_$b zwkFQ> ze__lX_t^BEd%()$jgq3cFKh`iMRoCJn7s$}BF#C?*X^gDR7W zv%h#*87%~hc2NBN^>ocGR@H)2GoOVZ`9cQYc{lnkhPy5%_ZTzoAAhoDW2=N{3+$A9 zcDg8b`gt{mqC^}eRR=NjD>XCX@6Hab>J>L|2~B-=c1aRKfHPbB>e(*hicBYzxi_j( zai^9(PM9_E&XoAMCfUAQ_Wqi#BAy|bM=Aei#N$Xjc6sw%s%c9xLdZci-UnNq((;ft zfYU0Za#E+hpTm?$!LrhhRa!h$CwVPSU#7&+e>@wUBgq{3UJUb}cCnM5g;(XtPZ~-< zUGn~r`_lpQbiB`bavwQuZ1?be2@Gfn@NZR0b}XX8d<(MyhY~59xN|=J>!h?*4z_kC zYU&5<213k3>U^=)@WmD9xHpEXHC#I!CCPA-*Yp>G3kXhe4?M-Z6}hi;u%7%L=;weR z#2dK|VhvtcEB3W`BbMbJ16^Q?XpF025ew*7d|}i8?lb5(E}Of0+z8IWj9q{*G_H8R z7EbGwx7V~7k}ac+^%0iwkm9(f&;)G9dtMv; z-rQwvaO|?ESOF)UPoiYa?)(c5e+NtZw9X4?O0amPGiL4cQmaOKg)fG3Zn3kap54W> zr^IxR+(id`$Tg$6 zLVArOf%93Dhq?3#jh_q0H=7qt8FjGT%ce!sOF=s@y?+z!u3pYYj>#BzK+W zUj4k|tc8}$C>v*RwPwE;D|=+iLi>%P=1ni@9(+@~tEc1eIIyt&=V{ChU6g3VD??ZE zaVms-@fOC${!z*ysY%|s6zGpRVA5@*o zZdk(mEJmk@rz?hh6$9Lv0hiEDwgyVJ$}UQv>&2A6RHW;7E+v(UIgFFH|5$bigp{l- zm}?qwrmxSo5W+Sf8lF&p^HhBLmFcB3y3C_swZiT7Blz~#fDPlzgX ztTTz~GUrSpY!lO zaBmf4I{B_cU!wD!w3dXsTq}?N)M@q~jhbXFOspD6wX|#<_<|G02W3YtCrBK%;#QWH zSVZ2-J&DhK)Vc9d!G@WNhCwbXfzE_765qcpsOhX_K>eGrC-m*A#lG1qaDyi zC;OpIU>s;RZ|a*7%&fTK#HL;RUp{bYIePoU7S5UvO+kU;+3{OLl`NMx2z|JGz`*gW ziqR{KU^7dN{%YOHfb%168^72DX*`F}?2mo1lcFvQDDe-L1YPQ8UOSTTQGs;p$!8C# z+5Z2S7?$f@nVcp#OADM|a`ZdrPQXd;hfla4#Scsk06up1DXCoKn*3dZZzKAg-eh$< zCi=K+F5vg~Tl#ipLMpo{0*06tUqFq}km2zX$p)xLawTNYu=oHd#Bt7C&NV%C*4_Uh zQG$`yq3e+TxPy^n#Qt1zpBtey%`&a9ymp?=wVH%5+7yVkg5$>Ma38yDR-oniY7w8F zfaX%SIR56X&m4Z&-;4i<#gsa+#bPbM03;D(Y3c7%J+(V26$PzF1?C+2>e2zFUu*Ch z=ZTsZ_qDi}b9R@RVo9jhMh$xaWFO4)k&%I@BG?!x~d+TC|A5`cK~{m$_?w!A(CUxuRpG06_v(lKBH#54=E4 zi-aN~Z%|%~eS;lKoL0$!_(jJuOG|*{DH5_|xZ!F>zEmg>^I5sT4rZUXINCy3qGco$ z3G!=H1ibP|n)>oT)208X3~oAJT*@KcT#-;FigU8 zmr?P;ZxT(wl%pWroP;F#k?yxTAb%EEtrBM1u0ovfI7unlM|vV3`I#gcg;pJAqO4Pl zF*QWTkGFfEl`UV&7sb0gC*`{lM?{m@S4ty5>mYs*8T+=Zt7Tl#$lIaHaOi~-%2rs_ zMyRVqvb9)M{r%21P*Z|=#A`^sA&rD-rv7g|#&{%Ket$~xZhOA7a;Ydg*OJJ~+kxU2 zSf(XpRcRm~&*dAfn<>61sdDDxffs)P&+M4U9dL^el4S68k}yiBohDy=TuN$=E?xk6 zQWAl)a+0-Q=RG){SIXnN`DJ;O{!~n1|ItBbiP2JK68Bqkd)Ss+(P=GU5Wg|{k}?^p z4mI*UeVcflIkdpmH_e!a2^lxdYCwtRm>iWfM){$CwTb!yh4U_}c8c}Dl`y@oG zoqr(j%Ma*Bl1x84x?9O*(8HQ(Ha3MdRxN1=Rsk2A+gF|Cp9)#xw0)F*-G$K(Oscai z)gGI|0|0`6oY+a<3xnFT>rQX2{o+6svVXvi&{uY6w*z++hD12&L$3#Z)Jz=#d`CANH##YU0Y&SCj6~kYPcJ<>6 zL`w-DoGpAT*wS>h(Mb&~N_K|5cR#o|W7`R&DZoihTJpAzg_c?I?5Cmwym*HWh%S*+ zS$#)iyvg)sut*xEs6|ttsIj)vgtyF>rK);~_A>u3)apXtA?w8dl(Ux1EoPjfLg_-L zKN;N{?3d7sP`kOrZDMnBhNxv-_re|2$DYSZ*Q-49r@!{;itome%8OVQ!oE;?+e$x$ zaXqEz+$l#Vg^h{>F)DQ0(^bTZ_4z_(kf&bxB)=3lB%~{{sDj;6jPo!($m=-n&a5c~ zkDrW5H6lQPiXj4=l+ij-H$EhYAp`K=iuG&w85GVr<-I35ezU#je$aBDv@iJj4+dY! z;zU$9OF_wsZNZbA_iX{oQO|ZOq$T#f6R2{&bw&e*M}MSxsCWgV@f_9StlH3`2PW;} zXS%`oq3-1PHsq`hSx=$b%g0`}L7y=_=ba}slS@)p8vZg)n_zS9TGK3j!PVAkW9-w7 zOE}c@|OUy`(;+17fhTmehji5JV@ zmeVfz)6{qE`ueK|6c`I=*&M{Mc;v_q!NMIa|MMJ}I18H^)gCgq$&y{{^hFbHBk>#Y z7i3M|`qDL5(qRo@%P#MmGS6V3N;-if&Sl@BHh-YB-W8oF)pHr=oFHVQJ0g8d>*U<} z;%18(82ZnnyJkgS_O1UGYD=~{4NIb|oHt7xGX+@}Ju)`L1&BskMM`izM8JseBrT)Z z80<^C-MOTfj9Y==#5t*|MO{b~dSi~Q{vC5B*(AC91j+Jy_QZ_;Qlyu?K4*F%O6Y zTj{%#&;NPi=gCw;1YJN%Ui&d|__J`!sD+pg0uW&P_Xr>dSxRaH^?eURIWd_5fAw9Pe^uz*CjEw{f+lFGLY&P3;e0p{7vR)d z9vBjMdM>{7snKnH1qx>@Xv_Cemncd#9;okOH?w``*LMeKRksibY5eky-z)JD51-!? zOrW{A&EF$OR4RG=yKMvF(b8RfYK1DY_Ufd?F<#W^FpgMKYwMY}F3q+)>LL0ZSNO$K z<(QVE56c16e@fMP8)J5SGP8Vs6>swuBjncrwtfL0$ZlU036cRSMt;57xqpe0-hH+R zrfeN9`H=)Cc&Z(FMlFaZoPg`GV~3OC&8I^W={XG~VI~`~z^#7yeP#F+-5VKAgoe4u zBOU+ctxF^;yC#Ct$JFxJFj}JH?o?%8mc3;Lb=RGE*Q)LwhrF-vUe6+rYb#d%^jmOm zTiY^*wVlwo4Q;KntuKGu5vls@!povmQZ3nS{?#=_apbTcHI<1aq_Ofew8-+tNM+rG zU(%IXYQ&M8>#DbSTq)V$PQ;zMDEIFt`+Y3xaXZbMm%1B(?kB}{Y>_`ixN=1O%GvV2 z$p!|#yIQb)%THd5H*ofJ0|U@VY-vICxt-<;W6>IGh1kPXIhgvU=B<@Gt~~bB3k-?y zI!I>3o*Jl=4o^6So4#b+-!!^vt1%DDYq=q6Vzs))P%#*lx2?Kglzl|;tiHN zZ?JNn_(yn%4-1wAzx?8mOT8?RftqeKn&D87Ga`5vR9aKipRrh7pj)MJdF#()6x%as zi?P-n_=j2O%?pdItgKcu)v;X-btyOTXrgg#Q4Xdzkpc`Pss?jCT*_{Wf3=yUt)wG_ z-V;o%z1cEznX)i*;d95!tOW~;(tidLXe(|8ohWi|9NymL9k8hSzWA1Y+aB{gWC@V; zcRcU~?ZQP5WdHL%Z6nmlkr9U2(%%7+a!o7X`6xIzvbXwV(wgu^)m`IhGs_=Zl58Wz zV39WGsFyYpkB&!A0ypoRLn8%GhSZcI#pqoXI16@SY4jZD3ik%doAxueta-{~Bw zB^3Jh$9PIsf{?^031Rq|CFbXIt1+LgDuLI8*h1@FiF06Vr7g-5^`Lh(f995-L<~&u z_89zT+KfZ9|ry=dnA(Z!D zpilY;ElP?i&Rf4BpF3BZ3oyGA?CL==9?#tDy>Eb1!?3M`f5CxJOU+$l@Tqj)U7s+N0^~d|cw5WKY15PhzP`@i85F!HxM144aqWhC zH29g^|1m&Q+=Nb{7zq62zX%p^A1uwFADsX#f$EnA*s`vU4{ijH(N<1A{2 zCzhMad@rRnt$jn_JHPL4nIY%gps%~E5flX8en*Lfxqf?b5ffm!iW|fnO4|vS3BB- z=~~F=Hd!FQGJvw)8Cu=Ia)%|vr*TwYCB+@h9N^VloAggXEl;1oklnD_4(Y)v)wrATKAI< zX>Cf%JJtEGkL%#qd>^~xwxlcoXPbx@)*F3vI1PZ@sdYmltgzj8fRg@y4I`DV5;#Jh zvaqprB!h)n)p2F6PQ*K>6^&6 zHn4EbzD?2-LkH820m^LXyMYZ7Gq;JWY+=c2C_`lfj>We>m;J5x9(8}HG*up0yy81c z{(H{m|Gl_zN?(Yb)jfHY)c(KjY<<7`Lvr`zvGk!oBR4XG2A9UB(04%r|HDME~6XgH49i) zFAD7N9L_J(IkJ+C`4(M|1q#8WMLkOHxdKZie`aowXs0dW1Vfxv>s%Ot>$r=*k;UoA zVQ@1~^YU~?+?ACofyoj`Y9V9w8=Qce_+BpcBVFds0bv@6a8bntoF++=-DticF}6)M zF{azRv%al;;Kk&pgh=tUl&PmoTr5OJ>ip~w^Cwa@9K*N~RQQxnCN1f=p79rLGZ11) zKOZ8_ANtyNX}As&`f){$o+34dhYvxA z`)I`s8D?b#lEQ&wy~9hsOl!HOS6x0MNpA8ZPo&&dtI#6YIPqHrJ#(s1fQ~Fa7yg+Cjv3pb_ z6@+)4Tms6%vgyfNcq^c$U?^p!)M&F3xJ#%e9!YJPD}8pIPitC(RN~I5(^(`_J-4Z) zNG_hkAQ4(AcoyNX6ys-;EJGK1Pn5U&^(47L56w6J^=+JV6-fZo35946EpNnnhxue< z{#zhh$vkcR@sbIYF&H6FQb6}!{W@XOm&EtYFr7N#{xU~Mq5>ix>j*3-u=Z;xaa#IE zAghci1*;^!O+{mMu|Rg-%vUONbl>-Ruqm0_@QNok}zOs&|rHKolzV*$&k;7 zM>xt&WGU?Tznv3ZiIiX+-^n-sBX-pb#lTBG<`V-(15S@4^Z! zJ%<$B>TO6eF|X;GGAmj@Q0{`OrUD0$dpXgN#A)VpY?A0nP%mJ7Jbs+{2h!dykcsTDV8l4biQ@-?B);44IUkpQ}6$=03270AOnz) zw2q)hD(g}Ul;3FmSlU?e)e?wl)Y|m#k-?M}&7m^vdk<(>>M22k!lH1y8I^@m67(*4 zK;$k_CIbG*8AE#1nX@lD@rUI#KP zbrhJ3Pu)&W!4;^+h1ZJXiOyd{YoKn2#vZ{$d?e2~gV=o0aWSi(G3-ww9_h6?x$zj% z4_o}poikT%YD@c!B}e0z*v?_TbEAPI@xq;n+YypEcL;Bp|8Cg+--}VWkP*>+xqfAm{}QEg^HZFk9uceFQUX7>v?x>f(TWgIomll{7v@F}!b zaU_e$|HEAhW~}{Qv@et@W|>iW?dv>aIL81fTXu;jl9DRdwbHJXJbiMeR7Jq=TjZw% zE*0e!myVRcS#>4VXuA(gw0yMv@s+|#s)@!ykT7ZH|-A-3#@oq71 zAO?yvspbj_*;wjP?5Ev?)_inpvG)oI`<+k|+QGsFS8M(0w-!<;*)FNq?OZ+q^zSD` z8=j<5d>aXvpW&FmH&V2>!~8|>8DNd zw0+FiRL0;arYY$5P#pCXwP5k3>niTxd3+0Qe2G@z$y2sZ1(edfm#22k<`XDD=nF4m2feCr z(emtslV!`8_4~-pEIBCERSA6O!VB*b<4hJNIx*?iXhn~V;@VWR!zxz<_g}_WGnE`@ zFAr5p$w0_SRwsgn01QAHRcmURx`eN=mEwogd?s!w4X( zYW_7~_PpT{Au$Jsm|@B-VOd`RQtF|lb8oMd7XI8az2Dbx zge*~{&{z*r*Qec2Xv`%(&kx`zsENEPql!Kkyb$y?wdd0=vY0A*;E&=H4Jo~4O|0HE zw#Kf;#eQx!yu+Kk`SLDB%o9s@IOpYbQwZ%qHBVygmZR^j%j;XOk9T=9vTYlx+SElD zR}}~|L0t=7Aqg7zT-_ftqXaX>v3WGA#^K0Q?QqDA zq`6^O-s1hQtXwV2(Im`L0r#FwH`VBDrE$;^1eA72x6A9qO1tI7mj9{j`aWXbpTQ=I~fQgjf;@Z zpS5|gY@!KNKdsoc(XvwB5%~IRTc2U5ojR_L&6_Yn+0$u-azD-MK$VDhc5&M8S9AP& ze*vEzj?}96$g#kG@qJy92y)=D{P;G+|H+gO7kNAp@uhi2KpZ=OP3D9y6Vrb~@F!g+ z!?&mOIj4yOd>^b^dE8&C7-tFUtWXSTXJp$vq0)Ce3^1C&^vMQyC-s-KgUSFTC_B2a zEcMljmDJ!QVg>vKb-sX!XJMVzNBOt>Y*hhGJXi@@9tkWV+= z%okh(#nvwingz)x8pmJKMdcT9X0uQCkNMsPea2pn-NItYn)05di~a$My7MnGpQcK*DA=6V{W)bi7+4y=j%e$bOgi_ot0Fs*A=_6BtLTIXY?hl<&yXF6JY zVsKq;fWLC8-dw4R_9o$3+<8eeNV7$zPUW;Hy^hTkK2LJcwJZHuYlN@^wa@D(acW%? zjb=KLguiqfE^5M>2$~_mVS-u!#<6b^8DMzRWk)`AX*f01i2YDn1=XM70GIRBf@WHh zJV8WTz#9}%$4R2tN6t~^cwBjn+$=1pFYIXbS}0*zi%V6(6w(#ckh}P+t-vQ@fcbs;1 zS+er=q*l<|F>8KjL^&`&z+xSanW6?nvb>tCX zt7mN4UBVrcnwuN=m&mH=%+uSIi(5}1`E-#*oX=;~d{)V&D%Bn%%DjI(HTfK0&sSVi zZ=_4t`;C~-_e+2(N1y;cFq+yf0XLA1^J)i$>&S!{d-Eus8XhD_Qwy|4V5Yd zR8sKl>~iv^Ps(N>bf{EACVS{Yv2i?FLFaXJzIX}PjE^LrFOGsf^1Rlc z#3=UEE9e>}yVR_Lhw=*n9yQqn@8?E$mU?pqJz}7fji^r!-HwX*a_%MW=nfL4mSLd&J z=`E>fr-@u(jkQuez1=7cWn~pp@}EbANR(#5;1guXec`V8fLlt%^iHG`;IwwN;;RPKahHuyigwRTLvnf{b#ButnzL0Z# zV4dN{Acq_I+i3hnn~_CvHYJ(bxn~=DFlVjGgn3-JAMKGQB4s`aEzR6$T*+-{8aF)X zPmp*KOo&y3jW^9~o=2LDYeTGxE6;M!F*MM}M^-B%I9kQG5ptaN+`KFxWu3Z_is`0V zo8}+okU;Ytp+l;$iabefIv)+w@+WlxB`?bMWT$=dUphp32Pbtac_Y>W%L8R_8voK_ z;>C`U?r%oeHbRM~q!!07{A19j24CQJ^cMxfBq8AkVscJF#|PV3biQAqw2kbAw5eBwljN2nkE#?*mlFx>V1 zkVGCy4!Ffv^fo#ofT>Vi%j5LPF03Mx@kDK)S88KF*o7xnbCxngf1(0JZO8@v;@ntg{eLI%2a8GNH(ugv+l_ zJ(RU1+I@z}J^=+B0@sw<1`MowL9icVhSmud< zEujQ{*ScLT!9rU+8_R#ANv;}B@tOpP5ze_m2A;>S{<-m39-Etrm36_}HoP1B0>qKm z0L$6_n}#K=S1rufT?}LUPc!Z$4`qWc{DG(+P5DReWoAYYIdBdPx_V5pSaBQdewqki z*#j5{=2gAC-Dxc0iJ&+*L3R8SuXKRC5=c;#EWt?PR&JZ518;G>iu)_0QPz8 zr2>wCSItcoBS2ZLzd$x(%Kq%)3dHSF2Mt;NNJCstH}z=6jC-89y3VDq@gfaI(fOH1 zp_`OolZt`8QCL%*x7F93{W(T{d7i{PZ!5~f=4Ky-lTCx-YF-_LkMg~0a-bTm0dz}( zUrh=p-q5P`$ziclA3pPT%9PovIG*D?@gfFoh~6R+fG7d1h$JJ3_XuPeq%CHT zRYv<_46vvT%+<|&uB6WJvx=T50e205R&th>Dvl}qDj!N+yrkodw)LU&@aOwZ8^>Xu z{-ilB-_&v-NnnBCs9UbwK{PV?d6V#ko|fW>oe9k?VW){O)|^O=dJ6vGcyS3YPjF*9 zNNa#OB-(go%hf}DBs^)1^aUvuhCB2Vos2f|t#EBw7DbvCxST;-P5I)~sNiVpozoW^ z_0;e?u6ckOgqt=hdWR2#Dow+DE!>qWCsRC)KS?c2#ACUKST+&BPI|l9_f3MO`Da@q z$p@A^ozuSpthYf%W}@%$LqXi(j~wHR>#UE_%UBk4p42BGWvt@S^Jw5RpB*y#m;;b? z@(vGyHFqieu6I{MOlD{szBc7~OMm4zltNJl2981Cvi8ZSizIC}jKq=~;!A0%=QgRo z>LuNkg!U-^2^|&*`-QG_7*!;s+uBkq<-TG#Cc{t+NL`e<{ng<0PhY_{p8B@Ps!`tH zry$*}6pP)HzwE9?w{#l(GpIK`dxSpCa>%7`R)kbm<9GnmMrJAvroRwa?pTCIS$|RU zuN{7bh{Q?Dbaq`4|C=eL!2WjBysEs2lct z@?t(TB#O)}uWqf4x(QoRpK4V){}9>o75Kg}Ryi-n5OU@`1GTOFc)8judO8V-Ob`lC z_W7GCV6td7J%@LG9Hf=5@?ZlbA6TZOIo9B6oT?m$2le}n%0Sk-#~vPN?sH5p2hA>? zU(7sQs*MQ|zHhV-Z=)qxD|09gb?iX=E?8~%>x@6eo7mI)Oh+5NKt6_d8Ge~5`q!I* zd(P54$j4TEDyDz2X>kxyALyL*Q9LMegW+XZI&52|etUrUkCSXoT=*=dS1($VFE`|N zmv#Hq-J zaexr_&uZJAqoXP>^JuGBQwKtOt1BFuz#Xl`XUW-ov}jzkN4a0Yi?z2xw?b`mBx!k3 zt4Q!J76xwBaJYU00lbEsg8gps#%~eFR%vzo_I?8Cv>9QPWtFRZL+k^M)#L?HB<{J>C?&X=Q;&KpQE?~ zsjqM3QNlGZR-*jDj$OL}5yCFhevBr=Fu|U^tWG>PI%L7{i%Z8053EuvM>FO}mdaOO ziLum+Je}*6zlW1?(L+PuM1klYt_}K@d)edP3>QAj{*&iV_H@mt3H9gE*zUNSqqL|{ z>TW&H5OL^&!IH9PV8Gv=&5veMY8C;FiI;T2=Q-QpRlnr=mX;dvJ*lP=%OF?qC43$G zvJ7@~%EN)Vx7XQz$!48tPMslKF3B?;g8*_)uee)blf$DzUN6#0Pip|pL|(&l$vI@8 zd{?Gy(B{7NxZ&~we!$-)9vS$`&MPTFx-V_HQ}H9RQVFj%>rE;U}IzS zPd#J&2BM?4SeR$CX=%9{nG>(R*X6-)&j)>jPk+?MQ9-%PA+1No+kDq4!HAEzA(<8`xyFJ3zi^HlB0iBre@bnYI2k&ZeA3$9)HpMg&FGn%Xg zk31}mXHuZCeE7~%o!)Be|1(|lf2yVIyE3@nO)do8vEc=|7>DpZ92Y!%`~R4EA;vqa zZ8aCBTjzp1=q)%&C){}h%+GSoEvFNBLDf*ZzVctsY<_IaIyr2E$=IoQbrz@>D7n9x zD_Y^Jy@vC+x@hT0nMf38ke0Q!2TJ+Q52ml_{O-h(#9x0ZcT~Q#&lBzp(JyXMc` zGM+b$1;H_vr9C?0luP03_P;pXlDjLea0;{7;?zwZr*#ly-{)6FOHhrfe1BU)OZz(_ zJ!#>GB~xY5Qr&z7*2(GL$kL+8^{r%JvxKG+YSTc7sv=p4n)+wum5=T(@&U-V$I~4+ z_do-y2s^B8>q^VJ=L3?59~-Ncky55VsyT*Z8JEN<84gFH34-#T61xW2PKd?sEAOCB zUn3oD7+(zIl0m24q@mkPTb!242k%*E;$DJ#`>)a=#I|~v%t}7=&6Kr5$xHp!^GXt* z>_Gyb1!(&xFl3@DgA@gXq<{M}>egGB9*rWZpsiBRTCs~h_oWm|yWzA~j;^iqR>LE6 zRd77jP@Z!|gl%TN@Z=``2_*eE<~2r(li`9#E&1)|f{T6fT=FfBS=wkL)eaY{mSh1+ zFwSbqF~xxE1DhF35+qzeazou#B0*add2}nx6+V7tO|II}_^uz6^DtCjl)O$cO&0A4 z4+wO7eOo!!V--!`I;ZBBd2BeK`uUjQD0E?X5x@S=Yb!z;X_zmonV`+E(X2wFb;uCK zvu=R<2-A^~#7||yr}}FDD9xij+Vqi<#e@sJxE%HTwnWzw5l=S#n7gno5ioKyD@`8&6Wa6qhIO|3X40F}F1lJwnySaiT?ef-Uv|(GBQ+2CREM)^G%gI_1w2z-`Ne zcb`1`Uc)l(WQSl{KUuI;pCR`9djo%`^8Il|8|LfhZpZH>2z2S4;+GSjt~6gELvNrs zCq$B#qL3H=a!pw0pG_E#rF=Ly^KpVsEuFJV6b>Zel%~CjC#o=SFBDg9_i_5ysIjcS zm_HbxzkuQYfOXW>s72Lns$@wsHG~v*>Som|=bKmAr^{@VY7W<1;T~*pu>J>luZPbf znnN-qfcxO*{M9(A^-7A`L^gbO^}M1H!;iGBhGFbTh;dA8YURuJs*z|9*bXF~|Mf z_j6t6iR?iOn3m@H8V-V*-i%xz&huz%U&&1E`V(-eQ4!Y@Z4Z`su?NebeDHT8>$%o) zlF!c5cFgnGUkM+2f2_)OnxlmFOoS`XXlPr>p9Yq)zITr44L!SEg|O z>LJH-{pRHE@M4+R6MkX#K`JAl-l!bjoF#8XyG<%MFbDn!boI>p`eap}^UoJV6PQiB zU>hagGd&tqb|nUnHQ!DO^$;9KFGQXm^-FCSNY1$CB3-JE53>ZLHb|S5#{a0;uP1z~M zp6%W({&0j~tx$keX?L%B^_BgcN$tUE^dFd+%r?%?vO|SE!I{~=0dp3|11q_CN#@9w z9=BkhX5sNWch7hCJ?%JgQR02Dt0n5?!~Rk5W*0qZv9(U3~aWs%vD)q5-Oh4<)p>hHK!QSzh-5tx} z2gr1Nd-F=0%+ak_n!l^vh6<=C9$*zM?9EhpF=~HeWZ^_Vf z#Z5D~(%!Bw4`fJ>v}s&LjwgNVr#7iw;g}~$ad24S5CeIS3Ez6A+)vCbId(c90_~Uo&1^EBryiI!MB4&ktBak zro%KXSv?6L_jRQBa~fca^IcUIKS`VEyHKGz&Q^IjX&kwk;_vB`*!KnkVv^+Z{0^xu z)%p43L3LkW7>J|N*7$Vobofs$KeF&Bxb4J@Xj_$0WOu5Ua-B?WQgekp)8LN#G|+Ay zOVn~mVOcv-P3;RM8bXxW#&2OL+j3i}uB7Q0WUC7$#w63IoQ!a#X+-P}>S8S7Ef>qm z9jrr-ZK>nA;<^9&hWYjoICO|utJD*0WRCWYN8!p6iDfuIipIn|b7E|E{ZtGj)Z^?C zqMk;YUxe7&t>XS5SO!`O+o`Y!IUT&?d>e!Ot1%NA?Os^E!H~w>}zGYs0A#7u- z>EyX;A>wJRa+5n_4-q+`WHkb6e;s&`@~UV}nh|OTrkYZZ9UJ&rEksr?rE?g%pVJ+S zlN;-3>BqEoxX%GBio(1wVXM7jJw|uL1qEgytYxh4^cxYKq-V*yJ@KPfcO55z`oH%_ zDTbtS3&@%^QK^W@(zpmEUZwC5-^$oY6;Kvwp(Op^bl=p^_!v95+_ve|N<5#a3QfVo z%Vq4%kN6Y5^A~`$ZX;VfvUuxS>nVXsj$SYET{3{zEPx85J2^r(m$~OX75R}%0}**_ zaA=K5sOMZy!@SXW=3rK3jr+$y)JwEo8Uq=rp6xgI6TY#ss>QTMtSep@9nx9zP2ny3 z$JEToOji00*saZ#>z^{7Y_8kpel-=rNcA3sTdI3GAHDLz&dqX#IcTmI**U$_9uwo- zSKvR^SJiwqXPnM*qiZesl1&MLKq{{!v;=4F8jehncK(bs-@T5POhK&(c5XlKto#S+ zOYB_14+;tB#t%%sxlFo=xkX>x-zg;P+?_(Wk+mSL=Ml{W#>jkShn)P?LW6)J>?LDM zI#J71>vmrGZ!=1^OV*2XUJcb1q|(-SFl#sP0BS0?A<*hh9`L39HjJK8c7f?Ls|W2& z1so_Hqi)%2I72>OehSq7Qc)+gaCTC=dafGtOaGwzavepTXX}ChF^Q_K2F6OUd zE)KN`BNGmz1-&I80*aJQbTCzjD%=_s)uPTKU-`u9uKLP<+xwD6@!P?W&VIef*aaD< zr-x+Y06mpSJMahd7LipTU(GsV1W|cSB$=jS6onKs!PY*-U|oX7`HETwVJqSK z#;jOUerPmsGfd-RCL@jr79V$zIqXEYzGac@hCzV@AV;YC%7A;ao4UZ@&!2UmXNPyn zSEX(@m=VuT-DjwfflM}Jy`k4k=|5IG<-g$Ctb`BCMppN^yKjJTuXlA2&omHEZzFj_ zdo1s)5A}xZ8Ov*OaZEfPBmyX2h3{Eq2=+o`7}g^oRd5o@Wz9yAgPi^BnC{Sgc5sCr z+<`Qb-}Vi9?%Axq5*=Y<1lm`eqww2#Put>slQ|bcd0Mq|@JSVBxm;wmNE(l8na5?8 z5!{7s=eMW4$~B)Ue?_@oM&AbrC;=|qZ%eo1;7et2bj z0rN=9i-@2@#8JfZgP?QM_ff~E#oBn9RsQ`dYge6($y zk#b9^ii$R@vtD(LJQCZ0w$Cy>`I>8FXwZ>GW z#-Xdp#!M4rN9SnRT>-{fw(8=#0|06OZ)^my!@yHQPQueJO9zy7r>MFM4 z4}Zn}cY6N6cUd=<-aQr_;8kkChEd@8|AGY`HqjNA&!6g2+^$d{uHYLk;B?^q{{6N8 zde?mt$XBuXKaLQ&-2b}j?v?%K@FO>C=I|g+e&6BPppo0bRj;I*`YzU6nG|xxq@uK> z*p$X`xOo`*R+U<6r#0yt%`kxj?PKX}`?@IhtW@xYVn~xT;4JUwE_l^JFY#w>!O6#^cG0ZiapJjBk}>FaJED6mTug~AB)v$>SKr?{Pn z@lW|o`tY%@oQlNTS5m4Hx$>yMkA&`-JKw8vVGWdP7$&AQm&>K@gPWf6P_1XK(0Vp8 z`+vV?X!u?P4K-8;{R>KHkv!v7J%EkV#jHf!OcTHi`eW}e$?ZLkBwe#ySRjNXU0pqa?q6X9Ac+#lZOFhUt z9KD{X15v|-Nc(?neoxNm&-UH%v%BM2_=s;G74v^2MTBi(idR1ECzt81;$NOpP&9ialcM%e+lKkwxp6l4!q_IpK6U1;?u8sfzNN3H622^52_NuM@8)`&#k(jH=62UwclVcqZpi zvm+f*q}N%zwG!ZI8ehCfbH6yL6%7bCIcEKACcYH2b8cIFKy#n)A+7&kqcRcxbGHTzm zav9;bY)c9Icl6(y&u(nobVzRETVD?EM!R!F0AS7V+ff2LHwYJ3Nn?<;d@~~O&z^|| zne?Qmi?c=*6X(~)<&e+z2kHKm)(7Pvyxg;+Z@Rj@s*;+HX2nsX8h4a%-8U z7U|b)&LSEu&_nz-tV!EuukE#=dgO1 zTu;ol>HHic(X%c(J}c#Hs_VQ!+!p<`#@rM{Qyq&|3CEw?U(COa6oYei?wtDwim_du z;1qVblHr(d!4G)Sa`Y51(5U;j);5;%mKPLCa&#?MQO=PE>`|0yP0D8kb9ws#hJ*iP zcm3f7CS%>=kJ5y#6K$rYiX`O0DFp$IJhOLg7V8tY9JOoJEeDx5fdyTSo*-3^+Qkyb zC9p|4N2>r(A8P01aN0Wq+6(@^Uwm2;hjvT95{}e)DY+Ktr^jo?EA2%7nzf!{{(ulD zwv7eU317k&*cLmM4z*6A=R%Ei42D4lGp4;lR|S|02Q zY>urLsZyzJ?5tZhIjQ=1q3O%&sUXNOLvse#8msx4xO;92MOQ&z2dxhVy**gYMHrSj zeigZot^FQ+<0;bi^FUH6;Td4@sbIh4(yz0?o@&oypzQZ%XMOil?>sRdGA}Q=LxK3? zCtBSwmpc|2fBcvfMyrJIeQ?0eQ<`#0XVBowdwi-Ug2-xsy>kS4)BNtJgV%c8pH z;&yn2)+mKKhRe~^3&A;GLPe?{jfj|Cd!34Z$9(BKMypO(+ z8fBH@Djb5ksXRW*8^DaroO^e;SE+&cNc6i0otSvtZ=Es!dFR;LZ!CKvZp)8_Dm@&& zU$>Qw_D7W`9^Jm)5N~+GdI`<5bX%DbyJp_ zFWPOjua(QH{~l%y?)!xRr0qA*T8^`#gi*CWQ8-3aPRqn!Z$*ue=*eZKU=|&(R-U) zPm?Sn(uWG}+b55>L9O%;u!Hkk8XxnF(bp}!C96G~-<(;_lCDf%uvZo*)+3w`-{D26l1-6%FZVz<~=EcM(?==uhI=)1R^emichcB*Hz3lMD&B@0E!u#@|~#y{P7;GCO(J$U?(g(Q?5bM0ekDmD}7H zR?+nST{6L3ZB`9mjk>g!i0Itm(9YAh2fpmhXZXMFr7V|n6dq0?He?Gl+b=7GQlmbx zS^N2_`m>YvhD`@pmu{gtyA#iB(wCh0l^hQ8Nu_k^)yhBeKWzlj-TV_F<0+YEKPuq&2rfOo9S-HC{iGadu62l=``a<_ zBdh0fO)t;*ukuj5xWMOFi!y`*Ki%1x9`U8wMZQh8Vr8C@jPEGe#zLDj zC$u26JXNH#KOlfXQ9;fv*%}aI%Nj=0lj0diz@mm|3wP948hQV&P|CA-@*=tG@h4XT z!Q0=~AIjjndl<&%Fqk58eV~XUzx68!g&k(e7pE+GDkGa{6a3 z%HNnrzf`Gk{RfR^)d_O)BZMC|msSUH{(e56kwhl{10nkUMH}XF4w}1W)rpn5nM$M@V!$_NK#m}(N7<0eVQaP2{|?&oFW-k(A&Ne@ItlXIW^ z?Hsx>w@u~~=Sal?vS7d3^sgNX*mSqeSSewDw-$O(25HP2@q70?9qca)S&~zps%ICX zx!#u`v*LXJNvUV;@f@YLm@7($IyE4D>b?FvQqQnKoGIxPC+8(aT}+R@h7clOTh6-n z!l|7qAovFabG|I`Tc6RRS7huMO6RElApG8F8g`qO;I*G06G&6iKrsA1a8t5JF^g}@ zO6~V`6yg8qNt`n+3K>VaY#z6CGtcf$)P^r2`%O-p^B$!&vDC8z-iK?X^dh(Y4g)lw zKN60HvqdgW{w?a5hpJ0x3@opGovnb3{`L(D;lsLqlpIj(tZJ6n@eV7^qK+$oDG^uW zu6)PvHnscnPtc<0W44?TrMd)M26ZDkyu8vzk`+8{i2dPo9Bvtppeb_3kd*?h^5hi~dt8nR@wO z!P(Su2{FccqnB74q2bJ)kLw@44uZBW2;1b;X-pW&@*xrp37Il4Dg(YcP+YtQ#0;sa zaJ-?mL^OyQ6#YCAB&~`0`}#Hv*dRd_<5T$XP|-#5ncsMM6ZO0Gq8`1Ah-u9s3l&rI zDTfy-bd0@IoOew~{>l3M6#b?J3I}~zcFNnv=L|o%an=GXw&bi>>Vz*4QpME;=Fj+y z)Gf{9UeE^X=<)Oy(K8&sFJ#eYB-i#0YoeO@MLMQXAVsX{$8IO^nlxrnD&o`fTD-;6 zRQIR<<|orWL6YJ%OWH?L|5Qb3{RutB0$45RjNv7fWA`=@Wyz}GMf}e)Kv2Wr8 zpG6Pf&Z_ad12uz~&Dq&+@-G^8_sj-&k=zOOCeSW`<=_0*TTnhSjpA){^83{0ZF*_G`4$89 zON!T{BZ1k;(DFNR#-&Z!iT1cp(u6K~#vjH%oQ-DGd~fp7wD_XN`F^Ke`9fAbytdC+ z+=_uKw3X)fjKE~bzF4uikA){?r+^GPaZH7dLOv5>a@jr~>f|BD^H$gNeI;1{Brm^!C|GN1%Y+Z#o+kPc@L? zE1B)h#4qXF67t?`<4xDRo7`Q0&D=I^%y)u%H|w^Ki3os!+>pL2@%HXZPG9%44{P*4WwcaQqx56`y7%2)`LPyl+2z`u=`$*Edec#i zS-I9$00acXMwe@#%vcY)R8CrE1Ea_ApNk5)DKXK-6 zd^ZqOX9f8})0ezoO7~HwgvZQruq-M&{c_7?NwskSDtdxLY#5&?6XHU&5{*DjWpLbB5>IR6t^{2U zMZlqd6*oS<7^JFwxTriFP4~kB>}k&sR%bA3vBb%l(SA_hn-MG zP5=aw$7O6cU$i_nj+1thnWf2m!d>zsMt8#h9|LB^;i`Y@yM&?^*L&HmjGVgHcp?hsTo(B>Yq$`Cxr?5RA z{A7SHJ3`EyFRu9vIecOCJ@seZ6Ed>?4~iOh%7Jd^Wb{-c-Wwt600pycj1t>wAoJ#U z0a|ZXzQA+Rw+hZ>;k}nYYv($$ow&zxWAH{D0DA#hvEA45(^8nf!Y^^D-PV|CyOh0Z ztj?aAr#BV#_7R%PL)=JB?4s<8AlhEwOs>5UaqH^lmGQebNZw7&y1A+`rok(7QFjaG zl+sB3Smlg_Oc$P6brtb6Ry#?q&5!V>#g`^d4CE_XZ|4EH{)<1z^#7WC|1~U6n$qkQFeU@unA=;{`Tr4IJv><8 zVmk_Y_?mqWN8F({)2049^eUW+Kejj#$sR~R%O9agm+z5&!ipFBS%NxE70GTlpial> zH38a>OuJFF7i(gYlUZ7c|mV&dBe2$tR<#UV%Uu^O)Cag#yVWa5O zQ$w$WvA&{>@;Nm>ten1y@V0JS#+QJ?ek~`PC}n!+H!G$Jyp&AEJn>!8@_o7V3H$ZU zdUXG@Py&RQ+8j+=NOMrWFatlGe37WYP!=}-Fhv3-j(ZmtqEgLw#jOSt3wCt?sXk8h z9(K@E0nj;P<9jSUC1ItNuRz4Y-5bAB>(#7%*|Jdmt`;|hQ>2QU8{=zrI(xxp%)?o@ zRid+HU8S&5YG17T#9hVtSw(Jgd}PtvL(a;D6Mp@5c~xvinUzia2i}9ruwFQ)<1Gi< z2exK5Q8wcjSs92>`pH%WDg!QLff z(P?;@oC;bRoZ_+38#1op34CysNUDnS&X6a#B4yZR81?#5v<1}~C6NIOIXXc5wc*H= zhDXoY!?GD>V!1_7$_O7CsDc(d24EzWj`?6{HW!?S^Ax`9=$nqmW-|JAEs7$dUYv~<} zb-W`h|A?TB6Nmq%?gw*J^zn2cnT=|t6E20*Rm|M7*n3q&+PDO!1-6wz*my5kC|N#a zd0K24vXUmBA*+#2g5Rj~f*Gjty)wU}+7;fd|J$CW-N2BB0?*7BnQm71@h_WImeQ4? z*X;T4DDD*wNHnph_(Iv8>1bXfC`+u%W5=G?rh^C*2D=kQGF3Awwvsdj_pGp>sZFcv z?>wU!uvu&$nGu6>^%(!v&?%QXyeoi&vM1zmV9D4Cnf3Uker`OyL_p?%o@aagUz^hZ zU=vOSLAEK=7{QFIVbKHm_vC=ePRzD z1FNw$h^{>b);68aIbl`7o<(5Go7Y(+cSY`I)E8NVbJ$28E?C2SaXOq|vtT(q*gMuN zRM*6*h^co<%Xw}|RQ~wpzq}plX|VyPh=t>MfZnlQ9aPwO^tXj$n?|haqY3!ijh@Nfb-FNpm z$dqR{L?qYl4ohS0H5|~0(p{`W*rsO-UOTvKN;O(+v76~{xt-)Xh~xwip!I`@>Ba2; zVHsU5@H?NB>DgJLQcjHdB53Ugry-t3&t&rVQLkzX+hXxczdJY-Q2XtvysqfJ0b1$t zD2rWa*ptYE-xpjje+BIj$*zs;%%YiyrjS^Ix{^GQxY@9711ppk>?c-J#hA5AS;475 zOS#Q6%?k@qVAq~1F1XxlJhR^X>drc?S_~GpZ{y_bdJ0iideF|(_mKG`%K!A<*;|kJ z=*6ro5F*a3jH>(iHn)4uC& zKHEgDHm;<@Ps>Z)niZVMULzVRl-r+YA0D3L2SWs|Hh(Ofgx{l*ezReY$lAM+A1aAo z>Cpw#lJP+MH;1+`uVbCIFjk9O7p6;`#yr%XZ0*%h9=jTdPLu9?q|XI*3yDnJye>;? z|Ig_5e{)8d@64fdddR0WfhPgx>Cc<@?i_?M?G1$SrLyBzO+AeSIDateeo(J>hv~C( zzSz*JecVq{(bwU={bN){2y%*)j^VeTp7=i&y&hkTk483l^ZB`U?;3axSzsK4f#O@4 z@)-wZeJ0Q4b>>uhuWAwL0!}cZL$6Xm8x#JmHp0Dm)k2-n2j4!P#UeKYl5i}`dB6X< zD>%D(N6U5>b{j}AH>dfV@fjzjmrk3#m{xAC!nkyVSXdP>scN@oajLPSr54*?MMsKs z&EE_`Vd>*~-~J5A0SRUaTLUVZJ7UmlzAc&j>L#(9q7yT*N1@O6#LB z$=79mOE>T*V(syiC267mQpcKmW!igIi-I1SKeLM5Fo;!?WJIXYNb7`hWtffOpf{_Vx7y2Mj=24=wa!*eJeW81dtz+ zWwG0wCzlo={68`NC*o7}vk3 zOg}*(h9kMY3W<*tYT36HQfS9`C!1SeP0q^Sk7)(u zQ{)fv2axlm9HT$bqn2g}6xcP%J?&CttChS-sC4;F<&<|M3t|0ektBPr`WZw0zLU2 zUr0Tg-i|eK$Xa0Ynjlp~EXo4$*Hik;R0`!)yRkA5Ft>67ae4h#O{LSK;H5uEiYtl8 zrd8eg^d<(+Xp|!3>_*cS&K(gVIPRiok#i|S6E7@a{dtxjn{)yquiKwNtm-VRt)Mei4X1jyv9#Npuv<- zXq!q#Zdl@3*p}1mM#;9(`PAQb%XEr@(J@KGMafZl^4VJz{)|c`djXg(=|2b|Qv;i2 zlKi!o3k7mMXvCkOLmt%H7tpTbpBbkb?Zge>5@0vR9};tjCxxl}p}R5=qRb^np#F`> zFm^AlBF7x$H^A|XPEp%ezfat^#>gft&Z-wF%Hb-SJiBLDmqo47*mG1AaA4%r=Fcik# ze1D4&neEUbzk4-HrU%N0Nx%>@^)bWUQVFtidGIz+(RDgjP?Naud6o2V7n@u!w|sa& z31eZ#L`Pij+Gr4MQL@N^2%4~OCo5=$2Kr&uNR%^?L7E-*3$`1O25bzrTkA&(2sjH9 z?MctudzZaKJ1J53)G%Nkact^qpJn_2&{!Mldl8(-a&p){3Dtk8$>cjK_d*7a(;KAt!uY6*UY>Ntdrp|nUN6OSL=}UaXHAb_cvXG3 zm-Hx7Yh&S@?#fCMRDp)i2Yy_!0OMRw6s4yZFZTJYBq6YPtw^Fo<5rRvw3P4lP2}z~ zKbCXC4RBsvE$EebMB%P1kC%L)>$Fz|$LYo8*85tzsRJiHT&+o4UXx!O_~<~p6<3L= zelX?ke;wT_f8)@h0d+frf1G`M3UsNyd|Hjf+d*{pbMkg3AJh^ebkHR~K^b!nY(;_g zrQx*{NjGeM!P?l;6_*CT!HH;i+d+K>V%zy0LU~K>#YQFMK953nRzFrM$ic@zAnDZJ zmdS8BaB(HY{dq7F6rNN?!rB48pd+|PxwyiS;i{&HmeDkNE}Vi{Is}i2&M|H^*96ic z$x{Bn^MvDr-*N+828in7UpNaK zbbk&2GYd?G)_=1;I~194Dud8&BV2QHX4ZJ#oaSp`AP zIg_TD+q|)TN5&2MLvK-1_+^z0;Mp2MMsVL%)wv8yk>%Z;MsVwef+aL5b1Od^b$xZq z9URQ!rFG7n0d(D=XWp8FHIi=HTO&dwKMJNVS?nG(oe1XqE)*6(+ctwlQKVb1!QCLrAhdY+sUjB!F%_D4@7lT z+oAatKtbg2;YUfQ3!bMNT66RHhxMoWzuTcFvU(5K-a+ajac+1+<3r`$9m1t4WQ0*_ zYKa+=xY|0Pxi?>T9GD8kxn3UU_FnR5?8k(iPm3w8&&EF^iN7QX=P*4wD^dIXOsii*x`JfQs?9C%cXSrp z1UaqgbLB(*Wxka733hR=@SiebRRlmlY@hDSn95S-Abr{t(wFAPsZ;4GFQ3JKwvpgf zS9!~wuVG{_{}ZPqn{)hXZkRMI#^$cmqMwfo;UyKF%rypRW50h|!{m`8^ly1nfpadU z@eP-`bt?4+X)5jcn}Vx?$4 z)xPUCho0qT74I}MF=}MOx2{2Q_3NuT`@n(So<`fcZXOq<2FVG0x-dIy2WL?zs^6p6I4^J$Kc9?pezxedU+jUZz?9q(ctUsH zOvw}ey}a?UOS1^xpOJ?}(8Mf)vsqusT~xxCNg_E{WG_=N|EobOIufL3f;?jol!ynneDEO!fND&iciU{6;s0n^8C7Tb^{e-WORcnE@Eu3Xs~7GW_DX}LqbaJ#kvk}zYK@3 z?6FMPt{~?LP4Imc_t%G&8^D%*NK~bS3O6Z!j*0ahj{iuE;k}S0-YtadVD%M@J)KpX zo#sNPtfrg%p0%%#XJ;bnAdvb|unTXo2L0xfQ~Ra-SHI9zabpz#_-w`vug1c8fhpLG zo{))@x3fw6-u1Ve+e2H)YE;X8VT?RHg0$(}u$4HL4 zo!J(PS*E?MbDj-i7IvR;wQCi-SFM#VlW))0;fF|g2^{H@((k8jNFS6Zwc~;$y9R+L ztaQCnvuNE)kXkVJQV;Jg492do8!cmr7*&62_#=iA^%vfIf>jx?FaS zDWa?-Jvg`&bO<98F{FQ5ssY)(ewBwlzuZ~3Msoh0%ob;Ep08!?&D!32rwIBq95B#{ zrN+Q-2hOFqgEvfYr>Kg;7Mk@xe33b5&Q+>E7{?NiYNj48}8C5I)+)N2mMPCA?wt#}*A|&H&^X^B*Ky zEFP0I*G_mQh}Z1?*ivqp+J5bJcl-OcT9y@NAuIt(lxAU-z{{?x?DgCE5uUTb&I{dZ?0U+^X(h}+}c-?cf5StBr0xvc$#FZ1xn zod+JT5x~;U&dx5LPMg;}GRsF=I>{9;F{muD02OaGo3Bl%4!D_BS6pEJKe@u*$=2Vu z?9SP6RxA-W%yk4RF8XF-@hMxCa>y&|B=9p?V>Piw&c zv|75>{wsQ`_@a=70wAD-wQmhCybmy-z!}PPJ!#uZ$tKa$SJu&ZOrzpgdh?xMDJE0; zg(jq^iy;A4UQ_PSQW3T|q}Q3Y5>psQUeHp1r^`(~a3GpBPcJLjMayBtZYiY7>L6sT zPH|2v@92Bj)I4!9#UDm|4h^I&$PON>eWiCFPj6|n%PjwV2c%MINiq)VB zoEdqxkyp5d&P#a-iwbpcTMOpJIvYRaw`@ar&}rbAhv2IfF&wAgnW(U{?Xj2yIuh7fQ!7jp!}E)&S`wqCJ_Nt ztu%Fgx&HA2tR}eg2X(GrIeT%J7NPZISmn!X6PvCn}iAM7=E zz2(UAK7sEoji+?44Y$fG0$>Y|LRR6N!|zl@vSkgNj(xPJ&bT z^!_t_(d{+hM-|6T`pEUTRw-HCL@lfeL7mxYNsS_ZM8 zb||rg;}ed!YfL*&D(@L%qxgpiyFxD-Xa4v-GtA1HuP)TD?tZIc7iY{d**S|DVyOu< z?o$VME|a|nIM=+waa!2gKPnEy-Ar_^eK@>NuHiQL-#s>4z8CtwS5!NP`)(K#i7nQ(7NkUe`o>VH?FwC+e7(r z1dO>n(9Pj_?t9~lqum0n+6!fA5w2z<+pX89AI+rv+SJ{}^%*>3ukOs2gIIadQvrWs zZ;R))BdRY$*={(le(GEjH!PK2Uj&MP(DzreHG-+3K&*Tw4f@kA6^a+XfN`EoIbehLg-nAR*>6{s)|1)I&qE!aI;-=xBdTvVyss4#Xl-IF9F3r1&k+br&Cpf9-UF8p6J2{P^tFVUVQCcEPV+eZSlrx1 zF<;3iSIG?GSs!BO{5~9XGRvY@c-t{Y_I|=%7Brzif04MU+M}uXWQqK1K>RmRfYym; zn0cd|x8DqYyhJ~c3P`DyQ|To|_>pD{tV*|Gi+U?(|7_Q6%E?0Y?R(3iz-8i-Mkhgs zIH^XjH>x`IV*})>R#xGHR~no4rwsVb;dAkoJ`!mDP(wonvv!Xl6?;jv#_|+Jk-5&= z5IFV1fKODRQP64sBFRunY&K-z3EzVJ;#>V)ntn@iYxdRV2%XX-0jZ|iy(##%MT>r? zzc3XFFFeit7}`h%Rru&T^PHH#`-6g=%LVdC`uEmw)Uq|&fgsjW4aRIT+~T5hS}#N9 z;ARbV`yuyJgW+G=U|goE&&@<4_~fZBPk>URjvpK3D$%mxopVq`5NtVjkfE!O^R<_2 z;a$SaMor#xbLMBQ;#>WKAJ-Wv0PjJF#~%JW*+rbjiJLcW-Puu`o=tq zWA1sF7!xC>ZbH?a1wY(8nwwSL`mikscbQMbc+7m%2<%MFEEm1&hlZg4kvL6WHCipS z2W{TW1yufsnF%*RWHKC=ihJH%Z|~VdFU|)yS9ne7FxfS^?w)2N6IaDD_H?|`z0#|$ zP<*H}4zUc5b7Ez9ol#ePFY|dkE51AH;eK)TmCYcGzx4QRm-Jp z*ui&c@E%4BGuR(^kbbUgUrnWd_9>zR#t?F?Xz$h1F?sjWU)Jd2l?v-ho{9XXS9qjz z&M~F%(QqN zC6)Q*il@-g^1jI)2bm^nqG7zQ!@R*esF65@ePWx|H3PH%O#Kz1lkQB} zLO6vaLFy|KtVVXutwl~CRS;W>Y|KA3Zq^g{A;0EH&dEVok7a&LZ3KXiLQ5caZ}kyd z9GdrtsIX}5qZ*t8!nTv6D{t{F<9fm3=9?r)L^!ktnF-vBg}`QR2SfAdh9i#;Y*qgXSm{q8VUFn+4zqvW)OCTOYAEQE@g8Y!WrN z?I6=SZu9P$=xX#Zq(Q@_>#gO($mo21^-|WUo*kll)Fzv7i?-s8AER!1a5H(VZCdk4+hBavJUzf zBjeaO8#VrUA|r~4;{#Ca(Q!_%f|+}~L+;cQXRB|cFe;}pxaGt_az79kt$~l@SqS{x zcJEBJXQIp6CClem+ByOm8Z=_eFaEL%Hzcce@1~8%#dKKc`zgu)H8U3m3Y&&VgLz z@dPTEn!Q&nUjv}frO$+CXM^u!A*LjIm$OM%`X>XyDB`%2uL-BNrUzE1bLZH02Pw8} z<;G6^HBZTFm$I#=Ma+8rF0Jn149puNH;Pt#1J>*th}u$B zqv&vZr!_qPs-MDg1>I?u$6f5+k$%wd^jaU#nK_7Z!a`!1S@3?gE#INu&?k^I=WRWs zqqyXG)&AZJp9F;WW0D159jiV1s2!NM{K&=owoZ0A9t1nyhab9C?Q)fs=6pQiqCmdq zll+_eqrpNDf@tIort;yJ+ylq3SZyaP-jD9P#8ZSrfXO0G>ccoH8&z_PAuC5(jwlTC{us|hmn{l zPDkL%DsncmDbpkL18E)fW!JOoK2BN5Tj!#olfzaDGhhUMyL$5KI8YQ3cXZKLUP zuuwW;IittmrYrFU+13KwU+sR(CothQCh6|rOh6j_;`pcAHR>ddHNTDJ;jb-E^2Wwv z{`Gzq^TEHr&d$HbS9soKj?3{(AK1JKK273V>kn1`aAz~`-}#D5(Eu!VEYfRivjdMy z>6+ln8fUR~T@X#aLA0@oG1G)nh53ygHV`ZJ0anE+-k<;8gsZyt4K75-Ow$qBboDMK>pNM>NZcz4uX%Ou8WW7e zHJhaQS`7UU0Rn3QK~C?`hj`Voa|&50b%6T_+4sR+Q*$7a z9<|$oq8h;l3>Ei>py?_pU~f5H=mRx@^GN!-k2@T*Mkx+m)N*w7G1l_D(r!Nj+e%Ly z$GxNzkk)lt^wVf97+JE7+t7XFkA9)NBn*J1S}pQ2i5(Edm%E&|L!2pI_jqfE(b~C2 zl>^V76EITw(S>;}l8f~ei34nuE4s*e2(KB5wUVm8c1K>Geo;J&Fj8EM^4i4N5{rNg zs@dl)z^;*0N2>KqKHh7$K7cOf{7$3UpV7;;5&th!G~fFy0`hPj7ef1RMbo8ob1xO< z|GyF_{9cUo6Pab4+!cnAHrT zcZ-+fh~)>)!FBe`0YH*GEDwyLx()_+T)#--4uJN*O|mViDS2BB3YMJ{*>HHP;%i8x zsLqSzM4a$|o!b_`sd73vH#VA8e%}l+EDW$__>RStAsi0F=>HwQtO)(^=fIPq3?KY` zOyN{2U-t5Q#~$5m6TE0SV?R2&)(bjf4S=)h*!?cwOdR~h)W%g9_|+sCS<4*jFKCmx zC1Lv}UedotHQ`?f$&9}2^0KrmLw8b8dZ3H&;Wkz>ob~$ zAiso1d9r+j0JAEcf~LJ`ffi}|lR|h*EB3)v?nE}bVWCo~^J8+_NW6f7X6zg=6^vsu z^BRi)O2NQ@NQdhb@4q;gc`KX`^gQC1BPvx6anHtObP^TZDYue0scmn5V@G2By}a;9 z`P4qqnV!v^Ol+~f#%S$^ZJjy=XJ=sLz;>b{eP`SiM+}l57vIT2qy88;^d#3RhDSgGeN z_PZplgZ_`!rp(48-dkwhC6 z4|!giKU@6!?v2jdn4UjnRHej9_2gQF@6J&_{Si8lww$kV-bs(_M}aBMFBqg$F6Q*=kf$+k-F#%loMD^O4~kr4UHtKsU=TXVvk{%k@y@%KEH@ZLGAS!_Z#JI;jUIm_sI<6|Gj@J=pG< zPsWsb>5pbR_ECfs*Z5k+wH^QN#!QK*KUL0#fTrMzJ+fY^_{5%F{`B}?kOWi>4?5EE zx9`YzB@1e)nzI6GuZa##$N9fCR>gd8ouXonL6I{`YBzH?`zQc1P*RPLFoZ9PqiEh; zEr3Fy$6)29y4SYHtiG}oY4O3rS_6Ntyu627QEL>iuWQ};hRKG_nZ=H9JmPr4cf6Z^ z;&E8~dN1Z}$phXGSPhbF%DT%{ilSj+a`!?yQ@vy{hl$$$@r9mdO6lH>L;>fLELxGN z(Qkymeiv7Huz-3dgI{qv*~Elw1b&wwQt$eE;bgek-hIcpwbYG_+MJd`s&1}!4*dG( z7PLFG6%};Ix+QmiidJrQA|{-0;?w&PoAjA%jyB3p1cSW5bkZ@cPF;`+#8_gxAF0liu~SB zz3ndp%T8%V{`^z3?GJI4ob%CZZj=UOV@_-5ZZ19trXVG2SMuBV-F8oY>M0f6xwZep z$I)~IxsfstxOCpx&Qy!@3;F7?fBHXW0dwWTIqpC5oM!*ES_m^}L zj4X2pm6#b2Lmemlti>l~j}OF3x#RD-2rR3lQ9PUGz5GnSotsy??@M=JjJNdH@ZfdV zDiPGxqZ5gL-nn0&cijKSYvUH{;OWk0?Uc}EqzQ7FnjX|5wREyp2=M;Wuy!aJIqp+u z0Wi0AdA%r#84!Ej2C&`45ty`;A`f8aHDO)Jx#zsRyS@~1OIbs|1T8&xvK=Q31J#L? z0g64}SpwbPUqZzx53xeqkEJ9xe@1uU&9(hM`*pvYVWNPC%XaufDOcc|HXr-PQ|p-*Hse8WYq*sCp-orfJFe6*wjmyyi(ooa1}65i39WVXl0N|> z19Q=cptYo45eptT=SGBa*PtQ3&zik>5iDqyEAerdX{Cvq!o_KF+JxN$WW8t}FC(ir z%cm8XUQ0nT{_?j|`L~y55|p4PMPT@fWDIxMzM|i3uYJg}dwQ8!G>NQjJX1Sv`m0Z+ z@nS+vN3YlIm9c0m&1^N&?HL4Z^YRP9~?0 zYm}SJEHTyUhOg?F?SJuFxQw5x+`hT6C9mQarVlRf45yMC$(>8Dh3#)|_x zMOhoShKed0?Y9CEjI*p~H?;Gg&dY58Z*-bQLl|#>;H(K*m9k$_#3Tz2QdWup(!~n- ztc@E?|IlCFL@Tm|)7n9G>FS>{!lJkSn3cl(i_&HOr>F#n8Yi!1wRke-d6F)|$-`|{&lO#t8o$RiN z+!jSgAC3dNU+(U$?8T?b(ILp41B3Jr{v4TD07$;Dt&Rxkx0|Ir?`!IFeapmhK*U^> z(2F7RH6bBJMCQ#_xRRfYYxKfzjkjiU^U`n5pKni?zAg6lh4;5*ELlgVJG+EjJM!LD zUCar+Vjq$`!7>^z*USR)M&Wm?moOsGB+l=; z=UHIXK_IMhyxeJieB%1`cwb<`5uT2U|Hhd`BTH+|DcBYk`Acciylzy9zqVC?!M$g{ z6t~aajkCrDDX`ORnRWP98aPoF|7+0%y#tTvR^)h&+>!tgY?$_x>i(lbwT3RLDEsoj zxbT*o@SO{nm*&fN-@6iUSEE%w&e@W%2<8dT=FVQe{TcCrOFlS0DDcdZ+APlJ{&C3RYr_T z%XAoCsx1+t0_8DgtZTkwexpmP&pKrk?BZ>UPv@1F?b#Z&NA65aUSHqSH9BJ6QX*2q zn})1ACNH z??TVx!XNul+ z5S0kHW{r6BV``?g%H3Xi)2RU`7h3|lNezkigf0W9G7c})+#1y^wY*g;<^OZ4;nsW-46 z>dh7n!hW&NeJ9D63Nnj9y+&CkoPwADxCYzdRwKsgy&VCimvsA`9$B^1-uVs?!khRV z`;{@be$iix4DszGS|L=zimhZdj^f)llF!oh#NdkJQw!k;*ol9T295lY&g z3HPS_g#_M=o`6@6OxgnHb{x#{rD%-Y@sLULroDu0isn)05z%Vtsyn${pT7YZEdxf8 zUIhCAPml~JrOx03h&sZyim0vhup_AEgd#rOCMYu`&X^!e=rAMIY{OF;@$c#?dkGX{ zbAsQ*1HS;T{_?vIs9hY{sZKJL?$F-(^NGqd@J6Wn9TSttPh>9mWGH_lA30pdK1bpC z;Zsys?aw*a3!X;FAHz>oNw>rDzOYY&UOl%+G!|8wa^lLUx055XJ~ytl(A&00L4$(k z*VV9Zz&!0M8aG`TUytI(4DlzNUj5t}W6#Lj{f`OiKdW&~)hssR^?IPhP4NHTt^amY zzdszh{o;per1Mzo+@uFj|4$6n*&K7(}D25ip%St}9xo|kD+&UR9)fPZ5w#T)@%4p4{DS|kXZ12DVb!1;qa>xulY28gE)|a@+ZAJD62P0dn$-XhANj>z~ z6NblKED2}dFSv|Ub zh4KeJF{{i(NN;!%eB%u%+v3+Mqf5J=s1#O!(>aYR7VYFzr6!1$l?V$q~&Kzq!Y z_%AyPHvAG$x{tZW&f2*{=~?tz)F|9`hmX5PyJ}n!<0lxFz=D(Dt7qT*8c|#{?bVA; zb~4HC_X2TDi+-;0%-<1w-3eAPo?Nv-yZI5Ki&;tE%Q+PUN?x!1A_d7Sh{t$6&T`?2 zdrrJPU&mex7^K<5QOb6hd&F|q37ua6T%4;>!AV~4ip}8O8F%=+;j>zz#TQ{Ms^5rG z5qMlWe*Y9BKePrN-7@$#fs_AIftWs3&XwYerjLGIkaVi$qrc(j-9Z)K7($;EAe=;5 zQkXKcr+kEjA;|#}il3J%FfSYy*{{x+>eH}eB5KD$lW8{}SODcxW(b3qhn!w+BJA!u z2m@Zl24?~awKBGB?V;M}!XP9`#y~PNvnLg|v#G-(q)TkKvu4FkICV$#fYGki>dv-x z^*3uzrX|exgcoXbb(4-1_q8cMqGy~N%D+f3BNFd_vqC2KIbNAbr53A^cGW_G$^oM2 z#V*AIJ9)aG=~OaL`Wdn>8ak$5J7nw*DYT3$5%^?hF|hc@!x9yd$;Z2Z1vY;bD{24; zTW{QUIFlm#&T8g_R<-sc|LyYKZK#VsX_AMTkGrsQmx#BkhF57UutcDnRz2Y{Va@llrf?ukf?s{vQja zui4#K0+V9%S%qUl^A>C%h1>eILUabltY#T=H12Q8z(oP8 zukOI;El~G^(pL9vAI~G&^rz1!BM&`vpcEJs+=gOKTnD;oPa~33=*A1QTx*U00 zcof{ZR8I7-F8xl-mf!c{x&oCU{R5k3)hla6fSokARVq-dF+c+COBbiq>sjP+e$n3Q z3ZFVNh(a#lHbr*E4J=%qe$7n`T!zuliu&?a@WlDy<5}ipE_tXP*xwZ%fqf_ZBp)p& z`JE!+C+;G!6Uv&Ziw?|-#*5auz@DtzTUbG1GuUCVtT|*SY8(*JNJ=vz%M0w}qocL}*m!Y>l zw&{JMlg613QWYLJNLpu_a+>)c-eMN3>wI}8&>{)x4VAnTxM=Rq=!H~Xh>Ubr;v4!a z?dCMM86n{;TFBBEdXEnEb&WeThFviSa~UYKmRK^M)CXaWbUW1b*?v%1QmPp?_(O)I zH)$_rTl-hN_SWKl(1NQLH0HELF8z)|Rri2aAv zSDy#)iJygwzlSp5*#3W1a{u>*TiV%-uE@&H7q01}|6ITSm;Kr^S@V8CnuSQ(a|L`! zp3wQ<2zbAgtu*QD=hqwbw@lYC%{xKA6(@5}CZ^h}dU_uWTGfWNqfP0~GwBYN%3uH0 z5o|Dcy3d#7t~-q~my~nM3)*Q*#c!r^@w2{Cj3s#K5Ae@atjU`$s4>WUeTy}$mQPI(bDApvYBB0TE!v@^hGAy~~ zs`!%nRJm4eg`@%)Pgqp0CpX8%z|9^+U;GLGpP=74k^5muE-3mo?_qV?T|>x!>`cKi ze2usa%TvRE=LqAWGl^q+xiExji3^^d5Hn|H4Xxu-TBQm_-fS9p(49Dl!4Yq1KkgKl}|bw zu|vNt5)Xl(M#)V7ICq&pV7z2ArBIZi+2?$KMOTdE7TNzz-BCNCcXKjxJY<|HJ!UJ6 zs16i+s5%K*)fVJqN{GvaHqI~q=zYdNV?r+>C!3|2J>Z`Iqq;6PnM+TEmZsoIGoydP ziLQJK(;}7ql#-zIk3DupUqC1GQ_5ihxfLE>W}~k0YdZUzd+-|P+RsST zNY4%5$;|>NNOBZjG}hD>iZJe^KDMYt%x%7!-uPmNHq={2wJSaoOZUdnuy>RWhi%t9 zo>+_e)6aF)wpg0-m8uzinrB1YIf~xZB|CjceKQb|B(>@ZPM6d6ko6w+*oE{4;wlMl zw9)d_2TyK%>iy72QP+}5pi(z;t;r4f93nR8;^b2MON>_z(dqClLE!+CefuDxOa6YG zn6r&c-%4I$EJsSt$cm0IJ&CNbPvEnDvQln0!t+?@F7ME&6ksJz$YDG5M37@oaa`Kj znOSJ5VZigLaHBOjSI|4+ldy-?MHv)?#K@gX`NXl5Ix{pnvicqmk1PEz-&BH$FRt}ay%X3O{>^g$U%CZyFlJly9jTDcq&AS|I zb7jkzI7z%?AkV;sTLyB)`j_xck3;w_3x=>q z&1%pu85`pRa=UFb})VeYXvC>F$L=Zk#27A;)>j zJ9Vq$7aJjY&13f)T3JIKLe|h>>NrJB|M59J+=x5Nb_rv8fZJy6#?$A*ymR#nz|=1q zRj~?Guo=cT?;BafAA%Y$SO2xxC@g&Tx;*Z@#JkMR>EC>Uk59$Y`JE9z)JDFD;WYFk zw!$;1XC%xFObnF7F-5^WPXvO$rF?rs;1Erg>3Op3+o!Q|cUQk(F?(RVatR~e4s}DE zhKLDwI#Z%@iBi?mG&fT_&9v^MLRW&A-&PB#+zdo zgbr~Q%M@%qf+nO)r-t9Hn?k?}@wIuy$w~IWqbz_4o98{OJoo#=orCDvh80LC_cw<} zCcpL*7I-yG8Qw@sM+RD_Fe7?ltodx`!ctD_HyR`_Z&QNr^>k5`LHH-*TrEHI9|=Ax z`p;Vz=_|l#A^ij@B4F(?Qp%R#z11I$i}Y8^otEdpl8cKzz?%6N%3TY0d1AteU$$;eCZT<|Fl1|6!*X@JWW9AH`jJTt_)^Xpe;3)JSQnK4Tj{&n#!s9JCy@hRgEVxU zfR7!`STxIX@1uf-B%VF7p7!0&>EzdIKFCRE^Wy&Tq{j!q>LiMO&TsrsT6|}DSq%Jp zMS3dncBE_6Y-LJ|9#%Q!Tmpne-BGkdB{>Q9L-`hpTBZYDh%31PgkExY$D>*(H9bE&n4tn;4S#r1AdZrLl!c%51X`Rdw}mmRDw6kei99 ztrw8{cv$IlBx$YWbt~TSpMWX~NNlb-HJ`p-8hlJXIp%(qn1C37`tY4A%z~IPJi5hy zoOOJOato-|507S|(F%lneZXa&atbyb`IJFbRQ6+ln7>?vlJ&D`%y5UFT(12afv<0k z1!CcUjZL&=(j+~K`26Ln8Rs?Li!B!Wolm~TaxJ$rdkgxKzp04?czS?8t z?5DwUdILoW#ZK$b8!Y%|1mnp%~Wry7%c6U;gnwJWac$ z*BNPlknVrIHtCu$V}ni9^y-MK-`-e}eK9*_YU5kZJfY)}6<<0fgC{YGeQR|{e(w{` zP+^lMpnjVj7trpd?7Q{Ldww`zL+&4KO8G-#3KMWa$gwAL;*;0sfpF^>Qn+X(Ic0XB$?u}BX}F?N(M zTITO4_2G2{Tqc}z8<{&k+f^MEW0cFrh`BW3MW_98aX5L)&NDPHi{s-nHLA>q)~jI? zdAi~**zRht4+;DnE{`jm%=2BH<{1zSt zQahDrix~yRdsf)Ksue_?Z04BSyw$9z=a;5z=R^mG-ry&DM7~cx%_645L^9&$NjBwv z&+qP4`q?PuCI@UO`1g%?eax?Q$TFGTjvt#k?uHTq6ecpIO2>Rvo|!HNvg2=Q-L~~z zB8X%$sU?Um`(w;#y|$xmD6ME56x4t2j}YM!dtf@|zEKAv1`bXd^u&GoBMNqkh8u>mXOZ??Gx)5U06d2tWyS`|8( ze82E%V1zz-5+sxs@BResHFEI+A${%<&NkbXmz_j_!WFrEt6(f=wKwvPaF6-Zd|R+0 zs~vVun7;)DBe%b-8IFUas8g1LWp;QK4_{89kd}N$z9LQC&;G>(N}_KA+boVaW__@i zT&EXs$rJF|-|pMdKRnL*2s@0l-QSD-94|8}>A`;l)`Y$ER+tKz3u}^SBK@Zlsv;SNqx`kR?mrT`BOHe4S!_2M$#kLw)Nk1n7IjwPbUXu(x9 z^Wa>}bdJB%_ygboIb?t2SidHM_^cbN74;w`&U~IB3SVDlczJhnMFUA>G403oB)a*6 zs^@M*B$C44=qUKV7bAR9)E<${G+TcLv$;KNfj6HNU9X1DB;B5VuGPz?!{2#3&JtG{ zyEuev&X^P0O{ocQAYr;Dm~;~s<`N=5Df5;8A)IoUxFFFKwf`Xi`z=;AkutkJr-u$v z3wHyaMy4NLRjS0pkud?V%iFa8g5!O^;TgMT7zL7Z#UVbmJnd=pfk%i@UAB51Jwpbl zpLE(c6(P#UX1tHJ6p4`RrSm>*=9e&aK_yk|LtIK$xyZ1q<*(kGG)Jlo)u3Mf`=2#g z)h0iOH|QRIMj~#x!nf)BrMv0|sW_x?aC>KubH4PAqNV6Av~!ZAZfnAq zbp-d_H28)JCe$uJa6M%-6s0;JfXMf&li6*|NUM0yYKFJc@C-!MA_eBr8p)MZ9auWz zQnBf7@iAWD^WGCUb+uia&!Y*hu=aXxz;LO?%WUb&ZE)<*%lw+X4;k2*CTpT zk}7r&zf}(|G2dF=2~_nrH=$~dtUdFzBjP#kZLiXUpL)efmmRy}yw*1jW+21Z(>z#6 zMb0!U;VYX~_ls&v%`>eB%OkT2k1*p}hB9uhm}p>8n;Ml1itBxPn#)N3)9oZdm7=b2 zq9i_}0uLN16D8U&>C&~pYm#a1&XnKZ29BGrE8vlV{0}DCkgWq#tV=7NgS)v%4|`5# zQ>1-aCzTqRv8cPe6ml{N3!a+;$>_e`L5v)oc#Ehw#y3Rx3~y;`Ub#@w8mGYn4gqIf zKfC3mJY9z=KrX*EuUb0(r`Z3e><0$goTM3!Y|x{FO4tG>K zC$HH@bn{P@_bw%IUHP*{MlCMjLv@P(-CmrO#=D5L@yjj-lMvV&YdqSZg9n&P>i%(Ebm>y!+q+GHq4*|VQ%abR8#(zC6AJB4^ zlD{_YkLD=|AP}{?n;=FHB>yI5;wa_D4KppaA+tUH^EwV@N+ujmA``wnVXw6*y0N<} z|FxJK#?94PDEN^!4VFA>OL^xUb9A0H_xz1k+}A#KE?Vd$-vQc?4^m|}Z>KyEeeI&Rt+8+&5pCC_VT!inWmh3d(ZbpeUL1>92^qZeCf zFPib8?Pp`y`%YX6zx&Q)L^Vk`DHHHUu@CeiC9s{ zPj&zp{YK74z*$xLM*{a8{TQQNzLnKnc|!P3Hz<&@)sgGDB_%0C^T7e1BB`JF6L7VE z($}(!7GG3pvACkIu+l4fJ=!of2SDXiSMzeK{x1sFS+3zNb|#X3iXAhQ7a_@Cx{9Kk z)c`C80?C|6Mg`oJDP7?VCOmZP;Epk2@^~(1t4mk(%!Eqyb-jZP-kt^NbL>M*xyCFR zdZ%+weL+P5gmk}fjgv*;edMi;WKTS0RTy<9FN`(%tk1K%*744O!_F%U9XWP(-nDeZpBa31*}t^U@B=xcyN|@!$YjT zIkTN5%4tPi6!ESRIN4@fT2K~&&xF@PUNYtY0d&(ek|1RrxrND26h7YZy5AEkX=u>e zyYzM=1hD{7X3$4l1eM{w&fE3dpC|v4DjPH-Ne#bSM8*}}VFx_1Mp^1PDtM1Lv(Kab zo9{?bgN%(OMHtpEZm&db_^#)Rusr-_&EmL*TGB`U?>&F-)F~w2{&(Utl^F zA*!cfouT{*N^APmxp?0i^!|+8_m_jck^qi*0v;bjV<^B*m0W$nJ-WHa!xE9VxnbnkuzGsyYS zjqoA`11}x}tdM;^I#eY}eb%^PSLpV_j5& z-xnknz~l4wSrg_QT=6?7c z?drM<9H*fY=oM8D)XZDWrk|klhl7)vNG6<6fpr7sFX3u!wAWzcjGBQE6;bH`;xsdFq;=RXP;~2 z_jwZmaIC-Jv3xS*Aun99O0611y6{hIZT-^H)mr^%cx4&A+QtbK%o!o}S3(?kcfPed zpWt^0vIV3HokTECwruZdJv&_ICd5VSTe55kV4!IaCvAu2Q@*VURCYS`FE@0)U!9&o zZHxbOiOZ>%N^cd68|UIeKA9i%!jEK!e|6GKTuBlCU*r7$t?-*=To1@W9&8A_Z8UF7 z;Zc)3R4)AgTMINMuYpGZB?zRffUWTeAaG~F{m-95fYFctuK(B9>5@bA^z)h|$tMA5 zlgF&B&6~C?q(*A>To2ulP4S#WXj72Y&N7t>{@g#Pte^& zr<+#N_3_3`G)H>2*h|X~F}Fc(Qi*HnCe^Q_vaUXm&xf`O$`i_3)*3}Ur5$hBGOwac@M1 zP^xtv3HvV`(s~-9M(*zp_9u6D(THXzho2pAcKaapGAJ&YV4n5zFo)XD}P%8q7_?opj4@571z3C!NM&G7#F zN4Gq$eSS$%PVCHl_%Gg>F#P1C%wbGJln?twd~Ll$B%l{Y(XpoNuzLP&0!pFt+mv)$ zxTULzKXW~0=xxJN)6bz*wrVp9tAvU!613_-HgW81-f~^Yb;x~}_ZeR`ISO9wtXwB* zWHL5v&l?{5{RnN>`wOF51tn%Y)eZX_6Qvd^mNQm;OE>j17o|OmjjBppA?_Ao&Tq8j zDM5~-vW}A=W85vY(JFvVyd9B7Vi~uWLUzl!0Ujr1e4*l|-LPFpu?l>M!;X>z^|DCy z#|pL+aRF}`{ua?+d^zyG?x2A&e9YDa74*-b*}~WpQeZd|in?m{n}!B5 zpGz9mK^1;U_vf1AX*3hW#+OQdzEfLlUO;$hJhTE=`CdQS>!=yNce$bJdc?Y3bYQ4a zn!5p{>Mhpl2tT0+_!_akU&DkK1^!(QenKLh+!rfYN09e_V$PI;Zl0r$|Ej)AVU@hY zI!i~~rIn>8IgiZFtCEB{=&;M#-KL4?JIs=Z@ ze1$U!JEfx5*7x7*r(*%Aw3M7h3D7Mf-K=c%_V-1Vn`ad*Ipj1WQKZ_N!evG|A6)oH*gO4b4ajO`D>iyC}{<)oKKKMJ^U*md% z4?}8N1SX@+4?gFqCtd?`s_adc?>F)Dc%0!rL_1>^ig)Yce?Yv}3$ORB680oqhf_X# z_GVdFw=s`A#h6PW1*K20I*w7xo;*Hz>NxfF%~>rw_zV?Ot%|ESTCe)zY=mFzEZqZXlNo0(k)J;lqp6&3i<>*2}ic=LBk&0U0( z1ChkGDBKhRVjAwF!A!N3ku&6&ZH|Kc8`YQ1`lM5QAD#m4r@rx@)qj_ zAPspYySLNH+#3$Kd8w1-Dq4ule?vdXf(X_tyD{B#E0f%r$SRJkU}NbYpzR_G>Q4rS%{oE1FXD6SRjpAWI?Gg%xUTFpU_iGRS9ncKKDBlu@n%oa)x4 zjcX>%l9!zmq^Qhkt*$)%nfArCQ`*eU;MXpiD=L?ZL@i&N)S`O%>o0zEe0|d4`0Cp< zA8JF!KJHMTw{D#XK6Bv!*Io8*J6PyzqtlMc@r|FtGL#-D#c83UGq1LDW{m85tOxO9 z;wOfrUfj?o?E*To^p&OMGG0z>G!v>X`(L6}FQQ<_ z`XzTu?g+Wdev|^b{M0}Ch`gs#_Ws9=XL+=;G`pG-b(m4Jt0oBv6~>@Fc-L9BbXXuB zS;?fOg@a;@Cs+3|Zxlpcm#Uojh6nz_&Lqd|-Zp62$8>pdLThER58_x&L00Bu^Ykk3 z)vdXoxy}7vMR*()b-^Onm$tHS`+%h1bRPJGp%vSW7>$?uxK!J|@dSe_4*$^eLz_k-dJ# z?+t5Vl?!~|vq%~kXN97X=>zAG%%H2$ z3*i|d=SRauKT5`Zn)~?q;gwwk1O!I@Gi^tZ+r}3KXOF`)$a^p9(xCSE-MW2Z>7k-#r&E|N7S`{HH-Gi{dmmOHOfuM1o~Iw0gN^m@Uk#l4Aj(HSqi zg-5xP{0Uhly7l+bv-au}=E7|_q{qcara~_+-Sqb}F(h7O9!Heek07Cn7eK3IY zz!hD~bSCG1)$DuSr$`H5Li3oLW6JIY5(^8Gig>T5wk^){_G@Nuz`tLzSg>z|+Nge_ zV7%4;!|XOIinB&}aGB%e`hVy;%b+--woQlN&Jco2aCdiix8UyX?(Q0bGlRRkLx2Fm z2G_tO!5J(#43MS1`gZr-+TFihT|Ixg=bY!wQoQzo= z@6f;SMPIO&N7<_z*%U;A4LsIGE4!Rwvdsw1vlG3D(+K*!?zR(9zl-INaYKZp`PxQF z4mdsCl&|s^h)xK>$n{W{kTr@K`=nmt>F8Eloz1Z6TBk~pe@i8^L;d{Az`rm_KofI` z6Puxzmy3mk!H}Pi{_mHsUs@|o&50krMVC3wM9n0~YmrRW}6nIkq9rh4tw zj-}j-Kl_DY)T=G@41<70p0V1n9hgfZKp=1CHZ=miV@n-3mnD*H_WL!1TxVl(A-2%P zks+Wyb6fxZrKQ$}jqyv$n&?=5ql-S$Zo*u_N^5fuSJUCMxV{hc3d3d;#{}JX$LXTP z!&ez*Z`oLDxQ@wgD9Tdhw%B&K!fXP|&T7&A88C0-G$$6}ae`}eB|&jf=(T3I$6S12 zA&q^qDdNCE@ytdAn4X@;!#P7$pr>2d*hV^x!n}AX9dH^OE_qV4W7|yWh!ML}VPD^h zwX)i$eWRGRJ(bljShF+*ZkSC&`fr-7xlq9)=RyBq(~FTLB;fgy{oKJeB!1|Ha>PCQlg6Lo-zxwd_3D+?Dv5 zv382O$mj~($r)HG{*%8F3cL)!klgmf zOGZpJKE!%GoI?tNjiMYot0 z6A|I2zjM^{^`!018(pG^@t3H?UD{h~CHrtb8i#h#t!GX?lly&HEv3NrJxQ@)sU5eW zVDe>QwoN-7zMxS06VsH{w7t(BrQROLd_Mk**&PdR0W}8TjFnbY`cT{xIhD1P;K;iA zv#dSm)llmBd!f~nK1onz^xPfdf{#R`ql_@sDnJ!f9!te%{GsGGtGtgg+kDM2(0zdW z3Hwi3jcg#%i7c4!^ShFZ?QY^tW=y~zYLg)N6d85fxN21fH?8S7($eYTs^mO}Ub80Z z=%UUrDM1h=`R!sXaUv1v=6vovSbLTTW&X>t&XE=qq>a)+$06m#LFrUCdDD8Fd~nBe z^NR&{Wi3+QY{-}|st-JsMRv3=#*kNgyW;e{!T^DGFh`y0^9!}6^Aq7$TC$X z$8kAPPI|c+d?vEwOSx8qa8|S^@)!Jh3ob;%?s7f;mU7Br+Fm4BIW_4k@}6dV;F;Yu z7j+Sqg*89ekL6>#UkAooG9{Z}n49Y5Ora0G^4M#6q(aD%!V#5~{d+6y^x?)A-OTR_ zP4S2_YdY4T$x2T$)lHl)ZTTiHqrq{$RZ60X-7TjmMN`g7m<+0Nkj`R95)q++BA7)K zXT4II|9;`htyBJ#H7E|y?h^2(M*JYu&n-drVm1cnQM!3Qr|?Q6o@$835ySOqQS=Ns zf%*F^F|3-82bxd0Zp!;(C2;Xxls}BAoLRb-DBVJNj>4fd|u^xMZN;ZeV+`(`@qqFk7)&tUgZH z+i7;~%9rV7gx@>Pp7~q*z+dC>J-NqKiv25;pNtMM0IX${7gYQoH%ks11(I z=ojuSx8Efv+B*AVr;U_37G1Lx79?O4kNYZ2MJG@Ry^~nB-RY;;W_;ha_>~Oc_uh>g z%Gm`WseAwoC|Zhf-r=L@n`~gA|I}#v4Eu!S*dQF0_jCKoXQkG0YOcq`2wM zHd6iI?&fw@IuPEXd-6i!9~6;0_vrxlVM@C~5RO_m?T-G8rNO1cJ{_NLw0qo<`c}Kh zK(T!8QYC1=wjIJT^b#5Y*qpWZwh|nDWY`F^>WvuWiA@(WQt$$`tE?&HMkIi{=Zyl! z7NIaS40mXtD@R37oYyY-#(d%dDbz2%CbRw_4MU#YyEJ$ft z6CAk6B6Z|R;dZ`2KIqN>lldnXWFZX2$nz52{dDs*M$I19{}y9eHbR{(QPGFYMnY9I z)^O{yLk6&XTgJ@+Er+po9<<^lXB@HkXH6x{UNZHq4?J!#fQ`rzw$tmVh>2=QxsRf< zxVY>X_otIQ;X!emog!Vfir<8U((ze?*giu%xJRrUWJe;Z_Tr4c4dJ6?*}Z%U2~MR6 zqVE0(DtgFt-v4wXT%&F`d-nx?HOPWb?w-V+P-gZURyeiyeLIkADeQ{-R4sCO1 zixwTmvVpVsEY|!{Du?j8Cv{8MaOcdmIb0)~jWaD?PIEUmMRya?GIea8t)L2v6;*T#3Ntu#7_BCALJeuCNDUr!FL zIm)?4hvKt8{qebY`4BfrW&C9RrUHkT^F<9g=s2LbHL|L9Vpt=J%X#PIq0BX>p^sCD z!?epcG2YlMpnzscSw@D&Y@Lyc>MIa7Ec*R{{kS`lEI5g4rLcFe5wbYilh+5hV5>Cb zFQhJ+D`*9ZH`%@{kK~iLJwtukqHxi+Fl=?QHVup>Q;WVf`}CULij==K9CHUB;oUKQ z+U*5P9@rVb_^2EAEv5G%(aOD$LjA2PgLV za1w#oyKC=y3p)bc-`ETBpK&5^`u~nDhhNoJtEk+NyWq*tzm6652^@`SPw~6)wP;!% z66jg%3YX0mbacr{|7ag}VKlegT+bd|z0_RzMh85zPaXMgqE9xWO7}zhlB=2vF0FrRHRk18K%hZbgUN1+B5@&$Ph67^I<8p7VKIa@bWvHWdE zLTZR3Qh4X7hT^2+=;H}k>hUMbdidCWVK)`@@7cI~OzAs)gEDI=Enci7i4&aUZQ?i- zjDCsj785vmY8DCf?eMuAO&F%*kzMLT+AQ`6{NBC+QRNZ%U?YON5!dBQjF~M=l{e~} z_z{+omp6(-yGsW=U6b309SmN-)TPU}42yWVya|y-LlV2@$v*%Ad3IHriS(DXr2YIy zU+k*)XltZH^CQnKzi^ZRAHZQ{h}jxV((`)SN?Wa|1E#nq4p@nnHABU_4UEX9xv9D) zUuYG?%h>rCZez&^l^%L@Lc|)Tj)KS5|}-ujQ)F!&5>6ZpC(oU4N+VYd z9ej5}I(^6nFz!3)x^jx=0}LB5?T_D%GcRwACp7-4z73VzTCoyhq+*o%s1?U;xvzwA z%Cdy-9NRClv>0n1Wh=AOb=c7G%A+8i63Gm?aD(rwe7;2(kna77agp->^qFYjg`=M> z@Ob==Z2va;jCE1<*X8=E%MDlf7UWM_@yi0G7ZC*Bq2yO8kmylvk)?24e(b4Ii8q&{ z^nT@)gCsI1%iF(cZliNe$LCBQPQTyb$ued7Z zj#xX%ROW$4>zte7-N>5sCq+va2?^bGGlR{p7%2Q!!EH{LFkzBXr|UMcF?W%gP46Jv zXay}aj`5iI;)SL%$NgO-I1e$0@ALO3SF9%-IEGr4Iu;eUs8JG)Psps!3tPS@sZq-B zNt2mD4n!lvuq|^4Q4;zGym{2lw zp2T~1RhK{EqqSF(6)IL~Ph}h3jpQOmO(c!bh1_4T%3u8b?cYJ6_=987|Gu-|mJTZ# zQnZYJPf8yPHbuMsd#{nsiF|iA^HwTl-oAD7zT&cu=5JuV{oQTZFX_7qe{q{xX~7cA z8Ypnm)v__~wM10#oSJb^S%`X2)1Ltt3T!%k!#F!RE%$NNGt{+G) zD$>_F3{VD?ius($1`YIw{)xKjcdLSSl|Cx-UWZp)+AM@G@?-qg{{7u0WX3ScX2WmT zjFkBBsRKzayZ%cvWVsEfU{}%U3S$-O;0<4KB;FjF31$kBTd5~tQnXh2=Iy{i@cc9W zJza_RrdHkhrsH)-c_+KQL++0SFkfl-D}%s5_TX@^SEG6x=rroEgNBF1`;zb9v8?Dy zO*)TK3mTjPW-q|UY6#wM);^WiR=GJ21$!CC-IM_jfJP_`s94tT1J%rl-oH7;Udzge7^6sy(743 za+=^Dd~^vogF?ucU%p`r7`b|mF43+_cc5+T(0sMWd{_Lwj01hC(-4$hr&Iar%*elW zmuF+0qYsjYDZ@;_uDqAyRnop|taj^WCp(Uw!y%tRfrh%7TSV=Z{5l+;Rx^)LMB7L= ziGr(x{+`YTidn!2LlhG^MQN@L`7^A&TXvwHQ2zl@av;H1jJN2x!V^-1x z2p<-MdyjS+l6tZ;+OvgBweBL(dBqfxH`ue*#ogU$Zk0@Zhcd66-hl63%7uPt{+a5R z!oAxg766cEZDd{Sbu?aJ972H3FNQMSZNGOe3v!!{`H%)2!F0RM0YfpXGgTO%zyZGM05 ze#`SrTB(EHF(Ub{_8u>9mNGW6fqGv}*wK$|eun}&7jFn<*2Xf0kO`d3G2c5jugPkp z6*21dBUqEnZjd z14$0w9MNmwS3%ZeoDe#MTzYf()$$4ysb7TyMlo1a8qEXJ_xrMkHaBX3p~JnZ^~d*c zBA1P0;vwdguYE=FIxZ%^ALid?H*R~*(53bNomsSUdW5Si{|MbU&`o1mw zBCtpHoYG77>)6r3{-XeOk=Ty^IdMDb4SKvjUUkchXmz@5(fJ9&T(1t+hD!eSl0f$8 zuBL`wjC#JQx_Pp$T~^Ugh;&<}+j8aD@*2`TkJOW%8xb%{4|T{)5W0}cJ}iUIUSk_0`bdMSMvH+X0#JLh(E1pLJRi<+e!A8H{q-b={J#hI6dr~3mh)_9FC zG4#0%bVE$FU(sPoCB_9_%qNVgSf!L|G{?Z%I0mfRPoy7B18(O|;R2?>R5~~%?yOJk za0cf=EXngyZ%kWMYJ&!KRx(uum}@e<47-Cpl(ST=g$fK#!2EM5%QLo|Xan$s5m9A;)@BUkYYESWG4nT>vb9yZ}Uhm5qsN zpodRHRxw=t(fpz|4j{psFB(&@b`ZJ@AgoG~0=2%T=%;68z_t4bT`Er9^`UJdh|_@& zNLKld@gIaFfD-)k{U8Rk;mXfxw|tL?D>G0tOPy=|+4Z?zbR1cV6MSjvRzq2p%i(M|qx|{Y38?mjT^=XtX zBPvV}!Hzi$tYEy?1l@>3uWmC=Jg&tLx>T9_zeVe%XU?M?47sd&(9XEER^U7kNg{o8 zmc8vIXFgb!F1wW}*-64auUx{~gHElMgDtM93#MIL-xwJGf_pAR&xu09wxJ*kpY3YW zDgur)+WH*Sqpi^phcxd~JO^KU5b9phN=^2`@EYnSS>UktP?fZ@LQT znC+eD6^|D~b$|ZUdo6_AS@x*bY{rHZ%3@{?Nt{Tte|rb$?`vd}T~=lYht}iZW*vx2 z4ht3z{qe_pibII0x2t3UZFa=MX7T=VCxW>)B34SB-wF96J}-MACd+QSSy|5uQIZHY z0M$dJuFaD61izm(U2~7z$Gk}PiU$KTE)F*^w?q2r4?3GwrWbFppJmmz)PhKkOg8&+ zUE=3gOTzv%^sG@B{0UjQ%$t{JC?bXR(8e%bY*T-hd$D(Z zpT1gdH&__$Vqk=ptD%)2mCaQy3-)4)j`&1w?)0NI9a&LfFyHCZ(G{ybarT((6WH%tIyk%MTw!_fVA-M z54!EXD&jf635Ug6uz%a7ba6Ug+fHr9a3}mcaM0%uHm&dt3=F>g$bd+wPdeTHV=y*o zDe-{)6n82%b=cr^>=f9CRc*uJJF}K+gU!T5>>J?3F0$0Z9dzq)=+(ts@BSIkB3;9y zIhC{?j1rhJSD$&T%*kRVbqap;{Jg-N>@6+2<_%!ej zS^U}Haem@excl~4J-EEk{|_o=C*qkjjDJ(RCm-?si_;-CYtUyUGOHllHfVsq*=Ek9 z+6SoDTo6A>i>g+{6rIWaxw-AxakaL>QzizYqX_S>CT=?p19D zeKal(8dp3%5vjEuKjLqy`7|6K7_-W5x%53i2^F-ljST`|;3&G)p82^9zkYai74;dj zwakd!`F#_XovH-wRn!@tv^0+WwFSPO-TdU&HJDZfz4>`>tK%GZ^7jb0eth@s zJA4dAGONXA{$<}7q2*J)3JxjZyW?lV6QZ*_F@0I7)huJ8)kSMH-y5mB4{G4#!67B> zLY6&qSIRj_+uJ3u0H~%4^gAHvxv=hE;@pz#mYaGzR!P`*K7vT~xUV({805m2t4OEkiUrxSGJ8s@XOTRtTd`_g!}xSo&CAE+Xs2~;FH}@qW5Nsrp`Uf{Fbb2J z7KE2M&zgEn&>bgY{n_y=e13b?=C22-*uZ!B?XAOGiu_d*xseu>g>Yal9X`%{^rw&N z#J{QnheagIQ}^7+i#uYRPPVE5uj9fP)I(0RX~EY=FJePqB8tyXbI6n(T6X@^;r3rj z_*D|Mdg#>`8y_3mr`K=TVGo?us{a{}dv>-FD*EB|`fw3-ad)~yIRx92{|_0xgWc@m z)NX&5;#J}1W!BXL9d-}te{$P*7`pGvJd{UOdqm856Vx;4u!?jvXmsQ>V<^Yv&_ zOIv+@G|JU;_TRNCp&qEIrO4Nua31uqSJiyfhmT4}tP z%ttu57zUO-PHCABp4)dUQrq(RNuSQS=RmgAE$wqZOK#z72^re(b>Q3VWt{NlI!%@j8n*W3k%~D z?K&epam&i6@fi1|Tx>{Cu0;}M_f%<^xq-Dbj5*DoU79aE zporwd00|0J=N8WNzr1ijlm^u)_Oga*#?jA_Ce$k@=x9qS;C5SG5xFg;X654%VjAvh ze@KQa?M(>o!COFetV14udAjKk4`cf4B)+-GYLdH2Rcts0q8D;NA8V-zbtmQm?oAZT zPkliEao=Ue{*`#-i!B#-U4Ur#tEaEIl0D~|+6(_vJ4(j4uatqJwK`*4cvhYEKQ2gG z9CX_V0Zuc@tj_cqr`6b18F&m3J zI56&?&H1hX8T-3;nrQ8&)y!N{Uqg2bAX)68ztO+(PJsL)MJ{5(SngpR$Z|ebJWnhM zVchskhT0!F%POpmC=b$GP%G44^l{n=b2j8LQR%C3*sz^ZyylS~bQz`o6a+cfs zV5K%-$lJ@7OlUabMcm#|&6H=q(LZj>cc=jmoKOo-z8w_j^x@F9B>7OWoKa8NCi};z zQatXua3ezBU2b{o2CfLSG?+(3{oz4HQ}{^eCD+L0E9ZyTP#1>03~EbR$(?}pbRL$a zN%>8jm%;J}jKo7P9VD0y$wN`fV1y|}%ffHB>;5~H%-Pq1$0ll5pi z>f8q~$f;p|sKn*3kMc0~5U$i{8H^r>LJ@ znQ!Fe!`Rxby|7}5@vkZkK^K+V70lu@p^+@eJT`6PPRNF<6rO45cRy?O7zg4^-W`iI zv^7e~LZ@%sFIhCRLO8v4i}aS_8vvoa<6*_g$juDqrvjy3Xcs-dK$wmf{Kn!(q(wb1 z@{QLH5NnpW_WE6|wH;upS^hcR8~*)k2^{q0*H{SoaoaaO_lFxRnZcfX*kusx$rc5)Z4@}8_3fFk|>mma=+`STg-XHMiVqL1C)?-|6vg|8uVe(8gY^8Vo}CFs+kkfMjlq=Cb75?0rBjcBy4e9t0a($VI9P4d)lMh;lLH6I=Bc3!Yse_s9z5j~t5c_PQUvo|Bhn!1i# zKZjYoJX<1dx0qZ=z(zyODc(Dwz6S0r_2m9BDLTPD4R}bdOMKxzqW%!%SA2*yZe2Gx zwE0&N@I-2?CI`F9dEEdhe|etM2C|sFF6QjLg>Gs@p1)zEhDOcp-8$>8qzy0}6z|78 zbTGV)-evze*Z-HRE)+KaEO>|j;&th;+s^xb$Ra5u2R*Hkxv-l5kktu&GZ-6 zjHd+3#NaG}#=XGr_4IfqD^t$JT9jNG)Y5G@+afCEl(ieOL$_bDCt0SM)7XD<+u=oA zTrW5p;qB)yX9yL_3rGRHg7PzlJ%FYu{+;&6n)=2#1{2_SS55ZatLU2!J?>DH!zXS{ z`Wnvcw?o>pEzF_{Ky5smHC|Oj7T?r>VVx760|8~(&BEkDWe$lJu7VWm1cB3X**)q2 ztAmS3!9)@aW8wE*TS-gq57`Xs&as=paMZFM_0mDB&j{0HS}tREDLraz`3o_VawK?Wy+aY2uM>UHNPWV{6kK_B z~|0WUnuqGc}Z4nUhsfXZ2irxD_I!055Fwm9f3iNyRK}d7evNKBPph zwvOTW8l)SG7xq&i>LK%m^4E8iNLb`jIvEgr#jG$QhVS?_W&|w?(e@pLAuMkPg8DMB1=H}c z9l!sB6*Arjxo!LfLl%2jvswC6iQ-@-b+q~^u78uvZTjaS`LmX#t^{P(E-~GT?n6HI zxM?^9`eFw|?HG-%)(Vk>68RFL)Ca7P#eKmUn5g!Vo?q>yrRI-bxY6EBBO1j`xhM?9 zror!phw)v2?7!4OC_4Txfg;_hr&l<4Os+dP!!%{0Dse1 z+wt6xi=w}DnD^`|LyfX{zci;&#>#d2#+~IXZev5<43%YTQ8mi7fpS)|DvI&B`59)Z zPb38jkv$(aUBd8jTIC6GRhgh=gH*-LGuI4A@v_52kJey}Lzk9l`@=a!gB+kw zVjZjA%!-M!$gm0ffELu!l56G&HM}Qlb~|NW(Y&Q9Vf+5XLq^z%?fibm6uBkDcA6&X zscO#FJmmKnPI~UYalTr6FI0o~$*d#67_Cs8-odQaP7u~TUwWq(&+@GMZUf4FtXF5} zlkDQiJm4{q(F8)}6SO|wZLQ+ptKP!<;Y1v~x5!ctKIw=qM$(>g01sV<_^M&Z@wP_2 z1{lVbClco61^j4>iVh{b;RW`n1#Rmtn}~w}Q8xmME4&mMb;67CP1R>ICQrV1q(G9W zfayDx+5*4Z!y6$NBVD3tjgn{N$ySBDkfnS*O!k!4zzl7-^h#eoCDE4QlV4* zMLA6fZ*3kHI$HMzx^789c;hk~+@Dg5mbczgO*SDQ&M`iZes*WS-??N=g8IzTj)#Z1 zS@b;x1rUw9JlP;KPkn3uNLbgLc%f)S2|No`UzUfJsq&B~@l%7jSnd*7IEzREp;HTT zoXqxC9DMfjVGma4N})L;SefehkHEyI>-6n%ld(_F8zzo~$>^;b0VAlfo)zAtsh0bf zHvN}@!4Rre0RdAP88x!r&XnoJ;Ap;YeTY`}Yn+jx`P4N3@>emF>7TJ08l9RM#E!N+bhm9&@x;1tYm%?Y)YCI%57tRVHEW!{?bGf?|?-SPI zuY191dWzD`PX@T5Ald{lc1-ZpWWuBhEj}XVUe5s?0L z8tC02%My#?Qp}Z-H{v;@)i;c0sqpuJ)Os*1_BAx={Z|PAy*2_zJDTKrS>cHGnlg_j zho1x`ysShFvZ1X!B^O5N8WQ?&2ApM+JISU0QglZ&Ow3;;OtxPJXi{*bv?yyS06kCg zpdSpVslVVM7>5=2GrWJcVuQ0Zcjnv&4RwNs`zbRCGmA0?1I5#aMJ}L6H=TCU{wk$E zFDQvCzw*-i`^LK`c@ojj?n1J`1Xcw5Df}{*feiz{h4z02O;)|3wgA5;FKUK7Z#WL@ z<($+qfhx5)aEv-~Y0h$LfT_tM6gvDpW_IObQB+D7pX-q$)?~POCiEV;JEeGiJ5l@$ z1$&e9Lk|-#0=C<${1*#8!eSx@$Il?m+V?Vh19sDJClz@%6SXu6w-$5co&;Gi`&O^lH*scR{6FE4GQkFC$2 zGL?M|Pp;~|;H$*y18&%9CE%#j6zYjiNxLrAUx);LnNO|>>r5-OBU!9>VgYr(kxxN} z>%%Ha3CnFs>aqh+T zWyCk@-Bx?@M9kC9^CAW`^9`XB`jA~O%>oL>ESZm$e8*&?q8xwy^$LjEi`>gAy-n@Q z4Pj599F+ctu0{8?AG&>kHMrH`l=Jh7uM2e(-X~5d@|MZ`H2qR|nY)7&lKgq(*~ z)`o(-&4F>_jOdrID|g_=RLaB+=ca7E_zUeD%)8$C$Mu3M)3vsgcN+UZ`isqC z$WtdPcB?`k@A!$n&hB%L{*sXL&Z^V4ulh@(aeGNdl>*qv6zLvH8Q{d(TB_Sok~+ZM zv%0hUqZTopqRXrs0Dau}HP2)tQVt#pJnd zP^dbv)5J&*6LSe!A(hz*ABx4>__Afnu5pn;$wKv)g(r&~l3IZ#4aC>Iqt9 zZhi?TiwHqJvMjW*#3lr)e|zOm;l8i2)Ab4BIkl)Sng_U?QnJ7~Sxi5gGAo;K!%IN9 zOL+~0)X+!mXuOM#Nekf=1lXS;=?QKsd`e?YaO7^bE0=+v3J(RA_B2no9+JeiawZ1T ziv)ki<9exVb-2PD^wzc5MUZ2g-WBwHjy@}-CSlRCa-bOph@gicAq+LhTEdM&ls{v= zHOb5g#vR?)V}@cRB7s!LsJH12!-VxX?u0q^k1_IfKh54wBSy8kvZO5t8?Sd*cb@5;} z6gYs4-&ct@+!N~-ot9Acg!sw98WM1m=@T^o(6jM3)nsqn$ON)MlzNAptLA&_Jy6d? zjd{l@6c(EY{8%AM^pR3Jg0q;*3c023u%6dvrnqVSKD_Z{D|8l`0pFIc`wbd=PY?p3 z==R09-3VEB>6G))jQ!B8p+l*yN}CXdROD79cDSmdB@mlLn1<^t!O;ZoBl_yQ%kY`D3=b;9PKeNX!za zzEy!2M}Iert)LzmY+9#f)u&LYmk>|bw@})c5Tad_BVhH}$jkiniJXNYm;Tz;(cW!L zOi!?O;Dh!*10oT8t!3e#ZrpW3TlcldbWS1Ja|=wUFh&uISTf%;UbzX(otQF&Y3q`g z@E+Um&o8qn{OMMo-p<`P|BtPm2RZ|N<8S#h#&GfHc80}EtMbPy2MF93juZxj);~a2PE>EEm^Z#ug*qsu@`{~gH z+mUZ&aT@IY&pkzG#D3JjzZ6##{~j}b^;NyhN$-_^@T2(OiM*DKw>Uj}Q`pwflo#l4 zkmp}n0$$U$-d3i=0T`*^#r7crgkF(KSN8`DEULE?cHWa|llSw^x9dyLPnhWy^Zk?V zhSvzHrh%TRM+|9KP+EOMgY1w=f#*z=aANH;LoP>?M|x8$9h(#p;QP6-%wDJLG)Ly} zyl^49!FTy^hn_6v-d7T84o_)aW%soG!4(E>X(vU(uSk+(c#Tj+;*u$r#Vj11c&-BJ zF`z6mZmR}#o!>erb{r9$SJ|_ND!&sK|D(`z`JTb6W}l?LGmX^|~i>O;Xj?-*)t ztR@M`fd^IlXFMN8pD}7UY-O#4E1!-}!<)bioRJMrHzF;yrmp8Ih9*VY7z~VlS5EBY z5~O6Z!ah?(h8m$j_Nm`7`lm=S7Hwd<|k zT5e6?gT@7>o&t~OnnNZfkJgY*4JK#OsqJVeFIIhK2q{I_lz(8_Y}7+JAzpj3Rq;+J zS0t5=5G10~*iq>0TOlinGztKw3vy1_|6to*xxra{v0{t!-n0qp+{O&fM%N)8fjPb> zJmZ3eRP`-k)YQh5>e7$el zSJDNAj}!S+6W<858T#Lqx&QkPQ;{2d??4^B*lEWpY~J8P9;0wwbjoi1-Yh)WrntNE z#@RIQOY4;i?E<+7UVB=G$NYErDfJl!hWIWo0Ds0K?KqltOm$!2fUL|@;CA}Kuj*QB zzJdfCp&rv5zAWC8oG&gMv1qr5{g6eG366q1SvvKl_!OQFAB-&5Zv5bZzbb{y)oPZ} zVPnQRF#eLb*{X{EX+k~ll~!15lNDr~0dv!=Nq9aQXwoc;ElmtH3kyVl%5}Ns&4?dR zs$eyrxx;e=rL;)9DQEiL&GLwd5O85FqZ;1<8?HrS?t%;svG8ZPmdNxks)mLQ}U-nI=HoAf#ePr-@LhDIZ*7jpE? zhjGrbmpGHqtZV)y4_{w;$vl_Hd_EN-y_{@{a7VVk>>_$1$tV(9hayTj2&s03D5I6Y&L;CoCSTb(Jjn6xTcQEdv>fvQ71}Nk_efC?=A01TFe>8r#eNEc$0f%NU+pNts zT+LLyRMI%?xL4NoF5I0j{n}A7pe=XzQLjY48LaeK=|Ajhn;3aHyCsP9r7cU^wu#!^ zz8{!9Zz|o;Tr1cAiZ*0nO5o~>H+9!c{%9iLq*9OJ7gajNJ;9c~5h@2+`K7|{1)dIk zgQ1|K+|SIN3=2exinVB-*q}aA=(-um^nn1Wv$FMv z4Lx<}>;4uKo>!<%TKHJ?hY_3U9WMlvSH2S`9zVvuhG^(Qb+)?h)1lK>0(uWxNS_N# zH4a*V$~0xl9k@m&;tbT7r|Sx!0YmBE1t~gPb{Hb3&`TI=AY@_9uCkpIaS8PR+|Fbp z>GR9)Ggj{j)^>l4t2J4R8IjohtL%|nv#gQHw{TKmz{887Lm;u;!a9Gca~|135N&lA z7^(O$;Yl=&eF{?Z6{dSJ<<1Bf!13JRHhK*j(`35#_ZK3ahVkH^Tue*)l2gvPf4c6`U)rNTc zpBy|M8`AOoFn*iiIVJ>Ktje~HmOII{%syB}auVjmw0!$D5Pi(O{_bmmtH-HePBm-4 z%GQ9A-0IrG{tV&Rp-)nW>fvh1*-UAAJc zaiJxu7X6gJVhD#wklpeIfhPS95;PefUFX<^1}FAHgV zh1kIop)aBLU$!8BvGkyV;^_aM{lz3A#PeLx3(}Hi_;*)>=v^3iOFLT2W-psD&t)M+4f?)B7 zx2g`Rv94xEhIm&6Wee!hf3-c*esU?FVq{>R}z*ic2SEClFr zfVsB?d3bXGU)Pzvwg%r$o23qht#Sh?o$BhYC@%gDtJgiP>8A#`BRBiSM@xHSywD7nd#V$ z&r*r(@25=6r2bn)tXk-}Tm-gDL^;LDvka)_G(&00piPipGeU4AL?oiL-kQg?nJfkD zi|a<~YDIjLDfem2o#)OadyirvV@rqUk~Dc&DUzlc`q?(&9aOnCj5ji%(+nh7X%{wyJivTLX%dx)61_!j1*=`sRlhcmG82F}%ZJ#rOUUWmyP}bc_ma}z$(sd? zPxDsKBu4A7BqOGpOl2UpHQMA7M_?$Kw{=&8wKlGC3@#L+uL+hWcqQ_vqwdH4Oh*4$ zT4q6j&WgM+Cd!Cgztp@9$S2lFrE?!9!{7v@0&8mVcg#}ro+l)amy?<~?U+mjopIyVjd76=vXmNIY-nS=7@hWTX zjl0TdhC&^iS+i27`nE~iRu`V4x>JCym$c|-O}52y{f^0?FFFB5$$|{nVg>D7#?dG; zpCUM8mI4bfsG?hIc5EjMZ#q|Eyp@eougglhsPJs3%|6ktgwjfQ$)3uy9?jN@2nDux z05Ng*-49`?+iW#vZl}}|-fFilbDXt?6cM~ONBikNCKBsRzI4o#0C&)Dq9(565)*Fq z`YUzTR`sKDY0K1AXoxnL&mZ}0Wx??aq>&j)#2Y-|LV}&^B6KzsF_7n`L*{`fG%asK zP>O!34Eh-4V#~3FG-$`ufp#hY{Gq~wxWRfVue4p2T-!3kc&%ltKuD&yS5xs9`XLa} z5(D4Xsr~$kH%;YDQrBr3c^I};$xQzd0hpc9v6`XN6;NM~Rhu@5_EsV?LB0F1&b!_A z_}qVPke78;f*mG9e%}qr9tgUhvJ?M@xG@wqRNq)Xc`}t~9K0ZV_4x0#zW)y``&asZ z=iGao>Vc)@V?TdPs^J$9j1>yv&gqe+swz6wzs~#|lpg$RJcK=c>6LLK^vVUJ;HNZq z81pLu64RNR{%B`AKjaff)LQHZD_$gJwKPfeK&{5a&upB-He0bpt%4Zdn zrdfxx03Px?HIb%(!E_30Q~Jw1(k3$n*-j2idJPMF2pt^`$GcWlyG$W)*2mQ!a@>}_ z$H%uNqCDPS`I|#|nrK3+Z90ySs$gJo8kcV*A3mg)cVn1639af8;@dcXP{n&SX*|Fi z8vv-gM+*6vwfjoEWss==jJ>tRXD$)_6dgj}$$Pox57x6&cf8g%Z>s8~SBdI<>>Z&% zo%pA8YlX}Usy01WVcMDwSnZteBFb`b2ZuZVRyf?FO;n1#XfHm6mtdS;F8WKRl)Zfm zW%e{bo!_rw+9a-qiK3_GS=gE7F%Bb@z<8_(Vt1AXOjpr`1G0MG-r18ZK^H4_MOjxX0wG!E23$?8 zlIium8^%dUTR|aZn*Zbg$z6fDGg1L9A%X{RFL?T+2a0`_MW(bd>b_IF*CxTlKCIB6Um{NE##PMO#+J8p0sb)&1H@(#@oTy>1wJ5%?Ox9OXoCXiRaD z=l9Z9?~HE5L!Z{P+=vWNp&{-eIZEtjHm z>Nh)*^j9`66A}Q-vUDv5hDC^Znunz_y!U$$f`x>wSg|N-Vi_`Z@py2%F9Wo`-Hcj|H=z7bjHpBMYvxQQiIJCGs1&X`7L$TuSUI;2{KvKq;*yH~#Un*nXQi7quj3^Onl(Z}t0N{WL-=(x%U;Iyij3TE)`k61Y@(;LPva$Zy#6E-_p&X&-TlIxqAZ^;*CFH^89}%9KUS3{W7t}Hs zM8Qlq2Nj|+^*uL=0+62D1H>yK9nr_8gmKNl*Q}er1by=Gz5pS38hm_SIlz6b?B*JN zxX6O`{OnR10x!-uLY`R;Gm*Xi@1n{7>IxW7pxIY=pymbMrZWpY21+citQ;mK__dOX zo*Se4-O*6k^;`}FA>I^=`SocjP$&4_q#W!9AvX8;SNL6{nIuB;0rA3raS{?W8v;k# z^uYfEBrcgi+JP1MU6AZx^!dI}Jf|a7d;YiXpy+(FQR0?a7je$Z&yo4jQ_Q42u}w zAQ`FCl)B)9p;nsQN22E6FbVg)Tv=e-;eDI@J-0LxA$&Vw+Z`Mc9{a@r5Y9;*KG&67 z5?S(E!IO@jl*lvG#>!zS^g``6{#Avz$~tL9JV#Tj3H9q;X!vg~xi(VEbQi)o^SX12 zqK6)M`nS+O?cSpvXZ?k-9bck6$+$}PFSX5?E_T)7muaUS@LDQpnTD-){*o)M3P`b( z!WdB#UG`z5mp51YJNQk~s5Z_s((hz|7Im{rVyUPcWpU?Vuo=p3V$yFpTELesy?^Jt zNAYj665ZC!Ub-^P=%lyg1lrdLg@|z~34J9=5K(K?f#J1tmES>ZQUsc*OvPuDUsuqZ zr&>1O_>(n-bJLW@dQh3a*%kgd9ID~KN9{rMXVKu|4oyp>0no&#+2NqRpDM@}4QQpDh*WD9`Ma0=w`+uw*do)8#S2uvfhD{&HuM%0BX18_gatWUe~VVoRxDZyRIZ*s3+kM0;GXvpEbgxAZ8S(7U7n?I1km{MH9dZt+2qnJxvAxJD=sgiXC zkH;dF;(VcKDGABbkn9l%={K1Xg*tlg_Fl58`9CcjBJaHR^}KjAr0Bd}>`XAf+-u)7 zMu=^)8~EW5ys+rXd_ZlmxaWSppi^+FA1LQrNzqe_&64x5w!ht&Zi*$W>Um`zd2jKX z@$mz?wCYX%ZP?&t_~Y4eHPpe8qrJl3G{Iabbv_^kg5wlR;8Yt>2U(&uudrttx;L-~ z#wpa6)eqNZWE&~@_ikjO6x%4>Uacn3TQ(?X-&?V^106}Q>-LBemPFs0i}}$jk`TvK zo&6yeig}w~$I|YK>vumD*d^PDWg*+lD~mt5V7xSAFG;Zq3h+|u3nGfK(fARr>FVrN zMVw7C8C7D*~zn?8w$O&vQ2D z?cxy&eR%Xx0sL~RTi9+x2%*fZa7+kkDFhc{28dOm7xBArTladmn011g?v=W{)S@mz zo{201$^p{G6u&U1iu8C=6d@}$>-k0b6?ay(D`(yf>2Uxzlo`zmEB?T}gt$}R7Humg zE^8;mGeZB01pN}&r+T^myyiw)YHE}9D?)@uYp)~mzV1Hc50C%cS(N;b7g#K*ZtwAX zZPp0MX6<}btsT?OXh4sgR||8OMfWS>aUeg9mba$)?CH9!7M^1w`XF|NEsv@vX8uWd z6=d$uHr`afE}fh=^bProYsvjDU>Bs7tIa0u%YcrSO}!65lUgjw?w0AxlK9uR7aKHT z`8B3&?5@W(dVPciJR57J^$Tk_{zuXjRbA!%wHLEIku3(TlZ4z$YGFGXq)F1yc|#JB zjm$IRe*qP|z?p0!8P#>%m#CKu66PS|0+%-L%umQhN&E)NxgzE!6z4k{yFKi}A+mYJ zUaC+2!BA;hBGLxn_Y1@MGZW*`fJ09!WWx0>43EigA_=Rd9z&f(rC4OCS8jpF^)#AR z;GeQ&RSjN;+0X#jll)F6ehOh`BBAuuxgv^R&8?S!95v5^7P>kMeoxbXz!WXa1t?1Z zL-_62al?Wy*r2E#5F;Rh(-+Gp{_&7q7r9lXF%Y@j!XV=$CHJB!ej|HZXo-hQyBxX7 zos47>{ccbKLIC)(q5rxxUGOJAzJBfl8Fs#y;=J6hq<^?Md# z?D%p)rlqzNU9q6u>kp$Ree3)nR-+x66Nu^PPm!FClFYAagP2#UuJ48BZ2mrf zw0RexawBxWW1--6ajc9h6z)vUW>=7%hmd0=d)1wI{c|QG#!zbT)Fkugc89x|~kYx(So%ynn`Hq^NDW`Y@BRp@7#=mPnR#+J$; z@^bCA)aQQ_d@KU&D^RVtxvLP?cJ`<-7-y;|2&ii>`X60M0h3%5*H84YbYF1!zt8f> zq?CW`7SVD^>%|XJrT&W`M-F=!)e|45oIdC%c#!_j4|(2^6MM>BygsJ?g*Yb@PHI1H zVc5@WJxO_&lpbWtJmU?bxuE+cx+1JM7KU-)rTu3P5IZA7YaQ!|0m|2OXD^;+RQWcb z802THSEc@y*obGAvC5UiC*Pqc$w=G}AYE!?6f-iy6WKEfQh$<5`B3R6-NoA>)YlN6 z_^#_)lhh`kC?Dq*$N+cKCSmo>r%$OCk!+5`s*WKsKUdNeRaGD|`kBi;>MeJm!zBu*p8>MDV{p&X> z{gT$_TVkI2q!^rT1P*@FT5P6OwQ7DZYlBw4=^cKXOP;jF7N$jPzrhxwM@L%<8B|+n zxnx&Z;!Q0(Gt+)2_My)RgXD30{RXeJs;;q~l#PD9a$L{IgSG-;kkQ(2FPvWi0t>mx z)?{1@*9AcQUe&>tJ1)08HHS#HB%c{W>1j7wFT!y_-mPkMa$+OVf*gC;*_34IG?Y|$vKw)j~JfDx3)$oLI&-Yn_OSW|Ki7>ajp1>K7;u|qJ=1SLyv zetmYzJyu~meO~m%cxxA#dL(Zsm7zl=xh^~A=DLhc7|J_pV$qpRb;VdjfRwZu{qs;b zNFj10?4yQEC9|wqM^hj>0Br<4X+2kImgdaN!kWi2j?>&u`aaT)W(b+Ed<C>gG(EB3Zpc%`fIRRWNL;h9%d@KL3hAW&Fd;`og$f4oT4R0# zlp2Ug$=X-uR=WYJG6l7y(jy$+dxta2Qngb>(xl@UGgvuBINhs&$y1B=3pB(CNk5a) zyoxl%(zXf8y!=Vdx>hihH+GaX{kQzZghkLis`lu=!08s$-#g zy-j^hieI)wv&qL{A(z8Wf+Uthq?8*UWNg1|Vd$yYg+KXeN@>b(z&EJ*-yxwA%Gx3M zl2t%}aeZJ2$rxYXiwE?w#^fn^Q!+&8xt^{SWFdK_>QGRUJHl#AzJhTfN2an;9sd>q zDOQQ5?Uk@QAbHu%SBfpHa;@3g1XLi>jWV)eBvvIkB5t7YMEXFwU)_4wje}SoU`%&F zft?DIUPINcC}u^DU!%vy^zE|PS6_HE3ej}gpgoAmd09qAJ@`OVoxOuGy+Iy8ot3X=@&QDfh z85Gtma%l><^OJPBS~^0zZ`IWQ$+RYD&AlrSp=frgGR>g%y5ttpz_S`(4l5)mR3>5y z`;4IXe$mae;_Pr~GR~3e$T!Bw!J;7a!qnHAx6JG!+i6zh%b{a})B6^rR(2ZmY5Of= zR4U$eIT_xxi&>Tj!j_;@tf}ufo6`5Gga(;G!&hXB2~C{fp15VCdkQ?JlD2{*tjTn; zTl^=}+63Zt{)JrXD#sUsI^f@4))HJQzsunlVH!t}AiAKtq>q!bz*K}nq$q4iL3jc;}@l$DEO>Gi;!i9 z)rvHlQwT3|f05DkDT*!+J6mJmeWf|hRUolN#JUG%9|t5^+OpaPdsU4K?~>k18VMj* z-sM?*KSpCGwez=KNDc1XU_FVPxX{FkHR_IOZhfk^Uo&M~En)PTdX4%8d>pt4(@dn% zLf$gD@SQ63c4D}nniSlQX;jcwtA6T*0ZeQDEJwaQrmo!g0{h-TwOiug43RzxX=RN}gBJG)2d)jS7MzrSTxYcxd}N62Nq>K~X-Zq>I$hU_ zPH=00x404*Nf5Yh3W5HE9q@MfZ9$r}gJIpdY~L+o|oGaNk`T4>4URE<+=7)HwbvF0^|3 zX~j>5dn%JX8`hTfh+Sqmaud{t*n$n*ZxWppv9P_OG$dp1I8%0|rq=C5?5UCH{G%AB zLb#;8RqT&$LM_Or_*lz)M>ZG_XH4sf+pn;1*tbT^*~HUYqM4aw0e#M42p6m~!neu- z-);bSn{)rqQb+jGV@1n3;5G+&kj&(zfQ@Wu-TkH|xw;yjnR;rEl?B}}1pYJ3nzn9+ zz=sdH3`4g?sBB3Rsi3=V_)ro0`w)k_DK)?6{TsL>`pwxtXXBYx`|AHdDIcROIkU!? zP2gnyZcSCu7s93T|BfUA>Hm3}LHXtr0P9P5i60JYuKIyUsodN?O1oJ*w$v1Eb!Ytj zjX=~8ihaxhM=8lm_!!O1@i8c}|4CUU3yX(f(Jx&gyk{IguU=hq*zGS4@<)*bzr0|S zfvOjblGLBLCwiErw6cu`v!YGX(gJEyvGn;-sbm3uUe^j+JYxdnmtsrr5CZZ1jjW6Z+v#Ki5r{k5(1) zaSqtmP2mhqXx9c;Q5n%sVmc_vl zS>*crk-q_FnMTq;-{Foz6jv*z2%HdUw3z{MyS~1UoR#^aBlC+$k<~Gxs3{HU9b&J?0#^MB`Ig(J{y4&yyKb`Mlx`bev-Cx&mk`Mof@dt= zeMfT;CrM8r5<;gd7idg56_vpJT0lX9MUxY3P1qB1kpgvytcu+FY`Iq1EI{&D3+KP* z{PeB~ODg|Oj0Wrc^#%9Exl3`wC}*fWU0L6Ut69p)Gj49&lA-#^<$3A|JmsHz;}Py$ z)D{`=OAwvB&U!DkQe7zvT11JKsk4_tm1SZ!s!Gw#@Yb4 zMe}#UU_iIE!6_--I$Ilur{kEu{C(kgMJyq2XuXp~X0c$SnDzWQFci4|)YkZY9b(!r z#KqB(?UdA{ZR_o?;M7MvpLLhj#RZ$D550}MJGdMP^46IW9y+a_3YDzep%lD9N51ES;OK*J??%{pt!f;pR2GV%0 zhpV@FtW0sYV&45)drWU5$kW>s9Npxo`3$U=_x(3!vfoT;dU+CtqJJMZ5Udv^g~^a+ z<1n?5s5b;cDZCmA&}EY%2?qNkeVvZ}jtsaXB`p2KltY)#HMsgQD^Jr2fl#6R=$1dG zLn-MB8GP%JPI~hwC}qVt%Ru_|X?`gBtl#!9k>#_buLN)+gNsZqSNm-3Pc$c4Z0eo` zQw`XtE|Ai+yt%E?chdgQ;IePJBbnzyXuo&(MKft5ne>C0)Lj;yYaj0UHSX-nggHo$ zp0~RnB=)_7poUs%)E<7Ozi6y=y99oHUi0cL`KbS`9HA2pD*@Os=&O0VVKJ+0= z3hTY*JIpV}wKTD5aVcngh>kmFyWe1i%1dRXoMfutMX(gCl^Wg|R^F3`F0(!p_ByNR zAso5Je|T?dep;(x<+m)_;9gIUNI=J81oK0z`f~(#uBwEb#O#C zqv^#1*j)?&;IJ&F^d?Wa``Z@~JUXxNajkr*CCk0|nN|3*ttf42daGAsIW<0USq($^ z7WRjw<3&tf84XY7JaB&i-qyTU$HLZ)=XJ!P=dlAJAngw3=-8c-1hp1-5lo?E`dVr0 z`Pb+E0!TJ8=N1BQ+S!D4NE-}Eg-O!e&bqxZ^J2NwR+ZTeN)_w{*-CoJ?nRMX69!XJ z@zE_1zFHFm&Kx~m6<#8Fg13Ylxm>1F%%@*^t1mx2(X+W4pB;^euCx87k3D%NiKQ^r zF$*t`YPT!P9FM$5gY~sI-ESwKvmKecxMj7Y)%rV=f8^3R&Y!t8Jbq1z2DUR>M7#lD z6EcT0&5SPTEcy<7M(ox)`J9_()^8fOCxSEL_GQ{q0aD#)kWnW<)(n_g3lEy%v8XEc z`t%Dp(tA9k))e97yF>DT=#C%Z9VNt3k0X@$3Fisq&_#L_cj`sDNS8JYGHGMZTsg^+ zu{y;KICf;AsyAVe7?6za5a5{?|ZJ&=1MGg3HB7w$cNM^t(?QOt^?$ zM-6%%_kfIUA|r&gX5dU9?;YQ5njP4sS749q|Ao1H3t+XulKv%B)P6#!v6vw z{QtwXR*f$s1suhHd{zHQjy)WXTO*-~tSsqdS?#0Wddn`D&`Ww6?@om`T&_UfYVYq0%o0JkSo zC{#DT_{+?as_J?;-6&LC=-@Yt9J4W5{aCdhB3nm(u_Hz;;olNCqC3Q`5DC2%wCz&c z;B7xPpy=TAK!IJpn@g3NP#(1=9$=NY<{;BR4PJgKa3y8R5U zUp`Z`X)jA@!CN9g=o#5jmh8+gCXOHCghV4_Em@rGdY66G3<7a-64tB7A)2kOB7eV~ zJ$gyeBi0}uQQtt9&r1%S-`g{pOUZ}ylRswiiu%qHXVrRe+`0XQ&MbQfRo8ba7w&NT zEI%Qm(^K6L+)~%zN}3Z}4B=if9KDZKQ5tqL;!of&X+Fo%mx~pn86#rK zA9Ajc0zO%wlUxGSXeN`q2q3CuB&WAXLbth?T-Dr^o6TC+0Z?!^Fk%~e>fYRdN0N zp#E8l_P!{dU