class BoundaryConditions: def __init__(self): self.boundaries= [] self.type = [] def read_config(self,json_dict): self.boundaries = json_dict['boundaries'] self.type = json_dict['type'] if self.type == 'mask': self.mask_boundaries(json_dict['mask']) return def mask_boundaries(self,json_dict): self.boundaries = InputFields() self.boundaries.get_info(json_dict) self.boundaries.get_mfds(json_dict) self.boundaries.get_get_grid(json_dict) self.boundaries.get_interpolants(method='nearest') def F(self,r): if self.type == 'mask': return self.boundaries.F_s(r) if self.type != 'mask': for i in range(0,len(self.boundaries)): if self.type[i] == 'periodic': mask_0 = r[:,i] < self.boundaries[i][0] r[mask_0,i] = self.boundaries[i][1] - np.abs(r[mask_0,i] - self.boundaries[i][0]) mask_1 = r[:,i] > self.boundaries[i][1] r[mask_1,i] = self.boundaries[i][0] + np.abs(r[mask_1,i] - self.boundaries[i][1]) elif self.type[i] == 'fixed': mask_0 = r[:,i] < self.boundaries[i][0] r[mask_0,i] = self.boundaries[i][0] mask_1 = r[:,i] > self.boundaries[i][1] r[mask_1,i] = self.boundaries[i][1] elif self.type[i] == 'void': mask_0 = r[:,i] < self.boundaries[i][0] r[mask_0,i] = np.nan mask_1 = r[:,i] > self.boundaries[i][1] r[mask_1,i] = np.nan elif self.type[i] == 'none': continue return r
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()