class Lagrangian: """ This class provides pure Lagrangian kernels. The particle just follows the local velocity flow field. Attributes: -Vi (function): The interpolant function, to evaluate the local flow velocity field V(r(t),t). """ def __init__(self,json_dict): """ Lagrangian kernel constructor. Args: Vi (function): Interpolant function. """ self.Vi = InputFields() self.Vi.get_info(json_dict['velocity']) self.Vi.get_mfds() self.Vi.get_grid() self.Vi.get_interpolants() self.boundaries = [] def F(self, r, t): """ Model Kernel function to send to the solver module. Args: r (array): Array containing the position of the particles [space_units]. t (float): Time instant [time_units] Returns: array: Array containing the velocity componentes evaluted. [space_units/time_units] """ if self.boundaries: r = self.boundaries.F(r) return self.Vi.F(r, t)
class LagrangianSpherical2D: """ This class provides Lagrangian Kernels, for spherical (lat,lon) integrations, considering a perfectly spherical earth with a 6370Km radius. The particle just follows the local flow velocity field. Attributes: - m_to_deg (float): Conversion from [m/s] to [degrees/s]. - Vi (function): The interpolant function, to evaluate the local flow velocity field V(r(t),t). """ def __init__(self, json_dict): """ Lagrangian kernel constructor. Args: Vi (function): Interpolant function. """ self.m_to_deg = (np.pi/180.)*6370000. self.Vi = InputFields() self.Vi.get_info(json_dict['velocity']) self.Vi.get_mfds() self.Vi.get_grid() self.Vi.get_interpolants() def F(self, r, t): """ Model Kernel function to send to the solver module. Args: - r (array): Array containing the position of the particles [space_units]. - t (float): Time instant [time_units]. Returns: - array: Array containing the velocity componentes evaluted [space_units/time_units]. """ drdt = self.Vi.F(r, t) drdt[:,0] = drdt[:,0]/(self.m_to_deg*np.cos((np.pi/180.)*r[:,1])) drdt[:,1] = drdt[:,1]/self.m_to_deg return drdt
class InitialConditions: """ This module creates regular meshes of multidimensional initial conditions, and turn it into arrays of points, using the order: [variable, solution_id]. Example: Given 10 Lagrangian particles in cartesian coordinates. we require x,y,z position to describe its state at t time, so the resulting array will be: [3,10]. Using this, :: r0[1] we take the x-component for all the particles. Also, if a mask is provided, the points can be masked and suppressed in order to create regular initial conditions with any shape considered. Future: The idea is to increase its functionality by adding new functions for multidimensional problems and non-regular geometries. Attributes: - r0 (np.array): An array of initial points. - r0_raw (list): Copy of the original array with a mask included, to - create a np.masked array. - mask (bool): Array of booleans to select the valid points to compute. - span (np.array): The initial grid of points to generate the initial conditions domain. """ def __init__(self): """ Init the object to create the initial conditions. Args: - **r0_dict: The information provided by the dictionary "r0" in the setup JSON file. """ self.setup = [] self.names = [] self.ranges = [] self.step = [] self.span = [] self.mask = [] self.maski = [] self.r0 = [] self.r0_raw = [] self.t0 = [] self.time_range = [] self.dt = [] self.r = [] self.t = [] self.t_fmt = [] self.t0_fmt = [] self.dt_fmt = [] self.ds = [] self.coords_names = [] self.tspan = [] self.tspan_fmt = [] self.parameters = [] def read_config(self, json_dict): self.names = json_dict['names'] if 'file' in json_dict: self.file = json_dict['file'] else: self.ranges = json_dict['ranges'] self.step = json_dict['step'] self.time_range = json_dict['time_range'] if 'reference_time' in json_dict: self.time_ref = json_dict['reference_time'] if 'parameters' in json_dict: self.parameters = json_dict['parameters'] return def spanning(self): """ Turns the information provided by the by the dictionary "r0" in the setup JSON file, regarding variables into arrays of initial conditions to define the initial mesh axis. Returns: - list: List of np.arrays. """ for k in range(0, len(self.names)): self.span.append( np.arange(self.ranges[k][0], self.ranges[k][1], self.step[k])) return def set_array(self): """ It creates the array of initial conditions. Returns: - array: np.array of initial conditions in the form [varible, particle_id] """ mesh = np.meshgrid(*self.span, indexing='ij') # here we use indexing ij, this is to work with the correspondence # x,y,z --> i,j,k indexing. Then, when the netcdf is writen, this is # tranposed in order to keep the netcdf convection (k,j,i indexing) self.coords_names = [name + "_0" for name in self.names] nvars = len(self.names) coords = dict(zip(self.coords_names, zip(self.coords_names, self.span))) variables = dict( zip(self.names, zip(nvars * [self.coords_names], mesh))) self.ds = xr.Dataset(variables, coords=coords) self.ds = self.ds.expand_dims(dim='time', axis=len(self.coords_names)) for var in self.names: self.ds[var].values.setflags(write=True) self.r0 = np.stack((list(map(np.ravel, mesh)))) self.r0 = self.r0.transpose() return def set_mask(self, json_dict): self.maski = InputFields() self.maski.get_info(json_dict['mask']) self.maski.get_mfds() self.maski.get_grid() self.maski.get_interpolants(method='nearest') def apply_mask(self): """ Apply the mask to the points, and filter the non-true points. It also generates a copy of the array with the mask included (numpy masked array) to reconstruct the initial array, in the postprocessing stage. WARNING:: The masked array cannot be passed directly to computations (it is heavily slow). """ print('\n') print('*************************************') print('LAGAR InitialConditions Information ') print('*************************************') print('Info: True condition => Point masked by position') print('Info: Points True condition => not integrate') print('Points pre-mask:', self.r0.shape[0]) print('Stage: Appliying mask filter points') self.mask = self.maski.F_s(self.r0) == 1 # Added tuple to avoid the Future Warning. remove_mask = (~self.mask).any(axis=1) self.r0 = self.r0[(remove_mask)] if self.ds: self.ds['mask'] = (self.coords_names, self.mask.reshape(self.get_dims())) print('Points post-mask:', self.r0.shape[0]) print('\n') return def read_points_file(self, delimiter=';'): """ Read the information of initial conditions from a CSV file. Args: delimiter (str, optional): delimiter for CSV files. Returns: void: sets the array with the CSV readed values. """ if self.file[-4:] == '.csv': csv = pd.read_csv(self.file, delimiter=delimiter) self.r0 = csv[self.names].values elif self.file[-3:] == '.nc': ds = xr.open_dataset(self.file) try: self.r0 = np.stack([ds[name].values for name in self.names], axis=1) except: self.r0 = np.array([ds[name].values for name in self.names]) return def span_time(self): if isinstance(self.time_range[0], str): self.tspan_fmt = pd.date_range(start=self.time_range[0], end=self.time_range[1], freq=self.time_range[2]) self.reference_time = pd.datetime.strptime(self.time_ref, '%Y-%m-%d %H:%M:%S') self.dt_fmt = self.time_range[2] self.t0_fmt = self.tspan_fmt[0] self.tspan = (self.tspan_fmt - self.reference_time ).astype('timedelta64[s]').astype('f8').values self.t0 = self.tspan[0] self.dt = float(self.time_range[2][0:-1]) print('+++++++++++++++++++++++++++++++') print('LAGAR InitialCondtions time') print('+++++++++++++++++++++++++++++++') print('Reference_time:', self.reference_time) print('Number of steps:', self.tspan) print('Time step used:', self.dt) print('Date started:', self.t0_fmt) print('Date end:', self.tspan_fmt[-1]) print('Date started in seconds', self.t0) print('Date end in seconds:', self.tspan_fmt[-1]) else: self.tspan = np.arange(self.time_range[0], self.time_range[1], self.time_range[2]) self.t0 = self.tspan[0] self.dt = self.time_range[2] print('+++++++++++++++++++++++++++++++') print('LAGAR InitialCondtions time') print('+++++++++++++++++++++++++++++++') print('Number of steps:', self.tspan.size) print('Time step used:', self.dt) print('Date started in seconds', self.t0) return def get_dims(self): """ Get the dimensions of the initial conditions domain. Returns: list: the list containing the dimensions of the initial conditions domain. """ return list(map(np.size, self.span)) def generate(self, json_dict): self.read_config(json_dict) if 'file' in json_dict: self.read_points_file() else: self.spanning() self.set_array() if 'mask' in json_dict: self.set_mask(json_dict) self.apply_mask() self.span_time() def update(self, json_dict): self.read_config(json_dict) self.span_time()