diff --git a/Analysis.py b/Analysis.py deleted file mode 100644 index f03d660..0000000 --- a/Analysis.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Jan 24 18:02:05 2023 - -@author: marcu -""" - -import matplotlib.pyplot as plt -import numpy as np -import h5py -import pickle -import seaborn as sns - -class CoefficientAnalysis(object): - - def __init__(self, visualizer): - """ - Nothing as yet... - - Returns - ------- - Also nothing... - - """ - self.visualizer = visualizer # need to re-structure this... or do I - - def JointPlot(self, model, y_var_str, x_var_str, t, x_range, y_range,\ - interp_dims, method, y_component_indices, x_component_indices): - y_data_to_plot, points = \ - self.visualizer.get_var_data(model, y_var_str, t, x_range, y_range, interp_dims, method, y_component_indices) - x_data_to_plot, points = \ - self.visualizer.get_var_data(model, x_var_str, t, x_range, y_range, interp_dims, method, x_component_indices) - fig = plt.figure(figsize=(16,16)) - sns.jointplot(x=x_data_to_plot.flatten(), y=y_data_to_plot.flatten(), kind="hex", color="#4CB391") - plt.title(y_var_str+'('+x_var_str+')') - fig.tight_layout() - plt.show() - - def DistributionPlot(self, model, var_str, t, x_range, y_range, interp_dims, method, component_indices): - - data_to_plot, points = \ - self.visualizer.get_var_data(model, var_str, t, x_range, y_range, interp_dims, method, component_indices) - - # print(data_to_plot) - - fig = plt.figure(figsize=(16,16)) - sns.displot(data_to_plot) - plt.title(var_str) - fig.tight_layout() - plt.show() - - - - - - - - - - - - - - - - - - - diff --git a/FileReaders.py b/FileReaders.py deleted file mode 100644 index 4b38791..0000000 --- a/FileReaders.py +++ /dev/null @@ -1,125 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Mar 31 10:00:00 2023 -@author: Thomas -""" - -import h5py -import glob -import numpy as np - -class METHOD_HDF5(object): - - def __init__(self, directory): - """ - Set up the list of files (from hdf5) and dictionary with dataset names - in the hdf5 file. - Parameters - ---------- - directory: string - the filenames in the directory have to be incremental (sorted is used) - """ - - hdf5_filenames = sorted( glob.glob(directory+str('*.hdf5'))) - self.hdf5_files = [] - for filename in hdf5_filenames: - self.hdf5_files.append(h5py.File(filename,'r')) - self.num_files = len(self.hdf5_files) - - self.hdf5_keys = dict.fromkeys(list(self.hdf5_files[0].keys())) - for key in self.hdf5_keys: - self.hdf5_keys[key] = list(self.hdf5_files[0][key].keys()) - - def get_hdf5_keys(self): - return self.hdf5_keys - - def read_in_data(self, micro_model): - """ - Store data from files into micro_model - Parameters - ---------- - micro_model: class MicroModel - strs in micromodel have to be the same as hdf5 files output from METHOD. - """ - for prim_var_str in micro_model.prim_vars: - try: - for counter in range(self.num_files): - micro_model.prim_vars[prim_var_str].append( self.hdf5_files[counter]["Primitive/"+prim_var_str][:] ) - # The [:] is for returning the arrays not the dataset - micro_model.prim_vars[prim_var_str] = np.array(micro_model.prim_vars[prim_var_str]) - except KeyError: - print(f'{prim_var_str} is not in the hdf5 dataset: check Primitive/') - - - for aux_var_str in micro_model.aux_vars: - try: - for counter in range(self.num_files): - micro_model.aux_vars[aux_var_str].append( self.hdf5_files[counter]["Auxiliary/"+aux_var_str][:] ) - micro_model.aux_vars[aux_var_str] = np.array(micro_model.aux_vars[aux_var_str]) - except KeyError: - print(f'{aux_var_str} is not in the hdf5 dataset: check Auxiliary/') - - # As METHOD saves endTime, the time variables (and points) need to be dealt with separately - for dom_var_str in micro_model.domain_int_strs: - try: - if dom_var_str == 'nt': - pass - else: - micro_model.domain_vars[dom_var_str] = int( self.hdf5_files[0]['Domain/' + dom_var_str][:]) - except KeyError: - print(f'{dom_var_str} is not in the hdf5 dataset: check Domain/') - - for dom_var_str in micro_model.domain_float_strs: - try: - if dom_var_str in ['tmin', 'tmax']: - pass - else: - micro_model.domain_vars[dom_var_str] = float( self.hdf5_files[0]['Domain/' + dom_var_str][:]) - except KeyError: - print(f'{dom_var_str} is not in the hdf5 dataset: check Domain/') - - for dom_var_str in micro_model.domain_array_strs: - try: - if dom_var_str in ['t','points']: - pass - else: - micro_model.domain_vars[dom_var_str] = self.hdf5_files[0]['Domain/' + dom_var_str][:] - except KeyError: - print(f'{dom_var_str} is not in the hdf5 dataset: check Domain/') - - # for dom_var_str in micro_model.domain_vars: - # try: - # if dom_var_str in ['t','nt','tmin','tmax','points']: - # pass - # if dom_var_str in ['x', 'y']: - # micro_model.domain_vars[dom_var_str] = self.hdf5_files[0]['Domain/' + dom_var_str][:] - # if dom_var_str in ['nx', 'ny']: - # micro_model.domain_vars[dom_var_str] = int(self.hdf5_files[0]['Domain/' + dom_var_str][:] - # else: - # micro_model.domain_vars[dom_var_str] = self.hdf5_files[0]['Domain/' + dom_var_str][:] - # except KeyError: - # print(f'{dom_var_str} is not in the hdf5 dataset: check Domain/') - - micro_model.domain_vars['nt'] = self.num_files - for counter in range(self.num_files): - micro_model.domain_vars['t'].append( float(self.hdf5_files[counter]['Domain/endTime'][:])) - micro_model.domain_vars['t'] = np.array(micro_model.domain_vars['t']) - micro_model.domain_vars['tmin'] = np.amin(micro_model.domain_vars['t']) - micro_model.domain_vars['tmax'] = np.amax(micro_model.domain_vars['t']) - micro_model.domain_vars['points'] = [micro_model.domain_vars['t'], micro_model.domain_vars['x'], \ - micro_model.domain_vars['y']] - - -if __name__ == '__main__': - - from MicroModels import * - - FileReader = METHOD_HDF5('./Data/test_res100/') - MicroModel = IdealMHD_2D() - - # print(FileReader.get_hdf5_keys()) - # FileReader.micro_model_compatibility(MicroModel) - FileReader.read_in_data(MicroModel) - for str in MicroModel.domain_vars: - print(str + ' ',type(MicroModel.domain_vars[str]),' ', MicroModel.domain_vars[str], '\n') - \ No newline at end of file diff --git a/Filters.py b/Filters.py deleted file mode 100644 index 1177f75..0000000 --- a/Filters.py +++ /dev/null @@ -1,392 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Mar 28 15:36:01 2023 - -@author: Marcus -""" - -import numpy as np -import scipy.integrate as integrate -from scipy.optimize import minimize -from itertools import product - -from MicroModels import * -from FileReaders import * -from system.BaseFunctionality import * - -class Favre_observers(object): - - def __init__(self, micro_model, box_len): - """ - Parameters: - ----------- - micro_model: instance of class containing the microdata - - Note: - ----- - To-do: think about checking compatibility (dimension + baryon currrent) - """ - self.micro_model = micro_model - self.spatial_dims = micro_model.spatial_dims - self.L = box_len - - def set_box_length(self, bl): - self.L = bl - - def get_tetrad_from_U(self, U): - """ - Build tetrad orthogonal to unit velocity with from complete velocity vector - - Parameters: - ----------- - U: list of d+1 floats, with d the number of spatial dimensions - - Return: - ------- - list of arrays: U + d unit vectors that complete it to a orthonormal basis - """ - es =[] - for _ in range(self.spatial_dims): - es.append(np.zeros(self.spatial_dims+1)) - for i in range(len(es)): - es[i][i+1] = 1 - tetrad = [U] - for i, vec in enumerate(es): #enumerate returns a tuple: so acts by value not reference! - vec = vec + np.multiply(Base.Mink_dot(vec, U), U) - for j in range(i-1,-1,-1): - vec = vec - np.multiply(Base.Mink_dot(vec, es[j]), es[j]) - es[i] = np.multiply(vec, 1 / np.sqrt(Base.Mink_dot(vec, vec))) - tetrad += [es[i]] - return tetrad - - def get_tetrad_from_vels(self, spatial_vels): - """ - Build tetrad orthogonal to unit velocity with spatial velocities spatial_vels - - Parameters: - ----------- - spatial_vels: list of d floats, with d the number of spatial dimensions - - Return: - ------- - list of arrays: U + d unit vectors that complete it to a orthonormal basis - """ - - U = np.array(Base.get_rel_vel(spatial_vels)) - return self.get_tetrad_from_U(U) - - - def Favre_residual(self, spatial_vels, point, lin_spacing): - """ - Compute the drift of baryons through the box built from vx_vy. - First get the center of the 2*(d+1) faces of the (d+1)-box, then build coords - for points to sample the flux through each face. - Next, approximate the flux integral as a sum - - Parameters: - ----------- - spatial_vels: list of d (spatial dimension) floats, spatial coord of vel - - point: list of d+1 floats (t,x,y) for the box center - - lin_spacing: integer, lin_spacing**spatial_dim is the # of points used to - sample the flux through each face. - - Returns: - -------- - float: absolute flux - - Notes: - ------ - Much faster than method based on inbuilt dblquad: 100 points (per face) gives decent - results, and is 250 times faster. - """ - - tetrad = self.get_tetrad_from_vels(spatial_vels) - flux = 0 - - for vec in tetrad: - rem_vecs = [x for x in tetrad if not (x==vec).all()] - for i in range(2): - center = point + np.multiply( (-1)**i * self.L / 2, vec) - - xs = [] - for i in range(self.spatial_dims): - xs.append(np.linspace(-self.L /2 , self.L /2, lin_spacing)) - coords = [] - for element in product(*xs): - coords.append(np.array(element)) - - surf_coords = [] - for coord in coords: - temp = center - for i in range(self.spatial_dims): - temp += np.multiply(coord[i], rem_vecs[i]) - surf_coords.append(temp) - - for coord in surf_coords: - U = self.micro_model.get_interpol_var(['bar_vel'], coord)[0] - rho = self.micro_model.get_interpol_var(['rho'], coord) - Na = np.multiply(rho, U) - flux += Base.Mink_dot(Na, vec) - - flux *= (self.L / lin_spacing) ** self.spatial_dims - return abs(flux) - - - def point_flux(self, x, y , point, Vx, Vy, normal): - """ - Compute the baryon flux at a point given two coordinates that param the surface - Identified by normal. - - Parameters: - ----------- - coords: d floats, adapted coordinates of the box face - - point: list of floats (t,x,y) - - Vx, Vy: (2+1)-arrays, tangent vectors to the box face - - normal: (2+1)-array, normal to the box face - - Returns: - -------- - Float: flux at the point - - Notes: - ------ - As this is used in Favre_residual_ib - which uses dblquad - this method has been - developed for the 2+1 dimensional case only. - """ - coords = point + np.multiply(x, Vx) + np.multiply(y, Vy) - U = self.micro_model.get_interpol_struct('bar_vel', coords) - rho = self.micro_model.get_interpol_prim(['rho'], coords) - Na = np.multiply(rho, U) - flux = Base.Mink_dot(Na, normal) - return flux - - def Favre_residual_ib(self, vx_vy, point): - """ - Compute the residual using inbuilt method dblquad - Based on function point_flux - - Parameters: - ----------- - Vs: list of d floats, spatial components of the velocity vec - - point: float, center of the box - - Returns: - -------- - tuple: absolute flux and error estimate - - Notes: - ------ - Vastly slower than method above. - As this is based on dblquad, this method works fine only if it's 2+1 dim - - """ - if self.spatial_dim != 2: - print('This method uses a 2-dim integrator, so works fine only for 2 spatial dimensions ') - return [] - - xy_range = [- self.L / 2, + self.L / 2] - tetrad = self.get_tetrad_from_vels(vx_vy) - flux = 0 - partial_flux = 0 - error = 0 - partial_error = 0 - - for vec in tetrad: - rem_vecs = [x for x in tetrad if not (x==vec).all()] - for i in range(2): - partial_flux, partial_error = integrate.dblquad(self.point_flux, xy_range[0], xy_range[1], xy_range[0], xy_range[1], \ - args = (point, rem_vecs[0],rem_vecs[1],vec), epsabs = 1e-6 )[:] - flux += partial_flux - error += partial_error - return abs(flux) , error - - - def find_observers(self, num_points, ranges, spacing): - """ - Main function: minimize the Favre_residual and find the Favre observers for points - in linearly spaced (with spacing num_points) in the ranges. - Spacing is the param passed to Favre_residual to sample the box faces. - - Parameters: - ----------- - num_points: list of floats (t,x,y,(z)) - number of points to find observers at - - ranges: list of lists of two floats [[t_min,t_max], ...] - Define the coord ranges to find observers at - - spacing: integer - param to be passed to Favre_residual - - Returns: - -------- - list of: - 1) coordinates at which minimization is successful - 2) corresponding observers - 3) corresponding residual - - list of coordinates at which the minimization failed - - Notes: - ------ - This uses the faster residual, not the one based on the inbuilt dblquad - """ - - list_of_coords = [] - for i in range(len(num_points)): - list_of_coords.append( np.linspace( ranges[i][0], ranges[i][-1] , num_points[i]) ) - - coords = [] - for element in product(*list_of_coords): - coords.append(np.array(element)) - - observers = [] - funs = [] - success_coords = [] - failed_coords = [] - - for coord in coords: - U = self.micro_model.get_interpol_var(['bar_vel'], coord)[0] - guess = [] - for i in range(1, len(U)): - guess.append(U[i] / U[0]) - guess = np.array(guess) - sol = minimize(self.Favre_residual, x0 = guess, args = (coord, spacing), bounds=((-0.8,0.8),(-0.8,0.8)),tol=1e-6) - # This rearrangement shouldn't be necessary!?!? - try: - if sol.success: - observers.append(Base.get_rel_vel(sol.x)) - funs.append(sol.fun) - success_coords.append(coord) - - if (sol.fun > 1e-5): - print(f"Warning, residual is large at {coord}: ", sol.fun) - except: - print(f'Failed for coordinates: {coord}, due to', sol.message) - failed_coords.append(coord) - - return [success_coords, observers, funs] , failed_coords - - def filter_prim_var(self, centre_coord, U, var_str, shape='box'): - """ - Filter a variable over the volume of a box with centre given by 'point'. - Originally done by scipy integration over the volume, but again incredibly - slow so now a manual sum over all the cells within the box and then a - division by total number of cells. - """ - tetrad = self.get_tetrad_from_U(U) - start_coord, end_coord = centre_coord, centre_coord - for coord, vec in zip(centre_coord, tetrad): - start_coord -= np.array(vec)*self.L/2 - end_coord += np.array(vec)*self.L/2 - integrand = 0 - counter = 0 - start_cell, end_cell = Base.find_nearest_cell(start_coord, self.micro_model.domain_vars['points']),\ - Base.find_nearest_cell(end_coord, self.micro_model.domain_vars['points']) - for i in range(start_cell[0],end_cell[0]+1): - for j in range(start_cell[1],end_cell[1]+1): - for k in range(start_cell[2],end_cell[2]+1): - integrand += self.micro_model.prim_vars[var_str][i,j,k] - counter += 1 - return integrand/counter - - def filter_struc(self, centre_coord, U, var_str, shape='box'): - """ - Filter a variable over the volume of a box with centre given by 'point'. - Originally done by scipy integration over the volume, but again incredibly - slow so now a manual sum over all the cells within the box and then a - division by total number of cells. - """ - # contruct tetrad... - tetrad = self.get_tetrad_from_U(U) - start_coord, end_coord = centre_coord, centre_coord - for coord, vec in zip(centre_coord, tetrad): - start_coord -= np.array(vec)*self.L/2 - end_coord += np.array(vec)*self.L/2 - integrand = 0 - counter = 0 - start_cell, end_cell = Base.find_nearest_cell(start_coord, self.micro_model.domain_vars['points']),\ - Base.find_nearest_cell(end_coord, self.micro_model.domain_vars['points']) - for i in range(start_cell[0],end_cell[0]+1): - for j in range(start_cell[1],end_cell[1]+1): - for k in range(start_cell[2],end_cell[2]+1): - integrand += self.micro_model.structures[var_str][i,j,k] - counter += 1 - return integrand/counter - - def get_interpol_var(self, t, x, y, var_str): - return self.micro_model.get_interpol_var(var_str, [t,x,y]) - - def filter_var_ib(self, point, spatial_vels, var_str, shape='box'): - """ - Filter a variable over the volume of a box with centre given by 'point'. - Originally done by scipy integration over the volume, but again incredibly - slow so now a manual sum over all the cells within the box and then a - division by total number of cells. - """ - # contruct tetrad... - tetrad = self.get_tetrad_from_vels(spatial_vels) - # t_range = - # corners = self.find_boundary_pts(E_x,E_y,point,self.L) - # start, end = corners[0], corners[2] - integrand = 0 - integrand, error = integrate.tplquad(self.get_interpol_var, t_range[0], t_range[1],\ - x_range[0], x_range[1], y_range[0], y_range[1],\ - args = var_str, epsabs = 1e-6 )[:] - - return vol_integrand/(self.L)**3, error - -# if __name__ == '__main__': -# CPU_start_time = time.process_time() - -# FileReader = METHOD_HDF5('./Data/Testing/') -# # micro_model = IdealMHD_2D() -# micro_model = IdealHydro_2D() -# FileReader.read_in_data(micro_model) -# micro_model.setup_structures() - -# filter = Favre_observers(micro_model,box_len=0.001) - -# # tetrad = filter.get_tetrad_from_vxvy([0.5,0.73]) -# # print(type(tetrad[0]),' ',type(tetrad[1]),' ',type(tetrad[2]),'\n', tetrad[0],' ',tetrad[1],' ',tetrad[2],'\n') -# # print(filter.Mink_dot(tetrad[0],tetrad[1]), filter.Mink_dot(tetrad[0],tetrad[2]), filter.Mink_dot(tetrad[1],tetrad[2])) -# # print(type(tetrad)) - -# # smart_guess = micro_model.get_interpol_prim(['vx','vy'],[0.5,0.5,0.5]) - -# # CPU_start_time = time.process_time() -# # res = filter.Favre_residual(smart_guess,[0.5,0.5,0.5], 10) -# # print('Residual: ',res,f'\nElapsed CPU time is {time.process_time() - CPU_start_time} with {10**filter.spatial_dim} points per face\n') - -# # CPU_start_time = time.process_time() -# # res, error = filter.Favre_residual_ib(smart_guess,[0.5,0.5,0.5])[:] -# # print('Residual: ',res,"\nError estimate: ",error,f'\nElapsed CPU time is {time.process_time() - CPU_start_time} with the inbuilt method') - -# CPU_start_time = time.process_time() -# coord_range = [[9.995,10.005],[-0.2,-0.3],[0.5,0.7]] -# num_points = [1,1,1] - -# min_res, failed_coord = filter.find_observers(num_points, coord_range, 10) -# for i in range(len(min_res[0])): -# for j in range(len(min_res)): -# print(min_res[j][i]) -# print('\n') - -# num_minim = 1 -# for x in num_points: -# num_minim *= x -# print(f'Elapsed CPU time for finding {num_minim} observer(s) is {time.process_time() - CPU_start_time}.') -# print('Failed coordinates:', failed_coord) - - - - - - - \ No newline at end of file diff --git a/MesoModels.py b/MesoModels.py deleted file mode 100644 index 912db1e..0000000 --- a/MesoModels.py +++ /dev/null @@ -1,383 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Mar 27 18:53:53 2023 - -@author: Marcus -""" - -import numpy as np -from scipy.integrate import quad -from multiprocessing import Process, Pool -from system.BaseFunctionality import * -import pickle - -class NonIdealHydro2D(object): - - def __init__(self, MicroModel, ObsFinder, Filter, interp_method = "linear"): - self.MicroModel = MicroModel - self.ObsFinder = ObsFinder - self.Filter = Filter - self.spatial_dims = 2 - self.n_dims = self.spatial_dims + 1 - self.interp_method = interp_method - - self.domain_var_strs = ('Nt','Nx','Ny','dT','dX','dY','points') - self.domain_vars = dict.fromkeys(self.domain_var_strs) - - #Dictionary for 'local' variables, - #obtained from filtering the appropriate MicroModel variables - self.filter_var_strs = ("BC","SET") - self.filter_vars = dict.fromkeys(self.filter_var_strs) - - #Dictionary for MesoModel variables - self.meso_var_strs = ("U","U_coords","U_errors","T~","N") - self.meso_vars = dict.fromkeys(self.meso_var_strs) - - #Strings for 'non-local' variables - ones we need to take derivatives of - self.nonlocal_var_strs = ("U","T~") - - #Dictionary for derivative variables - calculated by finite differencing - self.deriv_var_strs = ("dtU","dxU","dyU","dtT~","dxT~","dyT~") - self.deriv_vars = dict.fromkeys(self.deriv_var_strs) - - #Dictionary for dissipative residuals - from the NonId-SET that we take - #projections of - self.diss_residual_strs = ("Pi","q","pi") - self.diss_residuals = dict.fromkeys(self.diss_residual_strs) - - self.diss_var_strs = ("Theta","Omega","Sigma") - self.diss_vars = dict.fromkeys(self.diss_residual_strs) - - self.diss_coeff_strs = ("Zeta", "Kappa", "Eta") - self.diss_coeffs = dict.fromkeys(self.diss_coeff_strs) - - self.coefficient_strs = ("Gamma") - self.coefficients = dict.fromkeys(self.diss_coeff_strs) - self.coefficients['Gamma'] = 4.0/3.0 - - #Dictionary for all vars - useful for e.g. plotting - self.all_var_strs = self.filter_var_strs + self.meso_var_strs + self.deriv_var_strs\ - + self.diss_residual_strs + self.diss_var_strs + self.diss_coeff_strs - - # Stencils for finite-differencing - self.cen_SO_stencil = [1/12, -2/3, 0, 2/3, -1/12] - self.cen_FO_stencil = [-1/2, 0, 1/2] - self.fw_FO_stencil = [-1, 1] - self.bw_FO_stencil = [-1, 1] - - self.metric = np.zeros((3,3)) - self.metric[0,0] = -1 - self.metric[1,1] = self.metric[2,2] = +1 - - # Run some compatability test... - compatible = True - if self.spatial_dims != self.MicroModel.spatial_dims: - compatible = False - for filter_var_str in self.filter_var_strs: - if not (filter_var_str in MicroModel.vars.keys()): - compatible = False - - if compatible: - print("Meso and Micro models are compatible!") - else: - print("Meso and Micro models are incompatible!") - - def get_all_var_strs(self): - return self.all_var_strs - - def get_model_name(self): - return 'NonIdealHydro2D' - - def find_observers(self, num_points, ranges):#, spacing): - # self.meso_vars['U_coords'], self.meso_vars['U'], self.meso_vars['U_errors'] = \ - # self.ObsFinder.find_observers(num_points, ranges, spacing)[0] - self.meso_vars['U_coords'], self.meso_vars['U'], self.meso_vars['U_errors'] = \ - self.ObsFinder.find_observers_ranges(num_points, ranges)[0] - self.domain_vars['Nt'], self.domain_vars['Nx'], self.domain_vars['Ny'] = num_points[:] - self.domain_vars['dT'] = (ranges[0][-1] - ranges[0][0]) / self.domain_vars['Nt'] - self.domain_vars['dX'] = (ranges[1][-1] - ranges[1][0]) / self.domain_vars['Nx'] - self.domain_vars['dY'] = (ranges[2][-1] - ranges[2][0]) / self.domain_vars['Ny'] - self.domain_vars['points'] = [np.linspace(ranges[0][0], ranges[0][-1], num_points[0]),\ - np.linspace(ranges[1][0], ranges[1][-1], num_points[1]),\ - np.linspace(ranges[2][0], ranges[2][-1], num_points[2])] - - def setup_variables(self): - Nt, Nx, Ny = self.domain_vars['Nt'], self.domain_vars['Nx'], self.domain_vars['Ny'] - n_dims = self.spatial_dims+1 - self.meso_vars['U_coords'] = np.array(self.meso_vars['U_coords']).reshape([Nt, Nx, Ny, n_dims]) - self.meso_vars['U'] = np.array(self.meso_vars['U']).reshape([Nt, Nx, Ny, n_dims]) - self.meso_vars['U_errors'] = np.array(self.meso_vars['U_errors']).reshape([Nt, Nx, Ny, 2]) - self.meso_vars['T~'] = np.zeros((Nt, Nx, Ny)) - self.meso_vars['N'] = np.zeros((Nt, Nx, Ny)) - - self.filter_vars['BC'] = np.zeros((Nt, Nx, Ny, n_dims)) - self.filter_vars['SET'] = np.zeros((Nt, Nx, Ny, n_dims,n_dims)) - - for nonlocal_var_str in self.nonlocal_var_strs: - self.deriv_vars['dt'+nonlocal_var_str] = np.zeros_like(self.meso_vars[nonlocal_var_str]) - self.deriv_vars['dx'+nonlocal_var_str] = np.zeros_like(self.meso_vars[nonlocal_var_str]) - self.deriv_vars['dy'+nonlocal_var_str] = np.zeros_like(self.meso_vars[nonlocal_var_str]) - - self.diss_residuals['Pi'] = np.zeros((Nt, Nx, Ny)) - self.diss_vars['Theta'] = np.zeros((Nt, Nx, Ny)) - self.diss_residuals['q'] = np.zeros((Nt, Nx, Ny, n_dims)) - self.diss_vars['Omega'] = np.zeros((Nt, Nx, Ny, n_dims)) - self.diss_residuals['pi'] = np.zeros((Nt, Nx, Ny, n_dims, n_dims)) - self.diss_vars['Sigma'] = np.zeros((Nt, Nx, Ny, n_dims, n_dims)) - - # Single value for each coefficient (per data point) for now... - self.diss_coeffs['Zeta'] = np.zeros((Nt, Nx, Ny)) - self.diss_coeffs['Kappa'] = np.zeros((Nt, Nx, Ny, n_dims)) - self.diss_coeffs['Eta'] = np.zeros((Nt, Nx, Ny, n_dims, n_dims)) - - self.vars = self.filter_vars - self.vars.update(self.meso_vars) - self.vars.update(self.deriv_vars) - self.vars.update(self.diss_residuals) - self.vars.update(self.diss_vars) - self.vars.update(self.diss_coeffs) - - def p_from_EoS(self, rho, n): - """ - Calculate pressure from EoS using rho (energy density) and n (number density) - """ - p = (self.coefficients['Gamma']-1)*(rho-n) - return p - - - def filter_micro_variables(self): - """ - 'Spatially' average required variables from the micromodel w.r.t. - the observers that have been found. - """ - for h in range(self.domain_vars['Nt']): - for i in range(self.domain_vars['Nx']): - for j in range(self.domain_vars['Ny']): - self.filter_vars['BC'][h,i,j] = self.Filter.filter_var_point('BC', self.meso_vars['U_coords'][h,i,j], - self.meso_vars['U'][h,i,j]) - self.filter_vars['SET'][h,i,j] = self.Filter.filter_var_point('SET', self.meso_vars['U_coords'][h,i,j], - self.meso_vars['U'][h,i,j]) - - # self.filter_vars['n'][h,i,j] =\ - # self.Filter.filter_prim_var(self.meso_vars['U_coords'][h,i,j], self.meso_vars['U'][h,i,j], 'n') - # self.filter_vars['SET'][h,i,j] =\ - # self.Filter.filter_struc(self.meso_vars['U_coords'][h,i,j], self.meso_vars['U'][h,i,j], 'SET') - - # Should be able to convert here to only 1 filter function... (not prim/struct) - # for filter_var_str in self.filter_var_strs: - # filter_args = [(coord, U, filter_var_str) for coord, U in zip(self.U_coords, self.Us)] - # with Pool(2) as p: - # self.micro_vars[micro_var_str] = p.starmap(self.Filter.filter_var, filter_args) - # self.micro_vars[micro_var_str] = p.starmap(self.Filter.filter_prim_var, filter_args) - - - def calculate_derivatives(self): - """ - Calculate the required derivatives of MesoModel variables for - constructing the non-ideal terms. - """ - for nonlocal_var_str in self.nonlocal_var_strs: - self.calculate_time_derivatives(nonlocal_var_str) - self.calculate_x_derivatives(nonlocal_var_str) - self.calculate_y_derivatives(nonlocal_var_str) - - def calculate_time_derivatives(self, nonlocal_var_str): - deriv_var_str = 'dt'+nonlocal_var_str - stencil = self.cen_FO_stencil - samples = [-1,0,1] - for h in range(self.domain_vars['Nt']): - if h == 0: - stencil = self.fw_FO_stencil - samples = [0,1] - if h == (self.domain_vars['Nt']-1): - stencil = self.fw_FO_stencil - samples = [-1,0] - for i in range(self.domain_vars['Nx']): - for j in range(self.domain_vars['Ny']): - for s in range(len(samples)): - self.deriv_vars[deriv_var_str][h,i,j] \ - += (stencil[s]*self.meso_vars[nonlocal_var_str][h+samples[s],i,j]) / self.domain_vars['dT'] - - def calculate_x_derivatives(self, nonlocal_var_str): - deriv_var_str = 'dx'+nonlocal_var_str - stencil = self.cen_FO_stencil - samples = [-1,0,1] - for h in range(self.domain_vars['Nt']): - for i in range(self.domain_vars['Nx']): - if i == 0: - stencil = self.fw_FO_stencil - samples = [0,1] - if i == (self.domain_vars['Nx']-1): - stencil = self.fw_FO_stencil - samples = [-1,0] - for j in range(self.domain_vars['Ny']): - for s in range(len(samples)): - # print((stencil[s]*self.meso_vars[nonlocal_var_str][h,i+samples[s],j]) / self.domain_vars['dX']) - self.deriv_vars[deriv_var_str][h,i,j] \ - += (stencil[s]*self.meso_vars[nonlocal_var_str][h,i+samples[s],j]) / self.domain_vars['dX'] - - def calculate_y_derivatives(self, nonlocal_var_str): - deriv_var_str = 'dy'+nonlocal_var_str - stencil = self.cen_FO_stencil - samples = [-1,0,1] - for h in range(self.domain_vars['Nt']): - for i in range(self.domain_vars['Nx']): - for j in range(self.domain_vars['Ny']): - if j == 0: - stencil = self.fw_FO_stencil - samples = [0,1] - if j == (self.domain_vars['Ny']-1): - stencil = self.fw_FO_stencil - samples = [-1,0] - for s in range(len(samples)): - self.deriv_vars[deriv_var_str][h,i,j] \ - += (stencil[s]*self.meso_vars[nonlocal_var_str][h,i,j+samples[s]]) / self.domain_vars['dY'] - - def calculate_dissipative_residuals(self, h, i, j): - """ - Returns the inferred (residual) values of the - dissipative terms from the filtered MicroModel SET. - - Parameters - ---------- - indices : TYPE - DESCRIPTION. - - Returns - ------- - Pi_res : scalar float - Bulk viscosity - q_res : (d+1) vector of floats - Heat flux. - pi_res : (d+1)x(d+1) tensor of floats - Shear viscosity. - - """ - # Move this - # h, i, j = indices - # Filter the scalar fields - BC = self.filter_vars['BC'][h,i,j] - N = np.sqrt(-Base.Mink_dot(BC, BC)) - self.meso_vars['N'][h,i,j] = N - Id_SET = self.filter_vars['SET'][h,i,j] - U = self.meso_vars['U'][h,i,j] - - # Do required projections of SET - h_mu_nu = Base.orthogonal_projector(U, self.metric) - rho_res = Base.project_tensor(U,U,Id_SET) - - # Set Meso temperature from filtered quantities using EoS - p_tilde = self.p_from_EoS(rho_res, N) - T_tilde = p_tilde/N - self.meso_vars['T~'][h,i,j] = T_tilde - - # Calculate dissipative residual with tensor manipulation - q_res = np.einsum('ij,i,jk',Id_SET,U,h_mu_nu) - tau_res = np.einsum('ij,ik,jl',Id_SET,h_mu_nu,h_mu_nu) - tau_trace = np.trace(tau_res) - Pi_res = tau_trace - p_tilde - pi_res = tau_res - np.dot((p_tilde + Pi_res),h_mu_nu) - - # print(Pi_res, q_res, pi_res) - - self.diss_residuals['Pi'][h,i,j] = Pi_res - self.diss_residuals['q'][h,i,j] = q_res - self.diss_residuals['pi'][h,i,j] = pi_res - - def calculate_dissipative_variables(self, h, i, j): - """ - Calculates the non-ideal, dissipation terms (without coefficeints) - for the non-ideal MesoModel SET. - - Parameters - ---------- - indices : list of ints - Indices of data-point. - coord : list of floats - Coordinates in (t,x,y). - - Returns - ------- - Decomposition of the observer velocity: - - Theta : float (scalar) - Divergence (isotropic). - omega : vector of floats - Transverse momentum. - sigma : (d+1)x(d+1) tensor of floats - Symmetric, trace-free. - - """ - # h, i, j = indices - - T = self.meso_vars['T~'][h, i, j] - dtT = self.deriv_vars['dtT~'][h, i, j] - dxT = self.deriv_vars['dxT~'][h, i, j] - dyT = self.deriv_vars['dyT~'][h, i, j] - - # U = self.meso_vars['U'][h,i,j][:] - Ut, Ux, Uy = self.meso_vars['U'][h,i,j][:] - - # print(Ut, Ux, Uy) - - # dtU = self.deriv_vars['dtU'][h,i,j] - dtUt, dtUx, dtUy = self.deriv_vars['dtU'][h,i,j][:] - dxUt, dxUx, dxUy = self.deriv_vars['dxU'][h,i,j][:] - dyUt, dyUx, dyUy = self.deriv_vars['dyU'][h,i,j][:] - - # Need to do this with Einsum... - Theta = dtUt + dxUx + dyUy - a = np.array([Ut*dtUt + Ux*dxUt + Uy*dyUt, Ut*dtUx + Ux*dxUx + Uy*dyUx, Ut*dtUy + Ux*dxUy + Uy*dyUy]) - - Omega = np.array([dtT, dxT, dyT]) + np.multiply(T,a) - Sigma = np.array([[2*dtUt - (2/3)*Theta, dtUx + dxUt, dtUy + dyUt],\ - [dxUt + dtUx, 2*dxUx - (2/3)*Theta, dxUy + dyUx], - [dyUt + dtUy, dyUx + dxUy, 2*dyUy - (2/3)*Theta]]) - - self.diss_vars['Theta'][h,i,j] = Theta - self.diss_vars['Omega'][h,i,j] = Omega - self.diss_vars['Sigma'][h,i,j] = Sigma - - def calculate_dissipative_coefficients(self): - Nt, Nx, Ny = self.domain_vars['Nt'], self.domain_vars['Nx'], self.domain_vars['Ny'] - # parallel_args = [(h, i, j) for h in range(Nt) for i in range(Nx) for j in range(Ny)] - # # print(parallel_args) - # with Pool(2) as p: - # p.starmap(self.calculate_dissipative_residuals, parallel_args) - # p.starmap(self.calculate_dissipative_variables, parallel_args) - - # self.diss_coeffs['Zeta'] = -self.diss_residuals['Pi'] / self.diss_vars['Theta'] - for h in range(self.domain_vars['Nt']): - for i in range(self.domain_vars['Nx']): - for j in range(self.domain_vars['Ny']): - self.calculate_dissipative_residuals(h,i,j) - - # Derivative calculations need to be done after all residuals - self.calculate_derivatives() - for h in range(self.domain_vars['Nt']): - for i in range(self.domain_vars['Nx']): - for j in range(self.domain_vars['Ny']): - self.calculate_dissipative_variables(h,i,j) - self.diss_coeffs['Zeta'][h,i,j] = -self.diss_residuals['Pi'][h,i,j] / self.diss_vars['Theta'][h,i,j] - for k in range(self.n_dims): - self.diss_coeffs['Kappa'][h,i,j,k] = -self.diss_residuals['q'][h,i,j,k] / self.diss_vars['Omega'][h,i,j,k] - for l in range(self.n_dims): - self.diss_coeffs['Eta'][h,i,j,k,l] = -self.diss_residuals['pi'][h,i,j,k,l] / self.diss_vars['Sigma'][h,i,j,k,l] - - for diss_coeff_str in self.diss_coeff_strs: - coeffs_handle = open(diss_coeff_str+'.pickle', 'wb') - pickle.dump(self.diss_coeffs[diss_coeff_str], coeffs_handle, protocol=pickle.HIGHEST_PROTOCOL) - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MicroModels.py b/MicroModels.py deleted file mode 100644 index bb407f3..0000000 --- a/MicroModels.py +++ /dev/null @@ -1,555 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Mar 31 10:00:02 2023 - -@author: Thomas -""" - -from FileReaders import * -from scipy.interpolate import interpn -from system.BaseFunctionality import * -import numpy as np -import math -import time - -# These are the symbols, so be careful when using these to construct vectors! -# levi3D = np.array([[[ np.sign(i-j) * np.sign(j- k) * np.sign(k-i) \ -# for k in range(3)]for j in range(3) ] for i in range(3) ]) - -# levi4D = np.array([[[[ np.sign(i - j) * np.sign(j - k) * np.sign(k - l) * np.sign(i - l) \ -# for l in range(4)] for k in range(4) ] for j in range(4)] for i in range(4)]) - - -class IdealMHD_2D(object): - - def __init__(self, interp_method = "linear"): - """ - Sets up the variables and dictionaries, strings correspond to - those used in METHOD - - Parameters - ---------- - interp_method: str - optional method to be used by interpn - """ - self.spatial_dims = 2 - self.interp_method = interp_method - - self.metric = np.zeros((3,3)) - self.metric[0,0] = -1 - self.metric[1,1] = self.metric[2,2] = +1 - - # This is the Levi-Civita symbol, not tensor, so be careful when using it - self.Levi3D = np.array([[[ np.sign(i-j) * np.sign(j- k) * np.sign(k-i) \ - for k in range(3)]for j in range(3) ] for i in range(3) ]) - - #Dictionary for grid: info and points - self.domain_int_strs = ('nt','nx','ny') - self.domain_float_strs = ("tmin","tmax","xmin","xmax","ymin","ymax","dt","dx","dy") - self.domain_array_strs = ("t","x","y","points") - self.domain_vars = dict.fromkeys(self.domain_int_strs+self.domain_float_strs+self.domain_array_strs) - for str in self.domain_vars: - self.domain_vars[str] = [] - - #Dictionary for primitive var - self.prim_strs = ("vx","vy","n","p","Bx","By") - self.prim_vars = dict.fromkeys(self.prim_strs) - for str in self.prim_strs: - self.prim_vars[str] = [] - - #Dictionary for auxiliary var - self.aux_strs = ("W","h","b0","bx","by","bsq") - self.aux_vars = dict.fromkeys(self.aux_strs) - for str in self.aux_strs: - self.aux_vars[str] = [] - - #Dictionary for structures - self.structures_strs = ("BC","SET","Faraday") - self.structures = dict.fromkeys(self.structures_strs) - for str in self.structures_strs: - self.structures[str] = [] - - #Dictionary for all vars - self.all_var_strs = self.prim_strs + self.aux_strs + self.structures_strs - self.all_vars = self.prim_vars.copy() - self.all_vars.update(self.aux_vars) - self.all_vars.update(self.structures) - - def get_model_name(self): - return 'IdealMHD_2D' - - def get_spatial_dims(self): - return self.spatial_dims - - def get_domain_strs(self): - return self.domain_int_strs + self.domain_float_strs + self.domain_array_strs - - def get_prim_strs(self): - return self.prim_strs - - def get_aux_strs(self): - return self.aux_strs - - def get_structures_strs(self): - return self.structures_strs - - def get_all_var_strs(self): - return self.all_var_strs - - def setup_structures(self): - """ - Set up the structures (i.e baryon current, SET and Faraday) - - Structures are built as multi-dim np.arrays, with the first three indices referring - to the grid, while the last one or two refer to space-time components. - """ - self.structures["BC"] = np.zeros((self.domain_vars['nt'],self.domain_vars['nx'],self.domain_vars['ny'],3)) - self.structures["SET"] = np.zeros((self.domain_vars['nt'],self.domain_vars['nx'],self.domain_vars['ny'],3,3)) - self.structures["Faraday"] = np.zeros((self.domain_vars['nt'],self.domain_vars['nx'],self.domain_vars['ny'],3,3)) - - for h in range(self.domain_vars['nt']): - for i in range(self.domain_vars['nx']): - for j in range(self.domain_vars['ny']): - vel = np.array([self.aux_vars['W'][h,i,j],self.aux_vars['W'][h,i,j] * self.prim_vars['vx'][h,i,j] ,\ - self.aux_vars['W'][h,i,j] * self.prim_vars['vy'][h,i,j]]) - self.structures['BC'][h,i,j,:] = np.multiply(self.prim_vars['n'][h,i,j], vel ) - - - fibr_b = np.array([self.aux_vars['b0'][h,i,j],self.aux_vars['bx'][h,i,j],self.aux_vars['by'][h,i,j]]) - - - self.structures["SET"][h,i,j,:,:] = (self.prim_vars["n"][h,i,j] + self.prim_vars["p"][h,i,j] + self.aux_vars["bsq"][h,i,j]) * \ - np.outer( vel,vel) + (self.prim_vars["p"][h,i,j] + self.aux_vars["bsq"][h,i,j]/2) * self.metric \ - - np.outer(fibr_b, fibr_b) - - fol_vel_vec = np.zeros(3) - fol_vel_vec[0]=+1 - fol_b_vec = np.array([self.aux_vars["b0"][h,i,j],self.aux_vars["bx"][h,i,j],self.aux_vars["by"][h,i,j]]) - fol_e_vec = np.tensordot( self.Levi3D, np.outer(vel,fol_b_vec), axes = ([1,2],[0,1])) - - self.structures['Faraday'][h,i,j,:,:] = np.outer( fol_vel_vec,fol_e_vec) - np.outer(fol_e_vec,fol_vel_vec) -\ - np.tensordot(self.Levi3D,fol_b_vec,axes=([2],[0])) - - self.vars = self.prim_vars - self.vars.update(self.aux_vars) - self.vars.update(self.structures) - - def get_var_gridpoint(self, var, point): - """ - Returns variable corresponding to input 'var' at gridpoint - closest to input 'point'. - - Parameters: - ----------- - vars: string corresponding to primitive, auxiliary or structure variable - - point: list of 2+1 floats - - Returns: - -------- - Values or arrays corresponding to variable evaluated at the closest grid-point to input 'point'. - - Notes: - ------ - This method should be used in case using interpolated values - becomes too expensive. - """ - indices = Base.find_nearest_cell(point, self.domain_vars['points']) - if var in self.get_prim_strs(): - return self.prim_vars[var][tuple(indices)] - - elif var in self.get_aux_strs(): - return self.aux_vars[var][tuple(indices)] - - elif var in self.get_structures_strs(): - if var == "BC": - tmp = np.zeros(self.structures[var][0,0,0,:].shape) - for a in range(len(self.structures[var][0,0,0,:])): - tmp[a] = self.structures[var][tuple(indices+[a])] - return tmp - else: - tmp = np.zeros(self.structures[var][0,0,0,:,:].shape) - for a in range(len(tmp[:,0])): - for b in range(len(tmp[0,:])): - tmp[a,b] = self.structures[var][tuple(indices+[a,b])] - return tmp - else: - print(f"{var} is not a variable of IdealMHD_2D!") - return None - - def get_interpol_var(self, var, point): - """ - Returns the interpolated variables at the point. - - Parameters - ---------- - vars : str corresponding to primitive, auxiliary or structre variable - - point : list of floats - ordered coordinates: t,x,y - - Return - ------ - Interpolated values/arrays corresponding to variable. - Empty list if none of the variables is a primitive, auxiliary o structure of the micro_model - - Notes - ----- - Interpolation gives errors when applied to boundary - """ - - if var in self.get_prim_strs(): - return interpn(self.domain_vars['points'], self.prim_vars[var], point, method = self.interp_method)[0] - elif var in self.get_aux_strs(): - return interpn(self.domain_vars['points'], self.aux_vars[var], point, method = self.interp_method)[0] - elif var in self.get_structures_strs(): - return interpn(self.domain_vars['points'], self.structures[var], point, method = self.interp_method)[0] - else: - print(f'{var} is not a primitive, auxiliary variable or structure of the micro_model!!') - - - - """ - Returns the interpolated structure at the point - Parameters - ---------- - var : str corresponding to one of the structures - - point : list of floats - ordered coordinates: t,x,y - Return - ------ - Array with the interpolated values of any var - Empty list if var is not a structure in the micro_model - Notes - ----- - Interpolation gives errors when applied to boundary - """ - res = [] - for var_name in var_names: - try: - res.append( interpn(self.domain_vars["points"], self.vars[var_name], point, method = self.interp_method)[0]) - except KeyError: - print(f"{var_name} does not belong to the auxiliary variables of the micro_model!") - return res - -class IdealHydro_2D(object): - - def __init__(self, interp_method = "linear"): - """ - Sets up the variables and dictionaries, strings correspond to - those used in METHOD - - Parameters - ---------- - interp_method: str - optional method to be used by interpn - """ - self.spatial_dims = 2 - self.interp_method = interp_method - - self.metric = np.zeros((3,3)) - self.metric[0,0] = -1 - self.metric[1,1] = self.metric[2,2] = +1 - - # This is the Levi-Civita symbol, not tensor, so be careful when using it - self.Levi3D = np.array([[[ np.sign(i-j) * np.sign(j- k) * np.sign(k-i) \ - for k in range(3)]for j in range(3) ] for i in range(3) ]) - - #Dictionary for grid: info and points - self.domain_int_strs = ('nt','nx','ny') - self.domain_float_strs = ("tmin","tmax","xmin","xmax","ymin","ymax","dt","dx","dy") - self.domain_array_strs = ("t","x","y","points") - self.domain_vars = dict.fromkeys(self.domain_int_strs+self.domain_float_strs + self.domain_array_strs) - for str in self.domain_vars: - self.domain_vars[str] = [] - - #Dictionary for primitive var - self.prim_strs = ("v1","v2","rho","p","n") - self.prim_vars = dict.fromkeys(self.prim_strs) - for str in self.prim_strs: - self.prim_vars[str] = [] - - #Dictionary for auxiliary var - self.aux_strs = ("W","h","T") - self.aux_vars = dict.fromkeys(self.aux_strs) - for str in self.aux_strs: - self.aux_vars[str] = [] - - #Dictionary for structures - self.structures_strs = ("BC","SET") - self.structures = dict.fromkeys(self.structures_strs) - for str in self.structures_strs: - self.structures[str] = [] - - #Dictionary for all vars - self.all_var_strs = self.prim_strs + self.aux_strs + self.structures_strs - - def get_model_name(self): - return 'IdealHydro_2D' - - def get_spatial_dims(self): - return self.spatial_dims - - def get_domain_strs(self): - return self.domain_info_strs + self.domain_points_strs - - def get_prim_strs(self): - return self.prim_strs - - def get_aux_strs(self): - return self.aux_strs - - def get_structures_strs(self): - return self.structures_strs - - def get_all_var_strs(self): - return self.all_var_strs - - def setup_structures(self): - """ - Set up the structures (i.e baryon current, SET) - - Structures are built as multi-dim np.arrays, with the first three indices referring - to the grid, while the last one or two refer to space-time components. - """ - self.structures["BC"] = np.zeros((self.domain_vars['nt'],self.domain_vars['nx'],self.domain_vars['ny'],3)) - self.structures["SET"] = np.zeros((self.domain_vars['nt'],self.domain_vars['nx'],self.domain_vars['ny'],3,3)) - - for h in range(self.domain_vars['nt']): - for i in range(self.domain_vars['nx']): - for j in range(self.domain_vars['ny']): - vel = np.array([self.aux_vars['W'][h,i,j],self.aux_vars['W'][h,i,j] * self.prim_vars['v1'][h,i,j] ,\ - self.aux_vars['W'][h,i,j] * self.prim_vars['v2'][h,i,j]]) - self.structures['BC'][h,i,j,:] = np.multiply(self.prim_vars['n'][h,i,j], vel ) - - - self.structures["SET"][h,i,j,:,:] = (self.prim_vars["rho"][h,i,j] + self.prim_vars["p"][h,i,j]) * \ - np.outer(vel,vel) + self.prim_vars["p"][h,i,j] * self.metric - - self.vars = self.prim_vars - self.vars.update(self.aux_vars) - self.vars.update(self.structures) - - - def get_interpol_prim(self, var_names, point): - """ - Returns the interpolated variable at the point - - Parameters - ---------- - vars : list of strings - strings have to be in to prim_vars keys - point : list of floats - ordered coordinates: t,x,y - Return - ------ - list of floats corresponding to string of vars - - Notes - ----- - Interpolation raises a ValueError when out of grid boundaries. - """ - res = [] - - for var_name in var_names: - try: - res.append( interpn(self.domain_vars["points"], self.prim_vars[var_name], point, method = self.interp_method)[0]) #, bounds_error = False) - except KeyError: - print(f"{var_name} does not belong to the primitive variables of the micro_model!") - return res - - def get_interpol_aux(self, var_names, point): - """ - Returns the interpolated variable at the point - - Parameters - ---------- - vars : list of strings - strings have to be in to aux_vars keys - point : list of floats - ordered coordinates: t,x,y - Return - ------ - list of floats corresponding to string of vars - - Notes - ----- - Interpolation gives errors when applied to boundary - """ - - res = [] - for var_name in var_names: - try: - res.append( interpn(self.domain_vars["points"], self.aux_vars[var_name], point, method = self.interp_method)[0]) - except KeyError: - print(f"{var_name} does not belong to the auxiliary variables of the micro_model!") - return res - - def get_interpol_struct(self, var_name, point): - """ - Returns the interpolated structure at the point - - Parameters - ---------- - var : str corresponding to one of the structures - - point : list of floats - ordered coordinates: t,x,y - - Return - ------ - Array with the interpolated values of the var structure - Empty list if var is not a structure in the micro_model - - Notes - ----- - Interpolation gives errors when applied to boundary - """ - res = [] - if var_name == "BC": - res = np.zeros(len(self.structures[var_name][:,0,0,0])) - for a in range(len(self.structures[var_name][:,0,0,0])): - res[a] = interpn(self.domain_vars["points"], self.structures[var_name][a,:,:,:], point, method = self.interp_method)[0] - elif var_name == "SET": - res = np.zeros((len(self.structures[var_name][:,0,0,0,0]),len(self.structures[var][0,:,0,0,0]))) - for a in range(len(self.structures[var_name][:,0,0,0,0])): - for b in range(len(self.structures[var_name][0,:,0,0,0])): - res[a,b] = interpn(self.domain_vars["points"],self.structures[var_name][a,b,:,:,:], point, method = self.interp_method)[0] - else: - print(f"{var} does not belong to the structures in the micro_model") - return res - - # def get_interpol_var(self, var_names, point): - # """ - # Returns the interpolated structure at the point - - # Parameters - # ---------- - # var : str corresponding to one of the structures - - # point : list of floats - # ordered coordinates: t,x,y - - # Return - # ------ - # Array with the interpolated values of any var - # Empty list if var is not a structure in the micro_model - - # Notes - # ----- - # Interpolation gives errors when applied to boundary - # """ - # res = [] - # for var_name in var_names: - # try: - # res.append( interpn(self.domain_vars["points"], self.vars[var_name], point, method = self.interp_method)[0]) - # except KeyError: - # print(f"{var_name} does not belong to the variables of the micro_model!") - # return res - - - def get_interpol_var(self, var, point): - """ - Returns the interpolated variables at the point. - - Parameters - ---------- - vars : str corresponding to primitive, auxiliary or structre variable - - point : list of floats - ordered coordinates: t,x,y - - Return - ------ - Interpolated values/arrays corresponding to variable. - Empty list if none of the variables is a primitive, auxiliary o structure of the micro_model - - Notes - ----- - Interpolation gives errors when applied to boundary - """ - - if var in self.get_prim_strs(): - return interpn(self.domain_vars['points'], self.prim_vars[var], point, method = self.interp_method)[0] - elif var in self.get_aux_strs(): - return interpn(self.domain_vars['points'], self.aux_vars[var], point, method = self.interp_method)[0] - elif var in self.get_structures_strs(): - return interpn(self.domain_vars['points'], self.structures[var], point, method = self.interp_method)[0] - else: - print(f'{var} is not a primitive, auxiliary variable or structure of the micro_model!!') - - - - """ - Returns the interpolated structure at the point - Parameters - ---------- - var : str corresponding to one of the structures - - point : list of floats - ordered coordinates: t,x,y - Return - ------ - Array with the interpolated values of any var - Empty list if var is not a structure in the micro_model - Notes - ----- - Interpolation gives errors when applied to boundary - """ - res = [] - for var_name in var_names: - try: - res.append( interpn(self.domain_vars["points"], self.vars[var_name], point, method = self.interp_method)[0]) - except KeyError: - print(f"{var_name} does not belong to the auxiliary variables of the micro_model!") - return res - -# TC -if __name__ == '__main__': - - CPU_start_time = time.process_time() - - FileReader = METHOD_HDF5('./Data/test_res100/') - micro_model = IdealMHD_2D() - FileReader.read_in_data(micro_model) - micro_model.setup_structures() - - point = [1.502,0.4,0.2] - vars = ['SET', 'BC'] - for var in vars: - res = micro_model.get_interpol_var(var, point) - res2 = micro_model.get_var_gridpoint(var, point) - print(f'{var}: \n {res} \n {res2} \n ********** \n ') - -# MH -if __name__ == '__main__': - - CPU_start_time = time.process_time() - - FileReader = METHOD_HDF5('./Data/Testing/') - # micro_model = IdealMHD_2D() - micro_model = IdealHydro_2D() - FileReader.read_in_data(micro_model) - micro_model.setup_structures() - - res = micro_model.get_interpol_var(['v1','rho'],[10.0,0.3,0.2]) - print(type(res),"\n", res) - - res = micro_model.get_interpol_var(['T','W'],[10.0,0.3,0.2]) - print(type(res),"\n", res) - - # res = micro_model.get_interpol_var(['bar_vel','SET'],[10.0,0.3,0.2]) - # print(type(res),"\n", res) - # res = micro_model.get_interpol_aux(["b0","bx"],[0.5, 0.3,0.2]) - # print(type(res),"\n", res) - - # res = micro_model.get_interpol_struct("SET",[2.5, 0.3, 0.2]) - # print(res.shape, res[0],'\n') - - # print( micro_model.get_domain_strs() ) - - CPU_end_time = time.process_time() - CPU_time = CPU_end_time - CPU_start_time - print(f'The CPU time is {CPU_time} seconds') - diff --git a/Proof_of_principle/calibration_scripts/.DS_Store b/Proof_of_principle/calibration_scripts/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/Proof_of_principle/calibration_scripts/.DS_Store differ diff --git a/Proof_of_principle/calibration_scripts/compare_eta_cw.py b/Proof_of_principle/calibration_scripts/compare_eta_cw.py new file mode 100644 index 0000000..b16e129 --- /dev/null +++ b/Proof_of_principle/calibration_scripts/compare_eta_cw.py @@ -0,0 +1,236 @@ +import sys +# import os +sys.path.append('../../master_files/') +import pickle +import configparser +import json +import time +import math +from scipy import stats +from itertools import product +import multiprocessing as mp +from sklearn.metrics import mean_absolute_error + +from FileReaders import * +from MicroModels import * +from MesoModels import * +from Visualization import * +from Analysis import * + +if __name__ == '__main__': + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.") + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # LOADING MESO MODEL + pickle_directory = config['Directories']['pickled_files_dir'] + + print('================================================') + print(f'Starting job on data from {pickle_directory}') + print('================================================\n\n') + + meso_filename = config['Filenames']['meso_pickled_filename'] + + MesoModelLoadFile = pickle_directory + meso_filename + with open(MesoModelLoadFile, 'rb') as filehandle: + meso_model = pickle.load(filehandle) + + n_cpus = int(config['compare_eta_cw']['n_cpus']) + cw_dict = meso_model.EL_componentwise_parallel(n_cpus, store=False) + print('Finished computing the coefficients componentwise\n') + print(f'Keys in the dictionary: {cw_dict.keys()}') + + # meso_model.EL_style_closure_parallel(n_cpus) + # print('Finished recomputing the coefficients covariantly, now saving\n') + # pickle_directory = config['Directories']['pickled_files_dir'] + # filename = config['Filenames']['meso_pickled_filename'] + # MesoModelPickleDumpFile = pickle_directory + filename + # with open(MesoModelPickleDumpFile, 'wb') as filehandle: + # pickle.dump(meso_model, filehandle) + + eta_cw = cw_dict['eta_cw'] + print(f'eta_cw.shape: {eta_cw.shape}\n') + + eta_cw_shape = eta_cw[0,0,0].shape + Nt = meso_model.domain_vars['Nt'] + Nx = meso_model.domain_vars['Nx'] + Ny = meso_model.domain_vars['Ny'] + new_shape = tuple(list(eta_cw_shape) + [Nt, Nx, Ny]) + print(f'new_shape: {new_shape}\n') + eta_cw = eta_cw.reshape(new_shape) + + components = json.loads(config['compare_eta_cw']['components']) + components = [tuple(comp) for comp in components] + preprocess_data = json.loads(config['compare_eta_cw']['preprocess_data']) + statistical_tool = CoefficientsAnalysis() + + eta_comps = [eta_cw[comp] for comp in components] + eta_comps = statistical_tool.preprocess_data(eta_comps, preprocess_data) + + eta_quadratic = meso_model.meso_vars['eta'] + eta_quadratic = np.log10(np.abs(eta_quadratic)) + + print('Finished preparing data for plottting distributions\n') + + # # #################################################### + # # PLOTTING THE COMPONENTS DISTRIBUTIONS ALL AT ONCE + # # #################################################### + # plt.rc("font",family="serif") + # plt.rc("mathtext",fontset="cm") + # fig, ax = plt.subplots(1,1, figsize=[6,4]) + + # with warnings.catch_warnings(): + # warnings.filterwarnings("ignore", message='is_categorical_dtype is deprecated') + # warnings.filterwarnings('ignore', message='use_inf_as_na option is deprecated') + # for i in range(len(eta_comps)): + # label = 'comp = ' + str(components[i]) + # sns.histplot(eta_comps[i].flatten(), ax=ax, stat='density', kde=True, label=label) + + # label = 'squaring' + # sns.histplot(eta_quadratic.flatten(), ax=ax, stat='density', color='black', kde=True, label=label) + + # ax.set_ylabel('pdf', fontsize=10) + # xlabel = r'$\log(|\eta|)$' + # ax.set_xlabel(xlabel, fontsize=10) + # ax.legend(loc = 'best', prop={'size': 10}) + + + # fig.tight_layout() + + # fig_directory = config['Directories']['figures_dir'] + # filename = 'eta_cw_distrib' + # format = 'png' + # dpi = 400 + # filename += "." + format + # plt.savefig(fig_directory + filename, format=format, dpi=dpi) + # plt.close() + + # # ################################################################################## + # SINGLE PLOT WITH 6 PANELS AND HISTOGRAMS OF POSITIVE AND NEGATIVE VALUES SEPARATELY + # # ################################################################################## + components = [(0,0), (0,1), (0,2), (1,1), (1,2)] + eta_comps = [np.array(eta_cw[comp]) for comp in components] + + eta_quadratic = meso_model.meso_vars['eta'] + eta_quad_pos = ma.masked_where(eta_quadratic<0, eta_quadratic, copy=True).compressed() + eta_quad_neg = ma.masked_where(eta_quadratic>0, eta_quadratic, copy=True).compressed() + eta_quad_pos = np.log10(np.abs(eta_quad_pos)) + eta_quad_neg = np.log10(np.abs(eta_quad_neg)) + eta_quad_counts = [len(eta_quad_pos), len(eta_quad_neg)] + + tot_counts = [] + for i in range(len(eta_comps)): + eta_pos = ma.masked_where(eta_comps[i]<0, eta_comps[i], copy=True) + eta_neg = ma.masked_where(eta_comps[i]>0, eta_comps[i], copy=True) + eta_pos = eta_pos.compressed() + eta_neg = eta_neg.compressed() + eta_pos = np.log10(eta_pos) + eta_neg = np.log10(np.abs(eta_neg)) + eta_comps[i] = [eta_pos, eta_neg] + + counts = [len(eta_pos), len(eta_neg)] + tot_counts.append(counts) + tot_number = len((eta_cw[components[i]]).flatten()) + print(f'len(eta_comps[i].flatten()): {tot_number}') + + + # Computing the min and max to set a common range in the plot + min, max = np.amin(eta_quad_pos), np.amax(eta_quad_pos) + if np.amin(eta_quad_neg) < np.amin(eta_quad_pos): + min = np.amin(eta_quad_neg) + if np.amax(eta_quad_neg) > np.amax(eta_quad_pos): + max = np.amax(eta_quad_neg) + + for i in range(len(eta_comps)): + m1, M1 = np.amin(eta_comps[i][0]), np.amax(eta_comps[i][0]) + m2, M2 = np.amin(eta_comps[i][1]), np.amax(eta_comps[i][1]) + m, M = np.amin([m1,m2]), np.amax([M1,M2]) + if m < min: + min = m + if M > max: + max = M + if max >0: + max = 0 + + plt.rc("font",family="serif") + plt.rc("mathtext",fontset="cm") + fig, axes = plt.subplots(2,3, figsize=[12,8]) + axes = axes.flatten() + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message='is_categorical_dtype is deprecated') + warnings.filterwarnings('ignore', message='use_inf_as_na option is deprecated') + + for i in range(5): + # sns.histplot(eta_comps[i][0], ax=axes[i], stat='density', kde=True, label=r'$\eta_{+}$') + # sns.histplot(eta_comps[i][1], ax=axes[i], stat='density', kde=True, label=r'$\eta_{-}$') + label = r'$\eta_{+}$' + ', #=' + str(tot_counts[i][0]) + sns.histplot(eta_comps[i][0], ax=axes[i], label=label) + label = r'$\eta_{-}$' + ', #=' + str(tot_counts[i][1]) + sns.histplot(eta_comps[i][1], ax=axes[i], label=label) + + xlabel = r'$\log(|\eta|)$, comp=' + str(components[i]) + axes[i].set_xlabel(xlabel, fontsize=10) + axes[i].set_ylabel('counts', fontsize=10) + axes[i].set_xlim([min, max]) + + axes[i].legend(loc = 'best', prop={'size': 10}) + + label = r'$\eta_{+}$' + ', #=' + str(eta_quad_counts[0]) + sns.histplot(eta_quad_pos, ax=axes[5], label=label) + label = r'$\eta_{-}$' + ', #=' + str(eta_quad_counts[1]) + sns.histplot(eta_quad_neg, ax=axes[5], label=label) + + xlabel = r'$\log(|\eta|)$' + ', squaring' + axes[5].set_xlabel(xlabel, fontsize=10) + axes[5].set_ylabel('counts', fontsize=10) + axes[5].legend(loc = 'best', prop={'size': 10}) + axes[5].set_xlim([min, max]) + + + fig.tight_layout() + + fig_directory = config['Directories']['figures_dir'] + filename = 'pos_neg_histos' + format = 'png' + dpi = 400 + filename += "." + format + plt.savefig(fig_directory + filename, format=format, dpi=dpi) + plt.close() + + # ################################################# + # # PLOTTING THE SIGNS + # ################################################# + # for i in range(len(axes)): + # quantity = eta_cw[components[i]] + # quantity = np.sign(quantity) + # im = axes[i].imshow(quantity, origin='lower', cmap='Spectral_r') + # divider = make_axes_locatable(axes[i]) + # cax = divider.append_axes('right', size='5%', pad=0.05) + # fig.colorbar(im, cax=cax, orientation='vertical') + + # xlabel = r'$|\eta|/\eta$, comp=' + str(components[i]) + # axes[i].set_xlabel(xlabel, fontsize=12) + + # fig.tight_layout() + + # fig_directory = config['Directories']['figures_dir'] + # filename = 'eta_cw_signs' + # format = 'png' + # dpi = 400 + # filename += "." + format + # plt.savefig(fig_directory + filename, format=format, dpi=dpi) + # plt.close() + + + + + + + + diff --git a/Proof_of_principle/calibration_scripts/config_calibration.txt b/Proof_of_principle/calibration_scripts/config_calibration.txt new file mode 100644 index 0000000..aaffb85 --- /dev/null +++ b/Proof_of_principle/calibration_scripts/config_calibration.txt @@ -0,0 +1,148 @@ +# CONFIGURATION FILE FOR SCRIPTS: +########################################################## + +[Directories] + +pickled_files_dir = /scratch/tc2m23/KHIRandom/hydro/new_data/800X800/ET10/pickled/50dx_data + +figures_dir = ./ + +[Filenames] + +meso_pickled_filename = /rHD2d_cg=fw=bl=8dx.pickle + + +[Visualize_correlations] + +vars = ["eta", "shear_sq", "vort_sq", "det_shear", "T_tilde", "n_tilde", "Q1", "Q2"] +ranges = {"x_range": [0.03, 0.97], "y_range": [0.03, 0.97]} +num_T_slices = 3 + +# Set values to null if you do not want to restrict the data +preprocess_data = {"value_ranges": [[null, null], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null]], + "log_abs": [1, 1, 1, 1, 1, 1, 1, 1]} + +#set extractions 0 if you don't want to extract randomly from sample +extractions = 0 + +#Options for building weights: 'Q2' 'Q1_skew' 'Q1_non_neg' 'residual_weights' 'denominator_weights'. Any other choice correspond to no weights + +weighing_func = nope + +# this is only relevant when weights are built using 'residual_weights': specify a positive residual you want to consider for the weights +residual_str = pi_res_sq + +# this is only relevant when weights are built using 'denominator_weights': specify the positive quantity at the denominator of extracted coefficient +denominator_str = shear_sq + +# Set format_fig to either pdf or png +format_fig = png + +[Fs_residual_dependence] + +coeff = zeta +residual = Pi_res +EL_force = exp_tilde + +[compare_eta_cw] + +n_cpus = 30 +components = [[0,0], [0,1], [0,2], [1,1], [1,2]] +preprocess_data = {"value_ranges": [[null, null], [null, null], [null, null], [null, null], [null, null]], + "log_abs": [1, 1, 1, 1, 1]} + + +[Regression_settings] + +dependent_var = eta +regressors = ["vort_sq", "det_shear", "n_tilde", "T_tilde"] +ranges = {"x_range": [0.03, 0.97], "y_range": [0.03, 0.97]} +num_T_slices = 3 + +add_intercept = 0 +centralize = 1 +test_percentage = 0.2 + +# Set values to null if you do not want to restrict the data +preprocess_data = {"value_ranges": [[null, 0], [null, null], [null, null], [null, null], [null, null]], + "log_abs": [1, 1, 1, 1, 1]} + +#set extractions 0 if you don't want to extract randomly from sample: extraction only in the scatter plot? Not in regression? +extractions = 0 + +#Options for building weights: 'Q2' 'Q1_skew' 'Q1_non_neg' 'residual_weights' 'denominator_weights'. Any other choice correspond to no weights + +weighing_func = no_weights + +# only relevant when weights are built using 'residual_weights': specify a positive residual you want to consider for the weights +residual_str = pi_res_sq + +# this is only relevant when weights are built using 'denominator_weights': specify the positive quantity at the denominator of extracted coefficient +denominator_str = shear_sq + +# Set format_fig to either pdf or png +format_fig = png + +[Find_best_fit_settings] + +var_to_model = eta +regressors_strs = ["vort_sq", "det_shear", "shear_sq", "T_tilde", "n_tilde", "Q1", "Q2", "acc_mag", + "Theta_sq", "n_tilde_dot", "T_tilde_dot", "sD_n_tilde_sq", "dot_Dn_Theta", "exp_tilde"] + +ranges = {"x_range": [0.03, 0.97], "y_range": [0.03, 0.97]} +num_T_slices = 3 + +add_intercept = 0 +centralize = 1 +test_percentage = 0.2 + +# Set values to null if you do not want to restrict the data + +preprocess_data = {"log_abs": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]} + +n_cpus = 40 + +# Set format_fig to either pdf or png +format_fig = png + +[Regress+residual_check_settings] + +coeff_str = kappa +coeff_regressors_strs = ["shear_sq", "n_tilde", "Q1", "sD_n_tilde_sq", "dot_Dn_Theta"] +residual_str = q_res_sq +closure_ingr_str = Theta_sq + +regression_ranges = {"x_range": [0.04, 0.96], "y_range": [0.04, 0.96]} +idxs_time_slices = [1] + +add_intercept = 1 +centralize = 0 + +#set the following 0 if you don't want to split into train and test +test_percentage = 0. +preprocess_data = { "log_abs": [1, 1, 1, 1, 1, 1, 1, 1], + "sqrt": [0, 0, 0, 0, 0, 0, 1, 1]} + +[PCA_settings] + +dependent_var = eta +explanatory_vars = ["n_tilde", "T_tilde", "det_shear", "shear_sq", "vort_sq", "Q1", "Q2"] +ranges = {"x_range": [0.03, 0.97], "y_range": [0.03, 0.97]} +num_T_slices = 3 + +pcs_num = 1 +regressors_2_reduce = ["det_shear", "shear_sq", "vort_sq", "Q1", "Q2"] +variance_wanted = 0.9 + +# Careful, only one dictionary here +#preprocess_data = {"pos_or_neg": [1,1,1], "log_or_not": [1,1,1]} + +# Set values to null if you do not want to restrict the data +preprocess_data = {"value_ranges": [[null, null], [null, null], [null, null], [null, null], [null, null]], + "log_abs": [1, 1, 1, 1, 1]} + +#set extractions 0 if you don't want to extract randomly from sample +extractions = 0 + +# Set format_fig to either pdf or png +format_fig = png \ No newline at end of file diff --git a/Proof_of_principle/calibration_scripts/find_best_fit.py b/Proof_of_principle/calibration_scripts/find_best_fit.py new file mode 100644 index 0000000..cec5a40 --- /dev/null +++ b/Proof_of_principle/calibration_scripts/find_best_fit.py @@ -0,0 +1,344 @@ +import sys +# import os +sys.path.append('../../master_files/') +import pickle +import configparser +import json +import time +import math +from scipy import stats +from itertools import product +import multiprocessing as mp +from sklearn.metrics import mean_absolute_error + +from FileReaders import * +from MicroModels import * +from MesoModels import * +from Visualization import * +from Analysis import * + +if __name__ == '__main__': + + # ################################################################## + # # GIVEN A VAR TO MODEL AND A LIST OF REGRESSORS, PERFORM THE REGRESSION + # # WITH ANY POSSIBLE COMBINATION OF REGRESSORS TAKEN FROM THE INPUT LIST + # # FIND THE ONE THAT BEST DESCRIBE THE QUANITY TO BE MODELLED, AND PRODUCE + # # A SCATTER PLOT FOR IT + # ################################################################## + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.") + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # LOADING MESO MODEL + pickle_directory = config['Directories']['pickled_files_dir'] + meso_pickled_filename = config['Filenames']['meso_pickled_filename'] + MesoModelLoadFile = pickle_directory + meso_pickled_filename + + print('================================================') + print(f'Starting job on data from {MesoModelLoadFile}') + print('================================================\n\n') + + with open(MesoModelLoadFile, 'rb') as filehandle: + meso_model = pickle.load(filehandle) + + # # RE-COMPUTING DERIVATIVES AND STUFF FOR MODELLING COEFFICIENTS + # # adding labels to dictionary for better figures + # entry_dic = {'D_n_tilde' : r'$\nabla_{a}\tilde{n}$'} + # meso_model.update_labels_dict(entry_dic) + # entry_dic = {'D_eps_tilde' : r'$\nabla_{a}\tilde{\varepsilon}$'} + # meso_model.update_labels_dict(entry_dic) + # entry_dic = {'n_tilde_dot' : r'$\dot{\tilde{n}}$'} + # meso_model.update_labels_dict(entry_dic) + # entry_dic = {'T_tilde_dot' : r'$\dot{\tilde{T}}$'} + # meso_model.update_labels_dict(entry_dic) + # entry_dic = {'sD_T_tilde': r'$D_{a}\tilde{T}$'} + # meso_model.update_labels_dict(entry_dic) + # entry_dic = {'sD_n_tilde': r'$D_{a}\tilde{n}$'} + # meso_model.update_labels_dict(entry_dic) + # entry_dic = {'sD_n_tilde_sq' : r'$D_{a}\tilde{n}D^{a}\tilde{n}$'} + # meso_model.update_labels_dict(entry_dic) + # entry_dic = {'dot_Dn_Theta' : r'$D_{a}\tilde{n}\Theta^{a}$'} + # meso_model.update_labels_dict(entry_dic) + + # Nt = meso_model.domain_vars['Nt'] + # Nx = meso_model.domain_vars['Nx'] + # Ny = meso_model.domain_vars['Ny'] + + # meso_model.nonlocal_vars_strs = ['u_tilde', 'T_tilde', 'n_tilde', 'eps_tilde'] + # meso_model.deriv_vars.update({'D_n_tilde' : np.zeros((Nt,Nx,Ny,3))}) + # meso_model.deriv_vars.update({'D_eps_tilde' : np.zeros((Nt,Nx,Ny,3))}) + + # n_cpus = int(config['Find_best_fit_settings']['n_cpus']) + # start_time = time.perf_counter() + # meso_model.calculate_derivatives() + # time_taken = time.perf_counter() - start_time + # print('Finished computing derivatives (serial), time taken: {}\n'.format(time_taken), flush=True) + + # start_time = time.perf_counter() + # meso_model.closure_ingredients_parallel(n_cpus) + # time_taken = time.perf_counter() - start_time + # print('Finished computing the closure ingredients in parallel, time taken: {}\n'.format(time_taken), flush=True) + + # start_time = time.perf_counter() + # meso_model.EL_style_closure_parallel(n_cpus) + # time_taken = time.perf_counter() - start_time + # print('Finished computing the EL_style closure in parallel, time taken: {}\n'.format(time_taken), flush=True) + + # start_time = time.perf_counter() + # meso_model.modelling_coefficients_parallel(n_cpus) + # time_taken = time.perf_counter() - start_time + # print('Finished computing quantities to model extracted coefficients, time taken: {}\n'.format(time_taken), flush=True) + + # pickle_directory = config['Directories']['pickled_files_dir'] + # filename = config['Filenames']['meso_pickled_filename'] + # MesoModelPickleDumpFile = pickle_directory + filename + # with open(MesoModelPickleDumpFile, 'wb') as filehandle: + # pickle.dump(meso_model, filehandle) + + + # WHICH DATA YOU WANT TO RUN THE ROUTINE ON? + dep_var_str = config['Find_best_fit_settings']['var_to_model'] + dep_var = meso_model.meso_vars[dep_var_str] + regressors_strs = json.loads(config['Find_best_fit_settings']['regressors_strs']) + regressors = [] + for i in range(len(regressors_strs)): + temp = meso_model.meso_vars[regressors_strs[i]] + regressors.append(temp) + print(f'Dependent var: {dep_var_str},\n Explanatory vars: {regressors_strs}\n') + + + # WHICH GRID-RANGES SHOULD WE CONSIDER? + regression_ranges = json.loads(config['Find_best_fit_settings']['ranges']) + x_range = regression_ranges['x_range'] + y_range = regression_ranges['y_range'] + num_slices_meso = int(config['Find_best_fit_settings']['num_T_slices']) + time_of_central_slice = meso_model.domain_vars['T'][int((num_slices_meso-1)/2)] + ranges = [[time_of_central_slice, time_of_central_slice], x_range, y_range] + + # READING PREPROCESSING INFO FROM CONFIG FILE + preprocess_data = json.loads(config['Find_best_fit_settings']['preprocess_data']) + add_intercept = not not int(config['Find_best_fit_settings']['add_intercept']) + centralize = int(config['Find_best_fit_settings']['centralize']) + test_percentage = float(config['Find_best_fit_settings']['test_percentage']) + + data = [dep_var] + for i in range(len(regressors)): + data.append(regressors[i]) + + # PRE-PROCESSING: Trimming, pre-processing and splitting intro train + test set + statistical_tool = CoefficientsAnalysis() + model_points = meso_model.domain_vars['Points'] + new_data = statistical_tool.trim_dataset(data, ranges, model_points) + new_data = statistical_tool.preprocess_data(new_data, preprocess_data) + + if centralize: + new_data, means = statistical_tool.centralize_dataset(new_data) + dep_var_mean = means[0] + regressors_means = means[1:] + + training_data, test_data = statistical_tool.split_train_test(new_data, test_percentage=test_percentage) + + dep_var_train = training_data[0] + dep_var_test = test_data[0] + regressors_train = training_data[1:] + regressors_test = test_data[1:] + + + # REGRESSING IN PARALLEL: consider all possible combination of input regressors' list + # ALSO: EVALUATE GOODNESS OF FIT + num_regressors = len(regressors_train) + bool_regressors_combs = [seq for seq in product((True, False), repeat=num_regressors)][0:-1] + print(f'Number of tested combinations of regressors: {len(bool_regressors_combs)}\n') + + def parall_regress_task(comb_regressors): + """ + """ + # DOING THE REGRESSION GIVEN SOME COMBINATION OF INPUT REGRESSORS + actual_regressors = [] + actual_regressors_strs = [] + for i in range(num_regressors): + if comb_regressors[i] == True: + actual_regressors.append(regressors_train[i]) + actual_regressors_strs.append(regressors_strs[i]) + + coeffs, _ = statistical_tool.scalar_regression(dep_var_train, actual_regressors, add_intercept=add_intercept) + + # BUILDING TEST-DATA PREDICTIONs GIVEN THE REGRESSED MODEL + actual_regressors = [] + for i in range(num_regressors): + if comb_regressors[i] == True: + actual_regressors.append(regressors_test[i]) + + dep_var_model = np.zeros(dep_var_test.shape) + if centralize: + for i in range(len(actual_regressors)): + dep_var_model += np.multiply(coeffs[i], actual_regressors[i]) + + else: + if add_intercept: + dep_var_model += coeffs[0] + for i in range(len(actual_regressors)): + dep_var_model += np.multiply(coeffs[i+1], actual_regressors[i]) + elif not add_intercept: + for i in range(len(actual_regressors)): + dep_var_model += np.multiply(coeffs[i], actual_regressors[i]) + + # r, _ = stats.pearsonr(dep_var_test, dep_var_model) + # return r, coeffs , comb_regressors + + # mean_error = mean_absolute_error(dep_var_test, dep_var_model) + # return mean_error, coeffs , comb_regressors + + w = statistical_tool.wasserstein_distance(dep_var_test, dep_var_model, sample_points=300) + return w, coeffs, comb_regressors + + + n_cpus = int(config['Find_best_fit_settings']['n_cpus']) + # pearsons = [] + # mean_errors = [] + wassersteins = [] + fitted_coeffs = [] + regressors_combinations = [] + with mp.Pool(processes=n_cpus) as pool: + print('Performing all possible regression of {} in parallel with {} processes\n'.format(dep_var_str, pool._processes), flush=True) + for result in pool.map(parall_regress_task, bool_regressors_combs): + # pearsons.append(result[0]) + # mean_errors.append(result[0]) + wassersteins.append(result[0]) + fitted_coeffs.append(result[1]) + regressors_combinations.append(result[2]) + + + # FINDING BEST MODEL, RE-BUILDING DATA PREDICITON FOR TEST SET + # rs_squared = np.power(pearsons, 2) + # max_r_index = np.argmax(rs_squared) + # best_coeffs = fitted_coeffs[max_r_index] + # best_regressors_combination = regressors_combinations[max_r_index] + # ordering_idx = np.argsort(rs_squared) + # print('Printing max r-squared and the corresponding regression combination:') + + min_w_index = np.argmin(wassersteins) + best_coeffs = fitted_coeffs[min_w_index] + ordering_idx = np.argsort(wassersteins) + + + # for i in range(-1,-6,-1): + for i in range(0,6,1): + index = ordering_idx[i] + which_regressors = [] + for j in range(len(regressors_combinations[index])): + if regressors_combinations[index][j] == True: + which_regressors.append(regressors_strs[j]) + # print(f'r^2: {rs_squared[index]}, regressors: {which_regressors}') + print(f'wasserstein: {wassersteins[index]}, regressors: {which_regressors}') + + print(f'\nBest coefficients: {best_coeffs}') + + # # RECONSTRUCTING THE BEST MODEL FOR TEST DATA + # actual_regressors = [] + # actual_regressors_strs = [] + # if centralize: + # actual_regressors_means = [] + # for i in range(len(regressors_strs)): + # if best_regressors_combination[i] == True: + # actual_regressors.append(regressors_test[i]) + # actual_regressors_strs.append(regressors_strs[i]) + # if centralize: + # actual_regressors_means.append(regressors_means[i]) + + # dep_var_model = np.zeros(dep_var_test.shape) + # if centralize: + # for i in range(len(actual_regressors)): + # dep_var_model += np.multiply(best_coeffs[i], actual_regressors[i]) + + # else: + # if add_intercept: + # dep_var_model += best_coeffs[0] + # for i in range(len(actual_regressors)): + # dep_var_model += np.multiply(best_coeffs[i+1], actual_regressors[i]) + # elif not add_intercept: + # for i in range(len(actual_regressors)): + # dep_var_model += np.multiply(best_coeffs[i], actual_regressors[i]) + + + # # FINALLY, PLOTTING THE BEST MODEL PREDICTIONS VS TEST DATA + # ylabel = dep_var_str + # if hasattr(meso_model, 'labels_var_dict') and dep_var_str in meso_model.labels_var_dict.keys(): + # ylabel = meso_model.labels_var_dict[dep_var_str] + # if preprocess_data['log_abs'][0] == 1: + # ylabel = r"$\log($" + ylabel + r"$)$" + # statistical_tool.visualize_correlation(dep_var_model, dep_var_test, xlabel=r"$regression$ $model$", ylabel=ylabel) + + # # Building the annotation box with the specifics of the model + # if centralize: + # text_for_box = r"$Model$ $coeff.s$, $means:$" + "\n" + # add_text = dep_var_str + " : , " + # if hasattr(meso_model, 'labels_var_dict') and dep_var_str in meso_model.labels_var_dict.keys(): + # add_text = meso_model.labels_var_dict[dep_var_str] + " : , " + # sign, val = int(np.sign(dep_var_mean)), '%.3f' %np.abs(dep_var_mean) + # coeff_for_text_box = r"$+{}$".format(val) if sign == 1 else r"$-{}$".format(val) + # add_text += coeff_for_text_box + # text_for_box += add_text + + # for i in range(len(actual_regressors_strs)): + # add_text = "\n" + actual_regressors_strs[i] + " : " + # if hasattr(meso_model, 'labels_var_dict') and actual_regressors_strs[i] in meso_model.labels_var_dict.keys(): + # add_text = "\n" + meso_model.labels_var_dict[actual_regressors_strs[i]] + " : " + + # sign, val = int(np.sign(best_coeffs[i])), '%.3f' %np.abs(best_coeffs[i]) + # coeff_for_text_box = r"$+{}$".format(val) if sign == 1 else r"$-{}$".format(val) + # add_text += coeff_for_text_box + + # sign, val = int(np.sign(actual_regressors_means[i])), '%.3f' %np.abs(actual_regressors_means[i]) + # coeff_for_text_box = r", $+{}$".format(val) if sign == 1 else r", $-{}$".format(val) + # add_text += coeff_for_text_box + + # text_for_box += add_text + + + # else: + # text_for_box = r"$Model$ $coeff.s:$" + "\n" + # if add_intercept: + # sign, val = int(np.sign(best_coeffs[0])), '%.3f' %np.abs(best_coeffs[0]) + # text_for_box += r'$offset$' +' : ' + # coeff_for_text_box = r"$+{}$".format(val) if sign == 1 else r"$-{}$".format(val) + # text_for_box += coeff_for_text_box + # else: + # best_coeffs = [0] + best_coeffs + + # for i in range(len(actual_regressors_strs)): + # sign, val = int(np.sign(best_coeffs[i+1])), '%.3f' %np.abs(best_coeffs[i+1]) + # coeff_for_text_box = r"$+{}$".format(val) if sign == 1 else r"$-{}$".format(val) + + # add_text = "\n" + actual_regressors_strs[i] + " : " + # if hasattr(meso_model, 'labels_var_dict') and actual_regressors_strs[i] in meso_model.labels_var_dict.keys(): + # add_text = "\n" + meso_model.labels_var_dict[actual_regressors_strs[i]] + " : " + + # add_text += coeff_for_text_box + # text_for_box += add_text + + # bbox_args = dict(boxstyle="round", fc="0.95") + # plt.annotate(text=text_for_box, xy = (0.99,0.1), xycoords='figure fraction', bbox=bbox_args, ha="right", va="bottom", fontsize = 8) + + + # # Saving the figure + # saving_directory = config['Directories']['figures_dir'] + # if centralize: + # filename = f'/CentredBestFit_{dep_var_str}_vs' + # else: + # filename = f'/BestFit_{dep_var_str}_vs' + + # for i in range(0,len(regressors_strs)): + # filename += f'_{regressors_strs[i]}' + # format = str(config['Regression_settings']['format_fig']) + # filename += "." + format + # dpi = None + # if format == 'png': + # dpi = 400 + # plt.savefig(saving_directory + filename, format=format, dpi=dpi) + # print(f'Finished regression and scatter plot for {dep_var_str}, saved as {filename}\n\n') \ No newline at end of file diff --git a/Proof_of_principle/calibration_scripts/fs_residual_dependence.py b/Proof_of_principle/calibration_scripts/fs_residual_dependence.py new file mode 100644 index 0000000..e294b7c --- /dev/null +++ b/Proof_of_principle/calibration_scripts/fs_residual_dependence.py @@ -0,0 +1,213 @@ +import sys +# import os +sys.path.append('../../master_files/') +import pickle +import configparser +import json +import time +import math +from scipy import stats +from itertools import product +import multiprocessing as mp +from sklearn.metrics import mean_absolute_error + +from FileReaders import * +from MicroModels import * +from MesoModels import * +from Visualization import * +from Analysis import * + +if __name__ == '__main__': + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.") + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # LOADING MESO MODEL + pickle_directory = config['Directories']['pickled_files_dir'] + + print('================================================') + print(f'Starting job on data from {pickle_directory}') + print('================================================\n\n') + + meso_filename_8 = '/rHD2d_cg=fw=bl=8dx.pickle' + meso_filename_4 = '/rHD2d_cg=fw=bl=4dx.pickle' + meso_filename_2 = '/rHD2d_cg=fw=bl=2dx.pickle' + + MesoModelLoadFile = pickle_directory + meso_filename_2 + with open(MesoModelLoadFile, 'rb') as filehandle: + meso2 = pickle.load(filehandle) + + MesoModelLoadFile = pickle_directory + meso_filename_4 + with open(MesoModelLoadFile, 'rb') as filehandle: + meso4 = pickle.load(filehandle) + + MesoModelLoadFile = pickle_directory + meso_filename_8 + with open(MesoModelLoadFile, 'rb') as filehandle: + meso8 = pickle.load(filehandle) + + # # READING INFO ON RESIDUALS FROM CONFIG FILE + # coeff_str = config['Fs_residual_dependence']['coeff'] + # residual_str = config['Fs_residual_dependence']['residual'] + # EL_force_str = config['Fs_residual_dependence']['EL_force'] + + # print(f'coeff_str: {coeff_str}\nresidual_str: {residual_str}\nEL_force_str: {EL_force_str}\n') + + # coeff2 = np.abs(meso2.meso_vars[coeff_str]) + # coeff4 = np.abs(meso4.meso_vars[coeff_str]) + # coeff8 = np.abs(meso8.meso_vars[coeff_str]) + + # coeffs = [coeff2, coeff4, coeff8] + # filter_sizes = [2,4,8] + + # log_coeffs = [np.log10(elem) for elem in coeffs] + # means = [np.mean(elem) for elem in log_coeffs] + # print(f'The log-mean values of {coeff_str} are: {means[0]}, {means[1]}, {means[2]}') + + # coeffs_rescaled = [coeffs[i]/(filter_sizes[i]**2) for i in range(len(coeffs))] + # log_coeffs_rescaled = [np.log10(elem) for elem in coeffs_rescaled] + + # EL_force2 = np.abs(meso2.meso_vars[EL_force_str]) + # EL_force4 = np.abs(meso4.meso_vars[EL_force_str]) + # EL_force8 = np.abs(meso8.meso_vars[EL_force_str]) + + # residual2 = meso2.meso_vars[residual_str] + # residual4 = meso4.meso_vars[residual_str] + # residual8 = meso8.meso_vars[residual_str] + + # log_residuals = [np.log10(elem) for elem in [residual2, residual4, residual8]] + # log_EL_forces = [np.log10(elem) for elem in [EL_force2, EL_force4, EL_force8]] + + # means = [np.mean(elem) for elem in log_residuals] + # print(f'The log-mean value of pi_res_sq are: {means[0]}, {means[1]}, {means[2]}') + + + # plt.rc("font",family="serif") + # plt.rc("mathtext",fontset="cm") + # fig, axes = plt.subplots(1,3, figsize=[12,4]) + # axes = axes.flatten() + + # stat = 'density' + # with warnings.catch_warnings(): + # warnings.filterwarnings("ignore", message='is_categorical_dtype is deprecated') + # warnings.filterwarnings('ignore', message='use_inf_as_na option is deprecated') + + # sns.histplot(log_EL_forces[0].flatten(), stat=stat, kde=True, color='firebrick', ax=axes[0], label=r'$L=2dx$') + # sns.histplot(log_EL_forces[1].flatten(), stat=stat, kde=True, color='steelblue', ax=axes[0], label=r'$L=4dx$') + # sns.histplot(log_EL_forces[2].flatten(), stat=stat, kde=True, color='olive', ax=axes[0], label=r'$L=8dx$') + + # sns.histplot(log_residuals[0].flatten(), stat=stat, kde=True, color='firebrick', ax=axes[1], label=r'$L=2dx$') + # sns.histplot(log_residuals[1].flatten(), stat=stat, kde=True, color='steelblue', ax=axes[1], label=r'$L=4dx$') + # sns.histplot(log_residuals[2].flatten(), stat=stat, kde=True, color='olive', ax=axes[1], label=r'$L=8dx$') + + # sns.histplot(log_coeffs_rescaled[0].flatten(), stat=stat, kde=True, color='firebrick', ax=axes[2], label=r'$L=2dx$') + # sns.histplot(log_coeffs_rescaled[1].flatten(), stat=stat, kde=True, color='steelblue', ax=axes[2], label=r'$L=4dx$') + # sns.histplot(log_coeffs_rescaled[2].flatten(), stat=stat, kde=True, color='olive', ax=axes[2], label=r'$L=8dx$') + + + # axes[0].legend(loc = 'best', prop={'size': 10}) + # axes[1].legend(loc = 'best', prop={'size': 10}) + # axes[2].legend(loc = 'best', prop={'size': 10}) + + # axes[0].set_ylabel('pdf', fontsize=12) + # axes[1].set_ylabel('pdf', fontsize=12) + # axes[2].set_ylabel('pdf', fontsize=12) + + # xlabel = meso2.labels_var_dict[EL_force_str] + # xlabel = r'$\log(|$' + xlabel + r'$|)$' + # axes[0].set_xlabel(xlabel, fontsize=12) + + # xlabel = meso2.labels_var_dict[residual_str] + # xlabel = r'$\log($' + xlabel + r'$)$' + # axes[1].set_xlabel(xlabel, fontsize=12) + + # xlabel = meso2.labels_var_dict[coeff_str] + # xlabel = xlabel + r'$/\tilde{L}^2,\qquad \tilde{L} = L/dx$' + # axes[2].set_xlabel(xlabel, fontsize=12) + + # fig.tight_layout() + + # fig_directory = config['Directories']['figures_dir'] + # filename = 'fs_residual_dependence' + # format = 'png' + # dpi = 400 + # filename += "." + format + # plt.savefig(fig_directory + filename, format=format, dpi=dpi) + + # READING INFO ON RESIDUALS FROM CONFIG FILE + coeff_str = config['Fs_residual_dependence']['coeff'] + residual_str = config['Fs_residual_dependence']['residual'] + EL_force_str = config['Fs_residual_dependence']['EL_force'] + + print(f'coeff_str: {coeff_str}\nresidual_str: {residual_str}\nEL_force_str: {EL_force_str}\n') + + zeta2 = np.abs(meso2.meso_vars['zeta']) + zeta4 = np.abs(meso4.meso_vars['zeta']) + zeta8 = np.abs(meso8.meso_vars['zeta']) + zetas = [zeta2, zeta4, zeta8] + + kappa2 = np.abs(meso2.meso_vars['kappa']) + kappa4 = np.abs(meso4.meso_vars['kappa']) + kappa8 = np.abs(meso8.meso_vars['kappa']) + kappas = [kappa2, kappa4, kappa8] + + filter_sizes = [2,4,8] + + zetas_rescaled = [zetas[i]/(filter_sizes[i]**2) for i in range(len(zetas))] + kappas_rescaled = [kappas[i]/(filter_sizes[i]**2) for i in range(len(kappas))] + + log_zetas_rescaled = [np.log10(elem) for elem in zetas_rescaled] + log_kappas_rescaled = [np.log10(elem) for elem in kappas_rescaled] + + plt.rc("font",family="serif") + plt.rc("mathtext",fontset="cm") + fig, axes = plt.subplots(1,2, figsize=[8,4]) + axes = axes.flatten() + + stat = 'density' + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message='is_categorical_dtype is deprecated') + warnings.filterwarnings('ignore', message='use_inf_as_na option is deprecated') + + sns.histplot(log_zetas_rescaled[0].flatten(), stat=stat, kde=True, color='firebrick', ax=axes[0], label=r'$L=2dx$') + sns.histplot(log_zetas_rescaled[1].flatten(), stat=stat, kde=True, color='steelblue', ax=axes[0], label=r'$L=4dx$') + sns.histplot(log_zetas_rescaled[2].flatten(), stat=stat, kde=True, color='olive', ax=axes[0], label=r'$L=8dx$') + + sns.histplot(log_kappas_rescaled[0].flatten(), stat=stat, kde=True, color='firebrick', ax=axes[1], label=r'$L=2dx$') + sns.histplot(log_kappas_rescaled[1].flatten(), stat=stat, kde=True, color='steelblue', ax=axes[1], label=r'$L=4dx$') + sns.histplot(log_kappas_rescaled[2].flatten(), stat=stat, kde=True, color='olive', ax=axes[1], label=r'$L=8dx$') + + + axes[0].legend(loc = 'best', prop={'size': 10}) + axes[1].legend(loc = 'best', prop={'size': 10}) + + axes[0].set_ylabel('pdf', fontsize=12) + axes[1].set_ylabel('pdf', fontsize=12) + + xlabel = meso2.labels_var_dict['zeta'] + xlabel = xlabel + r'$/\tilde{L}^2,\qquad \tilde{L} = L/dx$' + axes[0].set_xlabel(xlabel, fontsize=12) + + xlabel = meso2.labels_var_dict['kappa'] + xlabel = xlabel + r'$/\tilde{L}^2,\qquad \tilde{L} = L/dx$' + axes[1].set_xlabel(xlabel, fontsize=12) + + fig.tight_layout() + + fig_directory = config['Directories']['figures_dir'] + filename = 'fs_residual_dependence' + format = 'png' + dpi = 400 + filename += "." + format + plt.savefig(fig_directory + filename, format=format, dpi=dpi) + + + + + + + diff --git a/Proof_of_principle/calibration_scripts/hunting_correlations.py b/Proof_of_principle/calibration_scripts/hunting_correlations.py new file mode 100644 index 0000000..cf65cec --- /dev/null +++ b/Proof_of_principle/calibration_scripts/hunting_correlations.py @@ -0,0 +1,148 @@ +import sys +# import os +sys.path.append('../../master_files/') +import pickle +import configparser +import json +import time +import math + +from FileReaders import * +from MicroModels import * +from MesoModels import * +from Visualization import * +from Analysis import * + +if __name__ == '__main__': + + # ################################################################## + # # USING PCA TO LOOK FOR CORRELATIONS: FIND THE FIRST FEW PRINCIPAL + # # COMPONENTS IN A DATASET, AND PRINT THEIR CORRELATION PLOT WITH + # # A RESIDUAL/DISSIPATIVE COEFFICIENT + # ################################################################## + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.") + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # LOADING MESO AND MICRO MODELS + pickle_directory = config['Directories']['pickled_files_dir'] + meso_pickled_filename = config['Filenames']['meso_pickled_filename'] + MesoModelLoadFile = pickle_directory + meso_pickled_filename + + print('================================================') + print(f'Starting job on data from {MesoModelLoadFile}') + print('================================================\n\n') + + with open(MesoModelLoadFile, 'rb') as filehandle: + meso_model = pickle.load(filehandle) + + # WHICH DATA YOU WANT TO RUN THE ROUTINE ON? + dep_var_str = config['PCA_settings']['dependent_var'] + explanatory_vars_strs = json.loads(config['PCA_settings']['explanatory_vars']) + explanatory_vars = [] + for i in range(len(explanatory_vars_strs)): + temp = meso_model.meso_vars[explanatory_vars_strs[i]] + explanatory_vars.append(temp) + dep_var = meso_model.meso_vars[dep_var_str] + print(f'Dependent var: {dep_var_str}, Explanatory vars: {explanatory_vars_strs}\n') + + + # WHICH GRID-RANGES SHOULD WE CONSIDER? + PCA_ranges = json.loads(config['PCA_settings']['ranges']) + x_range = PCA_ranges['x_range'] + y_range = PCA_ranges['y_range'] + num_slices_meso = int(config['PCA_settings']['num_T_slices']) + time_of_central_slice = meso_model.domain_vars['T'][int((num_slices_meso-1)/2)] + ranges = [[time_of_central_slice, time_of_central_slice], x_range, y_range] + + # READING PREPROCESSING INFO FROM CONFIG FILE + preprocess_data = json.loads(config['PCA_settings']['preprocess_data']) + extractions = int(config['PCA_settings']['extractions']) #This is only used for plotting, like in regression routines + + # PRE-PROCESSING + data = [dep_var] + for i in range(len(explanatory_vars)): + data.append(explanatory_vars[i]) + statistical_tool = CoefficientsAnalysis() + model_points = meso_model.domain_vars['Points'] + new_data = statistical_tool.trim_dataset(data, ranges, model_points) + new_data = statistical_tool.preprocess_data(new_data, preprocess_data) + # if extractions != 0: + # new_data = statistical_tool.extract_randomly(new_data, extractions) + + dep_var = new_data[0] + explanatory_vars = [] + for i in range(1,len(new_data)): + explanatory_vars.append(new_data[i]) + + # NOW DO THE PCA ANALYSIS + pcs_num = int(config['PCA_settings']['pcs_num']) + highest_pcs_decomp, scores = statistical_tool.PCA_find_regressors(dep_var, explanatory_vars, pcs_num=pcs_num) + + for i in range(len(highest_pcs_decomp)): + print(f'{i}-th highest PCs decomposition: \n{highest_pcs_decomp[i]}\nCorresponding score: {scores[i]}\n') + + # NOW, USE THE PCs DECOMPOSITION TO BUILD A LINEAR MODEL FOR DEP_VAR + # THIS IS USED FOR PRODUCING A CORRELATION PLOT TO VISUALLY GRASP HOW GOOD THE MODEL IS + # ONLY THE PC WITH HIGHEST SCORE ON DEP_VAR IS USED, THE OTHERS PROVIDE AUXILIARY INFO? + + + # BUILD THE LINEAR MODEL FROM PCA + best_pc_decomp = highest_pcs_decomp[0] + coeffs = [] + temp = 0 + for j in range(0, len(explanatory_vars)): + coeff = -best_pc_decomp[j+1] / best_pc_decomp[0] + print(f'{explanatory_vars_strs[j]}-rescaled coefficient: {coeff}\n') + coeffs.append(coeff) + temp += np.multiply(coeff, explanatory_vars[j]) + dep_var_model = temp + + # FINALLY, PLOTTING + print('\nProducing correlation plot using info from PCA') + x = dep_var_model + y = dep_var + if extractions != 0: + new_data = statistical_tool.extract_randomly([x,y], extractions) + x, y = new_data[0], new_data[1] + + ylabel = dep_var_str + if hasattr(meso_model, 'labels_var_dict') and dep_var_str in meso_model.labels_var_dict.keys(): + ylabel = meso_model.labels_var_dict[dep_var_str] + if preprocess_data['log_abs'][0] ==1: + ylabel = r"$\log($" + ylabel + r"$)$" + g=statistical_tool.visualize_correlation(x, y, xlabel=r"$Highest$ $PC$ $model$", ylabel=ylabel) + + # Building the text for the annotation box + text_for_box = r"$Coefficients$ $(\log)$:" + "\n" + legend_entries = [] + for i in range(len(explanatory_vars_strs)): + label = explanatory_vars_strs[i] + " : " + if hasattr(meso_model, 'labels_var_dict') and explanatory_vars_strs[i] in meso_model.labels_var_dict.keys(): + label = meso_model.labels_var_dict[explanatory_vars_strs[i]] + " : " + sign, val = int(np.sign(coeffs[i])), '%.3f' %np.abs(coeffs[i]) + coeff_for_label = r"$+{}$".format(val) if sign == 1 else r"$-{}$".format(val) + label += coeff_for_label + "\n" + legend_entries.append(label) + for i in range(len(legend_entries)): + text_for_box += legend_entries[i] + bbox_args = dict(boxstyle="round", fc="0.95") + plt.annotate(text=text_for_box, xy = (0.99,0.1), xycoords='figure fraction', bbox=bbox_args, ha="right", va="bottom", fontsize = 9) + + # Saving the figure + saving_directory = config['Directories']['figures_dir'] + filename = f'/PCA_hunt_{dep_var_str}' + format = str(config['Regression_settings']['format_fig']) + filename += "." + format + dpi = None + if format == 'png': + dpi = 400 + plt.savefig(saving_directory + filename, format=format, dpi=dpi) + print(f'Finished correlation plot for {dep_var_str}, saved as {filename}\n\n') + + \ No newline at end of file diff --git a/Proof_of_principle/calibration_scripts/py_parallel.slurm b/Proof_of_principle/calibration_scripts/py_parallel.slurm new file mode 100644 index 0000000..351d7be --- /dev/null +++ b/Proof_of_principle/calibration_scripts/py_parallel.slurm @@ -0,0 +1,34 @@ +#!/bin/bash +#################################### +# JOB INFO: parallel, single node +#################################### + +#SBATCH --output=outputs/parallel_%A.out +#SBATCH --nodes=1 +#SBATCH --ntasks=30 +#SBATCH --time=00:10:00 + +#SBATCH --mail-type=begin +#SBATCH --mail-type=fail +#SBATCH --mail-type=end +#SBATCH --mail-user=celora@ice.csic.es + + +#################################### +# LOADING THE CONDA ENVIRONMENT +#################################### + +module load conda/py3-latest +# I get warnings to use 'conda deactivate' instead but that doesn't work +source deactivate +conda activate myenv + +#################################### +# LAUNCHING THE JOBS +#################################### + +python3 -u find_best_fit.py config_calibration.txt + + + + diff --git a/Proof_of_principle/calibration_scripts/py_serial.slurm b/Proof_of_principle/calibration_scripts/py_serial.slurm new file mode 100644 index 0000000..3cf5524 --- /dev/null +++ b/Proof_of_principle/calibration_scripts/py_serial.slurm @@ -0,0 +1,42 @@ +#!/bin/bash +#################################### +# JOB INFO: serial +#################################### + + +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --time=00:10:00 +#SBATCH --output=outputs/serial_%A.out + + +#SBATCH --mail-type=begin # send email when job begins +#SBATCH --mail-type=fail +#SBATCH --mail-type=end # send email when job ends +#SBATCH --mail-user=celora@ice.csic.es + + +#################################### +# LOADING THE CONDA ENVIRONMENT +#################################### + +module load conda/py3-latest +# I get warnings to use 'conda deactivate' instead but that doesn't work +source deactivate +conda activate myenv + + +#################################### +# LAUNCHING THE JOBS +##################################### +# python3 -u regress+residual_check.py config_calibration.txt +# python3 -u visualizing_correlations.py config_calibration.txt +# python3 -u reducing_regressors.py config_calibration.txt +# python3 -u hunting_correlations.py config_calibration.txt +# python3 -u regressing_residual.py config_calibration.txt + +python3 -u L_dep_eta.py config_calibration.txt + + + + diff --git a/Proof_of_principle/calibration_scripts/reducing_regressors.py b/Proof_of_principle/calibration_scripts/reducing_regressors.py new file mode 100644 index 0000000..da4a84c --- /dev/null +++ b/Proof_of_principle/calibration_scripts/reducing_regressors.py @@ -0,0 +1,83 @@ +import sys +# import os +sys.path.append('../../master_files/') +import pickle +import configparser +import json +import time +import math + +from FileReaders import * +from MicroModels import * +from MesoModels import * +from Visualization import * +from Analysis import * + +if __name__ == '__main__': + + # ################################################################### + # # USING PCA TO REDUCE A LARGE LIST OF REGRESSOR TO A SMALLER SUBSET + # ################################################################### + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.") + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # LOADING MESO AND MICRO MODELS + pickle_directory = config['Directories']['pickled_files_dir'] + meso_pickled_filename = config['Filenames']['meso_pickled_filename'] + MesoModelLoadFile = pickle_directory + meso_pickled_filename + + print('================================================') + print(f'Starting job on data from {MesoModelLoadFile}') + print('================================================\n\n') + + with open(MesoModelLoadFile, 'rb') as filehandle: + meso_model = pickle.load(filehandle) + + + # WHICH DATA YOU WANT TO RUN THE ROUTINE ON + regressors_strs = json.loads(config['PCA_settings']['regressors_2_reduce']) + regressors = [] + for i in range(len(regressors_strs)): + temp = meso_model.meso_vars[regressors_strs[i]] + regressors.append(temp) + print('Trying to reduce the following list to a smaller subset: {}\n'.format(regressors_strs)) + + # WHICH GRID-RANGES SHOULD WE CONSIDER? + ranges = json.loads(config['PCA_settings']['ranges']) + x_range = ranges['x_range'] + y_range = ranges['y_range'] + num_slices_meso = int(config['PCA_settings']['num_T_slices']) + time_of_central_slice = meso_model.domain_vars['T'][int((num_slices_meso-1)/2)] + ranges = [[time_of_central_slice, time_of_central_slice], x_range, y_range] + + # READING PREPROCESSING INFO FROM CONFIG FILE + preprocess_data = json.loads(config['PCA_settings']['preprocess_data']) + extractions = int(config['PCA_settings']['extractions']) + + # PRE-PROCESSING and REGRESSING + statistical_tool = CoefficientsAnalysis() + model_points = meso_model.domain_vars['Points'] + new_data = statistical_tool.trim_dataset(regressors, ranges, model_points) + new_data = statistical_tool.preprocess_data(new_data, preprocess_data) + # if extractions != 0: + # new_data = statistical_tool.extract_randomly(new_data, extractions) + + # PERFORMING PCA TO EXTRACT PRINCIPAL COMPONENTS + var_wanted = float(config['PCA_settings']['variance_wanted']) + comp_decomp = statistical_tool.PCA_find_regressors_subset(new_data, var_wanted=var_wanted) + + saving_directory = config['Directories']['figures_dir'] + with open(saving_directory + '/Reducing_regressors.txt', 'w') as filehandle: + filehandle.write("===================================\nFeatures in the dataset:\n") + filehandle.write(regressors_strs[0]) + for i in range(1, len(regressors_strs)): + filehandle.write(", " + regressors_strs[i]) + filehandle.write("\n===================================\n") + for i in range(len(comp_decomp)): + filehandle.write(f'The {i}-th component decomposition in terms of original vars is\n\n{comp_decomp[i]}\n\n') diff --git a/Proof_of_principle/calibration_scripts/regress+residual_check.py b/Proof_of_principle/calibration_scripts/regress+residual_check.py new file mode 100644 index 0000000..95bd4b1 --- /dev/null +++ b/Proof_of_principle/calibration_scripts/regress+residual_check.py @@ -0,0 +1,298 @@ +import sys +# import os +sys.path.append('../../master_files/') +import pickle +import configparser +import json +import time +import math +from scipy import stats + +from FileReaders import * +from MicroModels import * +from MesoModels import * +from Visualization import * +from Analysis import * + +if __name__ == '__main__': + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.") + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # LOADING MESO MODEL + pickle_directory = config['Directories']['pickled_files_dir'] + meso_pickled_filename = config['Filenames']['meso_pickled_filename'] + MesoModelLoadFile = pickle_directory + meso_pickled_filename + + print('================================================') + print(f'Starting job on data from {MesoModelLoadFile}') + print('================================================\n\n') + + with open(MesoModelLoadFile, 'rb') as filehandle: + meso_model = pickle.load(filehandle) + + ################################################ + # # REGRESSING THE COEFFICIENT + ################################################ + statistical_tool = CoefficientsAnalysis() + + # WHICH DATA YOU WANT TO RUN THE ROUTINE ON? + coeff_str = config['Regress+residual_check_settings']['coeff_str'] + coeff = meso_model.meso_vars[coeff_str] + coeff_regressors_strs = json.loads(config['Regress+residual_check_settings']['coeff_regressors_strs']) + regressors = [] + for i in range(len(coeff_regressors_strs)): + temp = meso_model.meso_vars[coeff_regressors_strs[i]] + regressors.append(temp) + print(f'Dependent var: {coeff_str}, Explanatory vars: {coeff_regressors_strs}\n') + + residual_str = config['Regress+residual_check_settings']['residual_str'] + closure_ingr_str = config['Regress+residual_check_settings']['closure_ingr_str'] + residual = meso_model.meso_vars[residual_str] + closure_ingr = meso_model.meso_vars[closure_ingr_str] + print(f'Residuals and closure ingredient for model check: {residual_str}, {closure_ingr_str}\n') + + # PREPARING THE DATA + preprocess_data = json.loads(config['Regress+residual_check_settings']['preprocess_data']) + add_intercept = not not int(config['Regress+residual_check_settings']['add_intercept']) + centralize = int(config['Regress+residual_check_settings']['centralize']) + test_percentage = float(config['Regress+residual_check_settings']['test_percentage']) + + # first step: trimming and pre-processing + regression_ranges = json.loads(config['Regress+residual_check_settings']['regression_ranges']) + x_range = regression_ranges['x_range'] + y_range = regression_ranges['y_range'] + idxs_time_slices = json.loads(config['Regress+residual_check_settings']['idxs_time_slices']) + times = [meso_model.domain_vars['T'][i] for i in idxs_time_slices] + t_range = [np.amin(times), np.amax(times)] + ranges = [t_range, x_range, y_range] + + data = [coeff] + for i in range(len(regressors)): + data.append(regressors[i]) + data.append(residual) + data.append(closure_ingr) + model_points = meso_model.domain_vars['Points'] + new_data = statistical_tool.trim_dataset(data, ranges, model_points) + new_data = statistical_tool.preprocess_data(new_data, preprocess_data) + + # next step: centralizing the coefficients data + if centralize: + new_data, means = statistical_tool.centralize_dataset(new_data) + coeff_mean = means[0] + del means[0] + closure_ingr_mean = means[-1] + del means[-1] + residual_means = means[-1] + del means[-1] + regressors_means = means + + # second step: splitting into train and test + if test_percentage==0.: + coeff_train = new_data[0] + coeff_test = np.copy(new_data[0]) + del new_data[0] + closure_ingr_train = new_data[-1] + closure_ingr_test = np.copy(new_data[-1]) + del new_data[-1] + residual_train = new_data[-1] + residual_test = np.copy(new_data[-1]) + del new_data[-1] + regressors_train = new_data + regressors_test = [] + for elem in regressors_train: + regressors_test.append(np.copy(elem)) + else: + training_data, test_data = statistical_tool.split_train_test(new_data, test_percentage=test_percentage) + coeff_train = training_data[0] + coeff_test = test_data[0] + del training_data[0] + del test_data[0] + closure_ingr_train = training_data[-1] + closure_ingr_test = test_data[-1] + del training_data[-1] + del test_data[-1] + residual_train = training_data[-1] + residual_test = test_data[-1] + del training_data[-1] + del test_data[-1] + regressors_train = training_data + regressors_test = test_data + + # # fourth step: centralizing the coefficients data + # if centralize: + # tot_coeff = [np.stack((coeff_train, coeff_test))] + # tot_regress = [np.stack((regressors_test[i], regressors_train[i])) for i in range(len(regressors))] + + # _, means = statistical_tool.centralize_dataset(tot_coeff + tot_regress) + # coeff_mean = means[0] + # regressors_means = means[1:] + + # coeff_train -= coeff_mean + # for i in range(len(regressors_means)): + # regressors_train[i] -= regressors_means[i] + + coeffs, std_errors = statistical_tool.scalar_regression(coeff_train, regressors_train, add_intercept=add_intercept) + print('regression coeffs: {}\n'.format(coeffs)) + # print('Corresponding std errors: {}\n'.format(std_errors)) + + ###################################################### + # # RECONSTRUCTING PREDICTIONS FOR COEFF AND RESIDUAL + ###################################################### + # Note: the reconstruction depends on the order with which you pre-process above + + # building predictions for the coefficient + coeff_model = np.zeros(coeff_test.shape) + if centralize: + coeff_model += coeff_mean + coeff_test += coeff_mean + for i in range(len(regressors_test)): + # regressors_test[i] += regressors_means[i] + coeff_model += np.multiply(coeffs[i], regressors_test[i]) + else: + if add_intercept: + coeff_model += coeffs[0] + for i in range(len(regressors)): + coeff_model += np.multiply(coeffs[i+1], regressors_test[i]) + elif not add_intercept: + for i in range(len(regressors)): + coeff_model += np.multiply(coeffs[i], regressors_test[i]) + + # preprocessing and building model predictions for the residual + residual_model = np.zeros(residual_test.shape) + residual_model = coeff_model + closure_ingr_test + if centralize: + residual_model += closure_ingr_mean + residual_test += residual_means + + # FINALLY: PLOTTING + plt.rc("font", family="serif") + plt.rc("mathtext",fontset="cm") + plt.rc('font', size=10) + + fig, axes = plt.subplots(1,3, figsize=[13,4]) + axes = axes.flatten() + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message='is_categorical_dtype is deprecated') + warnings.filterwarnings('ignore', message='use_inf_as_na option is deprecated') + + coeff_model = coeff_model.flatten() + coeff_test = coeff_test.flatten() + residual_model = residual_model.flatten() + residual_test = residual_test.flatten() + + sns.set_theme(style="dark") + sns.scatterplot(x=coeff_model, y=coeff_test, s=4, color=".15", ax=axes[0]) + sns.histplot(x=coeff_model, y=coeff_test, bins=50, ax=axes[0], pthresh=.1, cmap="mako") + sns.kdeplot(x=coeff_model, y=coeff_test, levels=5, ax=axes[0], color="w", linewidths=1) + + sns.histplot(coeff_model, stat='density', kde=True, color='steelblue', ax=axes[1], label='model') + sns.histplot(coeff_test, stat='density', kde=True, color='firebrick', ax=axes[1], label='sim. data') + + sns.histplot(residual_model, stat='density', kde=True, color='steelblue', ax=axes[2], label='model') + sns.histplot(residual_test, stat='density', kde=True, color='firebrick', ax=axes[2], label='sim. data') + + print('finished plot, now making it nice\n') + + # adding labels + coeff_label = coeff_str + if hasattr(meso_model, 'labels_var_dict') and coeff_str in meso_model.labels_var_dict.keys(): + coeff_label = meso_model.labels_var_dict[coeff_str] + if preprocess_data['log_abs'][0] == 1: + coeff_label = r"$\log($" + coeff_label + r"$)$" + + residual_label = residual_str + if hasattr(meso_model, 'labels_var_dict') and residual_str in meso_model.labels_var_dict.keys(): + residual_label = meso_model.labels_var_dict[residual_str] + # comment the following line if you don't take the log of the residual + residual_label = r"$\frac{1}{2}\log($" + residual_label + r"$)$" + # residual_label = r"$\log($" + residual_label + r"$)$" + + axes[0].set_xlabel('Regression model', fontsize=12) + axes[0].set_ylabel(coeff_label, fontsize=12) + axes[1].set_xlabel(coeff_label, fontsize=12) + axes[1].set_ylabel('pdf', fontsize=12) + axes[2].set_xlabel(residual_label, fontsize=12) + axes[2].set_ylabel('pdf', fontsize=12) + + fig.tight_layout() + + # Building the annotation box with the specifics of the model + if centralize: + text_for_box = r'$Means,$ $model$ $coeffs$:' + '\n' + add_text = coeff_str + " : " + if hasattr(meso_model, 'labels_var_dict') and coeff_str in meso_model.labels_var_dict.keys(): + add_text = meso_model.labels_var_dict[coeff_str] + " : " + sign, val = int(np.sign(coeff_mean)), '%.3f' %np.abs(coeff_mean) + coeff_for_text_box = r"$+{}$".format(val) if sign == 1 else r"$-{}$".format(val) + add_text += coeff_for_text_box + text_for_box += add_text + + for i in range(len(coeff_regressors_strs)): + add_text = "\n" + coeff_regressors_strs[i] + " : " + if hasattr(meso_model, 'labels_var_dict') and coeff_regressors_strs[i] in meso_model.labels_var_dict.keys(): + add_text = "\n" + meso_model.labels_var_dict[coeff_regressors_strs[i]] + " : " + + sign, val = int(np.sign(regressors_means[i])), '%.3f' %np.abs(regressors_means[i]) + coeff_for_text_box = r"$+{}$".format(val) if sign == 1 else r", $-{}$".format(val) + add_text += coeff_for_text_box + + sign, val = int(np.sign(coeffs[i])), '%.3f' %np.abs(coeffs[i]) + coeff_for_text_box = r", $+{}$".format(val) if sign == 1 else r"$-{}$".format(val) + add_text += coeff_for_text_box + + text_for_box += add_text + + else: + text_for_box = r"$Model$ $coeff.s:$" + "\n" + if add_intercept: + sign, val = int(np.sign(coeffs[0])), '%.3f' %np.abs(coeffs[0]) + text_for_box += r'$offset$' +' : ' + coeff_for_text_box = r"$+{}$".format(val) if sign == 1 else r"$-{}$".format(val) + text_for_box += coeff_for_text_box + else: + coeffs = [0] + coeffs + + for i in range(len(coeff_regressors_strs)): + sign, val = int(np.sign(coeffs[i+1])), '%.3f' %np.abs(coeffs[i+1]) + coeff_for_text_box = r"$+{}$".format(val) if sign == 1 else r"$-{}$".format(val) + + add_text = "\n" + coeff_regressors_strs[i] + " : " + if hasattr(meso_model, 'labels_var_dict') and coeff_regressors_strs[i] in meso_model.labels_var_dict.keys(): + add_text = "\n" + meso_model.labels_var_dict[coeff_regressors_strs[i]] + " : " + + add_text += coeff_for_text_box + text_for_box += add_text + + + bbox_args = dict(boxstyle="round", fc="0.95") + plt.annotate(text=text_for_box, xy = (0.34,0.2), xycoords='figure fraction', bbox=bbox_args, ha="right", va="bottom", fontsize = 9) + + + # Adding legend to the distribution comparison panel + axes[1].legend(loc = 'best', prop={'size': 10}) + h, _ = axes[1].get_legend_handles_labels() + labels = ['Regression model', 'sim. data'] + axes[1].legend(h, labels, loc = 'best', prop={'size': 10, 'family' : 'serif'}) + + axes[2].legend(loc = 'best', prop={'size': 10}) + h, _ = axes[2].get_legend_handles_labels() + labels = ['Regression model', 'sim. data'] + axes[2].legend(h, labels, loc = 'best', prop={'size': 10, 'family' : 'serif'}) + + # Saving the figure + print(f'Finished plot, now saving...\n\n') + saving_directory = config['Directories']['figures_dir'] + filename = '/Regress_and_check' + format = 'png' + filename += "." + format + dpi = 300 + plt.savefig(saving_directory + filename, format=format, dpi=dpi) + + + diff --git a/Proof_of_principle/calibration_scripts/regressing_residual.py b/Proof_of_principle/calibration_scripts/regressing_residual.py new file mode 100644 index 0000000..0e43ac4 --- /dev/null +++ b/Proof_of_principle/calibration_scripts/regressing_residual.py @@ -0,0 +1,260 @@ +import sys +# import os +sys.path.append('../../master_files/') +import pickle +import configparser +import json +import time +import math +from scipy import stats + +from FileReaders import * +from MicroModels import * +from MesoModels import * +from Visualization import * +from Analysis import * + +if __name__ == '__main__': + + # ################################################################## + # # RUN THE REGRESSION ROUTINE GIVEN DEPENDENT DATA AND EXPLANATORY + # # VARS. DATA IS PRE-PROCESSED + # ################################################################## + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.") + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # LOADING MESO MODEL + pickle_directory = config['Directories']['pickled_files_dir'] + meso_pickled_filename = config['Filenames']['meso_pickled_filename'] + MesoModelLoadFile = pickle_directory + meso_pickled_filename + + print('================================================') + print(f'Starting job on data from {MesoModelLoadFile}') + print('================================================\n\n') + + with open(MesoModelLoadFile, 'rb') as filehandle: + meso_model = pickle.load(filehandle) + + + # WHICH DATA YOU WANT TO RUN THE ROUTINE ON? + dep_var_str = config['Regression_settings']['dependent_var'] + dep_var = meso_model.meso_vars[dep_var_str] + regressors_strs = json.loads(config['Regression_settings']['regressors']) + regressors = [] + for i in range(len(regressors_strs)): + temp = meso_model.meso_vars[regressors_strs[i]] + regressors.append(temp) + print(f'Dependent var: {dep_var_str}, Explanatory vars: {regressors_strs}\n') + + + # WHICH GRID-RANGES SHOULD WE CONSIDER? + regression_ranges = json.loads(config['Regression_settings']['ranges']) + x_range = regression_ranges['x_range'] + y_range = regression_ranges['y_range'] + num_slices_meso = int(config['Regression_settings']['num_T_slices']) + time_of_central_slice = meso_model.domain_vars['T'][int((num_slices_meso-1)/2)] + ranges = [[time_of_central_slice, time_of_central_slice], x_range, y_range] + + # READING PREPROCESSING INFO FROM CONFIG FILE + preprocess_data = json.loads(config['Regression_settings']['preprocess_data']) + add_intercept = not not int(config['Regression_settings']['add_intercept']) + extractions = int(config['Regression_settings']['extractions']) + centralize = int(config['Regression_settings']['centralize']) + test_percentage = float(config['Regression_settings']['test_percentage']) + + # BUILDING THE WEIGHTS + weighing_func_str = config['Regression_settings']['weighing_func'] + if weighing_func_str == 'Q2': + meso_model.weights_Q2() + weights = meso_model.meso_vars['weights'] + print(f'Finished building weights using {meso_model.weights_Q2.__name__}\n') + + elif weighing_func_str == 'Q1_skew': + meso_model.weights_Q1_skew() + weights = meso_model.meso_vars['weights'] + print(f'Finished building weights using {meso_model.weights_Q1_skew.__name__}\n') + + elif weighing_func_str == 'Q1_non_neg': + meso_model.weights_Q1_non_neg() + weights = meso_model.meso_vars['weights'] + print(f'Finished building weights using {meso_model.weights_Q1_non_neg.__name__}\n') + + elif weighing_func_str == "residual_weights": + residual_str = config['Regression_settings']['residual_str'] + meso_model.residual_weights(residual_str) + weights = meso_model.meso_vars['weights'] + print(f'Finished building weights using {meso_model.residual_weights.__name__}\n') + + elif weighing_func_str == "denominator_weights": + residual_str = config['Regression_settings']['denominator_str'] + meso_model.denominator_weights(residual_str) + weights = meso_model.meso_vars['weights'] + print(f'Finished building weights using {meso_model.denominator_weights.__name__}\n') + + else: + print(f'The string for building weights {weighing_func_str} does not match any of the implemented routines.\n') + weights = None + + # PRE-PROCESSING: Trimming, pre-processing and splitting intro train + test set + data = [dep_var] + for i in range(len(regressors)): + data.append(regressors[i]) + statistical_tool = CoefficientsAnalysis() + model_points = meso_model.domain_vars['Points'] + + if weights is not None: + new_data = statistical_tool.trim_dataset(data + [weights], ranges, model_points) + weights = new_data[-1] + del new_data[-1] + new_data, weights = statistical_tool.preprocess_data(new_data, preprocess_data, weights=weights) + + if centralize: + new_data, means = statistical_tool.centralize_dataset(new_data) + dep_var_mean = means[0] + regressors_means = means[1:] + + training_data, test_data = statistical_tool.split_train_test(new_data + [weights], test_percentage=test_percentage) + dep_var_train = training_data[0] + dep_var_test = test_data[0] + del training_data[0] + del test_data[0] + weights_train = training_data[-1] + weights_test = test_data[-1] + del training_data[-1] + del test_data[-1] + regressors_train = training_data + regressors_test = test_data + + + else: + new_data = statistical_tool.trim_dataset(data, ranges, model_points) + new_data = statistical_tool.preprocess_data(new_data, preprocess_data) + + if centralize: + new_data, means = statistical_tool.centralize_dataset(new_data) + dep_var_mean = means[0] + regressors_means = means[1:] + + training_data, test_data = statistical_tool.split_train_test(new_data, test_percentage=test_percentage) + dep_var_train = training_data[0] + dep_var_test = test_data[0] + del training_data[0] + del test_data[0] + regressors_train = training_data + regressors_test = test_data + + weights_train = weights_test = None + + + coeffs, std_errors = statistical_tool.scalar_regression(dep_var_train, regressors_train, add_intercept=add_intercept, weights=weights_train) + print('regression coeffs: {}\n'.format(coeffs)) + print('Corresponding std errors: {}\n'.format(std_errors)) + + + # BUILDING TEST-DATA PREDICTIONs GIVEN THE REGRESSED MODEL + dep_var_model = np.zeros(dep_var_test.shape) + if centralize: + for i in range(len(regressors_test)): + dep_var_model += np.multiply(coeffs[i], regressors_test[i]) + + else: + if add_intercept: + dep_var_model += coeffs[0] + for i in range(len(regressors)): + dep_var_model += np.multiply(coeffs[i+1], regressors_test[i]) + elif not add_intercept: + for i in range(len(regressors)): + dep_var_model += np.multiply(coeffs[i], regressors_test[i]) + + # EXTRACTIONS ARE NOT NEEDED IF YOU SAVE THE FIG AS PNG + if extractions != 0: + data_for_scatter = [dep_var_test, dep_var_model] + new_data = statistical_tool.extract_randomly(data_for_scatter + [weights_test], extractions) + if weights is not None: + weights_test = new_data[-1] + del new_data[-1] + + dep_var_test, dep_var_model = new_data[0], new_data[1] + + + # FINALLY: PLOTTING + ylabel = dep_var_str + if hasattr(meso_model, 'labels_var_dict') and dep_var_str in meso_model.labels_var_dict.keys(): + ylabel = meso_model.labels_var_dict[dep_var_str] + if preprocess_data['log_abs'][0] == 1: + ylabel = r"$\log($" + ylabel + r"$)$" + # statistical_tool.visualize_correlation(dep_var_model, dep_var_test , xlabel=r"$regression$ $model$", ylabel=ylabel, weights = weights) + fig = statistical_tool.compare_distributions(dep_var_model, dep_var_test, xlabel=r'$regression$ $model$', ylabel=ylabel) + + # Building the annotation box with the specifics of the model + if centralize: + text_for_box = r"$Model$ $coeff.s$, $means:$" + "\n" + add_text = dep_var_str + " : , " + if hasattr(meso_model, 'labels_var_dict') and dep_var_str in meso_model.labels_var_dict.keys(): + add_text = meso_model.labels_var_dict[dep_var_str] + " : , " + sign, val = int(np.sign(dep_var_mean)), '%.3f' %np.abs(dep_var_mean) + coeff_for_text_box = r"$+{}$".format(val) if sign == 1 else r"$-{}$".format(val) + add_text += coeff_for_text_box + text_for_box += add_text + + for i in range(len(regressors_strs)): + add_text = "\n" + regressors_strs[i] + " : " + if hasattr(meso_model, 'labels_var_dict') and regressors_strs[i] in meso_model.labels_var_dict.keys(): + add_text = "\n" + meso_model.labels_var_dict[regressors_strs[i]] + " : " + + sign, val = int(np.sign(coeffs[i])), '%.3f' %np.abs(coeffs[i]) + coeff_for_text_box = r"$+{}$".format(val) if sign == 1 else r"$-{}$".format(val) + add_text += coeff_for_text_box + + sign, val = int(np.sign(regressors_means[i])), '%.3f' %np.abs(regressors_means[i]) + coeff_for_text_box = r", $+{}$".format(val) if sign == 1 else r", $-{}$".format(val) + add_text += coeff_for_text_box + + text_for_box += add_text + + + else: + text_for_box = r"$Model$ $coeff.s:$" + "\n" + if add_intercept: + sign, val = int(np.sign(coeffs[0])), '%.3f' %np.abs(coeffs[0]) + text_for_box += r'$offset$' +' : ' + coeff_for_text_box = r"$+{}$".format(val) if sign == 1 else r"$-{}$".format(val) + text_for_box += coeff_for_text_box + else: + coeffs = [0] + coeffs + + for i in range(len(regressors_strs)): + sign, val = int(np.sign(coeffs[i+1])), '%.3f' %np.abs(coeffs[i+1]) + coeff_for_text_box = r"$+{}$".format(val) if sign == 1 else r"$-{}$".format(val) + + add_text = "\n" + regressors_strs[i] + " : " + if hasattr(meso_model, 'labels_var_dict') and regressors_strs[i] in meso_model.labels_var_dict.keys(): + add_text = "\n" + meso_model.labels_var_dict[regressors_strs[i]] + " : " + + add_text += coeff_for_text_box + text_for_box += add_text + + bbox_args = dict(boxstyle="round", fc="0.95") + plt.annotate(text=text_for_box, xy = (0.99,0.1), xycoords='figure fraction', bbox=bbox_args, ha="right", va="bottom", fontsize = 8) + + + # Saving the figure + saving_directory = config['Directories']['figures_dir'] + filename = f'/Regress_{dep_var_str}_vs' + for i in range(1,len(regressors_strs)): + filename += f'_{regressors_strs[i]}' + format = str(config['Regression_settings']['format_fig']) + filename += "." + format + dpi = None + if format == 'png': + dpi = 400 + plt.savefig(saving_directory + filename, format=format, dpi=dpi) + print(f'Finished regression and scatter plot for {dep_var_str}, saved as {filename}\n\n') + + diff --git a/Proof_of_principle/calibration_scripts/visualizing_correlations.py b/Proof_of_principle/calibration_scripts/visualizing_correlations.py new file mode 100644 index 0000000..8a4ec26 --- /dev/null +++ b/Proof_of_principle/calibration_scripts/visualizing_correlations.py @@ -0,0 +1,146 @@ +import sys +# import os +sys.path.append('../../master_files/') +import pickle +import configparser +import json +import time +import math + +from FileReaders import * +from MicroModels import * +from MesoModels import * +from Visualization import * +from Analysis import * + +if __name__ == '__main__': + + # ################################################################## + # #CORRELATION PLOTS: RESIDUALS VS CORRESPONDING CLOSURE INGREDIENTS + # ################################################################## + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.") + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # LOADING MESO MODEL + pickle_directory = config['Directories']['pickled_files_dir'] + meso_pickled_filename = config['Filenames']['meso_pickled_filename'] + MesoModelLoadFile = pickle_directory + meso_pickled_filename + + print('================================================') + print(f'Starting job on data from {MesoModelLoadFile}') + print('================================================\n\n') + + with open(MesoModelLoadFile, 'rb') as filehandle: + meso_model = pickle.load(filehandle) + + + # WHICH DATA YOU WANT TO RUN THE ROUTINE ON? + var_strs = json.loads(config['Visualize_correlations']['vars']) + vars = [] + for i in range(len(var_strs)): + vars.append(meso_model.meso_vars[var_strs[i]]) + print(f'Producing scatter plot for the vars: {var_strs}\n') + + # WHICH GRID-RANGES SHOULD WE CONSIDER? + regression_ranges = json.loads(config['Visualize_correlations']['ranges']) + x_range = regression_ranges['x_range'] + y_range = regression_ranges['y_range'] + num_slices_meso = int(config['Visualize_correlations']['num_T_slices']) + time_of_central_slice = meso_model.domain_vars['T'][int((num_slices_meso-1)/2)] + ranges = [[time_of_central_slice, time_of_central_slice], x_range, y_range] + + # READING PREPROCESSING INFO FROM CONFIG FILE, AND PREPROCESSING + preprocess_data = json.loads(config['Visualize_correlations']['preprocess_data']) + extractions = int(config['Visualize_correlations']['extractions']) + + # BUILDING THE WEIGHTS + weighing_func_str = config['Visualize_correlations']['weighing_func'] + if weighing_func_str == 'Q2': + meso_model.weights_Q2() + weights = meso_model.meso_vars['weights'] + print(f'Finished building weights using {meso_model.weights_Q2.__name__}\n') + + elif weighing_func_str == 'Q1_skew': + meso_model.weights_Q1_skew() + weights = meso_model.meso_vars['weights'] + print(f'Finished building weights using {meso_model.weights_Q1_skew.__name__}\n') + + elif weighing_func_str == 'Q1_non_neg': + meso_model.weights_Q1_non_neg() + weights = meso_model.meso_vars['weights'] + print(f'Finished building weights using {meso_model.weights_Q1_non_neg.__name__}\n') + + elif weighing_func_str == "residual_weights": + residual_str = config['Visualize_correlations']['residual_str'] + meso_model.residual_weights(residual_str) + weights = meso_model.meso_vars['weights'] + print(f'Finished building weights using {meso_model.residual_weights.__name__}\n') + + elif weighing_func_str == "denominator_weights": + residual_str = config['Visualize_correlations']['denominator_str'] + meso_model.denominator_weights(residual_str) + weights = meso_model.meso_vars['weights'] + print(f'Finished building weights using {meso_model.denominator_weights.__name__}\n') + + else: + print(f'The string for building weights {weighing_func_str} does not match any of the implemented routines.\n') + weights = None + + + # PRE-PROCESSING DATA + statistical_tool = CoefficientsAnalysis() + model_points = meso_model.domain_vars['Points'] + if weights is not None: + new_data = statistical_tool.trim_dataset(vars + [weights], ranges, model_points) + weights = new_data[-1] + del new_data[-1] + new_data, weights = statistical_tool.preprocess_data(new_data, preprocess_data, weights=weights) + if extractions != 0: + new_data = statistical_tool.extract_randomly(new_data + [weights], extractions) + weights = new_data[-1] + del new_data[-1] + vars = new_data + + else: + new_data = statistical_tool.trim_dataset(vars, ranges, model_points) + new_data = statistical_tool.preprocess_data(new_data, preprocess_data) + if extractions != 0: + new_data = statistical_tool.extract_randomly(new_data, extractions) + vars = new_data + + # FINALLY, THE CORRELATION PLOT + labels = [] + for var_str in var_strs: + label = var_str + if hasattr(meso_model, 'labels_var_dict') and var_str in meso_model.labels_var_dict.keys(): + label = meso_model.labels_var_dict[var_str] + if preprocess_data['log_abs'][0] == 1: + label = r"$\log($" + label + r"$)$" + labels.append(label) + + if len(var_strs) ==2: + statistical_tool.visualize_correlation(vars[0], vars[1], xlabel=labels[0], ylabel=labels[1], weights = weights) + else: + statistical_tool.visualize_many_correlations(vars, labels, weights = weights) + + saving_directory = config['Directories']['figures_dir'] + filename = '/Correlation' + if weights is not None: + filename = '/Correlation' + for i in range(len(var_strs)): + filename += "_" + var_strs[i] + + format = str(config['Regression_settings']['format_fig']) + filename += "." + format + dpi = None + if format == 'png': + dpi = 400 + plt.savefig(saving_directory + filename, format=format, dpi=dpi) + print(f'Finished producing correlation plot for {var_strs}, saved as {filename}\n') + diff --git a/Proof_of_principle/draft_scripts/.DS_Store b/Proof_of_principle/draft_scripts/.DS_Store new file mode 100644 index 0000000..87ed1dd Binary files /dev/null and b/Proof_of_principle/draft_scripts/.DS_Store differ diff --git a/Proof_of_principle/draft_scripts/comparison_zoom.py b/Proof_of_principle/draft_scripts/comparison_zoom.py new file mode 100644 index 0000000..fe8292c --- /dev/null +++ b/Proof_of_principle/draft_scripts/comparison_zoom.py @@ -0,0 +1,162 @@ +import sys +# import os +sys.path.append('../../master_files/') +import pickle +import configparser +import json +import time + +from FileReaders import * +from MicroModels import * +from MesoModels import * +from Visualization import * +from Analysis import * + +if __name__ == '__main__': + + # ############################################################## + # # SCRIPT TO COMPARE MICRO AND FILTERED DATA + # # one figure shows the relative difference over the full grid, + # # other two are produced to compare the data in a zoomed-in patch + # ############################################################## + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.") + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # LOADING MESO AND MICRO MODELS + pickle_directory = config['Directories']['pickled_files_dir'] + meso_pickled_filename = config['Filenames']['meso_pickled_filename'] + + print('=========================================================================') + print(f'Starting job on data from {pickle_directory+ meso_pickled_filename}') + print('=========================================================================\n\n') + + MesoModelLoadFile = pickle_directory + meso_pickled_filename + with open(MesoModelLoadFile, 'rb') as filehandle: + meso_model = pickle.load(filehandle) + micro_model = meso_model.micro_model + + print('Finished reading pickled data\n') + + # CHECKING WE ARE COMPARING DATA FROM THE SAME TIME-SLICE + num_snaps = micro_model.domain_vars['nt'] + central_slice_num = int(num_snaps/2.) + time_micro = micro_model.domain_vars['t'][central_slice_num] + num_slices_meso = int(config['Models_settings']['num_T_slices']) + time_meso = meso_model.domain_vars['T'][int((num_slices_meso-1)/2)] + if time_meso != time_micro: + print("Slices of meso and micro model do not coincide. Careful!\n") + else: + print("Comparing data at same time-slice, hurray!\n") + + # # PLOT SETTINGS + saving_directory = config['Directories']['figures_dir'] + visualizer = Plotter_2D() + + inset_ranges = json.loads(config['Plot_settings']['inset_ranges']) + inset_x_range = inset_ranges['x_range'] + inset_y_range = inset_ranges['y_range'] + + rel_diff_ranges = json.loads(config['Plot_settings']['plot_ranges']) + rel_diff_x_range = rel_diff_ranges['x_range'] + rel_diff_y_range = rel_diff_ranges['y_range'] + + + # Building the data for showing the relative difference between the various plots + var_str = 'BC' + comp = (0,) + + micro_data_zoom, extent_micro_zoom = visualizer.get_var_data(micro_model, var_str, time_meso, inset_x_range, inset_y_range, comp) + meso_data_zoom, extent_meso_zoom = visualizer.get_var_data(meso_model, var_str, time_meso, inset_x_range, inset_y_range, comp) + + micro_data, extent_micro = visualizer.get_var_data(micro_model, var_str, time_meso, rel_diff_x_range, rel_diff_y_range, comp) + meso_data, extent_meso = visualizer.get_var_data(meso_model, var_str, time_meso, rel_diff_x_range, rel_diff_y_range, comp) + ar_mean = (np.abs(micro_data) + np.abs(meso_data))/2 + rel_diff = np.abs(meso_data - micro_data)/ar_mean + + # now plotting + fig = plt.figure(figsize=[13,4]) + ax1 = fig.add_subplot(1, 3, 1) + ax2 = fig.add_subplot(1, 3, 2) + ax3 = fig.add_subplot(1, 3, 3, sharey=ax2) + plt.setp(ax3.get_yticklabels(), visible=False) + axes = [ax1, ax2, ax3] + axesRight = [ax2, ax3] + + + im = ax1.imshow(rel_diff, extent=extent_meso, origin='lower', cmap = 'plasma', norm='log') + divider = make_axes_locatable(ax1) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + images = [] + im = ax2.imshow(micro_data_zoom, extent=extent_micro_zoom, origin='lower', cmap='plasma') #, norm='log') + images.append(im) + im = ax3.imshow(meso_data_zoom, extent=extent_meso_zoom, origin='lower', cmap='plasma') #, norm='log') + images.append(im) + + + for i in range(len(axes)): + axes[i].set_xlabel(r'$x$') + axes[i].set_ylabel(r'$y$') + + title = r'$Rel.$ $difference$' + ax1.set_title(title, fontsize=10) + + title = micro_model.labels_var_dict[var_str] + title += r'$,$ $a=0$' + ax2.set_title(title, fontsize=10) + + title = meso_model.labels_var_dict[var_str] + title += r'$,$ $a=0$' + ax3.set_title(title, fontsize=10) + + # fig.tight_layout() + divider = make_axes_locatable(ax2) + cax = divider.append_axes('right', size='5%', pad=0.05) + cax.set_axis_off() + + divider = make_axes_locatable(ax3) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + # fig.colorbar(images[0], ax=axesRight, orientation='vertical', location='right', shrink=.8, pad=0.025 ) + # Make images respond to changes in the norm of other images (e.g. via the + # "edit axis, curves and images parameters" GUI on Qt), but be careful not to + # recurse infinitely! + def update(changed_image): + for im in images: + if (changed_image.get_cmap() != im.get_cmap() + or changed_image.get_clim() != im.get_clim()): + im.set_cmap(changed_image.get_cmap()) + im.set_clim(changed_image.get_clim()) + + for im in images: + im.callbacks.connect('changed', update) + + + # Adding boundaries of zoomed in regions + A = np.array([inset_x_range[0], inset_y_range[0]]) + B = np.array([inset_x_range[1], inset_y_range[0]]) + C = np.array([inset_x_range[1], inset_y_range[1]]) + D = np.array([inset_x_range[0], inset_y_range[1]]) + arrows_starts = [A, B, C, D] + arrows_increments = [arrows_starts[i] - arrows_starts[i-1] for i in range(1, len(arrows_starts))] + arrows_increments.append(A-D) + for i in range(len(arrows_starts)): + ax1.arrow(arrows_starts[i][0], arrows_starts[i][1], arrows_increments[i][0], arrows_increments[i][1], \ + width=0.005,color='white',head_length=0.0,head_width=0.0) + + fig.tight_layout() + + format='png' + dpi=400 + filename = f"/Compairing_zoom_{var_str}_{comp}." + format + plt.savefig(saving_directory + filename, format = format, dpi=dpi) + + diff --git a/Proof_of_principle/draft_scripts/config_draft.txt b/Proof_of_principle/draft_scripts/config_draft.txt new file mode 100644 index 0000000..4be187e --- /dev/null +++ b/Proof_of_principle/draft_scripts/config_draft.txt @@ -0,0 +1,34 @@ +# CONFIGURATION FILE FOR SCRIPTS VISUALIZING_*.PY +################################################# + +[Directories] + +pickled_files_dir = /scratch/tc2m23/KHIRandom/hydro/new_data/800X800/ET10/pickled/50dx_data + +figures_dir = ./ + + +[Filenames] + +meso_pickled_filename = /rHD2d_cg=fw=bl=8dx.pickle + + +[Models_settings] +#snapshots_opts are useful in case not all snapshots from METHOD in the folder are required. +#relevant only for visualizing_micro.py + +snapshots_opts = {"fewer_snaps_required": false, "smaller_list": [9,10,11]} + +num_T_slices = 3 + +[Plot_settings] +# method sets that used for producing difference plots: set it to 'interpolate' when plotting models with different grids +# interp_dims is relevant when 'interpolate' method is used: should be set to the dims of the coarser grid +# Else, set method to "raw_data" to use gridded data directly + +plot_ranges = {"x_range": [0.04, 0.96], "y_range": [0.04, 0.96]} + +inset_ranges = {"x_range": [0.15, 0.35], "y_range": [0.5, 0.7]} + +diff_plot_settings = {"method": "raw_data", "interp_dims": [300, 300]} + diff --git a/Proof_of_principle/draft_scripts/const_coeff_all.py b/Proof_of_principle/draft_scripts/const_coeff_all.py new file mode 100644 index 0000000..347a75f --- /dev/null +++ b/Proof_of_principle/draft_scripts/const_coeff_all.py @@ -0,0 +1,154 @@ +import sys +# import os +sys.path.append('../../master_files/') +import pickle +import configparser +import json +import time + +from FileReaders import * +from MicroModels import * +from MesoModels import * +from Visualization import * +from Analysis import * + +if __name__ == '__main__': + + # ############################################################## + # # SCRIPT TO COMPARE THE COMPUTED RESIDUALS WITH THEIR CONSTANT + # # COEFFICIENTS MODELLING + # ############################################################## + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.") + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # LOADING MESO AND MICRO MODELS + pickle_directory = config['Directories']['pickled_files_dir'] + meso_pickled_filename = config['Filenames']['meso_pickled_filename'] + MesoModelLoadFile = pickle_directory + meso_pickled_filename + + print('=========================================================================') + print(f'Starting job on data from {MesoModelLoadFile}') + print('=========================================================================\n\n') + + with open(MesoModelLoadFile, 'rb') as filehandle: + meso_model = pickle.load(filehandle) + micro_model = meso_model.micro_model + + print('Finished reading pickled data\n') + + + num_slices_meso = int(config['Models_settings']['num_T_slices']) + central_slice_num = int((num_slices_meso-1)/2) + + + # BUILDING THE VARIOUS MODELS WITH CONSTANT COEFF + eta = meso_model.meso_vars['eta'] + pi_res = meso_model.meso_vars['pi_res'] + shear_tilde = meso_model.meso_vars['shear_tilde'] + eta_const = np.mean(np.abs(eta)) + pi_res_model = np.multiply(eta_const, shear_tilde) + # print(f'data shape: {pi_res.shape} model shape: {pi_res_model.shape}') + + zeta = meso_model.meso_vars['zeta'] + Pi_res = meso_model.meso_vars['Pi_res'] + exp_tilde = meso_model.meso_vars['exp_tilde'] + zeta_const = np.mean(np.abs(zeta)) + Pi_res_model = np.multiply(zeta_const, np.abs(exp_tilde)) + # print(f'data shape: {Pi_res.shape} model shape: {Pi_res_model.shape}') + + # # Adding EOS residual to Pi_res + EOS_res = meso_model.meso_vars['eos_res'] + Pi_res = Pi_res + EOS_res + + kappa = meso_model.meso_vars['kappa'] + q_res = meso_model.meso_vars['q_res'] + Theta_tilde = meso_model.meso_vars['Theta_tilde'] + kappa_const = np.mean(np.abs(kappa)) + q_res_model = np.multiply(kappa_const, Theta_tilde) + # print(f'data shape: {q_res.shape} model shape: {q_res_model.shape}') + + # SQUARING THE TENSORS + metric = np.zeros((3,3)) + metric[0,0] = -1 + metric[1,1] = metric[2,2] = +1 + + Nx, Ny = meso_model.domain_vars['Nx'], meso_model.domain_vars['Ny'] + pi_res_sq = np.zeros((Nx, Ny)) + pi_res_sq_mod = np.zeros((Nx, Ny)) + q_res_sq = np.zeros((Nx, Ny)) + q_res_sq_mod = np.zeros((Nx, Ny)) + + h = central_slice_num + Pi_res = np.log10(Pi_res[h,:,:]) + Pi_res_model = np.log10(Pi_res_model[h,:,:]) + + for i in range(Nx): + for j in range(Ny): + temp = np.einsum('ij,kl,kj,li->', pi_res[h,i,j], pi_res[h,i,j], metric, metric) + temp = np.log10(np.sqrt(temp)) + pi_res_sq[i,j] = temp + + temp = np.einsum('ij,kl,kj,li->',pi_res_model[h,i,j], pi_res_model[h,i,j], metric, metric) + temp = np.log10(np.sqrt(temp)) + pi_res_sq_mod[i,j] = temp + + temp = np.einsum('i,ij,j->', q_res[h,i,j], metric, q_res[h,i,j]) + temp = np.log10(np.sqrt(temp)) + q_res_sq[i,j] = temp + + temp = np.einsum('i,ij,j->', q_res_model[h,i,j], metric, q_res_model[h,i,j]) + temp = np.log10(np.sqrt(temp)) + q_res_sq_mod[i,j] = temp + + # CREATING SUBPLOTS WITH CORRESPONDING DISTRIBUTIONS + plt.rc("font",family="serif") + plt.rc("mathtext",fontset="cm") + fig, axes = plt.subplots(1,3, figsize=[13,4]) + axes = axes.flatten() + + data = [[Pi_res, Pi_res_model], [pi_res_sq, pi_res_sq_mod], [q_res_sq, q_res_sq_mod],] + + x_axis_labels = [] + x_axis_labels.append(r'$\log(\tilde\Pi)$') + x_axis_labels.append(r'$\log(\sqrt{\tilde\pi_{ab}\tilde\pi^{ab}})$') + x_axis_labels.append(r'$\log(\sqrt{\tilde q_{a}\tilde q^{a}})$') + + + + for i in range(len(axes)): + X = data[i][0] + Y = data[i][1] + if X.shape != Y.shape: + print(f'Careful: shapes not compatible. i={i}') + + X = X.flatten() + Y = Y.flatten() + + stat = 'probability' + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message='is_categorical_dtype is deprecated') + warnings.filterwarnings('ignore', message='use_inf_as_na option is deprecated') + sns.histplot(X, stat=stat, kde=True, color='firebrick', ax=axes[i], label='sim. data') + sns.histplot(Y, stat=stat, kde=True, color='steelblue', ax=axes[i], label='model') + + axes[i].legend(loc = 'best', prop={'size': 10}) + axes[i].set_xlabel(x_axis_labels[i], fontsize=10) + axes[i].set_ylabel(stat, fontsize=10) + + + fig.tight_layout() + + fig_directory = config['Directories']['figures_dir'] + filename = f'/Const_coeff_models_' + f'{stat}' + 'EOS' + format = 'png' + dpi = 300 + filename += "." + format + plt.savefig(fig_directory + filename, format=format, dpi=dpi) + + diff --git a/Proof_of_principle/draft_scripts/filter_scaling.py b/Proof_of_principle/draft_scripts/filter_scaling.py new file mode 100644 index 0000000..f33c22c --- /dev/null +++ b/Proof_of_principle/draft_scripts/filter_scaling.py @@ -0,0 +1,390 @@ +import sys +# import os +sys.path.append('../../master_files/') +import pickle +import configparser +import json +import time +from matplotlib import colors +from mpl_toolkits.axes_grid1.inset_locator import InsetPosition + +from FileReaders import * +from MicroModels import * +from MesoModels import * +from Visualization import * +from Analysis import * + +if __name__ == '__main__': + + # ############################################################################################### + # # SCRIPT TO SHOW HOW THE IMPACT OF FILTERING SCALES WITH THE FILTER-SIZE: REL DIFFERENCES + # ############################################################################################### + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.") + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # LOADING MESO AND MICRO MODELS + pickle_directory = config['Directories']['pickled_files_dir'] + + + print('=========================================================================') + print(f'Starting job on data from {pickle_directory}') + print('=========================================================================\n\n') + + meso_pickled_filename = '/rHD2d_nocg_fw=2_bl=8dx.pickle' + MesoModelLoadFile = pickle_directory + meso_pickled_filename + with open(MesoModelLoadFile, 'rb') as filehandle: + meso_2 = pickle.load(filehandle) + micro_model = meso_2.micro_model + + meso_pickled_filename = '/rHD2d_nocg_fw=4_bl=8dx.pickle' + MesoModelLoadFile = pickle_directory + meso_pickled_filename + with open(MesoModelLoadFile, 'rb') as filehandle: + meso_4 = pickle.load(filehandle) + + meso_pickled_filename = '/rHD2d_nocg_fw=bl=8dx.pickle' + MesoModelLoadFile = pickle_directory + meso_pickled_filename + with open(MesoModelLoadFile, 'rb') as filehandle: + meso_8 = pickle.load(filehandle) + + + print('Finished reading pickled data\n') + + # CHECKING WE ARE COMPARING DATA FROM THE SAME TIME-SLICE + num_snaps = micro_model.domain_vars['nt'] + central_slice_num = int(num_snaps/2.) + time_micro = micro_model.domain_vars['t'][central_slice_num] + num_slices_meso = int(config['Models_settings']['num_T_slices']) + time_meso2 = meso_2.domain_vars['T'][int((num_slices_meso-1)/2)] + time_meso4 = meso_2.domain_vars['T'][int((num_slices_meso-1)/2)] + time_meso8 = meso_2.domain_vars['T'][int((num_slices_meso-1)/2)] + + compatible = True + if time_micro != time_meso2 or time_meso2 != time_meso4 or time_meso4 != time_meso8: + compatible = False + + if not compatible: + print("Slices of meso and micro models do not coincide. Careful!\n") + else: + print("Comparing data at same time-slice, hurray!\n") + + # # PLOT SETTINGS + plot_ranges = json.loads(config['Plot_settings']['plot_ranges']) + x_range = plot_ranges['x_range'] + y_range = plot_ranges['y_range'] + saving_directory = config['Directories']['figures_dir'] + visualizer = Plotter_2D() + # diff_plot_settings =json.loads(config['Plot_settings']['diff_plot_settings']) + + # Building the data for showing the relative difference between the various plots + models = [micro_model, meso_2, meso_4, meso_8] + var_str = 'BC' + comp = (0,) + + # full_range = [0.04,0.96] + datamicro, extentmicro = visualizer.get_var_data(micro_model, var_str, time_micro, x_range, y_range, comp) + data2, extent2 = visualizer.get_var_data(meso_2, var_str, time_meso2, x_range, y_range, comp) + data4, extent4 = visualizer.get_var_data(meso_4, var_str, time_meso4, x_range, y_range, comp) + data8, extent8 = visualizer.get_var_data(meso_8, var_str, time_meso8, x_range, y_range, comp) + + + abs_diff2 = data2 - datamicro + abs_diff4 = data4 - datamicro + abs_diff8 = data8 - datamicro + + ar_mean = (np.abs(data2) + np.abs(datamicro))/2 + rel_diff2 = np.abs(datamicro -data2)/ar_mean + rel_diff2 = rel_diff2 / 2. + + ar_mean = (np.abs(data4) + np.abs(datamicro))/2 + rel_diff4 = np.abs(data4 -datamicro)/ar_mean + rel_diff4 = rel_diff4 / 4 + + ar_mean = (np.abs(data8) + np.abs(datamicro))/2 + rel_diff8 = np.abs(data8 - datamicro)/ar_mean + rel_diff8 = rel_diff8 /8 + + + # PLOTTING + fig = plt.figure(figsize=[13,4]) + ax1 = fig.add_subplot(1, 4, 1) + ax2 = fig.add_subplot(1, 4, 2, sharey=ax1) + ax3 = fig.add_subplot(1, 4, 3, sharey=ax1) + ax4 = fig.add_subplot(1, 4, 4, sharey=ax1) + axes = [ax1, ax2, ax3, ax4] + plt.setp(ax2.get_yticklabels(), visible=False) + plt.setp(ax3.get_yticklabels(), visible=False) + plt.setp(ax4.get_yticklabels(), visible=False) + + + + images =[] + + im = ax1.imshow(datamicro, extent=extentmicro, origin='lower', cmap='plasma') #, norm='log') + divider = make_axes_locatable(ax1) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + im = ax2.imshow(rel_diff2, extent=extent2, origin='lower', cmap='plasma') #, norm='log') + divider = make_axes_locatable(ax2) + cax = divider.append_axes('right', size='5%', pad=0.05) + cax.set_axis_off() + images.append(im) + + im = ax3.imshow(rel_diff4, extent=extent4, origin='lower', cmap='plasma') #, norm='log') + divider = make_axes_locatable(ax3) + cax = divider.append_axes('right', size='5%', pad=0.05) + cax.set_axis_off() + images.append(im) + + im = ax4.imshow(rel_diff8, extent=extent8, origin='lower', cmap='plasma') #, norm='log') + divider = make_axes_locatable(ax4) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + images.append(im) + + for i in range(len(axes)): + axes[i].set_xlabel(r'$x$') + axes[i].set_ylabel(r'$y$') + + title = micro_model.labels_var_dict[var_str] + title += r'$,$ $a=0$' + axes[0].set_title(title, fontsize=10) + + title = r'$Scaled$ $rel.$ $diff.,$ $L=2dx$' + # title = meso_2.labels_var_dict[var_str] + # title += r'$,$ $a=0,$ $L=2dx$' + axes[1].set_title(title, fontsize=10) + + title = r'$Scaled$ $rel.$ $diff.,$ $L=4dx$' + # title = meso_4.labels_var_dict[var_str] + # title += r'$,$ $a=0,$ $L=4dx$' + axes[2].set_title(title, fontsize=10) + + title = r'$Scaled$ $rel.$ $diff.,$ $L=8dx$' + # title = meso_8.labels_var_dict[var_str] + # title += r'$,$ $a=0,$ $L=8dx$' + axes[3].set_title(title, fontsize=10) + + fig.tight_layout() + + # COMMON COLORMAP + # Finding the global min and max and setting the shared colormap based on these. + vmin = min(image.get_array().min() for image in images) + vmax = max(image.get_array().max() for image in images) + norm = colors.Normalize(vmin=vmin, vmax=vmax) + for im in images: + im.set_norm(norm) + + # fig.colorbar(images[0], ax=axes, orientation='vertical', location='right', shrink=0.52, pad=0.025 ) + + # Make images respond to changes in the norm of other images (e.g. via the + # "edit axis, curves and images parameters" GUI on Qt), but be careful not to + # recurse infinitely! + def update(changed_image): + for im in images: + if (changed_image.get_cmap() != im.get_cmap() + or changed_image.get_clim() != im.get_clim()): + im.set_cmap(changed_image.get_cmap()) + im.set_clim(changed_image.get_clim()) + + for im in images: + im.callbacks.connect('changed', update) + + + format='png' + dpi=400 + filename = f"/Lin_scale_filt_{var_str}_{comp[0]}." + format + plt.savefig(saving_directory + filename, format = format, dpi=dpi) + + + + # # ################################################################################################## + # # # SCRIPT TO SHOW HOW THE IMPACT OF FILTERING SCALES WITH THE FILTER-SIZE: COARSE-GRAINING + # # ################################################################################################## + + # # READING SIMULATION SETTINGS FROM CONFIG FILE + # if len(sys.argv) == 1: + # print(f"You must pass the configuration file for the simulations.") + # raise Exception() + + # config = configparser.ConfigParser() + # config.read(sys.argv[1]) + + # # LOADING MESO AND MICRO MODELS + # pickle_directory = config['Directories']['pickled_files_dir'] + + + # print('=========================================================================') + # print(f'Starting job on data from {pickle_directory}') + # print('=========================================================================\n\n') + + # meso_pickled_filename = '/rHD2d_cg=fw=bl=2dx.pickle' + # MesoModelLoadFile = pickle_directory + meso_pickled_filename + # with open(MesoModelLoadFile, 'rb') as filehandle: + # meso_2 = pickle.load(filehandle) + # micro_model = meso_2.micro_model + + # meso_pickled_filename = '/rHD2d_cg=fw=bl=4dx.pickle' + # MesoModelLoadFile = pickle_directory + meso_pickled_filename + # with open(MesoModelLoadFile, 'rb') as filehandle: + # meso_4 = pickle.load(filehandle) + + # meso_pickled_filename = '/rHD2d_cg=fw=bl=8dx.pickle' + # MesoModelLoadFile = pickle_directory + meso_pickled_filename + # with open(MesoModelLoadFile, 'rb') as filehandle: + # meso_8 = pickle.load(filehandle) + + + # print('Finished reading pickled data\n') + + # # CHECKING WE ARE COMPARING DATA FROM THE SAME TIME-SLICE + # num_snaps = micro_model.domain_vars['nt'] + # central_slice_num = int(num_snaps/2.) + # time_micro = micro_model.domain_vars['t'][central_slice_num] + # num_slices_meso = int(config['Models_settings']['num_T_slices']) + # time_meso2 = meso_2.domain_vars['T'][int((num_slices_meso-1)/2)] + # time_meso4 = meso_2.domain_vars['T'][int((num_slices_meso-1)/2)] + # time_meso8 = meso_2.domain_vars['T'][int((num_slices_meso-1)/2)] + + # compatible = True + # if time_micro != time_meso2 or time_meso2 != time_meso4 or time_meso4 != time_meso8: + # compatible = False + + # if not compatible: + # print("Slices of meso and micro models do not coincide. Careful!\n") + # else: + # print("Comparing data at same time-slice, hurray!\n") + + # # # PLOT SETTINGS + # plot_ranges = json.loads(config['Plot_settings']['plot_ranges']) + # x_range = plot_ranges['x_range'] + # y_range = plot_ranges['y_range'] + # saving_directory = config['Directories']['figures_dir'] + # visualizer = Plotter_2D() + # # diff_plot_settings =json.loads(config['Plot_settings']['diff_plot_settings']) + + # # Building the data for showing the relative difference between the various plots + # models = [micro_model, meso_2, meso_4, meso_8] + # var_str = 'BC' + # comp = (0,) + + # inset_ranges = json.loads(config['Plot_settings']['inset_ranges']) + # inset_x_range = inset_ranges['x_range'] + # inset_y_range = inset_ranges['y_range'] + + # # full_range = [0.04,0.96] + # datamicro, extentmicro = visualizer.get_var_data(micro_model, var_str, time_micro, x_range, y_range, comp) + # data2, extent2 = visualizer.get_var_data(meso_2, var_str, time_meso2, inset_x_range, inset_y_range, comp) + # data4, extent4 = visualizer.get_var_data(meso_4, var_str, time_meso4, inset_x_range, inset_y_range, comp) + # data8, extent8 = visualizer.get_var_data(meso_8, var_str, time_meso8, inset_x_range, inset_y_range, comp) + + + # # PLOTTING + # fig = plt.figure(figsize=[13,4]) + # ax1 = fig.add_subplot(1, 4, 1) + # ax2 = fig.add_subplot(1, 4, 2, sharey=ax1) + # ax3 = fig.add_subplot(1, 4, 3, sharey=ax1) + # ax4 = fig.add_subplot(1, 4, 4, sharey=ax1) + # axes = [ax1, ax2, ax3, ax4] + # plt.setp(ax3.get_yticklabels(), visible=False) + # plt.setp(ax4.get_yticklabels(), visible=False) + + + # images =[] + + # im = ax1.imshow(datamicro, extent=extentmicro, origin='lower', cmap='plasma') #, norm='log') + # divider = make_axes_locatable(ax1) + # cax = divider.append_axes('right', size='5%', pad=0.05) + # images.append(im) + + # im = ax2.imshow(rel_diff2, extent=extent2, origin='lower', cmap='plasma') #, norm='log') + # divider = make_axes_locatable(ax2) + # cax = divider.append_axes('right', size='5%', pad=0.05) + # cax.set_axis_off() + # images.append(im) + + # im = ax3.imshow(rel_diff4, extent=extent4, origin='lower', cmap='plasma') #, norm='log') + # divider = make_axes_locatable(ax3) + # cax = divider.append_axes('right', size='5%', pad=0.05) + # cax.set_axis_off() + # images.append(im) + + # im = ax4.imshow(rel_diff8, extent=extent8, origin='lower', cmap='plasma') #, norm='log') + # divider = make_axes_locatable(ax4) + # cax = divider.append_axes('right', size='5%', pad=0.05) + # fig.colorbar(im, cax=cax, orientation='vertical') + # images.append(im) + + # for i in range(len(axes)): + # axes[i].set_xlabel(r'$x$') + # axes[i].set_ylabel(r'$y$') + + # title = micro_model.labels_var_dict[var_str] + # title += r'$,$ $a=0$' + # axes[0].set_title(title) + + # # title = r'$Scaled$ $rel.$ $diff.,$ $L=2dx$' + # title = meso_2.labels_var_dict[var_str] + # title += r'$,$ $a=0,$ $L=2dx$' + # axes[1].set_title(title) + + # # title = r'$Scaled$ $rel.$ $diff.,$ $L=4dx$' + # title = meso_4.labels_var_dict[var_str] + # title += r'$,$ $a=0,$ $L=4dx$' + # axes[2].set_title(title) + + # # title = r'$Scaled$ $rel.$ $diff.,$ $L=8dx$' + # title = meso_8.labels_var_dict[var_str] + # title += r'$,$ $a=0,$ $L=8dx$' + # axes[3].set_title(title) + + # fig.tight_layout() + + # # COMMON COLORMAP + # # Finding the global min and max and setting the shared colormap based on these. + # vmin = min(image.get_array().min() for image in images) + # vmax = max(image.get_array().max() for image in images) + # norm = colors.Normalize(vmin=vmin, vmax=vmax) + # for im in images: + # im.set_norm(norm) + + # # fig.colorbar(images[0], ax=axes, orientation='vertical', location='right', shrink=0.52, pad=0.025 ) + + # # Make images respond to changes in the norm of other images (e.g. via the + # # "edit axis, curves and images parameters" GUI on Qt), but be careful not to + # # recurse infinitely! + # def update(changed_image): + # for im in images: + # if (changed_image.get_cmap() != im.get_cmap() + # or changed_image.get_clim() != im.get_clim()): + # im.set_cmap(changed_image.get_cmap()) + # im.set_clim(changed_image.get_clim()) + + # for im in images: + # im.callbacks.connect('changed', update) + + + # # Adding boundaries of zoomed in regions + # A = np.array([inset_x_range[0], inset_y_range[0]]) + # B = np.array([inset_x_range[1], inset_y_range[0]]) + # C = np.array([inset_x_range[1], inset_y_range[1]]) + # D = np.array([inset_x_range[0], inset_y_range[1]]) + # arrows_starts = [A, B, C, D] + # arrows_increments = [arrows_starts[i] - arrows_starts[i-1] for i in range(1, len(arrows_starts))] + # arrows_increments.append(A-D) + # for i in range(len(arrows_starts)): + # ax1.arrow(arrows_starts[i][0], arrows_starts[i][1], arrows_increments[i][0], arrows_increments[i][1], \ + # width=0.005,color='white',head_length=0.0,head_width=0.0) + + # fig.tight_layout() + + # format='png' + # dpi=400 + # filename = f"/Filter_scale_BC_bl=fw." + format + # plt.savefig(saving_directory + filename, format = format, dpi=dpi) \ No newline at end of file diff --git a/Proof_of_principle/draft_scripts/gamma_interp.py b/Proof_of_principle/draft_scripts/gamma_interp.py new file mode 100644 index 0000000..992738b --- /dev/null +++ b/Proof_of_principle/draft_scripts/gamma_interp.py @@ -0,0 +1,272 @@ +import sys +# import os +sys.path.append('../../master_files/') +import pickle +import configparser +import json +import time +import math +from scipy import stats +from scipy.interpolate import SmoothBivariateSpline + +from matplotlib.ticker import FuncFormatter + +from FileReaders import * +from MicroModels import * +from MesoModels import * +from Visualization import * +from Analysis import * + +if __name__ == '__main__': + + # ################################################################## + # EXTRACTING THE 1ST ADIABATIC COEFFICIENT LOCALLY + # ################################################################## + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.") + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # LOADING MESO MODEL + pickle_directory = config['Directories']['pickled_files_dir'] + meso_pickled_filename = config['Filenames']['meso_pickled_filename'] + MesoModelLoadFile = pickle_directory + meso_pickled_filename + + print('================================================') + print(f'Starting job on data from {MesoModelLoadFile}') + print('================================================\n\n') + + with open(MesoModelLoadFile, 'rb') as filehandle: + meso_model = pickle.load(filehandle) + + micro_model = meso_model.micro_model + + nx, ny = micro_model.domain_vars['nx'], micro_model.domain_vars['ny'] + central_slice_idx = int((micro_model.domain_vars['nt']-1)/2) + time_micro = micro_model.domain_vars['t'][central_slice_idx] + x_range = micro_model.domain_vars['xmin'], micro_model.domain_vars['xmax'] + y_range = micro_model.domain_vars['ymin'], micro_model.domain_vars['ymax'] + + visualizer = Plotter_2D() + + p_micro, micro_extent = visualizer.get_var_data(micro_model, 'p', time_micro, x_range, y_range) + n_micro, _ = visualizer.get_var_data(micro_model, 'n', time_micro, x_range, y_range) + int_en_micro, _ = visualizer.get_var_data(micro_model, 'e', time_micro, x_range, y_range) + + eps_micro = n_micro + np.multiply(n_micro, int_en_micro) + + p_micro_flat = p_micro.flatten() + n_micro_flat = n_micro.flatten() + eps_micro_flat = eps_micro.flatten() + + micro_SBS_p = SmoothBivariateSpline(n_micro_flat, eps_micro_flat, p_micro_flat) + micro_SBS_p_n = micro_SBS_p.partial_derivative(1, 0) + micro_SBS_p_eps = micro_SBS_p.partial_derivative(0, 1) + + def compute_Gamma(p, n,eps, partial_n_p, partial_eps_p): + """ + Given some values for the number density, the energy density and the pressure + Compute the corresponding adiabatic index (1st) using the cubic spline interpolations above + """ + Gamma = partial_n_p + (p+eps)/n * partial_eps_p + Gamma *= n/p + + return Gamma + + gamma_micro = np.zeros_like(p_micro) + rel_diff_gamma_micro = np.zeros_like(p_micro) + for i in range(len(p_micro[:,0])): + for j in range(len(p_micro[0,:])): + p = p_micro[i, j] + n = n_micro[i, j] + eps = eps_micro[i, j] + + partial_n_p = micro_SBS_p_n(n, eps) + partial_eps_p = micro_SBS_p_eps(n, eps) + gamma_micro[i,j] = compute_Gamma(p, n , eps, partial_n_p, partial_eps_p) -4./3. + rel_diff_gamma_micro[i,j] = np.abs(gamma_micro[i,j]) / (4/3) + + + Nt, Nx, Ny = meso_model.domain_vars['Nt'], meso_model.domain_vars['Nx'], meso_model.domain_vars['Ny'] + central_slice_idx = int((Nt-1)/2) + + time_meso = meso_model.domain_vars['T'][central_slice_idx] + x_range = meso_model.domain_vars['Xmin'], meso_model.domain_vars['Xmax'] + y_range = meso_model.domain_vars['Ymin'], meso_model.domain_vars['Ymax'] + + p_filt, meso_extent = visualizer.get_var_data(meso_model, 'p_filt', time_meso, x_range, y_range) + n_tilde, _ = visualizer.get_var_data(meso_model, 'n_tilde', time_meso, x_range, y_range) + eps_tilde, _ = visualizer.get_var_data(meso_model, 'eps_tilde', time_meso, x_range, y_range) + + p_filt_flat = p_filt.flatten() + n_tilde_flat = n_tilde.flatten() + eps_tilde_flat = eps_tilde.flatten() + + meso_SBS_p = SmoothBivariateSpline(n_tilde_flat, eps_tilde_flat, p_filt_flat) + meso_SBS_p_n = meso_SBS_p.partial_derivative(1, 0) + meso_SBS_p_eps = meso_SBS_p.partial_derivative(0, 1) + + gamma_meso = np.zeros_like(p_filt) + rel_diff_gamma_meso = np.zeros_like(p_filt) + for i in range(len(p_filt[:,0])): + for j in range(len(p_filt[0,:])): + p = p_filt[i, j] + n = n_tilde[i, j] + eps = eps_tilde[i, j] + + partial_n_p = meso_SBS_p_n(n, eps) + partial_eps_p = meso_SBS_p_eps(n, eps) + + gamma_meso[i,j] = compute_Gamma(p, n , eps, partial_n_p, partial_eps_p) -4./3. + rel_diff_gamma_meso[i,j] = np.abs(gamma_meso[i,j]) / (4/3.) + + + ############################################################################################### + # PLOTTING THE DIFFERENCE BETWEEN THE EXTRACTED ADIABATIC INDEX AND THE EXPECTED VALUE OF 4/3 + ############################################################################################### + plt.rc("font",family="serif") + plt.rc("mathtext",fontset="cm") + fig, axes = plt.subplots(nrows=1, ncols=2, figsize=[9,4]) + axes = axes.flatten() + + im = axes[0].imshow(gamma_micro, extent=micro_extent, origin='lower', cmap='Spectral_r', norm='symlog') + divider = make_axes_locatable(axes[0]) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + im = axes[1].imshow(gamma_meso, extent=meso_extent, origin='lower', cmap='Spectral_r', norm='symlog') + divider = make_axes_locatable(axes[1]) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + axes[0].set_title(r"$\Gamma_{micro} - 4/3$", fontsize =10) + axes[1].set_title(r"$\Gamma_{meso} - 4/3$", fontsize =10) + + for ax in axes: + ax.set_xlabel(r'$x$') + ax.set_ylabel(r'$y$') + + fig.tight_layout() + + fig_directory = config['Directories']['figures_dir'] + filename = 'Interp_Gamma' + format = 'png' + dpi = 300 + filename += "." + format + plt.savefig(fig_directory + filename, format=format, dpi=dpi) + plt.close() + + + plt.rc("font",family="serif") + plt.rc("mathtext",fontset="cm") + fig, axes = plt.subplots(nrows=1, ncols=2, figsize=[9,4]) + axes = axes.flatten() + + im = axes[0].imshow(rel_diff_gamma_micro, extent=micro_extent, origin='lower', cmap='plasma', norm='log') + divider = make_axes_locatable(axes[0]) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + im = axes[1].imshow(rel_diff_gamma_meso, extent=meso_extent, origin='lower', cmap='plasma', norm='log') + divider = make_axes_locatable(axes[1]) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + axes[0].set_title(r"$\frac{|\Gamma_{micro} - 4/3|}{4/3}$", fontsize =12) + axes[1].set_title(r"$\frac{|\Gamma_{meso} - 4/3|}{4/3}$", fontsize =12) + + for ax in axes: + ax.set_xlabel(r'$x$') + ax.set_ylabel(r'$y$') + + fig.tight_layout() + + fig_directory = config['Directories']['figures_dir'] + filename = 'Interp_Gamma_reldiff' + format = 'png' + dpi = 300 + filename += "." + format + plt.savefig(fig_directory + filename, format=format, dpi=dpi) + plt.close() + + + ################################################################################## + # PLOTTING THE INTERPOLATED P AND ITS DERIVATIVES: CHECK ON IMPACT OF ARTIFACTS + ################################################################################## + + p_filt_interp = np.zeros_like(p_filt) + d_n_p = np.zeros_like(p_filt) + d_eps_p = np.zeros_like(p_filt) + for i in range(len(p_filt[:,0])): + for j in range(len(p_filt[0,:])): + n = n_tilde[i, j] + eps = eps_tilde[i, j] + + p_filt_interp[i,j] = meso_SBS_p(n, eps) + d_n_p[i,j] = meso_SBS_p_n(n, eps) + d_eps_p[i,j] = meso_SBS_p_eps(n, eps) + + plt.rc("font",family="serif") + plt.rc("mathtext",fontset="cm") + + + fig, axes = plt.subplots(nrows=2, ncols=3, figsize=[13,8], sharex = True, sharey = True) + axes = axes.flatten() + + im = axes[0].imshow(p_filt, extent=meso_extent, origin='lower', cmap='plasma') + divider = make_axes_locatable(axes[0]) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + im = axes[1].imshow(n_tilde, extent=meso_extent, origin='lower', cmap='plasma') + divider = make_axes_locatable(axes[1]) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + im = axes[2].imshow(eps_tilde, extent=meso_extent, origin='lower', cmap='plasma') + divider = make_axes_locatable(axes[2]) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + im = axes[3].imshow(p_filt_interp, extent=meso_extent, origin='lower', cmap='plasma') + divider = make_axes_locatable(axes[3]) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + im = axes[4].imshow(d_n_p, extent=meso_extent, origin='lower', cmap='plasma') + divider = make_axes_locatable(axes[4]) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + im = axes[5].imshow(d_eps_p, extent=meso_extent, origin='lower', cmap='plasma') + divider = make_axes_locatable(axes[5]) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + + for ax in axes: + ax.set_xlabel(r'$x$') + ax.set_ylabel(r'$y$') + + + axes[0].set_title(r"$

$", fontsize=12) + axes[1].set_title(r"$\tilde n$", fontsize=12) + axes[2].set_title(r"$\tilde \varepsilon$", fontsize=12) + axes[3].set_title(r"$

_{interpolated}$", fontsize=12) + axes[4].set_title(r"$\partial_{\tilde{n}}

$", fontsize=14) + axes[5].set_title(r"$\partial_{\tilde{\varepsilon}}

$", fontsize=14) + + + fig.tight_layout() + + fig_directory = config['Directories']['figures_dir'] + filename = 'Interpolation_test' + format = 'png' + dpi = 300 + filename += "." + format + plt.savefig(fig_directory + filename, format=format, dpi=dpi) + plt.close() \ No newline at end of file diff --git a/Proof_of_principle/draft_scripts/outputs/.DS_Store b/Proof_of_principle/draft_scripts/outputs/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/Proof_of_principle/draft_scripts/outputs/.DS_Store differ diff --git a/Proof_of_principle/draft_scripts/py_serial.slurm b/Proof_of_principle/draft_scripts/py_serial.slurm new file mode 100644 index 0000000..e4ec88f --- /dev/null +++ b/Proof_of_principle/draft_scripts/py_serial.slurm @@ -0,0 +1,37 @@ +#!/bin/bash +#################################### +# JOB INFO: +#################################### + + +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --time=00:10:00 +#SBATCH --output=outputs/serial_%A.out + + +#SBATCH --mail-type=begin # send email when job begins +#SBATCH --mail-type=fail +#SBATCH --mail-type=end # send email when job ends +#SBATCH --mail-user=celora@ice.csic.es + + +#################################### +# LOADING THE CONDA ENVIRONMENT +#################################### + +module load conda/py3-latest +# I get warnings to use 'conda deactivate' instead but that doesn't work +source deactivate +conda activate myenv + + +#################################### +# LAUNCHING THE JOBS +##################################### +python3 -u filter_scaling.py config_draft.txt +python3 -u comparison_zoom.py config_draft.txt +python3 -u const_coeff_all.py config_draft.txt + + + diff --git a/Proof_of_principle/filter_scripts/.DS_Store b/Proof_of_principle/filter_scripts/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/Proof_of_principle/filter_scripts/.DS_Store differ diff --git a/Proof_of_principle/filter_scripts/config_filter.txt b/Proof_of_principle/filter_scripts/config_filter.txt new file mode 100644 index 0000000..3026fa3 --- /dev/null +++ b/Proof_of_principle/filter_scripts/config_filter.txt @@ -0,0 +1,48 @@ +# CONFIGURATION FILE FOR SCRIPT PICKLING_MESO.PY +################################################# + +[Directories] + +hdf5_dir = /scratch/tc2m23/KHIRandom/hydro/new_data/800X800/ET10/METHOD_output/50dx/ + +pickled_files_dir = /scratch/tc2m23/KHIRandom/hydro/new_data/800X800/ET10/pickled/50dx_data + +figures_dir = . + + +[Filenames] + +meso_pickled_filename = /rHD2d_nocg_fw=bl=8dx.pickle + +micro_pickled_filename = / + + +[Micro_model_settings] +#snapshots_opts are useful in case not all snapshots from METHOD in the folder are required. +#the options is activated using the first key, while the list refers to the index of the elements of +#ordered filenames' list to be retained (list ordered with glob in FileReaders.METHOD_HDF5 ) + +snapshots_opts = {"fewer_snaps_required": true, + "smaller_list": [15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35]} + +[Meso_model_settings] + +meso_grid = {"x_range": [0.03, 0.97], "y_range": [0.03, 0.97], "num_T_slices": 3, + "coarse_grain_factor": 1, "coarse_grain_time": false} + +filtering_options = {"box_len_ratio": 8.0, "filter_width_ratio": 2.0} + +n_cpus = 40 + + +[Plot_settings] +# method sets that used for producing difference plots: set it to 'interpolate' when plotting models with different grids +# interp_dims is relevant when 'interpolate' method is used: should be set to the dims of the coarser grid +# Else, set method to "raw_data" to use gridded data directly + +plot_ranges = {"x_range": [0.04, 0.96], "y_range": [0.04, 0.96]} + +diff_plot_settings = {"method": "raw_data", "interp_dims": [300, 300]} + + + diff --git a/Proof_of_principle/filter_scripts/pickling_meso.py b/Proof_of_principle/filter_scripts/pickling_meso.py new file mode 100644 index 0000000..dcff019 --- /dev/null +++ b/Proof_of_principle/filter_scripts/pickling_meso.py @@ -0,0 +1,161 @@ +import sys +import os +sys.path.append('../../master_files/') +import pickle +import time +import configparser +import json + +from FileReaders import * +from MicroModels import * +from Filters import * +from MesoModels import * + +if __name__ == '__main__': + + #################################################################################################### + # # MAIN SCRIPT OF PIPELINE: SET UP THE MESO MODEL FROM SIM DATA (MESO-GRID + OBSERVERS + FILTER) + # # AND DECOMPOSE THE RESIDUALS AS WELL AS COMPUTE DERIVATIVES AND QUANTITIES FOR MODELLING THEM + #################################################################################################### + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.", flush=True) + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # SETTING UP THE MICROMODEL + start_time = time.perf_counter() + hdf5_directory = config['Directories']['hdf5_dir'] + print(f'Starting job with data from {hdf5_directory}', flush=True) + print('==============================================\n') + filenames = hdf5_directory + snapshots_opts = json.loads(config['Micro_model_settings']['snapshots_opts']) + fewer_snaps_required = snapshots_opts['fewer_snaps_required'] + smaller_list = snapshots_opts['smaller_list'] + FileReader = METHOD_HDF5(filenames,fewer_snaps_required, smaller_list) + num_snaps = FileReader.num_files + micro_model = IdealHD_2D() + FileReader.read_in_data_HDF5_missing_xy(micro_model) + micro_model.setup_structures() + time_taken = time.perf_counter() - start_time + print(f'Finished reading micro data from hdf5, structures also set up. Time taken: {time_taken}', flush=True) + + # SETTING UP THE MESO MODEL + start_time = time.perf_counter() + meso_grid = json.loads(config['Meso_model_settings']['meso_grid']) + filtering_options = json.loads(config['Meso_model_settings']['filtering_options']) + + coarse_factor = meso_grid['coarse_grain_factor'] + coarse_time = meso_grid['coarse_grain_time'] + central_slice_num = int(num_snaps/2) + num_T_slices = meso_grid['num_T_slices'] + furthest_slice_number = int((num_T_slices-1)/2) + if coarse_time: + furthest_slice_number = int(coarse_factor * furthest_slice_number) + + t_range = [micro_model.domain_vars['t'][central_slice_num-furthest_slice_number], micro_model.domain_vars['t'][central_slice_num+furthest_slice_number]] + x_range = meso_grid['x_range'] + y_range = meso_grid['y_range'] + + ts = micro_model.domain_vars['t'][:] + print(f'ts: {ts}\n') + + box_len_ratio = float(filtering_options['box_len_ratio']) + filter_width_ratio = float(filtering_options['filter_width_ratio']) + + box_len = box_len_ratio * micro_model.domain_vars['dx'] + width = filter_width_ratio * micro_model.domain_vars['dx'] + find_obs = FindObs_root_parallel(micro_model, box_len) + filter = box_filter_parallel(micro_model, width) + meso_model = resHD2D(micro_model, find_obs, filter) + meso_model.setup_meso_grid([t_range, x_range, y_range], coarse_factor = coarse_factor, coarse_time = coarse_time) + + time_taken = time.perf_counter() - start_time + print('Grid is set up, time taken: {}\n'.format(time_taken), flush=True) + num_points = meso_model.domain_vars['Nx'] * meso_model.domain_vars['Ny'] * meso_model.domain_vars['Nt'] + print('Number of points: {}\n'.format(num_points), flush=True) + + # FINDING THE OBSERVERS + n_cpus = int(config['Meso_model_settings']['n_cpus']) + + start_time = time.perf_counter() + meso_model.find_observers_parallel(n_cpus) + time_taken = time.perf_counter() - start_time + print('Observers found in parallel: time taken= {}\n'.format(time_taken), flush=True) + + + # # UNCOMMENT IF MESO_MODEL EXISTS PICKLED EXIST ALREADY AND YOU WANT TO RE-RUN SOME OF THE LATER ROUTINES: + # # LOADING MESO MODEL + # pickle_directory = config['Directories']['pickled_files_dir'] + # meso_pickled_filename = config['Filenames']['meso_pickled_filename'] + # MesoModelLoadFile = pickle_directory + meso_pickled_filename + # n_cpus = int(config['Meso_model_settings']['n_cpus']) + + # print('=========================================================================') + # print(f'Starting job on data from {MesoModelLoadFile}') + # print('=========================================================================\n\n') + # with open(MesoModelLoadFile, 'rb') as filehandle: + # meso_model = pickle.load(filehandle) + + + # # This is to adjust the filter-size by retaining the observers computed above + # filtering_options = json.loads(config['Meso_model_settings']['filtering_options']) + # filter_width_ratio = filtering_options['filter_width_ratio'] + # micro_model = meso_model.micro_model + # width = filter_width_ratio * micro_model.domain_vars['dx'] + # filter = box_filter_parallel(micro_model, width) + # meso_model.set_filter(filter) + + + # FILTERING + start_time = time.perf_counter() + meso_model.filter_micro_vars_parallel(n_cpus) + time_taken = time.perf_counter() - start_time + print('Parallel filtering stage ended (fw= {}): time taken= {}\n'.format(int(filter_width_ratio), time_taken), flush=True) + + + # DECOMPOSING AND CALCULATING THE CLOSURE INGREDIENTS + start_time = time.perf_counter() + meso_model.decompose_structures_parallel(n_cpus) + time_taken = time.perf_counter() - start_time + print('Finished decomposing meso structures in parallel, time taken: {}\n'.format(time_taken), flush=True) + + + start_time = time.perf_counter() + meso_model.calculate_derivatives() + time_taken = time.perf_counter() - start_time + print('Finished computing derivatives (serial), time taken: {}\n'.format(time_taken), flush=True) + + start_time = time.perf_counter() + meso_model.closure_ingredients_parallel(n_cpus) + time_taken = time.perf_counter() - start_time + print('Finished computing the closure ingredients in parallel, time taken: {}\n'.format(time_taken), flush=True) + + start_time = time.perf_counter() + meso_model.EL_style_closure_parallel(n_cpus) + time_taken = time.perf_counter() - start_time + print('Finished computing the EL_style closure in parallel, time taken: {}\n'.format(time_taken), flush=True) + + start_time = time.perf_counter() + meso_model.modelling_coefficients_parallel(n_cpus) + time_taken = time.perf_counter() - start_time + print('Finished computing quantities to model extracted coefficients, time taken: {}\n'.format(time_taken), flush=True) + + # start_time = time.perf_counter() + # meso_model.build_weights_Q1() + # time_taken = time.perf_counter() - start_time + # print('Finished computing weights in serial, time taken: {}\n'.format(time_taken), flush=True) + + # PICKLING THE CLASS INSTANCE FOR FUTURE USE + pickle_directory = config['Directories']['pickled_files_dir'] + filename = config['Filenames']['meso_pickled_filename'] + MesoModelPickleDumpFile = pickle_directory + filename + with open(MesoModelPickleDumpFile, 'wb') as filehandle: + pickle.dump(meso_model, filehandle) + + + + diff --git a/Proof_of_principle/filter_scripts/py_parallel.slurm b/Proof_of_principle/filter_scripts/py_parallel.slurm new file mode 100644 index 0000000..1189a96 --- /dev/null +++ b/Proof_of_principle/filter_scripts/py_parallel.slurm @@ -0,0 +1,37 @@ +#!/bin/bash +#################################### +# JOB INFO: parallel, single node +#################################### + +#SBATCH --output=outputs/parallel_%A.out +#SBATCH --nodes=1 +#SBATCH --ntasks=40 +#SBATCH --time=01:00:00 + +#SBATCH --mail-type=begin +#SBATCH --mail-type=fail +#SBATCH --mail-type=end +#SBATCH --mail-user=celora@ice.csic.es + + +#################################### +# LOADING THE CONDA ENVIRONMENT +#################################### + +module load conda/py3-latest +# I get warnings to use 'conda deactivate' instead but that doesn't work +source deactivate +conda activate myenv + + + +#################################### +# LAUNCHING THE JOBS +#################################### + +python3 -u pickling_meso.py config_filter.txt + + + + + diff --git a/Proof_of_principle/filter_scripts/py_serial.slurm b/Proof_of_principle/filter_scripts/py_serial.slurm new file mode 100644 index 0000000..c00fa73 --- /dev/null +++ b/Proof_of_principle/filter_scripts/py_serial.slurm @@ -0,0 +1,42 @@ +#!/bin/bash +#################################### +# JOB INFO: serial +#################################### + + +#SBATCH --nodes=1 +#SBATCH --ntasks=1 +#SBATCH --time=00:10:00 +#SBATCH --output=outputs/serial_%A.out + + + +#SBATCH --mail-type=begin # send email when job begins +#SBATCH --mail-type=fail +#SBATCH --mail-type=end # send email when job ends +#SBATCH --mail-user=celora@ice.csic.es + + +#################################### +# LOADING THE CONDA ENVIRONMENT +#################################### + +module load conda/py3-latest +# I get warnings to use 'conda deactivate' instead but that doesn't work +source deactivate +conda activate myenv + + +#################################### +# LAUNCHING THE JOBS +##################################### + +# python3 -u visualizing_micro.py config_filter.txt +# python3 -u visualizing_obs.py config_filter.txt +python3 -u visualizing_meso.py config_filter.txt +# python3 -u visualizing_residuals.py config_filter.txt + + + + + diff --git a/Proof_of_principle/filter_scripts/visualizing_meso.py b/Proof_of_principle/filter_scripts/visualizing_meso.py new file mode 100644 index 0000000..7ffc655 --- /dev/null +++ b/Proof_of_principle/filter_scripts/visualizing_meso.py @@ -0,0 +1,338 @@ +import sys +# import os +sys.path.append('../../master_files/') +import pickle +import configparser +import json +import time + +from FileReaders import * +from MicroModels import * +from MesoModels import * +from Visualization import * +from Analysis import * + +if __name__ == '__main__': + + # ################################################################### + # SCRIPT TO VISUALIZE VARIOUS MESO QUANTITIES AND COMPARE WITH MICRO + # ################################################################### + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.") + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # LOADING MESO AND MICRO MODELS + pickle_directory = config['Directories']['pickled_files_dir'] + meso_pickled_filename = config['Filenames']['meso_pickled_filename'] + MesoModelLoadFile = pickle_directory + meso_pickled_filename + + print('=========================================================================') + print(f'Starting job on data from {MesoModelLoadFile}') + print('=========================================================================\n\n') + + with open(MesoModelLoadFile, 'rb') as filehandle: + meso_model = pickle.load(filehandle) + micro_model = meso_model.micro_model + + print('Finished reading pickled data\n') + + # CHECKING WE ARE COMPARING DATA FROM THE SAME TIME-SLICE + num_snaps = micro_model.domain_vars['nt'] + central_slice_num = int(num_snaps/2.) + time_micro = micro_model.domain_vars['t'][central_slice_num] + meso_grid_info = json.loads(config['Meso_model_settings']['meso_grid']) + num_slices_meso = meso_grid_info['num_T_slices'] + time_meso = meso_model.domain_vars['T'][int((num_slices_meso-1)/2)] + if time_meso != time_micro: + print("Slices of meso and micro model do not coincide. Careful!\n") + else: + print("Comparing data at same time-slice, hurray!\n") + + # # PLOT SETTINGS + plot_ranges = json.loads(config['Plot_settings']['plot_ranges']) + x_range = plot_ranges['x_range'] + y_range = plot_ranges['y_range'] + saving_directory = config['Directories']['figures_dir'] + visualizer = Plotter_2D([12, 8]) + diff_plot_settings =json.loads(config['Plot_settings']['diff_plot_settings']) + diff_method = diff_plot_settings['method'] + interp_dims = diff_plot_settings['interp_dims'] + + # # PLOTTING MICRO VS FILTERED BC AND SET + # # ##################################### + # start_time = time.perf_counter() + # vars = [['BC'],['BC']] + # models = [micro_model, meso_model] + # comp = 1 + # components= [[(comp,)],[(comp,)]] + # # norms = [['log'], ['log'], ['log']] + # norms = [['symlog'], ['symlog'], ['log']] + # # cmaps = [['plasma'],['plasma'],['plasma']] + # cmaps = [['coolwarm'],['coolwarm'],['plasma']] + # fig=visualizer.plot_vars_models_comparison(models, vars, time_meso, x_range, y_range, components_indices = components, + # interp_dims = interp_dims, method = diff_method, diff_plot=False, rel_diff=True, norms=norms, cmaps=cmaps) + # fig.tight_layout() + # format='png' + # dpi=400 + # filename = "/Comparing_BC" +f"_{comp}." + format + # plt.savefig(saving_directory + filename, format = format, dpi=dpi) + + # vars = [['SET'], ['SET']] + # models = [micro_model, meso_model] + # comp = 1 + # components = [[(comp,comp)], [(comp,comp)]] + # norms = [['log'], ['log'], ['log']] + # # norms = [['symlog'], ['symlog'], ['log']] + # cmaps = [['plasma'],['plasma'],['plasma']] + # # cmaps = [['coolwarm'],['coolwarm'],['plasma']] + # fig=visualizer.plot_vars_models_comparison(models, vars, time_meso, x_range, y_range, components_indices = components, + # interp_dims = interp_dims, method = diff_method, diff_plot=False, rel_diff=True, norms=norms, cmaps=cmaps) + # fig.tight_layout() + # format='png' + # dpi=400 + # filename = "/Comparing_SET" +f"_({comp},{comp})."+format + # plt.savefig(saving_directory + filename, format = format, dpi=dpi) + # time_taken = time.perf_counter() - start_time + # print(f'Finished plotting model comparison: time taken (X2) ={time_taken}\n') + + # # # PLOTTING THE DECOMPOSED SET + # # ############################# + vars_strs = ['pi_res', 'pi_res', 'pi_res', 'pi_res', 'pi_res', 'pi_res'] + norms = ['mysymlog', 'mysymlog', 'mysymlog', 'mysymlog', 'mysymlog', 'mysymlog'] + cmaps = ['Spectral_r','Spectral_r','Spectral_r','Spectral_r','Spectral_r','Spectral_r'] + components = [(0,0), (0,1), (0,2), (1,1), (1,2), (2,2)] + fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, components_indices=components, norms=norms, cmaps=cmaps) + fig.tight_layout() + time_for_filename = str(round(time_meso,2)) + filename = "/DecomposedSET_1.pdf" + plt.savefig(saving_directory + filename, format = 'pdf') + + vars_strs = ['q_res', 'q_res', 'q_res', 'Pi_res', 'p_tilde', 'p_filt'] + norms = ['mysymlog', 'mysymlog', 'mysymlog', 'log', 'log', 'log'] + cmaps = ['Spectral_r','Spectral_r','Spectral_r', 'plasma', 'plasma', 'plasma'] + components = [(0,), (1,), (2,), (), (), ()] + fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, components_indices=components, norms=norms, cmaps=cmaps) + fig.tight_layout() + time_for_filename = str(round(time_meso,2)) + filename = "/DecomposedSET_2.pdf" + plt.savefig(saving_directory + filename, format = 'pdf') + print('Finished plotting decomposition of SET\n') + + # # # # PLOTTING THE DERIVATIVES OF FAVRE VEL AND TEMPERATURE + # # ######################################################### + # favre_vel_components = [0,1,2] + # for i in range(len(favre_vel_components)): + # components = [tuple([favre_vel_components[i]])] + # for j in range(3): + # components.append(tuple([j,favre_vel_components[i]])) + # if i==0: + # norms = ['log', 'mysymlog', 'mysymlog', 'mysymlog'] + # cmaps = ['plasma', 'seismic', 'seismic', 'seismic'] + # else: + # norms = ['mysymlog', 'mysymlog', 'mysymlog', 'mysymlog'] + # cmaps = ['seismic', 'seismic', 'seismic', 'seismic'] + + # vars_strs = ['u_tilde', 'D_u_tilde', 'D_u_tilde', 'D_u_tilde'] + # fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, components_indices=components, norms=norms, cmaps=cmaps) + # fig.tight_layout() + # time_for_filename = str(round(time_meso,2)) + # filename = "/D_favre_{}.pdf".format(favre_vel_components[i]) + # plt.savefig(saving_directory + filename, format = 'pdf') + # print('Finished plotting derivatives of favre velocity\n', flush=True) + + # vars_strs = ['T_tilde', 'D_T_tilde', 'D_T_tilde', 'D_T_tilde'] + # components = [(), (0,), (1,), (2,)] + # norms = ['log', 'mysymlog', 'mysymlog', 'mysymlog'] + # cmaps = ['plasma', 'seismic', 'seismic', 'seismic'] + # fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, components_indices=components, norms=norms, cmaps=cmaps) + # fig.tight_layout() + # time_for_filename = str(round(time_meso,2)) + # filename = "/D_T.pdf" + # plt.savefig(saving_directory + filename, format = 'pdf') + # print('Finished plotting temperature derivatives\n', flush=True) + + # # # PLOTTING SHEAR, ACCELERATION, EXPANSION AND TEMPERATURE DERIVATIVES + # ######################################################################## + # vars_strs = ['shear_tilde', 'shear_tilde', 'shear_tilde', 'shear_tilde', 'shear_tilde', 'shear_tilde'] + # components = [(0,0), (0,1), (0,2), (1,1), (1,2), (2,2)] + # norms = ['mysymlog','mysymlog','mysymlog','mysymlog','mysymlog','mysymlog'] + # cmaps = ['seismic','seismic','seismic','seismic','seismic','seismic'] + # fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, components_indices=components, norms=norms, cmaps=cmaps) + # fig.tight_layout() + # time_for_filename = str(round(time_meso,2)) + # filename = "/Shear_comps.pdf" + # plt.savefig(saving_directory + filename, format = 'pdf') + # print('Finished plotting shear\n', flush=True) + + # vars_strs = ['acc_tilde', 'acc_tilde', 'acc_tilde', 'Theta_tilde', 'Theta_tilde', 'Theta_tilde'] + # components = [(0,), (1,), (2,), (0,), (1,), (2,)] + # norms = ['mysymlog','mysymlog','mysymlog','mysymlog','mysymlog','mysymlog'] + # cmaps = ['seismic','seismic','seismic','seismic','seismic','seismic'] + # fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, components_indices=components, norms=norms, cmaps=cmaps) + # fig.tight_layout() + # time_for_filename = str(round(time_meso,2)) + # filename = "/Acc+Tderivs.pdf" + # plt.savefig(saving_directory + filename, format = 'pdf') + # print('Finished plotting DaT\n', flush=True) + + # vars_strs = ['vort_tilde', 'vort_tilde', 'vort_tilde'] + # components = [(0,1), (0,2), (1,2)] + # norms = [None, None, None] #['mysymlog','mysymlog','mysymlog'] + # cmaps = ['plasma','plasma','plasma'] + # fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, components_indices=components, norms=norms, cmaps=cmaps) + # fig.tight_layout() + # time_for_filename = str(round(time_meso,2)) + # filename = "/Vorticity_comps.pdf" + # plt.savefig(saving_directory + filename, format = 'pdf') + # print('Finished plotting vorticity\n', flush=True) + + # # SUMMARY PLOT OF THE RESIDUALS + vars_strs = ['Pi_res', 'pi_res_sq', 'q_res_sq'] + vars = [] + extents = [] + for var_str in vars_strs: + temp_var, temp_extent = visualizer.get_var_data(meso_model, var_str, time_meso, x_range, y_range) + if var_str != 'Pi_res': + temp_var = np.sqrt(temp_var) + vars.append(temp_var) + extents.append(temp_extent) + + + + norms = ['log', 'log', 'log'] + cmaps = ['plasma', 'plasma', 'plasma'] + + + plt.rc("font",family="serif") + plt.rc("mathtext",fontset="cm") + fig, axes = plt.subplots(1,3, squeeze=False, figsize=[12,4], sharey=True) + axes = axes.flatten() + + images = [] + + for i in range(len(axes)): + + if norms[i] == 'mysymlog': + ticks, labels, nodes = MySymLogPlotting.get_mysymlog_var_ticks(vars[i]) + data_to_plot = MySymLogPlotting.symlog_var(vars[i]) + mynorm = MyThreeNodesNorm(nodes) + im = axes[i].imshow(data_to_plot, extent=extents[i], origin='lower', norm=mynorm, cmap=cmaps[i]) + divider = make_axes_locatable(axes[i]) + cax = divider.append_axes('right', size='5%', pad=0.05) + cbar = fig.colorbar(im, cax=cax, orientation='vertical') + cbar.set_ticks(ticks) + cbar.ax.set_yticklabels(labels) + + else: + im = axes[i].imshow(vars[i], extent=extents[i], origin='lower', cmap=cmaps[i], norm=norms[i]) + # divider = make_axes_locatable(axes[i]) + # cax = divider.append_axes('right', size='5%', pad=0.05) + # fig.colorbar(im, cax=cax, orientation='vertical') + images.append(im) + + axes[i].set_xlabel(r'$x$', fontsize=10) + axes[i].set_ylabel(r'$y$', fontsize=10) + + axes[0].set_title(r'$\tilde{\Pi}$', fontsize=14) + axes[1].set_title(r'$\sqrt{\tilde{\pi}_{ab}\tilde{\pi}^{ab}}$', fontsize=14) + axes[2].set_title(r'$\sqrt{\tilde{q}_{a}\tilde{q}^{a}}$', fontsize=14) + + fig.tight_layout() + + + ########################################################### + # Adapt the following if you want to have a single colormap + ########################################################### + # SETTING UP A COMMON COLORMAP + # Finding the global min and max and setting the colormap to be based on these. + vmin = min(image.get_array().min() for image in images) + vmax = max(image.get_array().max() for image in images) + norm = colors.LogNorm(vmin=vmin, vmax=vmax) + for im in images: + im.set_norm(norm) + + fig.colorbar(images[0], ax=axes.ravel().tolist(), orientation='vertical', location='right', shrink=0.9) + + # Make images respond to changes in the norm of other images (e.g. via the + # "edit axis, curves and images parameters" GUI on Qt), but be careful not to + # recurse infinitely! + def update(changed_image): + for im in images: + if (changed_image.get_cmap() != im.get_cmap() + or changed_image.get_clim() != im.get_clim()): + im.set_cmap(changed_image.get_cmap()) + im.set_clim(changed_image.get_clim()) + + for im in images: + im.callbacks.connect('changed', update) + + format='png' + dpi=400 + filename = f"/Residuals." + format + plt.savefig(saving_directory + filename, format = format, dpi=dpi) + + + # # NOW SUMMARY PLOT FOR THE CLOSURE INGREDIENTS + + vars_strs = ['exp_tilde', 'shear_sq', 'Theta_sq'] + vars = [] + extents = [] + for var_str in vars_strs: + temp_var, temp_extent = visualizer.get_var_data(meso_model, var_str, time_meso, x_range, y_range) + if var_str != 'exp_tilde': + temp_var = np.sqrt(temp_var) + vars.append(temp_var) + extents.append(temp_extent) + + + + norms = ['symlog', 'log', 'log'] + cmaps = ['Spectral_r', 'plasma', 'plasma'] + + + plt.rc("font",family="serif") + plt.rc("mathtext",fontset="cm") + fig, axes = plt.subplots(1,3, squeeze=False, figsize=[12,4], sharey=True) + axes = axes.flatten() + + images = [] + + for i in range(len(axes)): + + if norms[i] == 'mysymlog': + ticks, labels, nodes = MySymLogPlotting.get_mysymlog_var_ticks(vars[i]) + data_to_plot = MySymLogPlotting.symlog_var(vars[i]) + mynorm = MyThreeNodesNorm(nodes) + im = axes[i].imshow(data_to_plot, extent=extents[i], origin='lower', norm=mynorm, cmap=cmaps[i]) + divider = make_axes_locatable(axes[i]) + cax = divider.append_axes('right', size='5%', pad=0.05) + cbar = fig.colorbar(im, cax=cax, orientation='vertical') + cbar.set_ticks(ticks) + cbar.ax.set_yticklabels(labels) + + else: + im = axes[i].imshow(vars[i], extent=extents[i], origin='lower', cmap=cmaps[i], norm=norms[i]) + divider = make_axes_locatable(axes[i]) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + images.append(im) + + axes[i].set_xlabel(r'$x$', fontsize=10) + axes[i].set_ylabel(r'$y$', fontsize=10) + + axes[0].set_title(r'$\tilde{\theta}$', fontsize=14) + axes[1].set_title(r'$\sqrt{\tilde{\sigma}_{ab}\tilde{\sigma}^{ab}}$', fontsize=14) + axes[2].set_title(r'$\sqrt{\tilde{\Theta}_{a}\tilde{\Theta}^{a}}$', fontsize=14) + + fig.tight_layout() + + format='png' + dpi=400 + filename = f"/Gradients." + format + plt.savefig(saving_directory + filename, format = format, dpi=dpi) + diff --git a/Proof_of_principle/filter_scripts/visualizing_micro.py b/Proof_of_principle/filter_scripts/visualizing_micro.py new file mode 100644 index 0000000..78dc253 --- /dev/null +++ b/Proof_of_principle/filter_scripts/visualizing_micro.py @@ -0,0 +1,102 @@ +import sys +# import os +sys.path.append('../../master_files/') +import configparser +import json +import pickle + +from FileReaders import * +from MicroModels import * +from Visualization import * + +if __name__ == '__main__': + + #################################################################################################### + # SCRIPT TO PLOT MICRO-MODEL QUANTITIES: THIS IS THE DATA DIRECTLY FROM SIMULATIONS + #################################################################################################### + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.") + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # LOADING MICRO DATA FROM HDF5 OR PICKLE + micro_from_hdf5 = False + + if micro_from_hdf5: + hdf5_directory = config['Directories']['hdf5_dir'] + print('=========================================================================') + print(f'Starting job on data from {hdf5_directory}') + print('=========================================================================\n\n') + filenames = hdf5_directory + '/' + snapshots_opts = json.loads(config['Micro_model_settings']['snapshots_opts']) + fewer_snaps_required = snapshots_opts['fewer_snaps_required'] + smaller_list = snapshots_opts['smaller_list'] + FileReader = METHOD_HDF5(filenames, fewer_snaps_required, smaller_list) + num_snaps = FileReader.num_files + micro_model = IdealHD_2D() + FileReader.read_in_data_HDF5_missing_xy(micro_model) + micro_model.setup_structures() + print('Finished reading micro data from hdf5, structures also set up.') + + else: + pickle_directory = config['Directories']['pickled_files_dir'] + micro_pickled_filename = config['Filenames']['micro_pickled_filename'] + MicroModelLoadFile = pickle_directory + micro_pickled_filename + print('=========================================================================') + print(f'Starting job on data from {MicroModelLoadFile}') + print('=========================================================================\n\n') + with open(MicroModelLoadFile, 'rb') as filehandle: + meso_model = pickle.load(filehandle) + micro_model = meso_model.micro_model + # micro_model = pickle.load(filehandle) + + + # PLOT SETTINGS + plot_ranges = json.loads(config['Plot_settings']['plot_ranges']) + x_range = plot_ranges['x_range'] + y_range = plot_ranges['y_range'] + + num_snaps = micro_model.domain_vars['nt'] + central_slice_num = int(num_snaps/2.) + plot_time = micro_model.domain_vars['t'][central_slice_num] + + saving_directory = config['Directories']['figures_dir'] + visualizer = Plotter_2D([11.97, 8.36]) + + # FINALLY, PLOTTING + # Plotting the baryon current + vars = ['BC', 'BC', 'BC', 'W', 'vx', 'vy'] + norms= ['log', 'symlog', 'symlog', 'log', 'symlog', 'symlog'] + cmaps = [None, 'Spectral_r', 'Spectral_r', None, 'Spectral_r', 'Spectral_r'] + components = [(0,), (1,), (2,), (), (), ()] + fig=visualizer.plot_vars(micro_model, vars, plot_time, x_range, y_range, components_indices = components, norms=norms, cmaps=cmaps) + fig.tight_layout() + time_for_filename = str(round(plot_time,2)) + filename = "/micro_T_" + time_for_filename + "_BC.pdf" + plt.savefig(saving_directory + filename, format = "pdf") + + # Plotting the stress energy tensor + vars = ['SET', 'SET', 'SET', 'SET', 'SET', 'SET'] + components = [(0,0), (0,1), (0,2), (1,1), (1,2), (2,2)] + norms= ['log', 'symlog', 'symlog', 'log', 'symlog', 'log'] + cmaps = [None, 'Spectral_r', 'Spectral_r', None, 'Spectral_r', None] + fig=visualizer.plot_vars(micro_model, vars, plot_time, x_range, y_range, components_indices = components, norms=norms, cmaps=cmaps) + fig.tight_layout() + time_for_filename = str(round(plot_time,2)) + filename = "/micro_T_" + time_for_filename + "_SET.pdf" + plt.savefig(saving_directory + filename, format = "pdf") + + # plotting primitive quantities + vars = ['W', 'vx', 'vy', 'n', 'p', 'e'] + norms= ['log', 'symlog', 'symlog', 'log', 'log', 'log'] + cmaps = [None, 'Spectral_r', 'Spectral_r', None, None, None] + fig=visualizer.plot_vars(micro_model, vars, plot_time, x_range, y_range, norms=norms, cmaps=cmaps) + fig.tight_layout() + time_for_filename = str(round(plot_time,2)) + filename = "/micro_T_" + time_for_filename + "_prims.pdf" + plt.savefig(saving_directory + filename, format = "pdf") + diff --git a/Proof_of_principle/filter_scripts/visualizing_obs.py b/Proof_of_principle/filter_scripts/visualizing_obs.py new file mode 100644 index 0000000..1da6c46 --- /dev/null +++ b/Proof_of_principle/filter_scripts/visualizing_obs.py @@ -0,0 +1,177 @@ +#!/bin/bash + +import sys +# import os +sys.path.append('../../master_files/') +import pickle +import configparser +import json +from matplotlib.ticker import LogLocator + +from FileReaders import * +from MicroModels import * +from MesoModels import * +from Visualization import * + +if __name__ == '__main__': + + #################################################################################################### + # SCRIPT TO VISUALIZE THE FILTERING OBSERVERS: COMPARE WITH EITHER MICRO OR FAVRE VEL + #################################################################################################### + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.") + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # LOADING MESO AND MICRO MODELS + pickle_directory = config['Directories']['pickled_files_dir'] + meso_pickled_filename = config['Filenames']['meso_pickled_filename'] + MesoModelLoadFile = pickle_directory + meso_pickled_filename + + print('=========================================================================') + print(f'Starting job on data from {MesoModelLoadFile}') + print('=========================================================================\n\n') + + with open(MesoModelLoadFile, 'rb') as filehandle: + meso_model = pickle.load(filehandle) + micro_model = meso_model.micro_model + + print(f'Finished reading data from {MesoModelLoadFile}') + + # CHECKING WE ARE COMPARING DATA FROM THE SAME TIME-SLICE + num_snaps = micro_model.domain_vars['nt'] + central_slice_num = int(num_snaps/2.) + time_micro = micro_model.domain_vars['t'][central_slice_num] + meso_grid_info = json.loads(config['Meso_model_settings']['meso_grid']) + num_slices_meso = meso_grid_info['num_T_slices'] + time_meso = meso_model.domain_vars['T'][int((num_slices_meso-1)/2)] + if time_meso != time_micro: + print("Slices of meso and micro model do not coincide. Careful!") + else: + print("Comparing data at same time-slice, hurray!") + + + # PLOT SETTINGS + saving_directory = config['Directories']['figures_dir'] + plot_ranges = json.loads(config['Plot_settings']['plot_ranges']) + x_range = plot_ranges['x_range'] + y_range = plot_ranges['y_range'] + diff_plot_settings = json.loads(config['Plot_settings']['diff_plot_settings']) + diff_method = diff_plot_settings['method'] + interp_dims = diff_plot_settings['interp_dims'] + visualizer = Plotter_2D([10, 3]) + + # label_2_update = {'U' : r'$U^a$'} + # meso_model.upgrade_labels_dict(label_2_update) + + # FINALLY, PLOTTING + # models = [micro_model, meso_model] + # vars = [['bar_vel', 'bar_vel', 'bar_vel'],['u_tilde', 'u_tilde', 'u_tilde']] + # components_indices= [[(0,),(1,),(2,)], [(0,), (1,), (2,)]] + # norms = [['log','mysymlog','mysymlog'],['log','mysymlog','mysymlog'],['log','log','log']] + # cmaps = [['plasma','seismic','seismic'],['plasma','seismic','seismic'], ['plasma','plasma','plasma']] + # fig = visualizer.plot_vars_models_comparison(models, vars, time_meso, x_range, y_range, components_indices=components_indices, method=diff_method, + # interp_dims=interp_dims, diff_plot=False, rel_diff=True, norms=norms, cmaps=cmaps) + # fig.tight_layout() + # filename="/favreVSmicro.pdf" + # plt.savefig(saving_directory + filename, format="pdf") + # print('Finished plotting the favre velocities') + + + models = [micro_model, meso_model] + # vars = [['bar_vel', 'bar_vel', 'bar_vel'],['U', 'U', 'U']] + # norms = [['log','mysymlog','mysymlog'],['log','mysymlog','mysymlog'],['log','log','log']] + # cmaps = [['plasma','seismic','seismic'], ['plasma','seismic','seismic'], ['plasma','plasma','plasma']] + + # vars = [['bar_vel'], ['U']] + # norms = [['log'], ['log'], ['log']] + # cmaps = [['plasma'],['plasma'], ['plasma']] + # components_indices= [[(0,)], [(0,)]] + # fig = visualizer.plot_vars_models_comparison(models, vars, time_meso, x_range, y_range, components_indices=components_indices, method=diff_method, + # interp_dims=interp_dims, diff_plot=False, rel_diff=True, norms=norms, cmaps=cmaps) + + # axes = np.array(fig.axes) + # axes = axes.flatten() + # axes[0].set_title(micro_model.labels_var_dict['bar_vel'] + r"$,$ $a=0$") + # axes[1].set_title(meso_model.labels_var_dict['U'] + r"$,$ $a=0$") + # axes[2].set_title(r"$Relative$ $difference$") + + comp = (1,) + bar_vel_data, extent_micro = visualizer.get_var_data(micro_model, 'bar_vel', time_meso, x_range, y_range, comp) + obs_data, extent_meso = visualizer.get_var_data(meso_model, 'U', time_meso, x_range, y_range, comp) + ar_mean = (np.abs(bar_vel_data) + np.abs(obs_data))/2 + rel_diff = np.abs(bar_vel_data - obs_data)/ar_mean + + + fig = plt.figure(figsize=[13,4]) + ax1 = fig.add_subplot(1, 3, 1) + ax2 = fig.add_subplot(1, 3, 2) + ax3 = fig.add_subplot(1, 3, 3, sharey=ax2) + plt.setp(ax3.get_yticklabels(), visible=False) + axes = [ax1, ax2, ax3] + axesRight = [ax2, ax3] + + + im = ax1.imshow(rel_diff, extent=extent_meso, origin='lower', cmap = 'plasma', norm='log') + divider = make_axes_locatable(ax1) + cax = divider.append_axes('right', size='5%', pad=0.05) + cbar = fig.colorbar(im, cax=cax, orientation='vertical') + cbar.ax.minorticks_on() + + images = [] + im = ax2.imshow(bar_vel_data, extent=extent_micro, origin='lower', cmap='plasma') #, norm='log') + images.append(im) + im = ax3.imshow(obs_data, extent=extent_meso, origin='lower', cmap='plasma') #, norm='log') + images.append(im) + + + for i in range(len(axes)): + axes[i].set_xlabel(r'$x$') + axes[i].set_ylabel(r'$y$') + + title = r'$Rel.$ $difference$' + ax1.set_title(title) + + title = micro_model.labels_var_dict['bar_vel'] + title += r'$,$ $a=0$' + ax2.set_title(title) + + title = meso_model.labels_var_dict['U'] + title += r'$,$ $a=0$' + ax3.set_title(title) + + # fig.tight_layout() + divider = make_axes_locatable(ax2) + cax = divider.append_axes('right', size='5%', pad=0.05) + cax.set_axis_off() + + divider = make_axes_locatable(ax3) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + # fig.colorbar(images[0], ax=axesRight, orientation='vertical', location='right', shrink=.8, pad=0.025 ) + # Make images respond to changes in the norm of other images (e.g. via the + # "edit axis, curves and images parameters" GUI on Qt), but be careful not to + # recurse infinitely! + def update(changed_image): + for im in images: + if (changed_image.get_cmap() != im.get_cmap() + or changed_image.get_clim() != im.get_clim()): + im.set_cmap(changed_image.get_cmap()) + im.set_clim(changed_image.get_clim()) + + for im in images: + im.callbacks.connect('changed', update) + + + fig.tight_layout() + filename=f"/ObsVSmicro_{comp[0]}." + format = 'png' + dpi=400 + plt.savefig(saving_directory + filename + format, format=format, dpi=dpi) + print('Finished plotting the observers') + diff --git a/Proof_of_principle/filter_scripts/visualizing_residuals.py b/Proof_of_principle/filter_scripts/visualizing_residuals.py new file mode 100644 index 0000000..c0a66bd --- /dev/null +++ b/Proof_of_principle/filter_scripts/visualizing_residuals.py @@ -0,0 +1,253 @@ +import sys +# import os +sys.path.append('../../master_files/') +import pickle +import configparser +import json +import time +import math + +from FileReaders import * +from MicroModels import * +from MesoModels import * +from Visualization import * +from Analysis import * + +if __name__ == '__main__': + + # # ########################################################################################## + # # SCRIPT TO VISUALIZE THE RESIDUALS: PLOT THEM AGAINST THE CLOSURE INGREDIENTS AND THE + # # WEIGHING FUNCTIONS THAT CAN BE CONSIDERED FOR CALIBRATING THE MODEL + # ############################################################################################ + + + # READING SIMULATION SETTINGS FROM CONFIG FILE + if len(sys.argv) == 1: + print(f"You must pass the configuration file for the simulations.") + raise Exception() + + config = configparser.ConfigParser() + config.read(sys.argv[1]) + + # LOADING MESO AND MICRO MODELS + pickle_directory = config['Directories']['pickled_files_dir'] + meso_pickled_filename = config['Filenames']['meso_pickled_filename'] + MesoModelLoadFile = pickle_directory + meso_pickled_filename + + print('================================================') + print(f'Starting job on data from {MesoModelLoadFile}') + print('================================================\n\n') + + with open(MesoModelLoadFile, 'rb') as filehandle: + meso_model = pickle.load(filehandle) + + statistical_tool = CoefficientsAnalysis() + correlation_ranges = json.loads(config['Plot_settings']['plot_ranges']) + x_range = correlation_ranges['x_range'] + y_range = correlation_ranges['y_range'] + meso_grid_info = json.loads(config['Meso_model_settings']['meso_grid']) + num_slices_meso = meso_grid_info['num_T_slices'] + time_of_central_slice = meso_model.domain_vars['T'][int((num_slices_meso-1)/2)] + ranges = [[time_of_central_slice, time_of_central_slice], x_range, y_range] + model_points = meso_model.domain_vars['Points'] + saving_directory = config['Directories']['figures_dir'] + + # print('Producing correlation plot for pi_res') + # x = meso_model.meso_vars['shear_sq'] + # y = meso_model.meso_vars['pi_res_sq'] + # data = [x,y] + # data = statistical_tool.trim_dataset(data, ranges, model_points) + # preprocess_data = {"value_ranges": [[None, None], [None, None]], "log_abs": [1, 1]} + # data = statistical_tool.preprocess_data(data, preprocess_data) + # data = statistical_tool.extract_randomly(data, 30000) + # x, y = data[0], data[1] + # xlabel = r'$\log(\tilde{\sigma}_{ab}\tilde{\sigma}^{ab})$' + # ylabel = r'$\log(\tilde{\pi}_{ab}\tilde{\pi}^{ab})$' + # g2=statistical_tool.visualize_correlation(x, y, xlabel=xlabel, ylabel=ylabel) + # saving_directory = config['Directories']['figures_dir'] + # filename = '/pi_aniso_correlation.pdf' + # plt.savefig(saving_directory + filename, format='pdf') + # print(f'Finished correlation plot for pi_res, saved as {filename}\n\n') + + + # print('Producing correlation plot for q_res') + # x = meso_model.meso_vars['q_res_sq'] + # y = meso_model.meso_vars['Theta_sq'] + # data = [x,y] + # data = statistical_tool.trim_dataset(data, ranges, model_points) + # preprocess_data = {"value_ranges": [[None, None], [None, None]], "log_abs": [1, 1]} + # data = statistical_tool.preprocess_data(data, preprocess_data) + # data = statistical_tool.extract_randomly(data, 30000) + # x, y = data[0], data[1] + # xlabel = r'$\log(\tilde{q}_a\tilde{q}^a)$' + # ylabel = r'$\log(\tilde{\Theta}_a \tilde{\Theta}^a)$' + # model_points = meso_model.domain_vars['Points'] + # g2=statistical_tool.visualize_correlation(x, y, xlabel=xlabel, ylabel=ylabel) + # saving_directory = config['Directories']['figures_dir'] + # filename = '/q_res_correlation.pdf' + # plt.savefig(saving_directory + filename, format='pdf') + # print(f'Finished correlation plot for q_res, saved as {filename}\n\n') + + + # print('Producing correlation plot for Pi_res') + # x = np.power(meso_model.meso_vars['Pi_res'], 2) + # y = np.power(meso_model.meso_vars['exp_tilde'], 2) + # data = [x,y] + # data = statistical_tool.trim_dataset(data, ranges, model_points) + # preprocess_data = {"value_ranges": [[None, None], [None, None]], "log_abs": [1, 1]} + # data = statistical_tool.preprocess_data(data, preprocess_data) + # data = statistical_tool.extract_randomly(data, 30000) + # x, y = data[0], data[1] + # xlabel = r'$\log(\tilde{\Pi}^2)$' + # ylabel = r'$\log(\tilde{\theta}^2)$' + # model_points = meso_model.domain_vars['Points'] + # g2=statistical_tool.visualize_correlation(x, y, xlabel=xlabel, ylabel=ylabel) + # saving_directory = config['Directories']['figures_dir'] + # filename = '/Pi_res_correlation.pdf' + # plt.savefig(saving_directory + filename, format='pdf') + # print(f'Finished correlation plot for Pi_res, saved as {filename}\n\n') + + + # ############################################################# + # PLOTTING RESIDUALS VS CORRESPONDING CLOSURE INGREDIENTS + # ############################################################# + + meso_grid_info = json.loads(config['Meso_model_settings']['meso_grid']) + num_slices_meso = meso_grid_info['num_T_slices'] + time_meso = meso_model.domain_vars['T'][int((num_slices_meso-1)/2)] + visualizer = Plotter_2D() + + vars_strs = ['zeta', 'exp_tilde', 'Pi_res'] + norms = ['mysymlog', 'symlog', 'log'] + cmaps = ['Spectral', 'Spectral', 'plasma'] + fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, norms=norms, cmaps=cmaps) + fig.tight_layout() + time_for_filename = str(round(time_meso,2)) + filename = "/Bulk_viscosity.png" + plt.savefig(saving_directory + filename, format = 'png', dpi=300) + print('Finished bulk viscosity') + + vars_strs = ['eta', 'shear_sq', 'pi_res_sq'] + norms = ['mysymlog', 'log', 'log'] + cmaps = ['Spectral', 'plasma', 'plasma'] + fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, norms=norms, cmaps=cmaps) + fig.tight_layout() + time_for_filename = str(round(time_meso,2)) + filename = "/Shear_viscosity.png" + plt.savefig(saving_directory + filename, format = 'png', dpi=300) + print('Finished shear viscosity') + + vars_strs = ['kappa', 'Theta_sq', 'q_res_sq'] + norms = ['mysymlog', 'log', 'log'] + cmaps = ['Spectral', 'plasma', 'plasma'] + fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, norms=norms, cmaps=cmaps) + fig.tight_layout() + time_for_filename = str(round(time_meso,2)) + filename = "/Heat_conductivity.png" + plt.savefig(saving_directory + filename, format = 'png', dpi=300) + print('Finished heat conductivity') + + + # det_s = meso_model.meso_vars['det_shear'] + # vars_strs = ['eta', 'det_shear'] + # norms = ['mysymlog', 'mysymlog'] + # cmaps = ['seismic', 'seismic'] + # fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, norms=norms, cmaps=cmaps) + # fig.tight_layout() + # time_for_filename = str(round(time_meso,2)) + # filename = "/eta_vs_newdet.png" + # plt.savefig(saving_directory + filename, format = 'png', dpi = 400) + # print(f'Finished figure {filename}') + + + # ############################################################ + # # PLOTTING Q1 AND Q2 AGAINST THE RESIDUALS SQUARED + # ############################################################ + + # meso_grid_info = json.loads(config['Meso_model_settings']['meso_grid']) + # num_slices_meso = meso_grid_info['num_T_slices'] + # time_meso = meso_model.domain_vars['T'][int((num_slices_meso-1)/2)] + # visualizer = Plotter_2D() + + # vars_strs = ['Pi_res_sq', 'Q1', 'Q2'] + # norms = ['log', 'mysymlog', 'log'] + # cmaps = ['plasma', 'coolwarm', 'plasma'] + # fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, norms=norms, cmaps=cmaps) + # fig.tight_layout() + # filename = "/Pi_res_Q.pdf" + # plt.savefig(saving_directory + filename, format = 'pdf') + # print('Finished Pi_res') + + # vars_strs = ['pi_res_sq', 'Q1', 'Q2'] + # norms = ['log', 'mysymlog', 'log'] + # cmaps = ['plasma', 'coolwarm', 'plasma'] + # fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, norms=norms, cmaps=cmaps) + # fig.tight_layout() + # filename = "/an_pi_res_Q.pdf" + # plt.savefig(saving_directory + filename, format = 'pdf') + # print('Finished pi_res') + + # vars_strs = ['q_res_sq', 'Q1', 'Q2'] + # norms = ['log', 'mysymlog', 'log'] + # cmaps = ['plasma', 'coolwarm', 'plasma'] + # fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, norms=norms, cmaps=cmaps) + # fig.tight_layout() + # filename = "/q_res_Q.pdf" + # plt.savefig(saving_directory + filename, format = 'pdf') + # print('Finished q_res') + + # ############################################################ + # # PLOTTING WEIGHING FUNCTIONS + # ############################################################ + + # meso_grid_info = json.loads(config['Meso_model_settings']['meso_grid']) + # num_slices_meso = meso_grid_info['num_T_slices'] + # time_meso = meso_model.domain_vars['T'][int((num_slices_meso-1)/2)] + # visualizer = Plotter_2D() + + # d = {'Q1' : r'$\tilde{\sigma}_{ab}\tilde{\sigma}^{ab} - \tilde{\omega}_{ab}\tilde{\omega}^{ab}$'} + # meso_model.update_labels_dict(d) + # d = {'Q2' : r'$\tilde{\sigma}_{ab}\tilde{\sigma}^{ab}/\tilde{\omega}_{ab}\tilde{\omega}^{ab}$'} + # meso_model.update_labels_dict(d) + # d = {'weights' : r'$w$'} + # meso_model.update_labels_dict(d) + + # print('Building skew weights based on Q1') + # meso_model.weights_Q1_skew() + # print('Finished re-building the weights\n') + + # vars_strs = ['weights', 'Q1'] + # norms = [None, 'mysymlog'] + # cmaps = ['plasma', 'coolwarm'] + # fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, norms=norms, cmaps=cmaps) + # fig.tight_layout() + # filename = "/Q1_skew.pdf" + # plt.savefig(saving_directory + filename, format = 'pdf') + # print('Finished Q1weights_plot\n') + + # print('Building non-negative weights based on Q1') + # meso_model.weights_Q1_non_neg() + # print('Finished re-building the weights\n') + + # vars_strs = ['weights', 'Q1'] + # norms = [None, 'mysymlog'] + # cmaps = ['plasma', 'coolwarm'] + # fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, norms=norms, cmaps=cmaps) + # fig.tight_layout() + # filename = "/Q1_non_neg.pdf" + # plt.savefig(saving_directory + filename, format = 'pdf') + # print('Finished Q1weights_plot') + + + # print('Building weights based on Q2') + # meso_model.weights_Q2() + # print('Finished re-building the weights\n') + + # vars_strs = ['weights', 'Q2'] + # norms = [None, 'log'] + # cmaps = ['plasma', 'plasma'] + # fig = visualizer.plot_vars(meso_model, vars_strs, time_meso, x_range, y_range, norms=norms, cmaps=cmaps) + # fig.tight_layout() + # filename = "/Q2_weights.pdf" + # plt.savefig(saving_directory + filename, format = 'pdf') + # print('Finished Q1weights_plot\n') \ No newline at end of file diff --git a/Visualization.py b/Visualization.py deleted file mode 100644 index e0f8583..0000000 --- a/Visualization.py +++ /dev/null @@ -1,260 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Jun 5 03:14:43 2023 - -@author: marcu -""" - - -import matplotlib.pyplot as plt -import numpy as np -import h5py -from mpl_toolkits.axes_grid1 import make_axes_locatable -from scipy.interpolate import interpn -from system.BaseFunctionality import * - -class Plotter_2D(object): - - def __init__(self): - """ - Blank as yet. - """ - self.subplots_dims = {1 : (1,1), - 2 : (1,2), - 3 : (1,3), - 4 : (2,2), - 5 : (2,3), - 6 : (2,3)} - - - def get_var_data(self, model, var_str, t, x_range, y_range, interp_dims, method, component_indices): - """ - Retrieves the required data from model to plot a variable defined by - var_str over coordinates t, x_range, y_range, either from the model's - raw data or by interpolating between the model's raw data over the coords. - - Parameters - ---------- - model : Micro or Meso Model - var_str : str - Must match a variable of the model. - t : float - time coordinate (defines the foliation). - x_range : list of 2 floats: x_start and x_end - defines range of x coordinates within foliation. - y_range : list of 2 floats: y_start and y_end - defines range of y coordinates within foliation. - interp_dims : tuple of integers - defines the number of points to interpolate at in x and y directions. - method : str - currently either raw_data or interpolate. - component_indices : tuple - the indices of the component to pick out if the variable is a vector/tensor. - - Returns - ------- - data_to_plot : numpy array of floats - the (2D) data to be plotted by plt.imshow(). - points : numpy array of floats - the coordinates of the data-points. - - """ - if var_str in model.get_all_var_strs(): - - if method == 'interpolate': - - nx, ny = interp_dims[:] - xs, ys = np.linspace(x_range[0],x_range[1],nx), np.linspace(y_range[0],y_range[1],ny) - points = [t,xs,ys] - data_to_plot = np.zeros((nx,ny)) - - for i in range(nx): - for j in range(ny): - point = [t,xs[i],ys[j]] - data_to_plot[i,j] = interpn(model.domain_vars['points'],\ - model.vars[var_str], point,\ - method = model.interp_method)[0][component_indices] - elif method == 'raw_data': - - start_indices = Base.find_nearest_cell([t, x_range[0], y_range[0]], model.domain_vars['points']) - end_indices = Base.find_nearest_cell([t, x_range[1], y_range[1]], model.domain_vars['points']) - - h = start_indices[0] - i_s, i_f = start_indices[1], end_indices[1] - j_s, j_f = start_indices[2], end_indices[2] - - points = [model.domain_vars['points'][0][h],\ - model.domain_vars['points'][1][i_s:i_f+1],\ - model.domain_vars['points'][2][j_s:j_f+1]] - - data_to_plot = model.vars[var_str][h:h+1, i_s:i_f+1, j_s:j_f+1][0]#[:, :, component_indices] - # print(data_to_plot) - if component_indices: - for component_index in component_indices: - data_to_plot = data_to_plot[:, :, component_index] - - - - else: - print('Data method is not a valid choice! Must be interpolate or raw_data.') - - else: - print(f'{var_str} is not a plottable variable of the model!') - - return data_to_plot, points - - - def plot_vars(self, model, var_strs, t, x_range, y_range, interp_dims=(), \ - method='interpolate', components_indices=None): - """ - Plot variable(s) from model, defined by var_strs, over coordinates - t, x_range, y_range. Either from the model's raw data or by interpolating - between the model's raw data over the coords. - - Parameters - ---------- - model : Micro or Meso Model - var_strs : list of str - Must match entries in the models' 'vars' dictionary. - t : float - time coordinate (defines the foliation). - x_range : list of 2 floats: x_start and x_end - defines range of x coordinates within foliation. - y_range : list of 2 floats: y_start and y_end - defines range of y coordinates within foliation. - interp_dims : tuple of integers - defines the number of points to interpolate at in x and y directions. - method : str - currently either raw_data or interpolate. - components_indices : list of tuple(s) - the indices of the components to pick out if the variables are vectors/tensors. - Can be omitted is all variables are scalars, otherwise must be a list - of tuples matching the length of var_strs that corresponds with each - variable in the list. - - Output - ------- - Plots the (2D) data using imshow. Note that the plotting data's coordinates - may not perfectly match the input coordinates if method=raw_data as - nearest-cell data is used where the input coordinates do not coincide - with the model's raw data coordinates. - - """ - n_plots = len(var_strs) - n_rows, n_cols = self.subplots_dims[n_plots] - fig, axes = plt.subplots(n_rows,n_cols, figsize=(16,16)) # figsize should be adaptive.. - if n_plots == 1: - axes = [axes] - else: - axes = axes.flatten() - - for var_str, component_indices, ax in zip(var_strs, components_indices, axes): - data_to_plot, points = self.get_var_data(model, var_str, t, x_range, y_range, interp_dims, method, component_indices) - extent = [points[2][0],points[2][-1],points[1][0],points[1][-1]] - im = ax.imshow(data_to_plot, extent=extent) - divider = make_axes_locatable(ax) - cax = divider.append_axes('right', size='5%', pad=0.05) - fig.colorbar(im, cax=cax, orientation='vertical') - ax.set_title(model.get_model_name()) - # fig.suptitle(model.get_model_name(), fontsize=16) - ax.set_title(var_str) - ax.set_xlabel(r'$y$') - ax.set_ylabel(r'$x$') - - fig.tight_layout() - plt.show() - - - def plot_var_model_comparison(self, models, var_str, t, x_range, y_range, \ - interp_dims=(), method='interpolate', component_indices=(), diff_plot=True): - """ - Plot a variable from a number of models. If 2 models are given, a third - plot of the difference will be automatically plotted, too. If 'raw_data' - is chosen as the method, will check to see if the data points in the model - lie at the same coordinates, which they must. - - Parameters - ---------- - models : Micro or Meso Models - var_sts : str - Must match entries in the models' 'vars' dictionary. - t : float - time coordinate (defines the foliation). - x_range : list of 2 floats: x_start and x_end - defines range of x coordinates within foliation. - y_range : list of 2 floats: y_start and y_end - defines range of y coordinates within foliation. - interp_dims : tuple of integers - defines the number of points to interpolate at in x and y directions. - method : str - currently either raw_data or interpolate. - component_indices : tuple - the indices of the component to pick out if the variable is a vector/tensor. - - Output - ------- - Plots the (2D) data using imshow. Note that the plotting data's coordinates - may not perfectly match the input coordinates if method=raw_data as - nearest-cell data is used where the input coordinates do not coincide - with the model's raw data coordinates. - - """ - n_cols = len(models) - if not n_cols == 2: - diff_plot = False # Only plot difference of 2 models... - n_rows = 1 - if diff_plot: - n_cols+=1 - fig, axes = plt.subplots(n_rows,n_cols,sharex='row',sharey='col',figsize=(16,16)) - - for model, ax in zip(models, axes.flatten()): - data_to_plot, points = self.get_var_data(model, var_str, t, x_range, y_range, interp_dims, method, component_indices) - extent = [points[2][0],points[2][-1],points[1][0],points[1][-1]] - im = ax.imshow(data_to_plot, extent=extent) - divider = make_axes_locatable(ax) - cax = divider.append_axes('right', size='5%', pad=0.05) - fig.colorbar(im, cax=cax, orientation='vertical') - ax.set_title(model.get_model_name()) - ax.set_xlabel(r'$y$') - ax.set_ylabel(r'$x$') - - if diff_plot: - ax = axes.flatten()[-1] - data_to_plot1, points1 = self.get_var_data(models[0], var_str, t, x_range, y_range, interp_dims, method, component_indices) - data_to_plot2, points2 = self.get_var_data(models[1], var_str, t, x_range, y_range, interp_dims, method, component_indices) - # if len(points1) != len(points2): - # diff_plot = False - # pass - # for t_points1, t_points2 in zip(points1, points2): - # print(t_points1, t_points2) - # if len(t_points1) != t_points2.shape: - # diff_plot = False - # continue - # if not np.allclose(t_points1, t_points2): - # diff_plot = False - # if diff_plot: - try: - extent = [points1[2][0],points1[2][-1],points1[1][0],points1[1][-1]] - im = ax.imshow(data_to_plot1 - data_to_plot2, extent=extent) - divider = make_axes_locatable(ax) - cax = divider.append_axes('right', size='5%', pad=0.05) - fig.colorbar(im, cax=cax, orientation='vertical') - ax.set_title(model.get_model_name()) - ax.set_title('Model Difference') - ax.set_xlabel(r'$y$') - ax.set_ylabel(r'$x$') - except(ValueError): - print(f"Cannot plot the difference between {var_str} in the two " - "models. Likely due to the data coordinates not coinciding.") - fig.tight_layout() - plt.show() - - - - - - - - - - diff --git a/master_files/Analysis.py b/master_files/Analysis.py new file mode 100644 index 0000000..30d67cf --- /dev/null +++ b/master_files/Analysis.py @@ -0,0 +1,1251 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Jan 24 18:02:05 2023 + +@author: marcu +""" + +# USE !SCIKIT LEARN INSTEAD? IT'S THE PACKAGE FOR MACHINE LEARNING SO! + +import matplotlib.pyplot as plt +import numpy as np +import numpy.ma as ma +import pandas as pd +import h5py +import pickle +import seaborn as sns +import warnings +import random +from sklearn.linear_model import LinearRegression +from sklearn.decomposition import PCA +from sklearn.model_selection import train_test_split +# from scipy import stats +from scipy.stats import gaussian_kde, wasserstein_distance, pearsonr +# import statsmodels.api as sm + +from system.BaseFunctionality import * +from MicroModels import * +from FileReaders import * +from Filters import * +from Visualization import * +from MesoModels import * + + + +class CoefficientsAnalysis(object): + """ + Class containing a number of methods for performing statistical analysis on gridded data. + Methods include: regression, visualizing correlations, PCA routines + """ + def __init__(self): #, visualizer, spatial_dims): + """ + Nothing as yet... + + Returns + ------- + Also nothing... + + """ + # Change to latex font + plt.rc("font",family="serif") + plt.rc("mathtext",fontset="cm") + + def trim_data(self, data, ranges, model_points): + """ + Takes input gridded data 'data' (grid points given by input 'model_points') + and trim this to lie within ranges. + + Parameters: + ----------- + data: ndarray + gridded data, shape must be compatible with model_points + + ranges: list of lists of 2 floats + the min and max in each direction + + model_points: list of list of floats + + Returns: + -------- + data trimmed to within ranges + """ + if not (len(ranges)==len(model_points) and len(ranges) == len(data.shape)): + print('Check: i) data incompatible with ranges or ii) ranges incompatible with model_points. Skipping!') + return data + else: + mins = [i[0] for i in ranges] + maxs = [i[1] for i in ranges] + start_indices = Base.find_nearest_cell(mins, model_points) + end_indices = Base.find_nearest_cell(maxs, model_points) + + IdxsToRemove = [] + num_points = [len(model_points[i]) for i in range(len(model_points))] + for i in range(len(ranges)): + IdxsToRemove.append([ j for j in range(num_points[i]) if j < start_indices[i] or j > end_indices[i]]) + + newdata = np.delete(data, IdxsToRemove[0], axis=0) + for i in range(1, len(ranges)): + newdata = np.delete(newdata, IdxsToRemove[i], axis=i) + + return newdata + + def trim_dataset(self, list_of_data, ranges, model_points): + """ + wrapper of trim data: check the shapes of the various arrays are compatible + Then trim each of them individually. + + Parameters: + ----------- + list_of_data: list of np.arrays (gridded data) + the dataset you want to trim + + ranges: list of lists + the min and max in each direction + + model_points: list of lists + the gridpoints, len of this must be compatible with the ranges + + Returns: + -------- + list of arrays trimmed within ranges + """ + print('Trimming the dataset') + + if len(ranges) != len(model_points): + print('Ranges incompatible with model_points. Exiting') + return None + + ref_shape = list_of_data[0].shape + new_data = [list_of_data[0]] + + for i in range(1, len(list_of_data)): + if list_of_data[i].shape == ref_shape: + new_data.append(list_of_data[i]) + else: + print(f'The {i}th array in the dataset is not compatible with the first: ignoring it.') + + if len(new_data) <=1: + print('No two variables in the dataset are compatible. Exiting.') + return None + else: + for i in range(len(new_data)): + new_data[i] = self.trim_data(new_data[i], ranges, model_points) + return new_data + + def get_pos_or_neg_mask(self, pos_or_neg, array): + """ + NOT USED ANYMORE!!!!!! + + Function that return the mask that would be applied to an array in order to select + positive or negative values + + Parameters: + ----------- + pos_or_neg: can be int (0 or 1) or bool True or False + + array: np.array + + Return: + ------- + mask: is True where values are masked! + + Notes: + ------ + To be combined with others before applying all together + """ + if pos_or_neg: + mask = ma.masked_where(array<0, array, copy=True).mask + else: + mask = ma.masked_where(array>0, array, copy=True).mask + + return mask + + def restrict_data_range_mask(self, var, min=None, max=None): + """ + Takes input var and make a copy of array masking those entries that are outside the (min, max) range + Return compressed masked array. + """ + if min is None: + min = np.amin(var) + if max is None: + max = np.amax(var) + + restricted_var = ma.masked_where(var < min, var, copy = True) + restricted_var = ma.masked_where(restricted_var > max, restricted_var, copy=True) + restricted_var = restricted_var.compressed() + + return restricted_var + + def get_mask_min_max(self, array, min=None, max=None): + """ + Given an input 'array' and a list of 'value_ranges' to be considered, return + mask that would have to be applied to array to mask the entries outside the given + range + + Parameters: + ----------- + min, max: floats + the ranges to be considered + + array: nd.array + + Returns: + -------- + Mask + """ + if min is None: + min = -np.inf + if max is None: + max = np.inf + + masked_array = ma.masked_where(arraymax, masked_array, copy=True) + masked_array = ma.masked_where(masked_array == 0, masked_array, copy=True) + + mask = masked_array.mask + + return mask + + def preprocess_data(self, list_of_arrays, preprocess_data, weights=None): + """ + Takes input list of arrays with dictionary on how to preprocess_data + In case data comes with weights, these can be passed to be pre-processed accordingly + + Parameters: + ---------- + list_of_arrays: + list with the combined data to be processed + + preprocess_data: dictionary + each value of the dictionary must be a list long as the arrays passed (checked for) + Expected keys are: + + 'value_ranges': value correspond to list of min, max to be considered + + 'log_abs' is 1 if you want to take the logarithm of the (absolute) data + + weights: np.array, default to None + the array with weights for each gridpoint. + + Return: + ------ + The processed data + """ + print('Pre-processing dataset') + # Checking data is compatible + ref_shape = list_of_arrays[0].shape + processed_list = [list_of_arrays[0]] + for i in range(1, len(list_of_arrays)): + if list_of_arrays[i].shape == ref_shape: + processed_list.append(list_of_arrays[i]) + else: + print(f'The {i}th array in the dataset is not compatible with the first: ignoring it.') + if len(processed_list) <=1: + print('No two variables in the dataset are compatible. Exiting.') + return None + + # Checking preprocess info are compatible with data + num_arrays = len(list_of_arrays) + condition = False + for key in preprocess_data: + if len(preprocess_data[key]) != num_arrays: + condition=True + if condition: + print('Preprocess_data dictionary is not compatible with list_of_arrays. Exiting') + if weights is not None: + return list_of_arrays, weights + else: + return list_of_arrays + + # USE THIS IF YOU WANNA REVERT TO PREVIOUS STUFF: pos_or_neg key in preprocess dictionary + # for i in range(len(list_of_arrays)): + # pos_or_neg = preprocess_data['pos_or_neg'][i] + # temp = self.get_pos_or_neg_mask(pos_or_neg, list_of_arrays[i]) + # masks.append(temp) + + # Combining the masks of the different arrays + if preprocess_data.__contains__('value_ranges'): + masks = [] + for i in range(len(list_of_arrays)): + min, max = preprocess_data['value_ranges'][i] + temp = self.get_mask_min_max(list_of_arrays[i], min, max) + masks.append(temp) + + tot_mask = masks[0] + for i in range(1,len(masks)): + tot_mask = np.logical_or(tot_mask, masks[i]) + + # Masking the full dataset with combined mask, then taking log + processed_list = [] + for i in range(len(list_of_arrays)): + temp = ma.masked_array(list_of_arrays[i], tot_mask) + processed_list.append(temp.compressed()) + + if weights is not None: + Weights = np.ma.masked_array(weights, tot_mask).compressed() + #when array is compressed, this is automatically flattened! + + if preprocess_data.__contains__('sqrt'): + for i in range(len(processed_list)): + if preprocess_data['sqrt'][i]: + processed_list[i] = np.sqrt(processed_list[i]) + + if preprocess_data.__contains__('log_abs'): + for i in range(len(processed_list)): + if preprocess_data['log_abs'][i]: + processed_list[i] = np.log10(np.abs(processed_list[i])) + + # removing corresponding entries from weights + if weights is not None: + return processed_list, Weights + else: + return processed_list + + def split_train_test(self, list_of_arrays, test_percentage=0.2): + """ + Given a list of arrays, check compatibility among them, then each is split into + training set and test set (corresponding indices are selected from each array). + + Parameters: + ----------- + list_of_arrays: list of np.arrays, their compatibility is checked + + test_percentage: float, default to 0.2 + + Returns: + -------- + training_arrays, test_arrays + + Notes: + ------ + List of arrays can contain weights as well. + Yhese are treated on the same footage as the rest of the arrays. + """ + print('Splitting dataset into training and test set') + # Checking data is compatible + ref_shape = list_of_arrays[0].shape + new_data = [list_of_arrays[0].flatten()] + for i in range(1,len(list_of_arrays)): + if list_of_arrays[i].shape == ref_shape: + new_data.append(list_of_arrays[i].flatten()) + else: + print(f'The {i}th array in the dataset is not compatible with the first: ignoring it.') + + + # Using sklearn to do the split quickly. + len_array = len(new_data[0]) + available_indices = list(np.arange(0, len_array, dtype='int')) + + train_idxs, test_idxs = train_test_split(available_indices, test_size=test_percentage, shuffle=True) + train_data = [new_data[i][train_idxs] for i in range(len(new_data))] + test_data = [new_data[i][test_idxs] for i in range(len(new_data))] + + for i in range(len(train_data)): + train_data[i] = np.array(train_data[i]) + test_data[i] = np.array(test_data[i]) + + return train_data, test_data + + def extract_randomly(self, list_of_data, num_extractions): + """ + Extract randomly from data. + + Parameters: + ----------- + list_of_data: list of np.arrays (flattened or not) + + + num_extractions: number of values to be extracted + + Returns: + -------- + list of arrays with values randomly extracted from input ones + """ + print('Extracting randomly from dataset') + + # Checking data is compatible + ref_shape = list_of_data[0].shape + new_data = [list_of_data[0].flatten()] + for i in range(1,len(list_of_data)): + if list_of_data[i].shape == ref_shape: + new_data.append(list_of_data[i].flatten()) + else: + print(f'The {i}th array in the dataset is not compatible with the first: ignoring it.') + + available_indices = list(np.arange(len(new_data[0]))) + selected_indices = random.sample(available_indices, num_extractions) + extracted_data = [] + for i in range(len(new_data)): + shortened_list = [] + for j in range(len(selected_indices)): + shortened_list.append(new_data[i][selected_indices[j]]) + shortened_array = np.array(shortened_list) + extracted_data.append(shortened_array) + + return extracted_data + + def centralize_dataset(self, list_of_arrays): + """ + """ + print('Centralizing the dataset') + # Checking data is compatible + ref_shape = list_of_arrays[0].shape + new_data = [list_of_arrays[0].flatten()] + for i in range(1,len(list_of_arrays)): + if list_of_arrays[i].shape == ref_shape: + new_data.append(list_of_arrays[i].flatten()) + else: + print(f'The {i}th array in the dataset is not compatible with the first: ignoring it.') + + means = [] + centralized_data = [] + for i in range(len(new_data)): + x = new_data[i] + x_mean = np.mean(x) + centralized_data.append(x - x_mean) + means.append(x_mean) + + return centralized_data, means + + def weighted_mean(self, data, weights): + """ + Return mean of data weighted wrt weights. + + Parameters: + ----------- + data: 1D array + weights: 1D array + + Returns: + -------- + Weighted mean + + Notes: + ------ + data and weights are assumed to be 1D arrays of same shape + """ + mean = 0. + tot_weights = 0. + + for i in range(len(data)): + mean += data[i] * weights[i] + tot_weights += weights[i] + + mean = mean/ tot_weights + return mean + + def weighted_covariance(self, x, y, weights): + """ + Return covariance of x vs y weighed wrt weights + + Parameters: + ----------- + x, y, weights: 1D arrays of same length + + Returns: + -------- + weighted covariance (float) + + Notes: + ------ + x, y, weights are assumed to be flattened and checked for compatibility already + + """ + x_mean = self.weighted_mean(x, weights) + y_mean = self.weighted_mean(y, weights) + + weighted_cov = 0. + tot_weights = 0. + for i in range(len(x)): + weighted_cov += weights[i] * (x[i]- x_mean) * (y[i] - y_mean) + tot_weights += weights[i] + + weighted_cov = weighted_cov / tot_weights + return weighted_cov + + def weigthed_pearson(self, x, y, weights=None): + """ + Return the weighted correlation coefficient + + Parameters: + ----------- + x, y , weights: nd.arrays + + Returns: + -------- + weighted correlation coefficient + """ + if x.shape != y.shape: + print('Arrays do not have the same shape, returning None\n') + return None + + if weights is not None: + if x.shape != weights.shape: + print('weights do not have the same shape as input arrays, setting these to 1 everywhere\n') + weights = np.ones(x.shape) + else: + print('No weigths passed, setting these to 1. everywhere') + weights = np.ones(x.shape) + + + X = x.flatten() + Y = y.flatten() + W = weights.flatten() + + corr_x = self.weighted_covariance(X, X, W) + corr_y = self.weighted_covariance(Y, Y, W) + corr_xy = self.weighted_covariance(X, Y, W) + + weighted_pearson = corr_xy / np.sqrt(corr_x * corr_y) + return weighted_pearson + + def scalar_regression(self, y, X, weights=None, add_intercept=False, centralize=False): + """ + Routine to perform ordinary or weighted (multivariate) regression on some gridded data. + + Parameters: + ----------- + y: ndarray of gridded scalar data + the "measurements" of the dependent quantity + + X: list of ndarrays of gridded data (treated as indep scalars) + the "data" for the regressors + + weights: ndarray of gridded weights + + add_intercept: bool + whether to extend the dataset to account for a constant offset in the + regression model. + + Returns: + -------- + list of (fitted parameters, std errors) + (std errors = parameter * std error of the corresponding regressor.) + + Notes: + ------ + Works in any dimensions + """ + + # CHECKING COMPATIBILITY OF PASSED DATA + dep_shape = np.shape(y) + n_reg = len(X) + for i in range(n_reg): + if np.shape(X[i]) != dep_shape: + print(f'The {i}-th regressor data is not aligned with dependent data, removing {i}-th regressor data.') + X.remove(X[i]) + + if len(X)==0: + print('None of the passed regressors is compatible with dep data. Exiting.') + return None + + if weights is not None: + if np.shape(y) != np.shape(weights): + print(f'The weights passed are not not aligned with data, setting these to 1') + Weights=np.ones(y.shape) + + + # FLATTENING (IF NOT DONE YET IN PRE-PROCESSING) + FITTING + Y = y.flatten() + if weights is not None: + Weights = weights.flatten() + + XX =[] + if add_intercept: + const = np.ones(Y.shape) + # XX.insert(0, const) + XX.append(const) + n_reg = n_reg + 1 + + for i in range(len(X)): + XX.append(X[i].flatten()) + + XX = np.einsum('ij->ji', XX) + + # # VERSION USING STATSMODELS + # if weights: + # model=sm.WLS(y, Xfl, W) + # result= model.fit() + # return result.params, result.bse + # else: + # model=sm.OLS(y, Xfl) + # result=model.fit() + # return result.params, result.bse + + # VERSION USING SKLEARN: + model=LinearRegression(fit_intercept=False) + if weights is not None: + # print('Fitting with weights') + model.fit(XX, Y, sample_weight=Weights) + else: + # print('Fitting without weights') + model.fit(XX,Y) + regress_coeff = list(model.coef_) + + # # COMPUTING STD ERRORS ON REGRESSED COEFFICIENTS + # n_data = len(Y) + # Y_hat = model.predict(XX) + # res = Y - Y_hat + # res_sum_of_sq = np.dot(res,res) + # sigma_res_sq = res_sum_of_sq / (n_data-n_reg) + # var_beta = np.linalg.inv(np.einsum('ji,jl->il',XX,XX)) * sigma_res_sq + # bse = [var_beta[i,i]**0.5 for i in range(n_reg)] + bse=None + + return regress_coeff, bse + + def tensor_components_regression(self, y, X, spatial_dims, ranges=None, model_points=None, components=None, weights=None, add_intercept=False): + """ + Wrapper of scalar_regression: if no components is passed, perform regression on each tensor component + independently, otherwise only on a subset of these. All the component-wise results are returned. + + Parameters: + ----------- + y: ndarray of gridded tensorial data + the "measurements" of the dependent quantity + + X: list of ndarrays of gridded data + the "data" for the regressors + + spatial_dims: int + + ranges: list of lists of 2 floats + mins and max in each direction + + model_points: list of lists containing the gridpoints in each direction + + components: list of tuples + the tensor components to be considered for regression + + add_intercept: bool + whether to extemd the dataset to account for a constant offset in the + regression model. + + Returns: + -------- + list of fitted parameters + list of std errors associated with the fitted parameters, + (i.e. parameter * std error of the corresponding regressor.) + + Notes: + ------ + Weights are taken as the same for each component here. + For future: do you need to change this? + """ + # CHECKING THE RANK OF DATA AND REGRESSORS ARE COMPATIBLE + l=[0 for i in range(spatial_dims+1)] + dep_shape = y[tuple(l)].shape + for i, x in enumerate(X): + if x[tuple(l)].shape != dep_shape: + print('The {}-th regressor shape {} is not compatible with dependent shape {}. Removing it!'.format(i, x[0,0,0].shape, dep_shape)) + X.remove(x) + if len(X)==0: + print('None of the passed regressors is compatible with dep data. Exiting.') + return None + + # PREPARING COMPONENTS TO LOOP OVER: user-provided or all? + if components: + for c in components: + if len(c) != len(dep_shape): + print(f'The components indices passed {c} are not compatible with data. Ignoring this and moving on!') + components.remove(c) + else: + components = [] + for i in np.ndindex(dep_shape): + components.append(i) + + # RESHAPING: now grid indices come last, needed for flattening! + tot_shape=y.shape + reshaping=[tot_shape[i] for i in range(spatial_dims+1)] + reshaping=tuple(list(dep_shape)+reshaping) + y = y.reshape(reshaping) + for i in range(len(X)): + X[i]= X[i].reshape(reshaping) + + # SCALAR REGRESSION ON EACH COMPONENT + results = [] + for c in components: + yc = y[c] + Xc = [ x[c] for x in X] + results.append(self.scalar_regression(yc, Xc, ranges, model_points, weights, add_intercept)) + return results + + def visualize_correlation(self, x, y, xlabel=None, ylabel=None, weights=None, hue_array=None, style_array=None, legend_dict=None, palette=None, markers=None): + """ + Method that returns an instance of JointGrid, with plotted the scatter plot and univariate distributions. + Possibility to cut data to lie within ranges. + + Parameters: + ----------- + x,y: nd.arrays + must be of the same shape + + xlabel, ylabel: strs + labels for the quantites plotted + + ranges: list of lists of 2 floats + mins and max in each direction + + model_points: list of lists containing the gridpoints in each direction + + + Returns: + -------- + Instance of JointGrid. Figure can then be accessed at the model level + for further adjustments. + """ + if x.shape != y.shape: + print('Cannot check correlation: data is misaligned!') + return None + + if weights is not None: + if weights.shape != x.shape: + print('Weights not compatible with data: ignoring them.') + Weights = None + else: + Weights = weights + else: + Weights = None + + X=x.flatten() + Y=y.flatten() + if Weights is not None: + Weights = Weights.flatten() + + if hue_array is not None: + hue_array = hue_array.flatten() + if style_array is not None: + style_array = style_array.flatten() + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message='is_categorical_dtype is deprecated') + warnings.filterwarnings('ignore', message='use_inf_as_na option is deprecated') + g=sns.JointGrid() + + sns.set_theme(style="dark") + sns.scatterplot(x=X, y=Y, s=4, color=".15", ax=g.ax_joint, hue=hue_array, style=style_array, palette=palette, markers=markers) + sns.histplot(x=X, y=Y, bins=50, ax=g.ax_joint, pthresh=.1, cmap="mako") + sns.kdeplot(x=X, y=Y, levels=5, ax=g.ax_joint, color="w", linewidths=1) + sns.histplot(x=X, ax=g.ax_marg_x, kde=True, color= 'black') + sns.histplot(y=Y, ax=g.ax_marg_y, kde=True, color= 'black') + g.set_axis_labels(xlabel=xlabel, ylabel=ylabel) + g.fig.tight_layout() + + if legend_dict is not None: + handles, labels = scatter.get_legend_handles_labels() + new_labels = [legend_dict[label] for label in labels] + scatter.legend(handles, new_labels) + + if Weights is not None: + rw = self.weigthed_pearson(X,Y,Weights) + g.ax_joint.annotate(r"$r_w = {:.2f}$".format(rw), xy=(.1, .9), xycoords=g.ax_joint.transAxes) + else: + r, _ = pearsonr(X,Y) + g.ax_joint.annotate(r"$r = {:.2f}$".format(r), xy=(.1, .9), xycoords=g.ax_joint.transAxes) + + + return g + + def visualize_many_correlations(self, data, labels, weights=None): + """ + Method that returns an instance of PairGrid of correlation plots for a list of vars. + Plotted is: + daigonal: univariate histogram (with kde superimposed) + upper triangle: scatteplots + lower triangle: bivariate kde(s) + + Possibility to cut data to lie within ranges. + + Parameters: + ----------- + data: list of nd.arrays + must be of the same shape + + labels: list of strs + labels for the quantites plotted + + ranges: list of lists of 2 floats + mins and max in each direction + + model_points: list of lists containing the gridpoints in each direction + + + Returns: + -------- + Instance of PairGrid. Figure can then be accessed at the model level + for further adjustments. + """ + ref_shape=data[0].shape + Idx_to_delete = [] + for i in range(1,len(data)): + if data[i].shape != ref_shape: + print(f'Cannot use {labels[i]} data: misaligned with first array. Removing it.') + Idx_to_delete.append(i) + + if len(Idx_to_delete) >= 1: + data=list(np.delete(data, Idx_to_delete, axis=0)) + + Data = [] + for i in range(len(data)): + Data.append(data[i].flatten()) + + if weights is not None: + if weights.shape != ref_shape: + print('The weights passed are not compatible with the data. Ignoring them and moving on') + Weights = None + else: + Weights = weights.flatten() + else: + Weights = None + + + Data=np.column_stack(Data) + Data_df = pd.DataFrame(Data, columns=labels) + + def corrfunc(x, y, **kws): + r, _ = pearsonr(x, y) + ax = plt.gca() + ax.annotate(r"$r = {:.2f}$".format(r), xy=(.1, .9), xycoords=ax.transAxes) + + def corrfunc_weights(x, y, **kws): + X = np.array(x) + Y = np.array(y) + r_w = self.weigthed_pearson(X, Y, Weights) + ax = plt.gca() + ax.annotate(r"$r_w = {:.2f}$".format(r_w), xy=(.1, .9), xycoords=ax.transAxes) + + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message='is_categorical_dtype is deprecated') + warnings.filterwarnings('ignore', message='use_inf_as_na option is deprecated') + g=sns.PairGrid(Data_df) #, plot_kws=dict(scatter_kws=dict(s=4))) + # g.map_lower(sns.scatterplot, s=4, c='red') + # g.map_upper(sns.kdeplot, color='red') + # g.map_diag(sns.histplot, kde=True, color='red') + + def hide_current_axis(*args, **kwds): + plt.gca().set_visible(False) + + g.map_upper(hide_current_axis) + g.map_diag(sns.histplot, kde=True, color='black') + g.map_lower(sns.scatterplot, s=4, color=".15") + g.map_lower(sns.histplot, pthresh=.1, cmap="mako")#, bins=50 + g.map_lower(sns.kdeplot, levels=5, color='w', linewidths=1) + + # sns.set_theme(style="dark") + # sns.scatterplot(x=X, y=Y, s=5, color=".15", ax=g.ax_joint, hue=hue_array, style=style_array, palette=palette, markers=markers) + # sns.histplot(x=X, y=Y, bins=50, ax=g.ax_joint, pthresh=.1, cmap="mako") + # sns.kdeplot(x=X, y=Y, levels=5, ax=g.ax_joint, color="w", linewidths=1) + + # g.fig.tight_layout() + g.figure.tight_layout() + if Weights is not None: + g.map_lower(corrfunc_weights) + else: + g.map_lower(corrfunc) + + return g + + def PCA_find_regressors_subset(self, data, var_wanted = 0.9): + """ + Idea: pass list of quantities that you want to use for regression. + Check if there is a smaller subset of principal components to be retained that are sufficient + to explain enough of the observed variance in the dataset. + + Why: if regressors are highly correlated, linear regression is unstable and cannot be trusted + + Parameters: + ----------- + data: list of gridded data + + var_wanted: float + should be a number between 0. and 1. : the percetage of variance to be retained. + + Returns: + -------- + comp_decomp: array of shape (n_var, n_comp) + Matrix whose columns contains the decomposition of the principal components + written in the basis of the untransformed (original) variables. + + g: result of self.visualize_many_correlations --> show the PCs are indeed uncorrelated. + + Notes: + ------ + + """ + # CHECKING AND PREPROCESSING THE DATA + ref_shape = data[0].shape + n_vars = len(data) + Data =[data[0]] + for i in range(1, n_vars): + if data[i].shape != ref_shape: + print(f'The {i}-th feature passed is not aligned with the first, removing {i}-th feature.') + else: + Data.append(data[i]) + + if len(Data)==1: + print('No two vars are compatible. Exiting.') + return None + + for i in range(len(Data)): + Data[i] = Data[i].flatten() + + #STANDARDIZING DATA TO ZERO MEAN AND UNIT VARIANCE + for i in range(len(Data)): + x = Data[i] + mean = np.mean(x) + var = np.var(x) + y = np.array([x[j]-mean for j in range(len(x))]) + Data[i] = y/var + # Data[i] = y + Data = np.column_stack(tuple([Data[i] for i in range(len(Data))])) + + # HOW MANY PRINCIPAL COMPONENTS HAVE TO BE RETAINED? + pca_model = PCA().fit(Data) + n_comp=0 + exp_var_sum = np.cumsum(pca_model.explained_variance_ratio_) + var_captured = exp_var_sum[n_comp] + while var_captured <= var_wanted: + n_comp +=1 + var_captured= exp_var_sum[n_comp] + n_comp = n_comp+1 + # WORKING OUT THE COMPONENTS DECOMPOSITION + var2comp = pca_model.components_ # shape: n_comp * n_var + comp_decomp = [] + for i in range(n_comp): + # should be the inverse but var2comp is unitary, so transpose + comp_decomp.append(np.einsum('ij->ji', var2comp)[:,i]) + + # # CREATING THE FIGURE WITH THE COMPONENTS PROFILE TO CHECK THEY'RE UNCORRELATED + # # this block is temporary and will be removed later. + # pc_profiles = np.einsum('ij,kj->ik', Data, var2comp) + # labels = [f'{i} comp' for i in range(n_comp)] + # pc_for_plotting = [pc_profiles[:,i] for i in range(n_comp)] + # g = self.visualize_many_correlations(pc_for_plotting, labels) + + # print('Built the figure\n') + + # return comp_decomp, g + return comp_decomp + + def PCA_find_regressors(self, dependent_var, explanatory_vars, pcs_num=1): + """ + Idea: pass data to model and a list of explanatory variables. Identify the principal components of dataset + and find the onew that have highest score on the data you want to model. These will give you the direction(s) in + the dataset that better capture the variation in the dependent var. + + Returning the decomposition of such pc(s) in terms of the original features, one can then visually check how + well the model is explaining the var, and use this to perform a regression analysis in terms of a reduced + set of vars. + + Parameters: + ----------- + dependent_var: np.array with (gridded) data + + explanatory_vars: list of np.arrays with (gridded) data + + pcs_num: integer default to 1 + the number of principal components to be looked at and whose decomposition is returned + + Returns: + -------- + highest_pcs_decomp: list of arrays + each array contains the decomposition of the pcs in terms of original features + + corresponding_scores: list of floats + the score the one of the dependent_var on the principal components + + """ + # CHECKING AND PREPROCESSING THE DATA + dep_shape = dependent_var.shape + n_expl_vars = len(explanatory_vars) + Expl_vars = [] + for i in range(0, n_expl_vars): + if explanatory_vars[i].shape != dep_shape: + print(f'The {i}-th feature passed is not aligned with the first, removing {i}-th feature.') + else: + Expl_vars.append(explanatory_vars[i]) + if len(Expl_vars)==0: + print('No two vars are compatible. Exiting.') + return None + + # FLATTENING IN CASE DATA IS NOT PRE-PROCESSED + Dep_var = dependent_var.flatten() + for i in range(len(Expl_vars)): + Expl_vars[i] = Expl_vars[i].flatten() + + #STANDARDIZING DATA TO ZERO MEAN AND UNIT VARIANCE + Data = [] + for i in range(len(Expl_vars)): + x = Expl_vars[i] + mean = np.mean(x) + var = np.var(x) + y = np.array([x[j]-mean for j in range(len(x))]) + Data.append(y/var) + # Data.append(y) + mean = np.mean(Dep_var) + var = np.var(Dep_var) + y = (Dep_var - mean) + y=y/var + Data = np.column_stack(tuple([y] + [Data[i] for i in range(len(Expl_vars))])) + + #IDENTIFYING THE COMPONENTS WITH HIGHEST SCOREs ON THE DEPENDENT VAR + pca_model = PCA().fit(Data) + var2comp = pca_model.components_ + comp2var = np.einsum('ij->ji', var2comp) + + # print('components_: \n{}\n'.format(var2comp)) + scores_of_dep_var = var2comp[:,0] + print(f'Scores of dependent var on principal components: \n{scores_of_dep_var}\n') + sorted_scores_indices = np.argsort(np.abs(scores_of_dep_var)) + pos_of_highest_pcs = sorted_scores_indices[-pcs_num:] + + highest_pcs_decomp = [] + corresponding_scores = [] + # tot_explained_var = 0 + for i in range(pcs_num-1, -1, -1): + highest_pcs_decomp.append(comp2var[:,pos_of_highest_pcs[i]]) + corresponding_scores.append(scores_of_dep_var[pos_of_highest_pcs[i]]) + # tot_explained_var += pca_model.explained_variance_ratio_[pos_of_highest_pcs[i]] + + # print(f'Total explained variance: {tot_explained_var}\n') + + return highest_pcs_decomp, corresponding_scores + + def wasserstein_distance(self, x, y, sample_points=200): + """ + Given two data arrays, first build the gaussian_kde of the distributions for each. + Then sample the distributions at 'sample_points' linearly distributed sample points, + and evaluate the Wasserstein distance between the two. + + Parameters: + ----------- + x, y: nd.array + + Returns: + -------- + the Wasserstein distance between the two distributions extracted from the sample data + """ + + if x.shape != y.shape: + print('The two arrays passed are not compatible, exiting') + return None + + X = x.flatten() + Y = y.flatten() + + kde_x = gaussian_kde(X, bw_method='scott' ) + kde_y = gaussian_kde(Y, bw_method='scott' ) + + min, max = np.amin([X, Y]), np.amax([X, Y]) + eval_points = np.linspace(min, max, sample_points) + + kde_sampled_x = kde_x(eval_points) + kde_sampled_y = kde_y(eval_points) + + w = wasserstein_distance(kde_sampled_x, kde_sampled_y) + return w + + def compare_distributions(self, x, y, xlabel=None, ylabel=None): + """ + Given two arrays x, y, compute and plot the respective distributions. + These are extracted from data using a gaussian_kde. + + Parameters: + ----------- + x, y: nd.array + + Returns: + -------- + the figure for later usage + """ + + if x.shape != y.shape: + print('The two arrays passed are not compatible, exiting') + return None + + X = x.flatten() + Y = y.flatten() + + kde_X = gaussian_kde(X, bw_method='scott' ) + kde_Y = gaussian_kde(Y, bw_method='scott' ) + + min, max = np.amin([X, Y]), np.amax([X, Y]) + + fig, ax = plt.subplots() + _, bins_X, _ = ax.hist(X, bins='scott', alpha=0.5, density=True, color='firebrick') + _, bins_Y, _ = ax.hist(Y, bins='scott', alpha=0.5, density=True, color='steelblue') + + num_eval_points = np.min([len(bins_X), len(bins_Y)]) + eval_points = np.linspace(min, max, num_eval_points) + pdf_X_eval = kde_X(eval_points) + pdf_Y_eval = kde_Y(eval_points) + + ax.plot(eval_points, pdf_X_eval, c='firebrick', label=xlabel) + ax.plot(eval_points, pdf_Y_eval, c='steelblue', label=ylabel) + + plt.legend() + + return fig + + def scatter_distrib_compare(self, x, y, figsize=[9,4]): + """ + WORK IN PROGRESS + """ + if x.shape != y.shape: + print('The two arrays passed are not compatible, exiting') + return None + + X = x.flatten() + Y = y.flatten() + + # fig, axes = plt.subplots(1,3, figsize=figsize) + fig, axes = plt.subplots(1,2, figsize=figsize) + axes = axes.flatten() + + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message='is_categorical_dtype is deprecated') + warnings.filterwarnings('ignore', message='use_inf_as_na option is deprecated') + + sns.set_theme(style="dark") + sns.scatterplot(x=X, y=Y, s=4, color=".15", ax=axes[0]) + sns.histplot(x=X, y=Y, bins=50, ax=axes[0], pthresh=.1, cmap="mako") + sns.kdeplot(x=X, y=Y, levels=5, ax=axes[0], color="w", linewidths=1) + + # axes[0].set_axis_labels(xlabel=xlabel, ylabel=ylabel) + + sns.histplot(X, stat='density', kde=True, color='firebrick', ax=axes[1], label='X') + sns.histplot(Y, stat='density', kde=True, color='steelblue', ax=axes[1], label='Y') + + # sns.histplot(X, stat='probability', kde=True, color='firebrick', ax=axes[2]) + # sns.histplot(Y, stat='probability', kde=True, color='steelblue', ax=axes[2]) + + # Can update legend afterwards using: h, _ = axes.get_legend_handles_labels() + axes.legend(h, updated_labels) + + fig.tight_layout() + return fig, axes + + + # Not sure about these two methods. + def JointPlot(self, model, y_var_str, x_var_str, t, x_range, y_range,\ + interp_dims, method, y_component_indices, x_component_indices): + y_data_to_plot, points = \ + self.visualizer.get_var_data(model, y_var_str, t, x_range, y_range, interp_dims, method, y_component_indices) + x_data_to_plot, points = \ + self.visualizer.get_var_data(model, x_var_str, t, x_range, y_range, interp_dims, method, x_component_indices) + fig = plt.figure(figsize=(16,16)) + sns.jointplot(x=x_data_to_plot.flatten(), y=y_data_to_plot.flatten(), kind="hex", color="#4CB391") + plt.title(y_var_str+'('+x_var_str+')') + fig.tight_layout() + plt.show() + + def DistributionPlot(self, model, var_str, t, x_range, y_range, interp_dims, method, component_indices): + + data_to_plot, points = \ + self.visualizer.get_var_data(model, var_str, t, x_range, y_range, interp_dims, method, component_indices) + + # print(data_to_plot) + + fig = plt.figure(figsize=(16,16)) + sns.displot(data_to_plot) + plt.title(var_str) + fig.tight_layout() + plt.show() + + +if __name__ == '__main__': + # pass + # #TESTING REGRESSION + # x0 = np.arange(100).reshape((10,10)) + # x1 = np.sin(np.arange(100).reshape((10,10))) + # X = [x0, x1] + # y = 1 + 2 * x0 + 3* x1 + random.randint(20,30) + + # statistical_tool = CoefficientsAnalysis() + # result, errors = statistical_tool.scalar_regression(y,X, add_intercept=True) + # print('Coefficients: {}'.format(result)) + # print('Errors: {}\n'.format(errors)) + + # #TESTING PRE-PROCESS DATA + # x = np.arange(1, 200) + # y = np.arange(29, 228) + + # print('Min and max of x: {}, {}\n'.format(np.min(x), np.max(x))) + # print('Min and max of y: {}, {}\n'.format(np.min(y), np.max(y))) + + # preprocess_data = {"value_ranges": [[None, None], [None, None]], + # "log_abs": [1, 1]} + + + # statistical_tool = CoefficientsAnalysis() + # data = [x,y] + + + # x, y = statistical_tool.preprocess_data(data, preprocess_data) + + # print('Processing data....\n') + # print('Min and max of x: {}, {}\n'.format(np.min(x), np.max(x))) + # print('Min and max of y: {}, {}\n'.format(np.min(y), np.max(y))) + + + + # print('Extracting random vals from x...\n') + # print(len(x)) + # weights = np.ones(x.shape) + # extracted, weights = statistical_tool.extract_randomly([x,y], 10) + # print(extracted) + + # n = 500 + # mean = [0, 0] + # cov = [(2, .4), (.4, .2)] + # rng = np.random.RandomState(0) + # x, y = rng.multivariate_normal(mean, cov, n).T + + # ws = [] + # for i in range(len(x)): + # ws.append(random.uniform(0.5,1.)) + # ws = np.array(ws) + + # # print(x.shape, ws.shape) + # # r, _ = stats.pearsonr(x,y) + + # statistical_tool = CoefficientsAnalysis() + + # # wr = statistical_tool.weigthed_pearson(x,y,ws) + # # print(f'stats.pearsonr: {r}') + # # print(f'my_pearson: {wr}') + + # # mu, sigma = 0, 4 # mean and standard deviation + # # z = np.random.normal(mu, sigma, n) + + # statistical_tool = CoefficientsAnalysis() + # g = statistical_tool.visualize_correlation(x,y,weights=ws) + # legend_labels = ['pippo', 'pluto'] + # text_for_box = r'$pippo$' + '\n' + '%.3f' %1.234454 + # # plt.text(0.85, 0.1, text_for_box, fontsize=12, transform=plt.gcf().transFigure) + # bbox_args = dict(boxstyle="round", fc="0.95") + # plt.annotate(text=text_for_box, xy = (0.99,0.1), xycoords='figure fraction', bbox=bbox_args, ha="right", va="bottom", fontsize = 9) + + + + # # plt.legend(bbox_to_anchor=(1.02, 0.2), loc='upper left', borderaxespad=0) + + # # labels=['x','y','z'] + # # data=[x,y,z] + # # statistical_tool.visualize_many_correlations(data, labels, weights=ws) + # plt.show() + + size = 10000 + a = np.random.normal(loc=0.0, scale=1, size=size) + b = np.random.normal(loc=1, scale=1.5, size=size) + + statistical_tool = CoefficientsAnalysis() + dist = statistical_tool.wasserstein_distance(a,b) + print(f'wasserstein_distance: {dist}\n') + + # fig = statistical_tool.compare_distributions(a,b,'pippo', 'pluto') + fig, _ = statistical_tool.scatter_distrib_compare(a,b) + + plt.show() + diff --git a/master_files/FileReaders.py b/master_files/FileReaders.py new file mode 100644 index 0000000..6ed3e9d --- /dev/null +++ b/master_files/FileReaders.py @@ -0,0 +1,229 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Mar 31 10:00:00 2023 + +@author: Thomas +""" + +import h5py +import glob +import numpy as np + +class METHOD_HDF5(object): + + def __init__(self, directory, fewer_snaps=False, smaller_list=None): + """ + Set up the list of files (from hdf5) and dictionary with dataset names + in the hdf5 file. + + Parameters + ---------- + directory: string + the filenames in the directory have to be incremental (sorted is used) + + fewer_snaps: bool + set to true if you want micromodel to store fewer snapshots than can be found + in directory + + smaller_list: list + indices to be retained of list orderd via sorted(glob.glob(directory+str('*.hdf5'))) + """ + hdf5_filenames = sorted(glob.glob(directory+str('*.hdf5'))) + if fewer_snaps: + if smaller_list: + temp = [hdf5_filenames[i] for i in smaller_list] + hdf5_filenames = temp + + self.hdf5_files = [] + for filename in hdf5_filenames: + self.hdf5_files.append(h5py.File(filename,'r')) + self.num_files = len(self.hdf5_files) + + self.hdf5_keys = dict.fromkeys(list(self.hdf5_files[0].keys())) + for key in self.hdf5_keys: + self.hdf5_keys[key] = list(self.hdf5_files[0][key].keys()) + + def get_hdf5_keys(self): + return self.hdf5_keys + + def read_in_data(self, micro_model): + """ + Store data from files into micro_model + + Parameters + ---------- + micro_model: class MicroModel + strs in micromodel have to be the same as hdf5 files output from METHOD. + """ + + self.translating_prims = dict.fromkeys(micro_model.get_prim_strs()) + for prim_str in micro_model.get_prim_strs(): + if prim_str == "n": + self.translating_prims[prim_str] = "rho" + else: + self.translating_prims[prim_str] = prim_str + + for prim_var_str in micro_model.prim_vars: + try: + method_str = self.translating_prims[prim_var_str] + for counter in range(self.num_files): + micro_model.prim_vars[prim_var_str].append( self.hdf5_files[counter]["Primitive/"+method_str][:] ) + # The [:] is for returning the arrays not the dataset + micro_model.prim_vars[prim_var_str] = np.array(micro_model.prim_vars[prim_var_str]) + except KeyError: + print(f'{method_str} is not in the hdf5 dataset: check Primitive/') + + + self.translating_aux = dict.fromkeys(micro_model.get_aux_strs()) + for aux_str in micro_model.get_aux_strs(): + self.translating_aux[aux_str] = aux_str + + for aux_var_str in micro_model.aux_vars: + try: + method_str = self.translating_aux[aux_var_str] + for counter in range(self.num_files): + micro_model.aux_vars[aux_var_str].append( self.hdf5_files[counter]["Auxiliary/"+method_str][:] ) + micro_model.aux_vars[aux_var_str] = np.array(micro_model.aux_vars[aux_var_str]) + except KeyError: + print(f'{method_str} is not in the hdf5 dataset: check Auxiliary/') + + # As METHOD saves endTime, the time variables (and points) need to be dealt with separately + for dom_var_str in micro_model.domain_int_strs: + try: + if dom_var_str == 'nt': + pass + else: + micro_model.domain_vars[dom_var_str] = int( self.hdf5_files[0]['Domain/' + dom_var_str][:]) + except KeyError: + print(f'{dom_var_str} is not in the hdf5 dataset: check Domain/') + + for dom_var_str in micro_model.domain_float_strs: + try: + if dom_var_str in ['tmin', 'tmax']: + pass + else: + micro_model.domain_vars[dom_var_str] = float( self.hdf5_files[0]['Domain/' + dom_var_str][:]) + except KeyError: + print(f'{dom_var_str} is not in the hdf5 dataset: check Domain/') + + for dom_var_str in micro_model.domain_array_strs: + try: + if dom_var_str in ['t','points']: + pass + else: + micro_model.domain_vars[dom_var_str] = self.hdf5_files[0]['Domain/' + dom_var_str][:] + except KeyError: + print(f'{dom_var_str} is not in the hdf5 dataset: check Domain/') + + + micro_model.domain_vars['nt'] = self.num_files + for counter in range(self.num_files): + micro_model.domain_vars['t'].append( float(self.hdf5_files[counter]['Domain/endTime'][:])) + micro_model.domain_vars['t'] = np.array(micro_model.domain_vars['t']) + micro_model.domain_vars['tmin'] = np.amin(micro_model.domain_vars['t']) + micro_model.domain_vars['tmax'] = np.amax(micro_model.domain_vars['t']) + micro_model.domain_vars['points'] = [micro_model.domain_vars['t'], micro_model.domain_vars['x'], \ + micro_model.domain_vars['y']] + + def read_in_data_HDF5_missing_xy(self, micro_model): + """ + Store data from files into micro_model + + Parameters + ---------- + micro_model: class MicroModel + strs in micromodel have to be the same as hdf5 files output from METHOD. + """ + + self.translating_prims = dict.fromkeys(micro_model.get_prim_strs()) + for prim_str in micro_model.get_prim_strs(): + if prim_str == "n": + self.translating_prims[prim_str] = "rho" + else: + self.translating_prims[prim_str] = prim_str + + for prim_var_str in micro_model.prim_vars: + try: + method_str = self.translating_prims[prim_var_str] + for counter in range(self.num_files): + micro_model.prim_vars[prim_var_str].append( self.hdf5_files[counter]["Primitive/"+method_str][:] ) + # The [:] is for returning the arrays not the dataset + micro_model.prim_vars[prim_var_str] = np.array(micro_model.prim_vars[prim_var_str]) + except KeyError: + print(f'{method_str} is not in the hdf5 dataset: check Primitive/') + + + self.translating_aux = dict.fromkeys(micro_model.get_aux_strs()) + for aux_str in micro_model.get_aux_strs(): + self.translating_aux[aux_str] = aux_str + + for aux_var_str in micro_model.aux_vars: + try: + method_str = self.translating_aux[aux_var_str] + for counter in range(self.num_files): + micro_model.aux_vars[aux_var_str].append( self.hdf5_files[counter]["Auxiliary/"+method_str][:] ) + micro_model.aux_vars[aux_var_str] = np.array(micro_model.aux_vars[aux_var_str]) + except KeyError: + print(f'{method_str} is not in the hdf5 dataset: check Auxiliary/') + + # As METHOD saves endTime, the time variables (and points) need to be dealt with separately + # Similar is for x,y which are not stored properly in by METHOD parallelSaveDataHDF5 + for dom_var_str in micro_model.domain_int_strs: + try: + if dom_var_str == 'nt': + pass + else: + micro_model.domain_vars[dom_var_str] = int( self.hdf5_files[0]['Domain/' + dom_var_str][:]) + except KeyError: + print(f'{dom_var_str} is not in the hdf5 dataset: check Domain/') + + for dom_var_str in micro_model.domain_float_strs: + try: + if dom_var_str in ['tmin', 'tmax']: + pass + else: + micro_model.domain_vars[dom_var_str] = float( self.hdf5_files[0]['Domain/' + dom_var_str][:]) + except KeyError: + print(f'{dom_var_str} is not in the hdf5 dataset: check Domain/') + + for dom_var_str in micro_model.domain_array_strs: + try: + if dom_var_str in ['t','points', 'x', 'y']: + pass + else: + micro_model.domain_vars[dom_var_str] = self.hdf5_files[0]['Domain/' + dom_var_str][:] + except KeyError: + print(f'{dom_var_str} is not in the hdf5 dataset: check Domain/') + + + micro_model.domain_vars['nt'] = self.num_files + for counter in range(self.num_files): + micro_model.domain_vars['t'].append( float(self.hdf5_files[counter]['Domain/endTime'][:])) + micro_model.domain_vars['t'] = np.array(micro_model.domain_vars['t']) + micro_model.domain_vars['tmin'] = np.amin(micro_model.domain_vars['t']) + micro_model.domain_vars['tmax'] = np.amax(micro_model.domain_vars['t']) + + micro_model.domain_vars['x'] = np.zeros(micro_model.domain_vars['nx']) + for i in range(len(micro_model.domain_vars['x'])): + # micro_model.domain_vars['x'][i] = (micro_model.domain_vars['xmax']- micro_model.domain_vars['xmin']) / (2 * micro_model.domain_vars['nx']) + \ + # i * micro_model.domain_vars['dx'] + micro_model.domain_vars['x'][i] = micro_model.domain_vars['xmin'] + i * micro_model.domain_vars['dx'] + + micro_model.domain_vars['y'] = np.zeros(micro_model.domain_vars['ny']) + for i in range(len(micro_model.domain_vars['y'])): + # micro_model.domain_vars['y'][i] = (micro_model.domain_vars['ymax']- micro_model.domain_vars['ymin']) / (2 * micro_model.domain_vars['ny']) + \ + # i * micro_model.domain_vars['dy'] + micro_model.domain_vars['y'][i] = micro_model.domain_vars['ymin'] + i * micro_model.domain_vars['dy'] + + micro_model.domain_vars['points'] = [micro_model.domain_vars['t'], micro_model.domain_vars['x'], \ + micro_model.domain_vars['y']] + + +if __name__ == '__main__': + + from MicroModels import * + + FileReader = METHOD_HDF5('./Data/test_res100/') + MicroModel = IdealMHD_2D() + FileReader.read_in_data(MicroModel) + # FileReader.read_in_data_HDF5_missing_xy(MicroModel) diff --git a/master_files/Filters.py b/master_files/Filters.py new file mode 100644 index 0000000..75ec542 --- /dev/null +++ b/master_files/Filters.py @@ -0,0 +1,1641 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Mar 28 15:36:01 2023 + +@author: Thomas +""" + +import numpy as np +import time +import os +import scipy.integrate as integrate +import multiprocessing as mp +from scipy.optimize import minimize, root +from scipy.interpolate import interpn +from itertools import product +from system.BaseFunctionality import * + +from MicroModels import * +from FileReaders import * +from system.BaseFunctionality import * + +class FindObs_flux_min(object): + """ + Class for computing the observer by minimizing the (micro) baryon current + surface flux over a box. + Work in any dimension, read on construction from the micro-model. + """ + def __init__(self, micro_model, box_len): + """ + Parameters: + ----------- + micro_model: instance of class containing the microdata + + box_len: float, side of the box + """ + self.micro_model = micro_model + self.spatial_dims = micro_model.get_spatial_dims() + self.L = box_len + + self.flux_methods = { + "gauss" : self.flux_residual_Gauss , + "linear" : self.flux_residual , + "inbuilt" : self.flux_residual_ib + } + + def set_box_length(self, bl): + self.L = bl + + def get_tetrad_from_U(self, U): + """ + Build tetrad orthogonal to unit velocity with from complete velocity vector + + Parameters: + ----------- + U: list of d+1 floats, with d the number of spatial dimensions + + Return: + ------- + list of arrays: U + d unit vectors that complete it to a orthonormal basis + """ + if len(U) != 1+self.spatial_dims: + print('The dimension of U passed is not compatible with \ + micro_model dimensionality!') + return None + + es =[] + for _ in range(self.spatial_dims): + es.append(np.zeros(self.spatial_dims+1)) + for i in range(len(es)): + es[i][i+1] = 1 + tetrad = [U] + for i, vec in enumerate(es): #enumerate returns a tuple: so acts by value not reference! + vec = vec + np.multiply(Base.Mink_dot(vec, U), U) + for j in range(i-1,-1,-1): + vec = vec - np.multiply(Base.Mink_dot(vec, es[j]), es[j]) + es[i] = np.multiply(vec, 1 / np.sqrt(Base.Mink_dot(vec, vec))) + tetrad += [es[i]] + return tetrad + + def get_tetrad_from_vels(self, spatial_vels): + """ + Build tetrad orthogonal to unit velocity with spatial velocities spatial_vels + + Parameters: + ----------- + spatial_vels: list of d floats, with d the number of spatial dimensions + + Return: + ------- + list of arrays: U + d unit vectors that complete it to a orthonormal basis + """ + if len(spatial_vels) != self.spatial_dims: + print('The number of spatial velocities passed is not compatible with \ + micro_model dimensionality!') + return None + + U = np.array(Base.get_rel_vel(spatial_vels)) + return self.get_tetrad_from_U(U) + + def flux_residual(self, spatial_vels, point, lin_spacing = 10): + """ + Compute the drift of baryons through the box built from spatial_vels. + First get the center of the 2*(d+1) faces of the (d+1)-box, then build coords + for points to sample the flux through each face. + Next, approximate the flux integral as a sum + + Parameters: + ----------- + spatial_vels: list of d (spatial dimension) floats, spatial coord of vel + + point: list of d+1 floats (t,x,y) for the box center + + lin_spacing: integer, lin_spacing**spatial_dim is the # of points used to + sample the flux through each face. + + Returns: + -------- + float: absolute flux + + Notes: + ------ + Much faster than method based on inbuilt dblquad. + """ + tetrad = self.get_tetrad_from_vels(spatial_vels) + flux = 0 + + xs = [] + for i in range(self.spatial_dims): + xs.append(np.linspace(-self.L /2 , self.L /2, lin_spacing)) + coords = [] + for element in product(*xs): + coords.append(np.array(element)) + + for vec in tetrad: + rem_vecs = [x for x in tetrad if not (x==vec).all()] + for i in range(2): + center = point + np.multiply( (-1)**i * self.L / 2, vec) + + surf_coords = [] + for coord in coords: + temp = center + for i in range(self.spatial_dims): + temp += np.multiply(coord[i], rem_vecs[i]) + surf_coords.append(temp) + + for coord in surf_coords: + flux += Base.Mink_dot(self.micro_model.get_interpol_var('BC', coord), vec) + + flux *= (self.L / lin_spacing) ** self.spatial_dims + return abs(flux) + + def flux_residual_Gauss(self, spatial_vels, point, order = 3): + """ + Alternative for computing manually the flux residual, using the Gauss + Legendre method. + + Parameters: + ----------- + spatial_vels: list of d (spatial dimension) floats, spatial coord of vel + + point: list of d+1 floats (t,x,y) for the box center + + order: integer, order of the Gauss-Legendre sampling method. + + Returns: + -------- + float: absolute flux + + Notes: + ------ + Much faster than method based on inbuilt dblquad. Should be more accurate + than the one based on linearly spaced points. + """ + ps1d = [] + ws1d = [] + if order == 3: + ps1d = [0, + np.sqrt(3/5), - np.sqrt(3/5)] + ws1d = [8./9. , 5./9. , 5./9.] + elif order == 4: + p1 = np.sqrt(3./7 - 2/7 * np.sqrt(6/5)) + p2 = np.sqrt(3./7 + 2/7 * np.sqrt(6/5)) + w1 = (18. + np.sqrt(30) ) / 36. + w2 = (18. - np.sqrt(30) ) / 36. + ps1d = [p1, -p1, p2, -p2] + ws1d = [w1, w1, w2, w2] + elif order == 5: + p1 = np.sqrt(5 - 2* np.sqrt(10/7.))/3 + p2 = np.sqrt(5 + 2* np.sqrt(10/7.))/3 + w1 = (322 + 13 * np.sqrt(70)) /900 + w2 = (322 - 13 * np.sqrt(70)) /900 + ps1d = [0, p1, -p1, p2, -p2] + ws1d = [128/225, w1, w1, w2, w2] + else: + print("The method is implemented for Gauss-Legendre quadrature of order 3, 4 and 5 only!") + return [] + + xs = [] + ws = [] + for _ in range(self.spatial_dims): + xs.append(ps1d) + ws.append(ws1d) + + coords = [] + for element in product(*xs): + coords.append(np.multiply(self.L/2, np.array(element) )) + + totws = [] + for element in product(*ws): + temp = 1. + for w in element: + temp *= w + totws.append(temp) + + tetrad = self.get_tetrad_from_vels(spatial_vels) + flux = 0. + for vec in tetrad: + rem_vecs = [x for x in tetrad if not (x==vec).all()] + for i in range(2): + center = point + np.multiply( (-1)**i * self.L / 2, vec) + + surf_coords = [] + for coord in coords: + temp = center + for i in range(self.spatial_dims): + temp += np.multiply(coord[i], rem_vecs[i]) + surf_coords.append(temp) + + for i, coord in enumerate(surf_coords): + Na = self.micro_model.get_interpol_var('BC',coord) + flux += Base.Mink_dot(Na, vec) * totws[i] + + flux *= (self.L /2 ) ** self.spatial_dims + return abs(flux) + + def flux_residual_ib(self, spatial_vels, point, abserr = 1e-7): + """ + Compute the flux residual using inbuilt method dblquad or tplquad + Based on function point_flux (nested definition) + + Parameters: + ----------- + Vs: list of d floats, spatial components of the velocity vec + + point: float, center of the box + + Returns: + -------- + tuple: absolute flux and error estimate + + Notes: + ------ + Vastly slower than method above. + As this is based on dblquad, this method works fine only if it's 2+1 dim + + Also, dblquad returns an estimate of the error. This info is presently discarded + in order to have the method return a scalar value so that this can be minimized + by find observer. + + """ + tetrad = self.get_tetrad_from_vels(spatial_vels) + flux = 0 + partial_flux = 0 + error = 0 + partial_error = 0 + + if self.spatial_dims == 2: + def point_flux(x, y , center, Vx, Vy, normal): + coords = center + np.multiply(x, Vx) + np.multiply(y, Vy) + Na = self.micro_model.get_interpol_var('BC', coords) + flux = Base.Mink_dot(Na, normal) + return flux + + for vec in tetrad: + rem_vecs = [x for x in tetrad if not (x==vec).all()] + for i in range(2): + center = point + np.multiply( (-1)**i * self.L / 2, vec) + partial_flux, partial_error = integrate.dblquad(point_flux, -self.L / 2, self.L / 2, -self.L / 2, self.L / 2, \ + args = (center, rem_vecs[0], rem_vecs[1], vec), epsabs = abserr)[:] + flux += partial_flux + error += partial_error + return abs(flux) + + elif self.spatial_dims == 3: + def point_flux(x, y, z , center, Vx, Vy, Vz, normal): + coords = center + np.multiply(x, Vx) + np.multiply(y, Vy) + np.multiply(z, Vz) + Na = self.micro_model.get_interpol_var('BC', coords) + flux = Base.Mink_dot(Na, normal) + return flux + + for vec in tetrad: + rem_vecs = [x for x in tetrad if not (x==vec).all()] + for i in range(2): + center = point + np.multiply( (-1)**i * self.L / 2, vec) + partial_flux, partial_error = integrate.tplquad(point_flux, -self.L / 2, self.L / 2, -self.L / 2, self.L / 2, -self.L / 2, self.L / 2,\ + args = (center, rem_vecs[0], rem_vecs[1], vec), epsabs = abserr)[:] + flux += partial_flux + error += partial_error + return abs(flux) + + def find_observer(self, point, flux_str = "gauss", initial_guess = None): + """ + Key function: minimize the flux residual and find the observer at point. + + Parameters: + ----------- + point: list of spatial_dims+1 floats (t,x,y) + + flux_str: string, default to "gauss" + string for the method used to compute the flux, must be chosen within + ['gauss', 'linear', 'inbuilt'] + + initial_guess: DEPRECATED, list of d floats: the spatial velocities of the initial guess + if the number of spatial vels passed does not match the micro_model dimensionality, the + pointwise velocity is used instead. + + Returns: + -------- + Successful minimization: Boolean True, observer, error + Failed minimization: Boolean False, coordinates + + Notes: + ------ + To change the number of points to be used with linear or Gauss-Legendre methods, + or the absolute relative of the inbuilt method, change the default values of + the optional arguments in the corresponding methods. + """ + + guess = [] + if initial_guess is not None and len(initial_guess) == self.spatial_dims: + guess = initial_guess + else: + U = np.multiply(1 / self.micro_model.get_interpol_var('n', point) , self.micro_model.get_interpol_var('BC', point) ) + for i in range(1, len(U)): + guess.append(U[i] / U[0]) + guess = np.array(guess) + + try: + sol = minimize(self.flux_methods[flux_str], x0 = guess, args = (point), bounds=((-0.8,0.8),(-0.8,0.8)),tol=1e-6) + if sol.success: + observer = Base.get_rel_vel(sol.x) + if sol.fun > 1e-5: + print(f'Warning: residual is large at {point}', sol.fun) + return sol.success, observer, sol.fun + if not sol.success: + return sol.success, point + except KeyError: + print(f"The method you want to use for computing the flux, {flux_str}, does not exist!") + return None + + def find_observers_points(self, points, flux_str = "gauss"): + """ + Key function: minimize the flux residual and find the observers for points. + flux_str determines the method to compute the flux residual. + + Parameters: + ----------- + points: list of spatial_dims+1 floats, ordered as (t,x,y) + + flux_str: string, default to "gauss" + string for the method used to compute the flux, must be chosen within + ['gauss', 'linear', 'inbuilt'] + + Returns: + -------- + list of: + 1) coordinates at which minimization is successful + 2) corresponding observers + 3) corresponding residual + + list of coordinates at which the minimization failed + + Notes: + ------ + To change the number of points to be used with linear or Gauss-Legendre methods, + change the default values of the optional arguments in the corresponding methods. + """ + observers = [] + errors = [] + success_coords = [] + failed_coords = [] + + for point in points: + sol = self.find_observer(point, flux_str) + if sol[0]: + observers.append(sol[1]) + errors.append(sol[2]) + success_coords.append(point) + + if not sol[0]: + failed_coords.append[point] + + return [success_coords, observers, errors] , failed_coords + + def find_observers_ranges(self, num_points, ranges, flux_str = "gauss" ): + """ + Key function: minimize the flux_residual and find the observers for points + in the ranges. flux_str determines the method to compute the flux residual. + + Parameters: + ----------- + num_points: list of integers + number of points to find observers at in each direction + + ranges: list of lists of two floats [[t_min,t_max], ...] + Define the coord ranges to find observers at + + flux_str: string, default to "gauss" + string for the method used to compute the flux, must be chosen within + ['gauss', 'linear', 'inbuilt'] + + Returns: + -------- + list of: + 1) coordinates at which minimization is successful + 2) corresponding observers + 3) corresponding residual + + list of coordinates at which the minimization failed + + Notes: + ------ + To change the number of points to be used with linear or Gauss-Legendre methods, + change the default values of the optional arguments in the corresponding methods. + """ + + list_of_coords = [] + for i in range(len(num_points)): + list_of_coords.append( np.linspace( ranges[i][0], ranges[i][-1] , num_points[i]) ) + + points = [] + for element in product(*list_of_coords): + points.append(np.array(element)) + + return self.find_observers_points(points, flux_str) + + +class FindObs_drift_root(object): + """ + Class for computing the observer by root-finding on the net baryon current + drift over a box. + Work in any dimension, read on construction from the micro-model. + """ + def __init__(self, micro_model, box_len): + """ + Parameters: + ----------- + micro_model: instance of micro_model, the micro data to be filtered + + box_len: float, side of the box for computing drift + """ + self.micro_model = micro_model + self.L = box_len + self.spatial_dims = micro_model.get_spatial_dims() + + self.drift_methods = { + "gauss" : self.drift_residual_gauss , + "linear" : self.drift_residual , + "inbuilt" : self.drift_residual_ib + } + + def set_box_len(self, box_len): + self.L = box_len + + def get_tetrad_from_vels(self, spatial_vels): + """ + Build tetrad orthogonal to unit velocity with spatial velocities spatial_vels + + Parameters: + ----------- + spatial_vels: list of d floats, with d the number of spatial dimensions + + Return: + ------- + list of arrays: U + d unit vectors that complete it to a orthonormal basis + """ + if len(spatial_vels) != self.spatial_dims: + print('The number of spatial velocities passed is not compatible with \ + micro_model dimensionality!') + return None + U = Base.get_rel_vel(spatial_vels) + es =[] + for _ in range(self.spatial_dims): + es.append(np.zeros(self.spatial_dims+1)) + for i in range(len(es)): + es[i][i+1] = 1 + tetrad = [U] + for i, vec in enumerate(es): #enumerate returns a tuple: so acts by value not reference! + vec = vec + np.multiply(Base.Mink_dot(vec, U), U) + for j in range(i-1,-1,-1): + vec = vec - np.multiply(Base.Mink_dot(vec, es[j]), es[j]) + es[i] = np.multiply(vec, 1 / np.sqrt(Base.Mink_dot(vec, vec))) + tetrad += [es[i]] + return tetrad + + def drift_residual(self, spatial_vels, point, lin_spacing = 10): + """ + Compute the averaged baryon current over box, using linearly spaced sample points. + Then compute the drift with respect to each element in the triad completing U (built from + spatial vels) to a tetrad. + + Parameters: + ----------- + spatial_vels: list of d floats, the spatial vels of the observer + + point: list of d+1 floats, the coord of the center of the box + + lin_spacing: integer, number of points (in each direction) used for sampling the box + + Returns: + -------- + list of d floats, the drifts in each direction of the triad. + """ + tetrad = self.get_tetrad_from_vels(spatial_vels) + + xs = [] + for i in range(self.spatial_dims+1): + xs.append(np.linspace(-self.L /2 , self.L /2, lin_spacing)) + adapt_coords = [] + for element in product(*xs): + adapt_coords.append(np.array(element)) + + coords = [] + for coord in adapt_coords: + temp = np.array(point) + for i in range(self.spatial_dims+1): + temp += np.multiply(coord[i], tetrad[i]) + coords.append(temp) + + integral = np.zeros(1+self.spatial_dims) + for coord in coords: + DeltaV = (self.L / lin_spacing)**(1+self.spatial_dims) + integral += np.multiply(DeltaV, self.micro_model.get_interpol_var('BC', coord)) + + drifts = [] + for i in range(len(tetrad)-1): + drifts.append(Base.Mink_dot(integral, tetrad[i+1])) + + return drifts + + def drift_residual_gauss(self, spatial_vels, point, order = 3): + """ + Method to compute net drift residual through the box, using Gauss-Legendre method + + Parameters: + ----------- + spatial_vels: list of d floats, the spatial velocities of the observer + + point: list of d+1 floats ordered as (t,x,y,...), the centre of the box + + order: integer, the order of the Gauss-Legendre approximation + + Returns: + -------- + list of d floats, the average drift in each direction of the triad. + + """ + ps1d = [] + ws1d = [] + if order == 3: + ps1d = [0, + np.sqrt(3/5), - np.sqrt(3/5)] + ws1d = [8./9. , 5./9. , 5./9.] + elif order == 4: + p1 = np.sqrt(3./7 - 2/7 * np.sqrt(6/5)) + p2 = np.sqrt(3./7 + 2/7 * np.sqrt(6/5)) + w1 = (18. + np.sqrt(30) ) / 36. + w2 = (18. - np.sqrt(30) ) / 36. + ps1d = [p1, -p1, p2, -p2] + ws1d = [w1, w1, w2, w2] + elif order == 5: + p1 = np.sqrt(5 - 2* np.sqrt(10/7.))/3 + p2 = np.sqrt(5 + 2* np.sqrt(10/7.))/3 + w1 = (322 + 13 * np.sqrt(70)) /900 + w2 = (322 - 13 * np.sqrt(70)) /900 + ps1d = [0, p1, -p1, p2, -p2] + ws1d = [128/225, w1, w1, w2, w2] + else: + print("The method is implemented for Gauss-Legendre quadrature of order 3, 4 and 5 only!") + return [] + + xs = [] + ws = [] + for _ in range(self.spatial_dims+1): + xs.append(ps1d) + ws.append(ws1d) + + adapt_coords = [] + for element in product(*xs): + adapt_coords.append(np.multiply(self.L/2, np.array(element) )) + + totws = [] + for element in product(*ws): + temp = 1. + for w in element: + temp *= w + totws.append(temp) + + tetrad = self.get_tetrad_from_vels(spatial_vels) + coords = [] + for coord in adapt_coords: + temp = np.array(point) + for i in range(self.spatial_dims+1): + temp += np.multiply(coord[i], tetrad[i]) + coords.append(temp) + + integral = np.zeros(1+self.spatial_dims) + for i, coord in enumerate(coords): + integral += np.multiply(totws[i], self.micro_model.get_interpol_var('BC', coord)) + integral *= (self.L /2 ) ** (self.spatial_dims+1) + + drifts = [] + for i in range(len(tetrad)-1): + drifts.append(Base.Mink_dot(integral, tetrad[i+1])) + + return drifts + + def drift_residual_ib(self, spatial_vels, point, abserr = 1e-7): + """ + Method to compute net drift residual through the box, using Gauss-Legendre method + + Parameters: + ----------- + spatial_vels: list of d floats, the spatial velocities of the observer + + point: list of d+1 floats ordered as (t,x,y,...), the centre of the box + + abserr = optional float, absolute error to be passed to tplquad + + Returns: + -------- + list of d floats, the average drift in each direction of the triad. + """ + if self.spatial_dims != 2: + print('In built methods for integrals can be used only in 2+1 dimensions (i.e. tplquad). The 1+1 \ + dimensional case is not really interesting so not implemented.') + return None + else: + tetrad = self.get_tetrad_from_vels(spatial_vels) + integral = np.zeros(3) + error = np.zeros(3) + + for i in range(3): + def BC_point_value(t, x, y): + coord = point + np.multiply(t, tetrad[0]) + np.multiply(x, tetrad[1]) + np.multiply(y, tetrad[2]) + return self.micro_model.get_interpol_var('BC', coord)[i] + + integral[i], error[i] = integrate.tplquad(BC_point_value, -self.L / 2, self.L / 2, -self.L / 2, self.L / 2, \ + -self.L/2, self.L/2, args = (), epsabs = abserr)[:] + + drifts = [] + for i in range(len(tetrad)-1): + drifts.append(Base.Mink_dot(integral, tetrad[i+1])) + + return drifts + + def find_observer(self, point, drift_str = "gauss", initial_guess= None): + """ + Key method: use optimize.root to find the roots of the drift function. + + Parameters: + ----------- + point: list of d+1 floats, ordered (t,x,y,...), the coordinates of the box centre + + drift_str: string, the method used to compute the net drift + must be either 'gauss', 'linear', 'inbuilt' + + initial_guess: DEPRECATED, list of d floats: the spatial velocities of the initial guess + if the number of spatial vels passed does not match the micro_model dimensionality, the + pointwise velocity is used instead. + + Returns: + -------- + Successful root-finding: Boolean True, observer, avg error + Failed minimization: Boolean False, coordinates + + Notes: + ------ + Change the optional values of the corresponding drift methods to change + the number of points used to sample the d+1 box. + """ + guess = [] + if initial_guess is not None and len(initial_guess) == self.spatial_dims: + guess = initial_guess + else: + U = np.multiply(1 / self.micro_model.get_interpol_var('n', point) , self.micro_model.get_interpol_var('BC', point) ) + for i in range(1, len(U)): + guess.append(U[i] / U[0]) + guess = np.array(guess) + + try: + sol = root(self.drift_methods[drift_str], x0 = guess, args = (point)) + if sol.success: + observer = Base.get_rel_vel(sol.x) + error = 0 + for i in range(len(sol.fun)): + error += sol.fun[i] + avg_error = error/ len(sol.fun) + if avg_error > 1e-5: + print(f'Warning: residual is large at {point}', avg_error) + return sol.success, observer, avg_error + if not sol.success: + return sol.success, point + except KeyError: + print(f"The method you want to use for computing the flux, {drift_str}, does not exist!") + return None + + def find_observers_points(self, points, drift_str = "gauss"): + """ + Key method: use optimize.root to find the roots of the drift function. + + Parameters: + ----------- + points: list of spatial_dims+1 floats, ordered as (t,x,y) + + drift_str: string, default to "gauss" + string for the method used to compute the drifts, must be chosen within + ['gauss', 'linear', 'inbuilt'] + + Returns: + -------- + list of: + 1) coordinates at which root finding is successful + 2) corresponding observers + 3) corresponding residual + + list of coordinates at which root finding failed + + Notes: + ------ + Change the optional values of the corresponding drift methods to change + the number of points used to sample the d+1 box. + """ + observers = [] + avg_errors = [] + success_coords = [] + failed_coords = [] + + for point in points: + sol = self.find_observer(point, drift_str) + if sol[0]: + observers.append(sol[1]) + avg_errors.append(sol[2]) + success_coords.append(point) + + if not sol[0]: + failed_coords.append[point] + + return [success_coords, observers, avg_errors] , failed_coords + + def find_observers_ranges(self, num_points, ranges, drift_str = "gauss" ): + """ + Key function: minimize the flux_residual and find the observers for points + in the ranges. drift_str determines the method to compute the flux residual. + + Parameters: + ----------- + num_points: list of integers + number of points to find observers at in each direction + + ranges: list of lists of two floats [[t_min,t_max], ...] + Define the coord ranges to find observers at + + drift_str: string, default to "gauss" + string for the method used to compute the flux, must be chosen within + ['gauss', 'linear', 'inbuilt'] + + Returns: + -------- + list of: + 1) coordinates at which root finding is successful + 2) corresponding observers + 3) corresponding residual + + list of coordinates at which the root finding failed + + Notes: + ------ + Change the optional values of the corresponding drift methods to change + the number of points used to sample the d+1 box. + """ + + list_of_coords = [] + for i in range(len(num_points)): + list_of_coords.append( np.linspace( ranges[i][0], ranges[i][-1] , num_points[i]) ) + + points = [] + for element in product(*list_of_coords): + points.append(np.array(element)) + + return self.find_observers_points(points, drift_str) + + +class FindObs_root_parallel(object): + """ + Parallel version (streamlined) of FindObs_drift_root + Currently: based on gauss-quadrature with order 3 + interpolation of quantities from micro_model. + """ + def __init__(self, micro_model, box_len): + """ + Parameters: + ----------- + micro_model: instance of micro_model, the micro data to be filtered + + box_len: float, side of the box for computing drift + """ + self.micro_model = micro_model + self.L = box_len + + def set_box_length(self, box_len): + """ + Method to change the width of the filter. + + Parameters: + ----------- + filter_width: float + + Returns: + -------- + None + """ + self.L = box_len + + @staticmethod + def get_tetrad_from_vels(spatial_vels): + """ + Build tetrad orthogonal to unit velocity with spatial velocities spatial_vels + + Parameters: + ----------- + spatial_vels: list of d floats + + Return: + ------- + list of arrays: U + d unit vectors that complete it to a orthonormal basis + """ + spatial_dims = len(spatial_vels) + U = Base.get_rel_vel(spatial_vels) + es =[] + for _ in range(spatial_dims): + es.append(np.zeros(spatial_dims+1)) + for i in range(len(es)): + es[i][i+1] = 1 + tetrad = [U] + for i, vec in enumerate(es): + vec = vec + np.multiply(Base.Mink_dot(vec, U), U) + for j in range(i-1,-1,-1): + vec = vec - np.multiply(Base.Mink_dot(vec, es[j]), es[j]) + es[i] = np.multiply(vec, 1 / np.sqrt(Base.Mink_dot(vec, vec))) + tetrad += [es[i]] + return tetrad + + @staticmethod + def initializer(L, grid, BC): + """ + Initializer for processes in pool. + Build the adapted coordinates and weights: this is independent of specific point + so can be done once for all workers within a process managed by pool. + + Parameters: + ----------- + spatial dimensions: int + L: float + + Notes: + ------ + As this is built as static method, spatial_dims and L cannot be read + from the specific instance of class + """ + global adapt_coords + global totws + global micro_spatial_dims + global micro_grid + global micro_BC + global box_len + + micro_grid = grid + micro_spatial_dims = len(grid)-1 + micro_BC = BC + box_len = L + # print(f'Baryon current passed to initializer: {micro_BC}') + + ps1d = [0, + np.sqrt(3/5), - np.sqrt(3/5)] + ws1d = [8./9. , 5./9. , 5./9.] + xs = [] + ws = [] + for _ in range(micro_spatial_dims+1): + xs.append(ps1d) + ws.append(ws1d) + + adapt_coords = [] + for element in product(*xs): + adapt_coords.append(np.multiply(box_len/2, np.array(element))) + + totws = [] + for element in product(*ws): + temp = 1. + for w in element: + temp *= w + totws.append(temp) + # print('Initialized process in the pool', flush=True) + + @staticmethod + def find_observer_Gauss(point, pos_in_list_points, initial_guess=None): + """ + CPU-bound task to be run in parralel. + The routine combines what has been split into many in the serial + version of this class. + + Parameters: + ----------- + point_pos: list + point_pos[0] contains the coordinates of point where to find observer + + point_pos[1] contains the position in a larger list passed to pool.map() + + Returns: + -------- + Successful root-finding: Boolean True, position of point in list passed to pool.map(), + observer, avg error + + Failed minimization: Boolean False, position of point in list passed to pool.map() + + Notes: + ------ + To be combined with method in MesoModel.find_obsevers_parallel() + """ + # Declaring global vars set up by initializer + global adapt_coords + global totws + global micro_spatial_dims + global micro_grid + global micro_BC + global box_len + + # Building the initial guess + # point = point_pos[0] + # pos_in_list_points = point_pos[1] + # CHECK IF YOU REALLY WANT THIS: USEFUL FOR ROOTVSMIN.PY + if initial_guess is not None: + guess = initial_guess + else: + guess = [] + BC_point = interpn(micro_grid, micro_BC, point)[0] + n_point = np.sqrt(-Base.Mink_dot(BC_point, BC_point)) + U_point = np.multiply( 1 / n_point , BC_point) + # U_point = np.multiply( 1 / self.micro_model.get_interpol_var('n', point), self.micro_model.get_interpol_var('BC', point) + for i in range(1, len(U_point)): + guess.append(U_point[i] / U_point[0]) + + + # Routine to compute drift residual via Gauss-Legendre quadrature. + def residual_gauss(spatial_vels, point): #, micro_model): + # spatial_dims = micro_model.get_spatial_dims() + tetrad = FindObs_root_parallel.get_tetrad_from_vels(spatial_vels) + coords = [] + for coord in adapt_coords: + temp = np.array(point) + for i in range(micro_spatial_dims+1): + temp += np.multiply(coord[i], tetrad[i]) + coords.append(temp) + + integral = np.zeros(1 + micro_spatial_dims) + for i, coord in enumerate(coords): + BC_coord = interpn(micro_grid, micro_BC, coord)[0] + # integral += np.multiply(totws[i], micro_model.get_interpol_var('BC', coord)) + integral += np.multiply(totws[i], BC_coord) + integral *= (box_len /2) ** (micro_spatial_dims+1) + + drifts = [] + for i in range(len(tetrad)-1): + drifts.append(Base.Mink_dot(integral, tetrad[i+1])) + return drifts + + # observer: root of the residual gauss routine + sol = root(residual_gauss, x0 = guess, args = (point)) #, self.micro_model)) + if sol.success: + observer = Base.get_rel_vel(sol.x) + avg_error = np.sum(sol.fun[1]) + avg_error /= len(sol.fun) + if avg_error > 1e-5: + print(f'Warning: residual is large at {point}: ', avg_error, flush=True) + return sol.success, pos_in_list_points, observer, avg_error + if not sol.success: + return sol.success, pos_in_list_points + + def find_observers_parallel(self, points, n_cpus, initial_guesses=None): + """ + Method to run find_observer_Gauss in parallel on a list of points + + Parameters: + ----------- + points: list of d+1 float, d is the spatial dimension of micro_model + + n_cpus: int + number of processes + + Returns: + -------- + + successes: list of lists + successes[0]: positions of point in input list of points + successes[1]: observers found at successful points + successes[2]: average error + + failures: list (typically empty) + position of failed points in input list of points + + there is no default value for n_cpus so that this has to be passed + explicitely in the tests below, or decided at the MesoModel level. + """ + observers = [] + avg_errors = [] + success_pos = [] + failed_pos = [] + + # spatial_dims = self.micro_model.get_spatial_dims() + L = self.L + BC = self.micro_model.vars['BC'] + grid = self.micro_model.domain_vars['points'] + args_for_pool = [ (points[i], i) for i in range(len(points))] + if initial_guesses is not None: + if len(initial_guesses) == len(points): + print('Using provided initial guesses') + args_for_pool = [ (points[i], i, initial_guesses[i]) for i in range(len(points))] + else: + args_for_pool = [ (points[i], i) for i in range(len(points))] + + init = FindObs_root_parallel.initializer + # initargs = (spatial_dims, L) + initargs = (L, grid, BC) + + with mp.Pool(initializer=init, initargs=initargs, processes=n_cpus) as pool: + print('Finding observers in parallel with {} processes\n'.format(pool._processes), flush=True) + for result in pool.starmap(self.find_observer_Gauss, args_for_pool): + if (result[0] == True): + success_pos.append(result[1]) + observers.append(result[2]) + avg_errors.append(result[3]) + elif (result[0] == False): + failed_pos.append(result[1]) + + return [success_pos, observers, avg_errors] , failed_pos + + +class spatial_box_filter(object): + """ + Class for box-filtering the variables of a micro_model. + Work in any dimensions, read on construction from the micro_model. + The observers for covariant filtering are computed separately and must be + passed when the fitlering methods are called. + """ + def __init__(self, micro_model, filter_width): + """ + Parameters: + ---------- + filter_width: float, width of the filter window in each direction + + micro_model: instance of micro_model class, so to have access to its structures + """ + self.micro_model = micro_model + self.spatial_dims = micro_model.get_spatial_dims() + self.filter_width = filter_width + + def set_filter_width(self, filter_width): + """ + Method to change the width of the filter. + + Parameters: + ----------- + filter_width: float + + Returns: + -------- + None + """ + self.filter_width = filter_width + + def set_micro_model(self, micro_model): + """ + Method to change the micro_model to be filtered. + + Parameters: + ----------- + micro_model: instance of micro_model class + + Returns: + -------- + None + """ + self.micro_model = micro_model + self.spatial_dims = micro_model.get_spatial_dims() + + def complete_U_tetrad(self, U): + """ + Build unit vectors orthogonal to observer U + U has to be normalized to -1. + + Parameters: + ----------- + U: np.array of shape (1+spatial_dims,) + + Returns: + list of spatial_dims numpy arrays of shape (1+spatial_dims,) + which complete U to a orthonormal tetrad + """ + es =[] + for _ in range(self.spatial_dims): + es.append(np.zeros(self.spatial_dims+1)) + for i in range(len(es)): + es[i][i+1] = 1. + triad = [] + for i, vec in enumerate(es): #enumerate returns a tuple: so acts by value not reference! + vec = vec + np.multiply(Base.Mink_dot(vec, U), U) + for j in range(i-1,-1,-1): + vec = vec - np.multiply(Base.Mink_dot(vec, es[j]), es[j]) + es[i] = np.multiply(vec, 1/np.sqrt(Base.Mink_dot(vec, vec))) + triad += [es[i]] + return triad + + def filter_var_point(self, var_str, point, observer, sample_method = "gauss", num_points = 3): + """ + First complete the observer to a tetrad at the point. Then build coords for + sample points in the spatial directions adapted to observer. Then approximate the + filter integral as a Riemann sum. + + Parameters: + ----------- + var_str: string corresponding to a variable of the micro_model + + point: list of floats (t,x,y) + + observer: np.array of shape (1+spatial_dims,) . + + sample_method: string, either 'gauss' or 'linear'. Decide how to sample the d-volume for + filtering. + + num_points: integer, number of sample points in each spatial direction. + + Returns: + -------- + nd.array with shape of the variable, the box_filtered quantity. + + Notes: + ------ + Current version uses interpolated values. Should this be too expensive, change + get_interpol_var for get_var_gridpoint! + + Gauss quadrature gives more accurate results for low values of num-points, hence + reduce number of interpolations. + The alternative is to use the gridpoint method with linearly spaced sampling points. + """ + xs = [] + coords = [] + if sample_method == "linear": + for i in range(self.spatial_dims): + xs.append(np.linspace(-self.filter_width /2 , self.filter_width /2, num_points)) + for element in product(*xs): + coords.append(np.array(element)) + + vecs = self.complete_U_tetrad(observer) + sample_points = [] + for coord in coords: + temp = np.array(point) + for i in range(self.spatial_dims): + temp += np.multiply(coord[i], vecs[i]) + sample_points.append(temp) + + filtered_var = np.zeros(self.micro_model.get_interpol_var(var_str, point).shape) + for sample in sample_points: + filtered_var += self.micro_model.get_interpol_var(var_str, sample) + + return np.multiply(filtered_var, 1 / (num_points**self.spatial_dims)) + + elif sample_method == "gauss": + ps1d = [] + ws1d = [] + ws = [] + totws = [] + if num_points == 3: + ps1d = [0, + np.sqrt(3/5), - np.sqrt(3/5)] + ws1d = [8./9. , 5./9. , 5./9.] + elif num_points == 4: + p1 = np.sqrt(3./7 - 2/7 * np.sqrt(6/5)) + p2 = np.sqrt(3./7 + 2/7 * np.sqrt(6/5)) + w1 = (18. + np.sqrt(30) ) / 36. + w2 = (18. - np.sqrt(30) ) / 36. + ps1d = [p1, -p1, p2, -p2] + ws1d = [w1, w1, w2, w2] + elif num_points == 5: + p1 = np.sqrt(5 - 2* np.sqrt(10/7.))/3 + p2 = np.sqrt(5 + 2* np.sqrt(10/7.))/3 + w1 = (322 + 13 * np.sqrt(70)) /900 + w2 = (322 - 13 * np.sqrt(70)) /900 + ps1d = [0, p1, -p1, p2, -p2] + ws1d = [128/225, w1, w1, w2, w2] + else: + print("The method is implemented for Gauss-Legendre quadrature of order 3, 4 and 5 only!") + return None + + for _ in range(self.spatial_dims): + xs.append(ps1d) + ws.append(ws1d) + + for element in product(*xs): + coords.append(np.multiply(self.filter_width/2, np.array(element) )) + + for element in product(*ws): + temp = 1. + for w in element: + temp *= w + totws.append(temp) + + vecs = self.complete_U_tetrad(observer) + sample_points = [] + for coord in coords: + temp = np.array(point) + for i in range(self.spatial_dims): + temp += np.multiply(coord[i], vecs[i]) + sample_points.append(temp) + + filtered_var = np.zeros(self.micro_model.get_interpol_var(var_str, point).shape) + for i, sample in enumerate(sample_points): + filtered_var += totws[i] * self.micro_model.get_interpol_var(var_str, sample) + + return np.multiply(filtered_var, 1 / (2**self.spatial_dims)) + + else: + print("Sample methods to filter variable must be either 'gauss' or 'linear'! ") + return None + + def filter_var_manypoints(self, var_str, points, observers, sample_method = "gauss", num_points = 3): + """ + Method to filter a variable in the micro_model given a list of points and observers. + + Parameters: + ----------- + var_str: string corresponding to a variable in the micro_model + + points: list of N lists of floats, ordered as (t,x,y) + + observers: list of N np.array of shape (1 + spatial_dims,) + + sample_method, num_points: string, int passed to filter method at a point to choose how to + sample the d-volume for box filtering. + + Returns: + -------- + List with the filtered var (np.float or nd.array depending on var_str) at all points + + Notes: + ------ + Current version uses interpolated values. Should this be too expensive, change + get_interpol_var for get_var_gridpoint! + """ + if len(points) != len(observers): + print("The number of points and observers do not match!") + return [] + + filtered_var = [] + for i, point in enumerate(points): + filtered_var.append(self.filter_var_point(var_str, point, observers[i], sample_method = sample_method, num_points = num_points )) + + return filtered_var + + def filter_var_point_inbuilt(self, var_str, point, observer): + """ + Computed the filtered variable using the inbuilt scipy quad method and the interpolated + values of the quantity. + + Parameters: + ----------- + var_str: string, must correspond to a string in the micro_model + + point: list of 1+spatial_dims floats, center of the box-filter + + observer: nd.array of shape (1+spatial_dims,) + + Returns: + -------- + The filtered quantity at the point, or none if the dimensionality is more than 3+1. + + Notes: + ------ + Much slower than corresponding filter_var_manypoints_ip method, this has been implemented + to check the accuracy of the alternative methods. + """ + vecs = self.complete_U_tetrad(observer) + integrand = np.zeros(self.micro_model.get_interpol_var(var_str,point).shape) + error = np.zeros(self.micro_model.get_interpol_var(var_str,point).shape) + + if self.spatial_dims == 2: + def interpol_var_adapt_coord(x, y, *ind): + p = np.array(point) + p += np.multiply(x, vecs[0]) + np.multiply(y, vecs[1]) + return self.micro_model.get_interpol_var(var_str, p)[ind] + + for ind in np.ndindex(integrand.shape): + integrand[ind], error[ind] = integrate.dblquad(interpol_var_adapt_coord, -self.filter_width/2 , self.filter_width/2, -self.filter_width/2, self.filter_width/2, \ + args = (ind), epsabs = 1e-7 )[:] + + elif self.spatial_dims ==3: + def interpol_var_adapt_coord(self, x, y, z, *ind): + p = np.array(point) + p += np.multiply(x, vecs[0]) + np.multiply(y, vecs[1]) + np.multiply(z, vecs[2]) + return self.micro_model.get_interpol_var(var_str, p)[ind] + + for ind in np.ndindex(integrand.shape): + integrand[ind], error[ind] = integrate.tplquad(interpol_var_adapt_coord, -self.filter_width/2, self.filter_width/2, -self.filter_width/2, self.filter_width/2, \ + -self.filter_width/2, self.filter_width/2, args = (point), epsabs = 1e-7 )[:] + else: + print("The inbuilt method is implemented in 2+1 and 3+1 dims only!") + return None + + return np.multiply( integrand, 1 / (self.filter_width**self.spatial_dims)), error + + +class box_filter_parallel(object): + """ + Parallel version (streamlined) of spatial_box_filter + Currently: based on gauss-quadrature with order 3 + interpolation of quantities from micro_model. + """ + def __init__(self, micro_model, filter_width): + """ + Constructor + + Parameters: + ----------- + micro_model: instance of a micro_model class + micro data to be filtered + + filter_width: float + """ + self.micro_model = micro_model + self.spatial_dims = micro_model.get_spatial_dims() + self.filter_width = filter_width + + def set_filter_width(self, filter_width): + """ + Method to change the width of the filter. + + Parameters: + ----------- + filter_width: float + + Returns: + -------- + None + """ + self.filter_width = filter_width + + @staticmethod + def complete_U_tetrad(U): + """ + Given a time-like vector in d+1 dims, build and return + d vectors that complete U to an ON basis. + + Notes: + ------ + This method is static: does not depend on instance vars of the class + But it makes sense to have it belong to the class nonetheless. + """ + spatial_dims = len(U)-1 + es =[] + for _ in range(spatial_dims): + es.append(np.zeros(spatial_dims+1)) + for i in range(len(es)): + es[i][i+1] = 1. + triad = [] + for i, vec in enumerate(es): #enumerate returns a tuple: so acts by value not reference! + vec = vec + np.multiply(Base.Mink_dot(vec, U), U) + for j in range(i-1,-1,-1): + vec = vec - np.multiply(Base.Mink_dot(vec, es[j]), es[j]) + es[i] = np.multiply(vec, 1/np.sqrt(Base.Mink_dot(vec, vec))) + triad += [es[i]] + return triad + + @staticmethod + def initializer(spatial_dims, filter_width, grid, var): + """ + Initializer for processes in pool. + Build the adapted coordinates and weights: this is independent of specific point + so can be done once for all workers within a process managed by pool. + + Currently: gauss-legendre quadrature of order 3. + """ + global abstract_coords + global totws + global micro_spatial_dims + global micro_var + global micro_grid + + + micro_spatial_dims = spatial_dims + micro_grid = grid + micro_var = var + + ps1d = [0, + np.sqrt(3/5), - np.sqrt(3/5)] + ws1d = [8./9. , 5./9. , 5./9.] + + xs = [] + ws = [] + for _ in range(spatial_dims): + xs.append(ps1d) + ws.append(ws1d) + + abstract_coords = [] + for element in product(*xs): + abstract_coords.append(np.multiply(filter_width/2, np.array(element) )) + + totws = [] + for element in product(*ws): + temp = 1. + for w in element: + temp *= w + totws.append(temp) + + @staticmethod + def filter_var_point_gauss(point, observer, pos_in_list_points): + """ + CPU-bound task to be run in parralel. + The routine combines what has been split into many in the serial + version of this class. + + Parameters: + ----------- + packed_args: [[point, observer, [vars_to_be_filtered]], position] + + point: list of floats + the point at which filtering + + observer: nd.array + the observer wrt which filtering + + [vars_to_be_filtered]: list of strs + strings corresponding to vars in micro_model to be filtered + + position: integer + number of specific point etc in larger list passed to pool.map() + + Returns: + -------- + position: same as input + + filtered_vars: list containing vars corresponding to pass list, filtered + at point 'point' wrt observer 'observer' + + """ + # declaring global vars set up by initializer + global abstract_coords + global totws + global micro_spatial_dims + global micro_var + global micro_grid + + + # Unpacking arguments passed by pool.map + # unpacked_args= packed_args[0] + # pos = packed_args[1] + # point = unpacked_args[0] + # observer = unpacked_args[1] + # vars_strs = unpacked_args[2] + + # From abstract to "real" coordinates + vecs = box_filter_parallel.complete_U_tetrad(observer) + sample_points = [] + for coord in abstract_coords: + temp = np.array(point) + for i in range(micro_spatial_dims): + temp += np.multiply(coord[i], vecs[i]) + sample_points.append(temp) + + # Filtering the var + # filtered_vars = [] + # for var in vars_strs: + # filtered_var = np.zeros(self.micro_model.get_var_gridpoint(var,0,0,0).shape) + filtered_var = np.zeros(micro_var[tuple([ 0 for _ in range(len(micro_grid))])].shape) + for i, sample in enumerate(sample_points): + filtered_var += totws[i] * interpn(micro_grid, micro_var, sample)[0] + filtered_var = np.multiply(filtered_var, 1 / (2**micro_spatial_dims)) + # filtered_vars.append(filtered_var) + + # Different variables are returned as list (ordered as var_strs) + # Safer to return a dictionary: check redux in performance though. + return pos_in_list_points, filtered_var + + def filter_var_parallel(self, points_observers, var, n_cpus): + """ + Method to run filter_vars_point_gauss in parallel given a list of points, + observers and vars. + + Parameters: + ----------- + + list_packed_args: [[point,observer, [vars_to_be_filtered]]] + + point: list of floats + + observer: nd.array + + [vars_to_be_filtered]: list of strs + each item must match a var in micromodel + + n_cpus: int + number of processes + + Returns: + -------- + position_in_list: list of integers + position in original list of points at which filtering + Required as pool.map not necessarily returns processes in order + + filtered_vars: list of lists containing filtered vars + + there is no default value for n_cpus so that this has to be passed + explicitely in the tests below, or decided at the MesoModel level. + """ + position_in_list = [] + filtered_var = [] + args_for_pool = [ tuple([*points_observers[i], i]) for i in range(len(points_observers))] + # print(args_for_pool[0]) + + init = box_filter_parallel.initializer + micro_grid = self.micro_model.domain_vars['points'] + micro_var = self.micro_model.vars[var] + initargs=(self.spatial_dims, self.filter_width, micro_grid, micro_var) + + with mp.Pool(initializer=init, initargs=initargs, processes=n_cpus) as pool: + print('Filtering {} in parallel with {} processes\n'.format(var, pool._processes), flush=True) + for result in pool.starmap(self.filter_var_point_gauss, args_for_pool): + position_in_list.append(result[0]) + filtered_var.append(result[1]) + + return position_in_list, filtered_var + + +if __name__ == '__main__': + + ######################################################## + # TESTING SERIAL IMPLEMENTATION + ######################################################## + # CPU_start_time = time.process_time() + + # FileReader = METHOD_HDF5('../Data/test_res100/') + # micro_model = IdealMHD_2D() + # FileReader.read_in_data(micro_model) + # micro_model.setup_structures() + + # find_obs = FindObs_drift_root(micro_model, 0.001) + # # find_obs = FindObs_flux_min(micro_model, 0.001) + # filter = spatial_box_filter(micro_model, 0.003) + + # vars = ['BC','SETfl', 'Fab', 'SETem'] + # points = [[1.502,0.3,0.5],[1.503,0.4,0.2]] + # observers = find_obs.find_observers_points(points)[0][1] + + # for i in range(len(points)): + # for var in vars: + # CPU_start_time = time.process_time() + # filtvar1 = filter.filter_var_point_inbuilt(var, points[i], observers[i]) + # inbuilt_time = time.process_time() - CPU_start_time + + # CPU_start_time = time.process_time() + # filtvar2 = filter.filter_var_point(var, points[i], observers[i]) + # gauss_time = time.process_time() - CPU_start_time + # print(f"CPU time speed-up to filter {var} with Gauss method at {points[i]} is {inbuilt_time/gauss_time}. ") + # print(f'Filtered quantity with Gauss is \n {filtvar2}') + # print(f"Difference with method based on inbuilt integration is: \n {filtvar1[0] - filtvar2}") + # print('\n************************\n') + + ######################################################## + # TESTING PARALLEL IMPLEMENTATION + ######################################################## + FileReader = METHOD_HDF5('../Data/test_res100/') + micro_model = IdealHD_2D() + FileReader.read_in_data(micro_model) + micro_model.setup_structures() + + # setting up the points for testing - pass all the points within a range + t_range = [1.502, 1.504] + x_range = [0.05, 0.15] + y_range = [0.05, 0.15] + + patch_min = [t_range[0], x_range[0], y_range[0]] + patch_max = [t_range[1], x_range[1], y_range[1]] + idx_mins = Base.find_nearest_cell(patch_min, micro_model.domain_vars['points']) + idx_maxs = Base.find_nearest_cell(patch_max, micro_model.domain_vars['points']) + + ts = micro_model.domain_vars['t'][idx_mins[0]:idx_maxs[0]] + xs = micro_model.domain_vars['x'][idx_mins[1]:idx_maxs[1]] + ys = micro_model.domain_vars['y'][idx_mins[2]:idx_maxs[2]] + + points = [] + for elem in product(ts,xs,ys): + points.append(list(elem)) + + print('Number of points: {}'.format(len(points))) + + # Now find observers - serial version + # start_time = time.perf_counter() + # find_obs_serial = FindObs_drift_root(micro_model, 0.001) + # observers = find_obs_serial.find_observers_points(points) + # serial_time = time.perf_counter() - start_time + # print('Serial time: {}\n'.format(serial_time)) + + # Now find observers - parallel version + n_cpus = os.cpu_count() + start_time = time.perf_counter() + find_obs_parallel = FindObs_root_parallel(micro_model, 0.001) + point = points[0] + result, failed = find_obs_parallel.find_observers_parallel(points, n_cpus) + print('Number of points failed: {}'.format(len(failed))) + parallel_time = time.perf_counter() - start_time + print('Finished finding observers. Parallel time: {}\n'.format(parallel_time)) + # print('Speed-up factor: {}\n'.format(serial_time/parallel_time)) + + + observers = result[1] + vars = ['BC', 'SET'] + # Filtering serial + # start_time = time.perf_counter() + # serial_filter = spatial_box_filter(micro_model, 0.003) + # for var in vars: + # serial_filter.filter_var_manypoints(var, points, observers) + # serial_time = time.perf_counter() - start_time + # print('Finished filtering in serial, time-taken: {}'.format(serial_time)) + + # Preparing args for parallel filter routine + args_list = [] + for elem in zip(points, observers): + # args_list.append([*elem, vars]) + args_list.append(tuple(elem)) + + start_time = time.perf_counter() + parallel_filter = box_filter_parallel(micro_model, 0.003) + for var in vars: + parallel_filter.filter_var_parallel(args_list, var, n_cpus) + parallel_time = time.perf_counter() - start_time + print('Finished filtering in parallel, time-taken: {}'.format(parallel_time)) + # print('Speed-up factor: {}\n'.format(serial_time/parallel_time)) + diff --git a/master_files/MesoModels.py b/master_files/MesoModels.py new file mode 100644 index 0000000..0ec1b94 --- /dev/null +++ b/master_files/MesoModels.py @@ -0,0 +1,2906 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Mar 27 18:53:53 2023 + +@author: Marcus +""" + +import numpy as np +from scipy.interpolate import interpn +import multiprocessing as mp +from multimethod import multimethod + +import matplotlib.pyplot as plt +import seaborn as sns +import scipy.stats as scst +from scipy.linalg import det + + +from system.BaseFunctionality import * +from MicroModels import * +from FileReaders import * +from Filters import * +from Visualization import * +from Analysis import * + +class NonIdealHydro2D(object): + + def __init__(self, MicroModel, ObsFinder, Filter, interp_method = "linear"): + self.MicroModel = MicroModel + self.ObsFinder = ObsFinder + self.Filter = Filter + self.spatial_dims = 2 + self.n_dims = self.spatial_dims + 1 + self.interp_method = interp_method + + self.domain_var_strs = ('Nt','Nx','Ny','dT','dX','dY','points') + self.domain_vars = dict.fromkeys(self.domain_var_strs) + + #Dictionary for 'local' variables, + #obtained from filtering the appropriate MicroModel variables + self.filter_var_strs = ("BC","SET") + self.filter_vars = dict.fromkeys(self.filter_var_strs) + + #Dictionary for MesoModel variables + self.meso_var_strs = ("U","U_coords","U_errors","T~","N") + self.meso_vars = dict.fromkeys(self.meso_var_strs) + + #Strings for 'non-local' variables - ones we need to take derivatives of + self.nonlocal_var_strs = ("U","T~") + + #Dictionary for derivative variables - calculated by finite differencing + self.deriv_var_strs = ("dtU","dxU","dyU","dtT~","dxT~","dyT~") + self.deriv_vars = dict.fromkeys(self.deriv_var_strs) + + #Dictionary for dissipative residuals - from the NonId-SET that we take + #projections of + self.diss_residual_strs = ("Pi","q","pi") + self.diss_residuals = dict.fromkeys(self.diss_residual_strs) + + self.diss_var_strs = ("Theta","Omega","Sigma") + self.diss_vars = dict.fromkeys(self.diss_residual_strs) + + self.diss_coeff_strs = ("Zeta", "Kappa", "Eta") + self.diss_coeffs = dict.fromkeys(self.diss_coeff_strs) + + self.coefficient_strs = ("Gamma") + self.coefficients = dict.fromkeys(self.diss_coeff_strs) + self.coefficients['Gamma'] = 4.0/3.0 + + #Dictionary for all vars - useful for e.g. plotting + self.all_var_strs = self.filter_var_strs + self.meso_var_strs + self.deriv_var_strs\ + + self.diss_residual_strs + self.diss_var_strs + self.diss_coeff_strs + + # Stencils for finite-differencing + self.cen_SO_stencil = [1/12, -2/3, 0, 2/3, -1/12] + self.cen_FO_stencil = [-1/2, 0, 1/2] + self.fw_FO_stencil = [-1, 1] + self.bw_FO_stencil = [-1, 1] + + self.metric = np.zeros((3,3)) + self.metric[0,0] = -1 + self.metric[1,1] = self.metric[2,2] = +1 + + # Run some compatability test... + compatible = True + if self.spatial_dims != self.MicroModel.spatial_dims: + compatible = False + for filter_var_str in self.filter_var_strs: + if not (filter_var_str in MicroModel.vars.keys()): + compatible = False + + if compatible: + print("Meso and Micro models are compatible!") + else: + print("Meso and Micro models are incompatible!") + + def get_all_var_strs(self): + return self.all_var_strs + + def get_model_name(self): + return 'NonIdealHydro2D' + + def find_observers(self, num_points, ranges):#, spacing): + # self.meso_vars['U_coords'], self.meso_vars['U'], self.meso_vars['U_errors'] = \ + # self.ObsFinder.find_observers(num_points, ranges, spacing)[0] + self.meso_vars['U_coords'], self.meso_vars['U'], self.meso_vars['U_errors'] = \ + self.ObsFinder.find_observers_ranges(num_points, ranges)[0] + self.domain_vars['Nt'], self.domain_vars['Nx'], self.domain_vars['Ny'] = num_points[:] + self.domain_vars['dT'] = (ranges[0][-1] - ranges[0][0]) / self.domain_vars['Nt'] + self.domain_vars['dX'] = (ranges[1][-1] - ranges[1][0]) / self.domain_vars['Nx'] + self.domain_vars['dY'] = (ranges[2][-1] - ranges[2][0]) / self.domain_vars['Ny'] + self.domain_vars['points'] = [np.linspace(ranges[0][0], ranges[0][-1], num_points[0]),\ + np.linspace(ranges[1][0], ranges[1][-1], num_points[1]),\ + np.linspace(ranges[2][0], ranges[2][-1], num_points[2])] + + def setup_variables(self): + Nt, Nx, Ny = self.domain_vars['Nt'], self.domain_vars['Nx'], self.domain_vars['Ny'] + n_dims = self.spatial_dims+1 + self.meso_vars['U_coords'] = np.array(self.meso_vars['U_coords']).reshape([Nt, Nx, Ny, n_dims]) + self.meso_vars['U'] = np.array(self.meso_vars['U']).reshape([Nt, Nx, Ny, n_dims]) + self.meso_vars['U_errors'] = np.array(self.meso_vars['U_errors']).reshape([Nt, Nx, Ny, 2]) + self.meso_vars['T~'] = np.zeros((Nt, Nx, Ny)) + self.meso_vars['N'] = np.zeros((Nt, Nx, Ny)) + + self.filter_vars['BC'] = np.zeros((Nt, Nx, Ny, n_dims)) + self.filter_vars['SET'] = np.zeros((Nt, Nx, Ny, n_dims,n_dims)) + + for nonlocal_var_str in self.nonlocal_var_strs: + self.deriv_vars['dt'+nonlocal_var_str] = np.zeros_like(self.meso_vars[nonlocal_var_str]) + self.deriv_vars['dx'+nonlocal_var_str] = np.zeros_like(self.meso_vars[nonlocal_var_str]) + self.deriv_vars['dy'+nonlocal_var_str] = np.zeros_like(self.meso_vars[nonlocal_var_str]) + + self.diss_residuals['Pi'] = np.zeros((Nt, Nx, Ny)) + self.diss_vars['Theta'] = np.zeros((Nt, Nx, Ny)) + self.diss_residuals['q'] = np.zeros((Nt, Nx, Ny, n_dims)) + self.diss_vars['Omega'] = np.zeros((Nt, Nx, Ny, n_dims)) + self.diss_residuals['pi'] = np.zeros((Nt, Nx, Ny, n_dims, n_dims)) + self.diss_vars['Sigma'] = np.zeros((Nt, Nx, Ny, n_dims, n_dims)) + + # Single value for each coefficient (per data point) for now... + self.diss_coeffs['Zeta'] = np.zeros((Nt, Nx, Ny)) + self.diss_coeffs['Kappa'] = np.zeros((Nt, Nx, Ny, n_dims)) + self.diss_coeffs['Eta'] = np.zeros((Nt, Nx, Ny, n_dims, n_dims)) + + self.vars = self.filter_vars + self.vars.update(self.meso_vars) + self.vars.update(self.deriv_vars) + self.vars.update(self.diss_residuals) + self.vars.update(self.diss_vars) + self.vars.update(self.diss_coeffs) + + def p_from_EoS(self, rho, n): + """ + Calculate pressure from EoS using rho (energy density) and n (number density) + """ + p = (self.coefficients['Gamma']-1)*(rho-n) + return p + + def filter_micro_variables(self): + """ + 'Spatially' average required variables from the micromodel w.r.t. + the observers that have been found. + """ + for h in range(self.domain_vars['Nt']): + for i in range(self.domain_vars['Nx']): + for j in range(self.domain_vars['Ny']): + self.filter_vars['BC'][h,i,j] = self.Filter.filter_var_point('BC', self.meso_vars['U_coords'][h,i,j], + self.meso_vars['U'][h,i,j]) + self.filter_vars['SET'][h,i,j] = self.Filter.filter_var_point('SET', self.meso_vars['U_coords'][h,i,j], + self.meso_vars['U'][h,i,j]) + + # self.filter_vars['n'][h,i,j] =\ + # self.Filter.filter_prim_var(self.meso_vars['U_coords'][h,i,j], self.meso_vars['U'][h,i,j], 'n') + # self.filter_vars['SET'][h,i,j] =\ + # self.Filter.filter_struc(self.meso_vars['U_coords'][h,i,j], self.meso_vars['U'][h,i,j], 'SET') + + # Should be able to convert here to only 1 filter function... (not prim/struct) + # for filter_var_str in self.filter_var_strs: + # filter_args = [(coord, U, filter_var_str) for coord, U in zip(self.U_coords, self.Us)] + # with Pool(2) as p: + # self.micro_vars[micro_var_str] = p.starmap(self.Filter.filter_var, filter_args) + # self.micro_vars[micro_var_str] = p.starmap(self.Filter.filter_prim_var, filter_args) + + def calculate_derivatives(self): + """ + Calculate the required derivatives of MesoModel variables for + constructing the non-ideal terms. + """ + for nonlocal_var_str in self.nonlocal_var_strs: + self.calculate_time_derivatives(nonlocal_var_str) + self.calculate_x_derivatives(nonlocal_var_str) + self.calculate_y_derivatives(nonlocal_var_str) + + def calculate_time_derivatives(self, nonlocal_var_str): + deriv_var_str = 'dt'+nonlocal_var_str + stencil = self.cen_FO_stencil + samples = [-1,0,1] + for h in range(self.domain_vars['Nt']): + if h == 0: + stencil = self.fw_FO_stencil + samples = [0,1] + if h == (self.domain_vars['Nt']-1): + stencil = self.fw_FO_stencil + samples = [-1,0] + for i in range(self.domain_vars['Nx']): + for j in range(self.domain_vars['Ny']): + for s in range(len(samples)): + self.deriv_vars[deriv_var_str][h,i,j] \ + += (stencil[s]*self.meso_vars[nonlocal_var_str][h+samples[s],i,j]) / self.domain_vars['dT'] + + def calculate_x_derivatives(self, nonlocal_var_str): + deriv_var_str = 'dx'+nonlocal_var_str + stencil = self.cen_FO_stencil + samples = [-1,0,1] + for h in range(self.domain_vars['Nt']): + for i in range(self.domain_vars['Nx']): + if i == 0: + stencil = self.fw_FO_stencil + samples = [0,1] + if i == (self.domain_vars['Nx']-1): + stencil = self.fw_FO_stencil + samples = [-1,0] + for j in range(self.domain_vars['Ny']): + for s in range(len(samples)): + # print((stencil[s]*self.meso_vars[nonlocal_var_str][h,i+samples[s],j]) / self.domain_vars['dX']) + self.deriv_vars[deriv_var_str][h,i,j] \ + += (stencil[s]*self.meso_vars[nonlocal_var_str][h,i+samples[s],j]) / self.domain_vars['dX'] + + def calculate_y_derivatives(self, nonlocal_var_str): + deriv_var_str = 'dy'+nonlocal_var_str + stencil = self.cen_FO_stencil + samples = [-1,0,1] + for h in range(self.domain_vars['Nt']): + for i in range(self.domain_vars['Nx']): + for j in range(self.domain_vars['Ny']): + if j == 0: + stencil = self.fw_FO_stencil + samples = [0,1] + if j == (self.domain_vars['Ny']-1): + stencil = self.fw_FO_stencil + samples = [-1,0] + for s in range(len(samples)): + self.deriv_vars[deriv_var_str][h,i,j] \ + += (stencil[s]*self.meso_vars[nonlocal_var_str][h,i,j+samples[s]]) / self.domain_vars['dY'] + + def calculate_dissipative_residuals(self, h, i, j): + """ + Returns the inferred (residual) values of the + dissipative terms from the filtered MicroModel SET. + + Parameters + ---------- + indices : TYPE + DESCRIPTION. + + Returns + ------- + Pi_res : scalar float + Bulk viscosity + q_res : (d+1) vector of floats + Heat flux. + pi_res : (d+1)x(d+1) tensor of floats + Shear viscosity. + + """ + # Move this + # h, i, j = indices + # Filter the scalar fields + BC = self.filter_vars['BC'][h,i,j] + N = np.sqrt(-Base.Mink_dot(BC, BC)) + self.meso_vars['N'][h,i,j] = N + Id_SET = self.filter_vars['SET'][h,i,j] + U = self.meso_vars['U'][h,i,j] + + # Do required projections of SET + h_mu_nu = Base.orthogonal_projector(U, self.metric) + rho_res = Base.project_tensor(U,U,Id_SET) + + # Set Meso temperature from filtered quantities using EoS + p_tilde = self.p_from_EoS(rho_res, N) + T_tilde = p_tilde/N + self.meso_vars['T~'][h,i,j] = T_tilde + + # Calculate dissipative residual with tensor manipulation + q_res = np.einsum('ij,i,jk',Id_SET,U,h_mu_nu) + tau_res = np.einsum('ij,ik,jl',Id_SET,h_mu_nu,h_mu_nu) + tau_trace = np.trace(tau_res) + Pi_res = tau_trace - p_tilde + pi_res = tau_res - np.dot((p_tilde + Pi_res),h_mu_nu) + + # print(Pi_res, q_res, pi_res) + + self.diss_residuals['Pi'][h,i,j] = Pi_res + self.diss_residuals['q'][h,i,j] = q_res + self.diss_residuals['pi'][h,i,j] = pi_res + + def calculate_dissipative_variables(self, h, i, j): + """ + Calculates the non-ideal, dissipation terms (without coefficeints) + for the non-ideal MesoModel SET. + + Parameters + ---------- + indices : list of ints + Indices of data-point. + coord : list of floats + Coordinates in (t,x,y). + + Returns + ------- + Decomposition of the observer velocity: + + Theta : float (scalar) + Divergence (isotropic). + omega : vector of floats + Transverse momentum. + sigma : (d+1)x(d+1) tensor of floats + Symmetric, trace-free. + + """ + # h, i, j = indices + + T = self.meso_vars['T~'][h, i, j] + dtT = self.deriv_vars['dtT~'][h, i, j] + dxT = self.deriv_vars['dxT~'][h, i, j] + dyT = self.deriv_vars['dyT~'][h, i, j] + + # U = self.meso_vars['U'][h,i,j][:] + Ut, Ux, Uy = self.meso_vars['U'][h,i,j][:] + + # print(Ut, Ux, Uy) + + # dtU = self.deriv_vars['dtU'][h,i,j] + dtUt, dtUx, dtUy = self.deriv_vars['dtU'][h,i,j][:] + dxUt, dxUx, dxUy = self.deriv_vars['dxU'][h,i,j][:] + dyUt, dyUx, dyUy = self.deriv_vars['dyU'][h,i,j][:] + + # Need to do this with Einsum... + Theta = dtUt + dxUx + dyUy + a = np.array([Ut*dtUt + Ux*dxUt + Uy*dyUt, Ut*dtUx + Ux*dxUx + Uy*dyUx, Ut*dtUy + Ux*dxUy + Uy*dyUy]) + + Omega = np.array([dtT, dxT, dyT]) + np.multiply(T,a) + Sigma = np.array([[2*dtUt - (2/3)*Theta, dtUx + dxUt, dtUy + dyUt],\ + [dxUt + dtUx, 2*dxUx - (2/3)*Theta, dxUy + dyUx], + [dyUt + dtUy, dyUx + dxUy, 2*dyUy - (2/3)*Theta]]) + + self.diss_vars['Theta'][h,i,j] = Theta + self.diss_vars['Omega'][h,i,j] = Omega + self.diss_vars['Sigma'][h,i,j] = Sigma + + def calculate_dissipative_coefficients(self): + Nt, Nx, Ny = self.domain_vars['Nt'], self.domain_vars['Nx'], self.domain_vars['Ny'] + # parallel_args = [(h, i, j) for h in range(Nt) for i in range(Nx) for j in range(Ny)] + # # print(parallel_args) + # with Pool(2) as p: + # p.starmap(self.calculate_dissipative_residuals, parallel_args) + # p.starmap(self.calculate_dissipative_variables, parallel_args) + + # self.diss_coeffs['Zeta'] = -self.diss_residuals['Pi'] / self.diss_vars['Theta'] + for h in range(self.domain_vars['Nt']): + for i in range(self.domain_vars['Nx']): + for j in range(self.domain_vars['Ny']): + self.calculate_dissipative_residuals(h,i,j) + + # Derivative calculations need to be done after all residuals + self.calculate_derivatives() + for h in range(self.domain_vars['Nt']): + for i in range(self.domain_vars['Nx']): + for j in range(self.domain_vars['Ny']): + self.calculate_dissipative_variables(h,i,j) + self.diss_coeffs['Zeta'][h,i,j] = -self.diss_residuals['Pi'][h,i,j] / self.diss_vars['Theta'][h,i,j] + for k in range(self.n_dims): + self.diss_coeffs['Kappa'][h,i,j,k] = -self.diss_residuals['q'][h,i,j,k] / self.diss_vars['Omega'][h,i,j,k] + for l in range(self.n_dims): + self.diss_coeffs['Eta'][h,i,j,k,l] = -self.diss_residuals['pi'][h,i,j,k,l] / self.diss_vars['Sigma'][h,i,j,k,l] + + for diss_coeff_str in self.diss_coeff_strs: + coeffs_handle = open(diss_coeff_str+'.pickle', 'wb') + pickle.dump(self.diss_coeffs[diss_coeff_str], coeffs_handle, protocol=pickle.HIGHEST_PROTOCOL) + +class resMHD2D(object): + """ + Idea: alternative meso_models (e.g. with different closures scheme) will + only have to change the method model_residuals and list of non_local_vars + (possibly but less likely decompose_structure_gridpoint) + """ + def __init__(self, micro_model, find_obs, filter, interp_method = 'linear'): + """ + ADD DESCRIPTION OF THIS + """ + self.micro_model = micro_model + self.find_obs = find_obs + self.filter = filter + self.spatial_dims = 2 + self.interp_method = interp_method + + self.domain_int_strs = ('Nt','Nx','Ny') + self.domain_float_strs = ("Tmin","Tmax","Xmin","Xmax","Ymin","Ymax","Dt","Dx","Dy") + self.domain_array_strs = ("T","X","Y","Points") + self.domain_vars = dict.fromkeys(self.domain_int_strs+self.domain_float_strs+self.domain_array_strs) + for var in self.domain_vars: + self.domain_vars[var] = [] + + # This is the Levi-Civita symbol, not tensor, so be careful when using it + self.Levi3D = np.array([[[ np.sign(i-j) * np.sign(j- k) * np.sign(k-i) \ + for k in range(3)]for j in range(3) ] for i in range(3) ]) + + self.metric = np.zeros((3,3)) + self.metric[0,0] = -1 + self.metric[1,1] = self.metric[2,2] = +1 + + self.filter_vars_strs = ['U', 'U_errors', 'U_success'] + self.filter_vars = dict.fromkeys(self.filter_vars_strs) + for var in self.filter_vars: + var = [] + + self.meso_structures_strs = ['SETfl', 'SETem', 'BC', 'Fab'] + self.meso_structures = dict.fromkeys(self.meso_structures_strs) + for var in self.meso_structures: + self.meso_structures[var] = [] + + self.meso_scalars_strs = ['eps_tilde', 'n_tilde', 'p_tilde', 'p_filt', 'eos_res', 'Pi_res', 'sigma_tilde', 'b_tilde', 'T_tilde'] + self.meso_vectors_strs = ['u_tilde', 'q_res', 'e_tilde', 'j', 'J_tilde'] + self.meso_r2tensors_strs = ['pi_res'] + self.meso_vars_strs = self.meso_scalars_strs + self.meso_vectors_strs + self.meso_r2tensors_strs + self.meso_vars = dict.fromkeys(self.meso_vars_strs) + + self.coefficient_strs = ["Gamma"] + self.coefficients = dict.fromkeys(self.coefficient_strs) + self.coefficients['Gamma'] = 4.0/3.0 + + # Dictionary with stencils and coefficients for finite differencing + self.differencing = dict.fromkeys((1, 2)) + self.differencing[1] = dict.fromkeys(['fw', 'bw', 'cen']) + self.differencing[1]['fw'] = {'coefficients' : [-1, 1] , 'stencil' : [0, 1]} + self.differencing[1]['bw'] = {'coefficients' : [1, -1] , 'stencil' : [0, -1]} + self.differencing[1]['cen'] = {'coefficients' : [-1/2., 0, 1/2.] , 'stencil' : [-1, 0, 1]} + self.differencing[2] = dict.fromkeys(['fw', 'bw', 'cen']) + self.differencing[2]['fw'] = {'coefficients' : [-3/2., 2., -1/2.] , 'stencil' : [0, 1, 2]} + self.differencing[2]['bw'] = {'coefficients' : [3/2., -2., 1/2.] , 'stencil' : [0, -1, -2]} + self.differencing[2]['cen'] = {'coefficients' : [1/12., -2/3., 0., 2/3., -1/12.] , 'stencil' : [-2, -1, 0, 1, 2]} + + + # dictionary with non-local quantities (keys must match one of meso_vars or structure) + self.nonlocal_vars_strs = ['u_tilde', 'Fab', 'T_tilde'] + # self.nonlocal_vars_strs = ['u_tilde', 'Fab'] + Dstrs = ['D_' + i for i in self.nonlocal_vars_strs] + self.deriv_vars = dict.fromkeys(Dstrs) + + # Additional, closure-scheme specific vars must be added appropriately using model_residuals() + + # self.all_var_strs = self.meso_vars_strs + Dstrs + self.meso_structures_strs + # Run some compatibility test... + compatible = True + error = '' + if self.spatial_dims != micro_model.get_spatial_dims(): + compatible = False + error += '\nError: different dimensions.' + for struct in self.meso_structures_strs: + if struct not in self.micro_model.get_structures_strs(): + compatible = False + error += f'\nError: {struct} not in micro_model!' + + if not compatible: + print("Meso and Micro models are incompatible:"+error) + + def get_all_var_strs(self): + return list(self.meso_vars.keys()) + list(self.deriv_vars.keys()) + list(self.meso_structures.keys()) + + def get_gridpoints(self): + """ + Pretty self-explanatory + """ + return self.domain_vars['Points'] + + def get_interpol_var(self, var, point): + """ + Returns the interpolated variables at the point. + + Parameters + ---------- + var: str corresponding to meso_structure, meso_vars or deriv_vars + + point : list of floats + ordered coordinates: t,x,y + + Return + ------ + Interpolated values/arrays corresponding to variable. + Empty list if none of the variables is not meso_structures/meso_vars or deriv_vars of the model. + + Notes + ----- + Interpolation gives errors when applied to boundary + """ + if var in self.meso_structures: + return interpn(self.domain_vars['Points'], self.meso_structures[var], point, method = self.interp_method)[0] + elif var in self.meso_vars: + return interpn(self.domain_vars['Points'], self.meso_vars[var], point, method = self.interp_method)[0] + elif var in self.deriv_vars: + return interpn(self.domain_vars['Points'], self.deriv_vars[var], point, method = self.interp_method)[0] + elif var in self.filter_vars: + return interpn(self.domain_vars['Points'], self.filter_vars[var], point, method = self.interp_method)[0] + else: + print('{} does not belong to either meso_structures/meso_vars/deriv_vars/filter_vars. Check! (Method: get_interpol_var(var, point))'.format(var)) + + @multimethod + def get_var_gridpoint(self, var: str, h: object, i: object, j: object): + """ + Returns variable corresponding to input 'var' at gridpoint + identified by h,i,j + + Parameters: + ----------- + var: string corresponding to structure, meso_vars or deriv_vars + + h,i,j: int + integers corresponding to position on the grid. + + Returns: + -------- + Values or arrays corresponding to variable evaluated at the closest + grid-point to input 'point'. + + Notes: + ------ + This method is useful e.g. for plotting the raw data. + """ + if var in self.meso_structures: + return self.meso_structures[var][h,i,j] + elif var in self.meso_vars: + return self.meso_vars[var][h,i,j] + elif var in self.deriv_vars: + return self.deriv_vars[var][h,i,j] + elif var in self.filter_vars: + return self.filter_vars[var][h,i,j] + else: + print('{} does not belong to either meso_structures/meso_vars/deriv_vars/filter_vars. Check! (Method: get_var_gridpoint(h,i,j))'.format(var)) + return None + + @multimethod + def get_var_gridpoint(self, var: str, point: object): + """ + Returns variable corresponding to input 'var' at gridpoint + closest to input 'point'. + + Parameters: + ----------- + vars: string corresponding to structure, meso_vars or deriv_vars + + point: list of 2+1 floats + + Returns: + -------- + Values or arrays corresponding to variable evaluated at the closest + grid-point to input 'point'. + + Notes: + ------ + This method should be used in case using interpolated values becomes + too expensive. + """ + indices = Base.find_nearest_cell(point, self.domain_vars['Points']) + if var in self.meso_structures: + return self.meso_structures[var][tuple(indices)] + elif var in self.meso_vars: + return self.meso_vars[var][tuple(indices)] + elif var in self.deriv_vars: + return self.deriv_vars[var][tuple(indices)] + elif var in self.filter_vars: + return self.filter_vars[var][tuple(indices)] + else: + print('{} does not belong to either meso_structures/meso_vars/deriv_vars/filter_vars. Check! (Method: get_var_gridpoint(point))'.format(var)) + return None + + def get_model_name(self): + return 'resMHD2D' + + def set_find_obs_method(self, find_obs): + self.find_obs = find_obs + + def setup_meso_grid(self, patch_bdrs, coarse_factor = 1): + """ + Builds the meso_model grid using the micro_model grid points contained in + the input patch (defined via 'patch_bdrs'). The method allows for coarse graining + the grid in the spatial directions ONLY. + Then store the info about the meso grid and set up arrays of definite rank and size + for the quantities needed later. + + Parameters: + ----------- + patch_bdrs: list of lists of two floats, + [[tmin, tmax],[xmin,xmax],[ymin,ymax]] + + coarse_factor: integer + + Notes: + ------ + If the patch_bdrs are larger than micro_grid, the method will not set-up the meso_grid, + and an error message is printed. This is extra safety measure! + """ + + # Is the patch within the micro_model domain? + conditions = patch_bdrs[0][0] < self.micro_model.domain_vars['tmin'] or \ + patch_bdrs[0][1] > self.micro_model.domain_vars['tmax'] or \ + patch_bdrs[1][0] < self.micro_model.domain_vars['xmin'] or \ + patch_bdrs[1][1] > self.micro_model.domain_vars['xmax'] or \ + patch_bdrs[2][0] < self.micro_model.domain_vars['ymin'] or \ + patch_bdrs[2][1] > self.micro_model.domain_vars['ymax'] + + if conditions: + print('Error: the input region for filtering is larger than micro_model domain!') + return None + + #Find the nearest cell to input patch bdrs + patch_min = [patch_bdrs[0][0], patch_bdrs[1][0], patch_bdrs[2][0]] + patch_max = [patch_bdrs[0][1], patch_bdrs[1][1], patch_bdrs[2][1]] + idx_mins = Base.find_nearest_cell(patch_min, self.micro_model.domain_vars['points']) + idx_maxs = Base.find_nearest_cell(patch_max, self.micro_model.domain_vars['points']) + + # Set meso_grid spacings + self.domain_vars['Dt'] = self.micro_model.domain_vars['dt'] + self.domain_vars['Dx'] = self.micro_model.domain_vars['dx'] * coarse_factor + self.domain_vars['Dy'] = self.micro_model.domain_vars['dy'] * coarse_factor + + # Building the meso_grid + h, i, j = idx_mins[0], idx_mins[1], idx_mins[2] + while h <= idx_maxs[0]: + t = self.micro_model.domain_vars['t'][h] + self.domain_vars['T'].append(t) + h += 1 + while i <= idx_maxs[1]: + x = self.micro_model.domain_vars['x'][i] + self.domain_vars['X'].append(x) + i += coarse_factor + while j <= idx_maxs[2]: + y = self.micro_model.domain_vars['y'][j] + self.domain_vars['Y'].append(y) + j += coarse_factor + + # Saving the info about the meso_grid + self.domain_vars['Points'] = [self.domain_vars['T'], self.domain_vars['X'], self.domain_vars['Y']] + self.domain_vars['Tmin'] = np.amin(self.domain_vars['T']) + self.domain_vars['Xmin'] = np.amin(self.domain_vars['X']) + self.domain_vars['Ymin'] = np.amin(self.domain_vars['Y']) + self.domain_vars['Tmax'] = np.amax(self.domain_vars['T']) + self.domain_vars['Xmax'] = np.amax(self.domain_vars['X']) + self.domain_vars['Ymax'] = np.amax(self.domain_vars['Y']) + self.domain_vars['Nt'] = len(self.domain_vars['T']) + self.domain_vars['Nx'] = len(self.domain_vars['X']) + self.domain_vars['Ny'] = len(self.domain_vars['Y']) + + # Setup arrays for structures + Nt, Nx, Ny = self.domain_vars['Nt'], self.domain_vars['Nx'], self.domain_vars['Ny'] + self.meso_structures['BC'] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1)) + self.meso_structures['SETfl'] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1, self.spatial_dims+1)) + self.meso_structures['SETem'] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1, self.spatial_dims+1)) + self.meso_structures['Fab'] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1, self.spatial_dims+1)) + + # Setup arrays for meso_vars + for str in self.meso_scalars_strs: + self.meso_vars[str] = np.zeros((Nt, Nx, Ny)) + for str in self.meso_vectors_strs: + self.meso_vars[str] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1)) + for str in self.meso_r2tensors_strs: + self.meso_vars[str] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1, self.spatial_dims+1)) + + # Setup arrays for filter_vars + self.filter_vars['U'] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1)) + self.filter_vars['U_errors'] = np.zeros((Nt, Nx, Ny)) + self.filter_vars['U_success'] = dict() + + + # Setup arrays for derivatives of the model. + # THOMAS'S WAY (SAVE THEM AS TENSORS) + self.deriv_vars['D_u_tilde'] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1, self.spatial_dims+1)) + self.deriv_vars['D_Fab'] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1, self.spatial_dims+1, self.spatial_dims+1)) + self.deriv_vars['D_T_tilde'] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1)) + + # MARCUS'S WAY + # for str in self.non_local_vars_strs: + # if str in self.meso_vars: + # self.deriv_vars.update({'dt'+str:np.zeros_like(self.meso_vars[str])}) + # self.deriv_vars.update({'dx'+str:np.zeros_like(self.meso_vars[str])}) + # self.deriv_vars.update({'dy'+str:np.zeros_like(self.meso_vars[str])}) + # if str in self.meso_structures: + # self.deriv_vars.update({'dt'+str:np.zeros_like(self.meso_structures[str])}) + # self.deriv_vars.update({'dx'+str:np.zeros_like(self.meso_structures[str])}) + # self.deriv_vars.update({'dy'+str:np.zeros_like(self.meso_structures[str])}) + + def find_observers(self): + """ + Method to compute filtering observers at grid points built with setup_meso_grid. + The observers found (and relative errors) are saved in the dictionary self.filter_vars. + Set up the entry self.filter_vars['U_success'] as a dictionary with (tuples of) indices + on the meso_grid as keys, and bool as values (true if the observer has been found, false otherwise) + + Notes: + ------ + Requires setup_meso_grid() and setup_variables() to be called first. + """ + for h, t in enumerate(self.domain_vars['T']): + for i, x in enumerate(self.domain_vars['X']): + for j, y in enumerate(self.domain_vars['Y']): + point = [t,x,y] + sol = self.find_obs.find_observer(point) + if sol[0]: + self.filter_vars['U'][h,i,j] = sol[1] + self.filter_vars['U_errors'][h,i,j] = sol[2] + self.filter_vars['U_success'].update({(h,i,j) : True}) + + if not sol[0]: + self.filter_vars['U_success'].update({(h,i,j) : False}) + print('Careful: obs could not be found at: ', self.domain_vars['Points'][h][i][j]) + + def filter_micro_variables(self): + """ + Filter all meso_model structures AND micro pressure at the grid_points built with setup_meso_grid(). + This method relies on filter_var_point implemented separately for the filter, e.g. spatial_box_filter + + Parameters: + ----------- + + Notes: + ------ + Requires find_observers() and setup_meso_grid() to be called first. + """ + for h, t in enumerate(self.domain_vars['T']): + for i, x in enumerate(self.domain_vars['X']): + for j, y in enumerate(self.domain_vars['Y']): + point = [t, x, y] + if self.filter_vars['U_success'][h,i,j]: + obs = self.filter_vars['U'][h,i,j] + for struct in self.meso_structures: + self.meso_structures[struct][h,i,j] = self.filter.filter_var_point(struct, point, obs) + self.meso_vars['p_filt'][h,i,j] = self.filter.filter_var_point('p', point, obs) + else: + print('Could not filter at {}: observer not found.'.format(point)) + + def p_from_EOS(self, eps, n): + """ + Compute pressure from Gamma-law EoS + """ + return (self.coefficients['Gamma']-1)*eps*n + + def decompose_structures_gridpoint(self, h, i, j): + """ + Decompose the fluid part of SET as well as Fab at grid point (h,i,j) + + Parameters: + ----------- + h, i, j: integers + the indices on the grid where the decomposition is performed. + + Returns: + -------- + None + + Notes: + ------ + Decomposition of BC, fluid SET and Faraday tensor. The EM part of SET is decomposed later + via non local operations (same story for the charge current). + + """ + # Computing the Favre density and velocity + n_t = np.sqrt(-Base.Mink_dot(self.meso_structures['BC'][h,i,j], self.meso_structures['BC'][h,i,j])) + u_t = np.multiply(1 / n_t, self.meso_structures['BC'][h,i,j]) + T_ab = self.meso_structures['SETfl'][h,i,j,:,:] # Remember this is rank (2,0) + + # Computing the decomposition at each point + eps_t = np.einsum('i,j,ik,jl,kl', u_t, u_t, self.metric, self.metric, T_ab) + h_ab = np.einsum('ij,jk->ik', self.metric + np.einsum('i,j->ij', u_t, u_t), self.metric) # This is a rank (1,1) tensor, i.e. a real projector. + q_a = np.einsum('ij,jk,kl,l->i',h_ab, T_ab, self.metric, u_t) + s_ab = np.einsum('ij,kl,jl->ik',h_ab, h_ab, T_ab) + s = np.einsum('ii',s_ab) + p_t = self.p_from_EOS(eps_t, n_t) + + + # Storing the decomposition with appropriate names. + self.meso_vars['n_tilde'][h,i,j] = n_t + self.meso_vars['u_tilde'][h,i,j,:] = u_t + self.meso_vars['eps_tilde'][h,i,j] = eps_t + self.meso_vars['q_res'][h,i,j,:] = q_a + self.meso_vars['pi_res'][h,i,j,:,:] = s_ab - np.multiply(s / self.spatial_dims , self.metric +np.outer(u_t,u_t)) + self.meso_vars['p_tilde'][h,i,j] = p_t + self.meso_vars['Pi_res'][h,i,j] = s - p_t + self.meso_vars['eos_res'][h,i,j] = self.meso_vars['p_filt'][h,i,j] - p_t + self.meso_vars['T_tilde'][h,i,j] = p_t / n_t + + # Repeat for the Faraday tensor (which is rank (0,2)) + Fab = self.meso_structures['Fab'][h,i,j] + self.meso_vars['e_tilde'][h,i,j,:] = np.einsum('ij,j->i',Fab, u_t) + self.meso_vars['b_tilde'][h,i,j] = np.einsum('ijk,i,jl,km,lm', self.Levi3D, u_t, self.metric, self.metric, Fab) + + # Corresponding 3+1 d formulae: only change in B + # np.einsum('ijkl,i,km,ln,mn->i', self.Levi4D, u_t, self.metric, self.metric, Fab) + + def decompose_structures(self): + """ + Decompose structures at all points on the meso_grid where observers could be found. + """ + for h, t in enumerate(self.domain_vars['T']): + for i, x in enumerate(self.domain_vars['X']): + for j, y in enumerate(self.domain_vars['Y']): + point = [t,y,x] + if self.filter_vars['U_success'][h,i,j]: + self.decompose_structures_gridpoint(h,i,j) + else: + print('Structures not decomposed at {}: observer could not be found.'.format(point)) + + def calculate_derivatives_gridpoint(self, nonlocal_var_str, h, i, j, direction, order = 1): + """ + Calculate partial derivative in the input 'direction' of the variable corresponding to 'nonlocal_var_str' + at the position on the grid identified by indices h,i,j. The order of the differencing scheme + can also be specified, default to 1. + + Parameters: + ----------- + nonlocal_var_str: string + quantity to be taken derivate of, must be in self.nonlocal_vars_strs() + + h, i ,j: integers + indices for point on the meso_grid + + direction: integer < self.spatial_dim + 1 + + order: integer, defatult to 1 + order of the differencing scheme + + Returns: + -------- + Finite-differenced quantity at (h,i,j) + + Notes: + ------ + The method returns the value instead of storing it, so that these can be + rearranged as preferred later, that is in calculate_derivatives() + """ + + if direction > self.spatial_dims: + print('Directions are numbered from 0 up to {}'.format(self.spatial_dims)) + return None + + if order > len(self.differencing): + print('Maximum order implemented is {}: continuing with it.'.format(len(self.differencing))) + order = len(self.differencing) + + # Forward, backward or centered differencing? + k = [h,i,j][direction] + N = len(self.domain_vars['Points'][direction]) + + if k in [l for l in range(order)]: + coefficients = self.differencing[order]['fw']['coefficients'] + stencil = self.differencing[order]['fw']['stencil'] + elif k in [N-1-l for l in range(order)]: + coefficients = self.differencing[order]['bw']['coefficients'] + stencil = self.differencing[order]['bw']['stencil'] + else: + coefficients = self.differencing[order]['cen']['coefficients'] + stencil = self.differencing[order]['cen']['stencil'] + + temp = 0 + for s, sample in enumerate(stencil): + idxs = [h,i,j] + idxs[direction] += sample + temp += np.multiply( coefficients[s] / self.domain_vars['Dx'], self.get_var_gridpoint(nonlocal_var_str, *idxs)) + return temp + + def calculate_derivatives(self): + """ + Compute all the derivatives of the quantities corresponding to nonlocal_vars_strs, for all + gridpoints on the meso-grid. + + Notes: + ------ + The derived quantities are stored as 'tensors' as follows: + 1st three indices refer to the position on the grid + + 4th index refers to the directionality of the derivative + + last indices (1 or 2) correspond to the components of the quantity to be derived + + The index corresponding to the derivative is covariant, i.e. down. + + Example: + + Fab [h,i,j,a,b] : h,i,j grid; a,b, spacetime components + + D_Fab[h,i,j,c,a,b]: h,i,j grid; c direction of derivative; a,b as for Fab + + """ + for h, t in enumerate(self.domain_vars['T']): + for i, x in enumerate(self.domain_vars['X']): + for j, y in enumerate(self.domain_vars['Y']): + point = [t,y,x] + if self.filter_vars['U_success'][h,i,j]: + for dir in range(self.spatial_dims+1): + for str in self.nonlocal_vars_strs: + dstr = 'D_' + str + self.deriv_vars[dstr][h,i,j,dir] = self.calculate_derivatives_gridpoint(str, h, i, j, dir) + else: + print('Derivatives not calculated at {}: observer could not be found.'.format(point)) + + def closure_ingredients_gridpoint(self, h, i, j): + """ + Decompose quantities obatined via non_local operations (i.e. derivatives) as needed + for the closure scheme. This is done at gridpoint (h,i,j) and then relevant values are + returned + + Parameters: + ----------- + h, i, j: integers + the indices of the gridpoint + + Returns: + -------- + list of strings: names of the quantities computed + + list of nd.arrays with quantities computed + + Notes: + ------ + The ingredients for the closure scheme are model specific: it makes sense that the relevant + dictionary is set up not on construction. + + UNDER CONSTRUCTION: THE EM PART NEEDS MORE THINKING + + Rename: closure_ingredients_gridpoint() ?? + Coefficients can be computed here directly, but more general models will require more work. + Do not split this, at least for now + """ + # COMPUTING THE MESO-CURRENT AND DECOMPOSING IT WRT FAVRE_OBS + u_t = self.meso_vars['u_tilde'][h,i,j] + u_t_cov = np.einsum('ij,j->i', self.metric, u_t) + Nabla_Fab = self.deriv_vars['D_Fab'][h,i,j] + + # CLOSURE INGREDIENTS: FLUID BIT - WORKING WITH (2,0) + nabla_u = self.deriv_vars['D_u_tilde'][h,i,j] # This is a rank (1,1) tensor + nabla_u = np.einsum('ij,jk->ik', self.metric, nabla_u) #this is a rank (2,0) tensor + acc_t = np.einsum('i,ij', u_t_cov, nabla_u) # vector + + # The following two quantities should be identically zero, but they won't be due to numerical errors. + # Computing them and removing to project velocity gradients + unit_norm_violation = np.einsum('ij,j', nabla_u, u_t_cov) #vector + acc_orthogonality_violation = np.einsum('i,i->', u_t_cov, acc_t) + Daub = nabla_u + np.einsum('i,j->ij', u_t, acc_t) + np.einsum('i,j->ij', unit_norm_violation, u_t) +\ + np.multiply(acc_orthogonality_violation, np.einsum('i,j->ij', u_t, u_t)) #Should be a (2,0) tensor + h_ab = self.metric + np.einsum('i,j->ij', u_t, u_t) + exp_t = np.einsum('ii->',Daub) + shear_t = np.multiply(1/2., Daub + np.einsum('ij->ji', Daub)) - np.multiply( 1/self.spatial_dims * exp_t, h_ab) + vort_t = np.multiply(1/2., Daub - np.einsum('ij->ji', Daub)) + + + # CLOSURE INGREDIENTS: HEAT FLUX + nabla_T = self.deriv_vars['D_T_tilde'][h,i,j] + projector = np.zeros((3,3)) + np.fill_diagonal(projector, 1) + projector += np.einsum('i,j->ij',u_t_cov, u_t) + DaT = np.einsum('ij,j->i', projector, nabla_T) + Theta_tilde = DaT + np.multiply(self.meso_vars['T_tilde'][h,i,j], np.einsum('ij,j->i', self.metric, acc_t)) + + # CLOSURE INGREDIENTS: EM PART - DO YOU NEED MORE? + # current = np.einsum('ij, kl, kjl->i', self.metric, self.metric, DFab) + # sigma_t = np.einsum('i,ij,j->',current, self.metric, u_t) + # J_t = current - np.multiply(sigma_t, u_t) + # # self.meso_vars['j'][h,i,j,:] = current ?? + + closure_vars_strs = ['shear_tilde', 'exp_tilde', 'acc_tilde', 'Theta_tilde'] + closure_vars = [shear_t, exp_t, acc_t, Theta_tilde] + return closure_vars_strs, closure_vars + + def closure_ingredients(self): + """ + Wrapper of the corresponding gridpoint method. + Set up the dictionary for the closure variables not yet defined. + Loop over the grid and store the decomposed variables. + """ + Nt, Nx, Ny = self.domain_vars['Nt'], self.domain_vars['Nx'], self.domain_vars['Ny'] + for h in range(Nt): + for i in range(Nx): + for j in range(Ny): + if self.filter_vars['U_success'][h,i,j]: + keys, values = self.closure_ingredients_gridpoint(h,i,j) + # try-except block to extend the meso_vars dictionary + for idx, key in enumerate(keys): + try: + self.meso_vars[key] + except KeyError: + print('The key {} does not belong to meso_vars yet, adding it!'.format(key)) + shape = values[idx].shape + self.meso_vars.update({key: np.zeros(([Nt, Nx, Ny] + list(shape)))}) + finally: + self.meso_vars[key][h,i,j] = values[idx] + + def EL_style_closure_gridpoint(self, h, i, j): + """ + Compute bulk, shear via scalarization + PA trick, thermal conductivity later. + At a point. + Returns: coefficient(s) at a point. + """ + coefficients_names=[] + coefficients=[] + + # CALCULATING BULK VISCOUS COEFF + zeta = self.meso_vars['Pi_res'][h,i,j] / self.meso_vars['exp_tilde'][h,i,j] + coefficients_names.append('zeta') + coefficients.append(zeta) + + # CALCULATING SHEAR VISCOUS COEFF + pi_res_sq = np.einsum('ij,kl,ik,jl->', self.meso_vars['pi_res'][h,i,j], self.meso_vars['pi_res'][h,i,j], self.metric, self.metric) + shear_sq = np.einsum('ij,kl,ik,jl->', self.meso_vars['shear_tilde'][h,i,j], self.meso_vars['shear_tilde'][h,i,j], self.metric, self.metric) + eta = pi_res_sq/shear_sq + # Compute sign of eta by looking at the PA of pi_res with higher eigenvalue. + # Change shear to the eigenbasis of pi_res (using that the matrix is unitary as pi_res is sym) + pi_eig, pi_eigv = np.linalg.eigh(self.meso_vars['pi_res'][h,i,j]) + transformed_shear = np.einsum('ji,jk,kl', pi_eigv, self.meso_vars['shear_tilde'][h,i,j], pi_eigv) + abs_pi_eig = np.abs(pi_eig) + pos = list(abs_pi_eig).index(np.max(abs_pi_eig)) + eta = eta * np.sign(pi_eig[-1] / transformed_shear[pos,pos]) + coefficients_names.append('eta') + coefficients.append(eta) + + # CALCULATING THE HEAT CONDUCTIVITIY + q_res = self.meso_vars['q_res'][h,i,j] + Theta_t = self.meso_vars['Theta_tilde'][h,i,j] + q_res_sq = np.einsum('i,ij,j', q_res, self.metric, q_res) + Theta_t_sq = np.einsum('i,ij,j', Theta_t, self.metric, Theta_t) + cos_angle = np.einsum('i,ij,j', q_res, self.metric, Theta_t) / (q_res_sq * Theta_t_sq) + sign = np.sign(cos_angle) + kappa = sign * np.sqrt(q_res_sq / Theta_t_sq) + coefficients_names.append('kappa') + coefficients.append(kappa) + + return coefficients_names, coefficients + + def EL_style_closure(self): + """ + Wrapper of EL_style_closure_gridpoint() + """ + Nt, Nx, Ny = self.domain_vars['Nt'], self.domain_vars['Nx'], self.domain_vars['Ny'] + for h in range(Nt): + for i in range(Nx): + for j in range(Ny): + if self.filter_vars['U_success'][h,i,j]: + keys, values = self.EL_style_closure_gridpoint(h,i,j) + # try-except block to extend the meso_vars dictionary with the dissipative coefficients + for idx, key in enumerate(keys): + try: + self.meso_vars[key] + except KeyError: + print('The key {} does not belong to meso_vars yet, adding it!'.format(key)) + shape = values[idx].shape + self.meso_vars.update({key: np.zeros(([Nt, Nx, Ny] + list(shape)))}) + finally: + self.meso_vars[key][h,i,j] = values[idx] + + def EL_style_closure_regression(self, CoefficientAnalysis): + """ + Takes in a list of correlation quantities (strings? The regressors needed are computed + previously via closure ingredients) + Pass the dataset to a CoefficientAnalysis instance. + Plot the correlations with the regressors and perform the regression using methods from a Coefficient + Analysis instance. + """ + # FICTITIOUS RANGES AND POINTS TO TEST TRIM_DATA ROUTINE + ranges = [[self.domain_vars['Tmin'], self.domain_vars['Tmax']],\ + [self.domain_vars['Xmin'], self.domain_vars['Xmax']],\ + [self.domain_vars['Ymin'], self.domain_vars['Ymax']]] + points = self.domain_vars['Points'] + + # TESTING SCALAR REGRESSION ROUTINE + # y = self.meso_vars['zeta'] + # X = [self.meso_vars['eps_tilde'], self.meso_vars['n_tilde']] + # stats_result = CoefficientAnalysis.scalar_regression(y, X, ranges, points) + # print(*stats_result) + + # TESTING TENSOR REGRESSION ROUTINE + # y=self.meso_vars['q_res'] + # X=[self.meso_vars['e_tilde'], self.meso_vars['u_tilde']] + # # stats_result = CoefficientAnalysis.tensor_components_regression(y, X, 2,ranges, points, components=[(0,),(2,)]) + # stats_result = CoefficientAnalysis.tensor_components_regression(y, X, 2,ranges, points, add_intercept=False) + # for i in range(len(stats_result)): + # print('{}-th component: \n {} \n\n'.format(i, stats_result[i])) + + # TESTING CORRELATION VISUALIZATION ROUTINES + # x = self.meso_vars['n_tilde'] + # y = self.meso_vars['eps_tilde'] + # labels = ['n_tilde','eps_tilde'] + # g1=CoefficientAnalysis.visualize_correlation(x, y, xlabel=labels[0], ylabel=labels[1]) + # fig=g1.figure + # fig.savefig('Single_correlation_plot.pdf', format='pdf') + + # data=[self.meso_vars['eta'], self.meso_vars['b_tilde'], self.meso_vars['eps_tilde'], self.meso_vars['n_tilde']] + # labels=['eta', 'b_tilde', 'eps_tilde', 'n_tilde'] + # g2=CoefficientAnalysis.visualize_many_correlations(data, labels) + + # data=[self.meso_vars['zeta'], self.meso_vars['b_tilde'], self.meso_vars['eps_tilde'], self.meso_vars['n_tilde']] + # labels=['zeta', 'b_tilde', 'eps_tilde', 'n_tilde'] + # gbis= CoefficientAnalysis.visualize_many_correlations(data, labels) + # # plt.savefig('Many_correlations_plot.pdf', format='pdf') + # plt.show() + + # TESTING THE CHECK REGRESSORS ROUTINE: + # data=[self.meso_vars['b_tilde'], self.meso_vars['eps_tilde'], self.meso_vars['n_tilde']] + # labels=['b_tilde', 'eps_tilde', 'n_tilde'] + # g1=CoefficientAnalysis.visualize_many_correlations(data, labels) + # comps, g2 = CoefficientAnalysis.PCA_find_regressors_subset(data, ranges = ranges, model_points = points) + # g2.fig.suptitle("Correlation between PC (standardized data)") + # g2.fig.subplots_adjust(top=0.94) + # print(comps) + # plt.show() + +class resHD2D(object): + """ + CUT AND PAST OF resMHD_2D --> removing the magnetic bits + """ + def __init__(self, micro_model, find_obs, filter, interp_method = 'linear'): + """ + ADD DESCRIPTION OF THIS + """ + self.micro_model = micro_model + self.find_obs = find_obs + self.filter = filter + self.spatial_dims = 2 + self.interp_method = interp_method + + self.domain_int_strs = ('Nt','Nx','Ny') + self.domain_float_strs = ("Tmin","Tmax","Xmin","Xmax","Ymin","Ymax","Dt","Dx","Dy") + self.domain_array_strs = ("T","X","Y","Points") + self.domain_vars = dict.fromkeys(self.domain_int_strs+self.domain_float_strs+self.domain_array_strs) + for var in self.domain_vars: + self.domain_vars[var] = [] + + # This is the Levi-Civita symbol, not tensor, so be careful when using it + self.Levi3D = np.array([[[ np.sign(i-j) * np.sign(j- k) * np.sign(k-i) \ + for k in range(3)]for j in range(3) ] for i in range(3) ]) + + self.metric = np.zeros((3,3)) + self.metric[0,0] = -1 + self.metric[1,1] = self.metric[2,2] = +1 + + self.filter_vars_strs = ['U', 'U_errors', 'U_success'] + self.filter_vars = dict.fromkeys(self.filter_vars_strs) + for var in self.filter_vars: + var = [] + + self.meso_structures_strs = ['SET', 'BC'] + self.meso_structures = dict.fromkeys(self.meso_structures_strs) + for var in self.meso_structures: + self.meso_structures[var] = [] + + self.meso_scalars_strs = ['eps_tilde', 'n_tilde', 'p_tilde', 'p_filt', 'eos_res', 'Pi_res', 'T_tilde'] + self.meso_vectors_strs = ['u_tilde', 'q_res'] + self.meso_r2tensors_strs = ['pi_res'] + self.meso_vars_strs = self.meso_scalars_strs + self.meso_vectors_strs + self.meso_r2tensors_strs + self.meso_vars = dict.fromkeys(self.meso_vars_strs) + + self.coefficient_strs = ["Gamma"] + self.coefficients = dict.fromkeys(self.coefficient_strs) + self.coefficients['Gamma'] = 4.0/3.0 + + # Dictionary with stencils and coefficients for finite differencing + self.differencing = dict.fromkeys((1, 2)) + self.differencing[1] = dict.fromkeys(['fw', 'bw', 'cen']) + self.differencing[1]['fw'] = {'coefficients' : [-1, 1] , 'stencil' : [0, 1]} + self.differencing[1]['bw'] = {'coefficients' : [1, -1] , 'stencil' : [0, -1]} + self.differencing[1]['cen'] = {'coefficients' : [-1/2., 0, 1/2.] , 'stencil' : [-1, 0, 1]} + self.differencing[2] = dict.fromkeys(['fw', 'bw', 'cen']) + self.differencing[2]['fw'] = {'coefficients' : [-3/2., 2., -1/2.] , 'stencil' : [0, 1, 2]} + self.differencing[2]['bw'] = {'coefficients' : [3/2., -2., 1/2.] , 'stencil' : [0, -1, -2]} + self.differencing[2]['cen'] = {'coefficients' : [1/12., -2/3., 0., 2/3., -1/12.] , 'stencil' : [-2, -1, 0, 1, 2]} + + + # dictionary with non-local quantities (keys must match one of meso_vars or structure) + self.nonlocal_vars_strs = ['u_tilde', 'T_tilde', 'n_tilde', 'eps_tilde'] + Dstrs = ['D_' + i for i in self.nonlocal_vars_strs] + self.deriv_vars = dict.fromkeys(Dstrs) + + # Additional, closure-scheme specific vars must be added appropriately using model_residuals() + # self.all_var_strs = self.meso_vars_strs + Dstrs + self.meso_structures_strs + + # Run some compatibility test... + compatible = True + error = '' + if self.spatial_dims != micro_model.get_spatial_dims(): + compatible = False + error += '\nError: different dimensions.' + for struct in self.meso_structures_strs: + if struct not in self.micro_model.get_structures_strs(): + compatible = False + error += f'\nError: {struct} not in micro_model!' + + if not compatible: + print("Meso and Micro models are incompatible:"+error) + + self.labels_var_dict = {'SET' : r'$$', + 'BC' : r'$$', + 'U' : r'$U^a$', + 'Gamma' : r'$\Gamma$', + 'eos_res' : r'$M$', + 'Pi_res' : r'$\tilde{\Pi}$', + 'q_res' : r'$\tilde{q}^a$', + 'pi_res' : r'$\tilde{\pi}^{ab}$', + 'eps_tilde' : r'$\tilde{\varepsilon}$', + 'n_tilde' : r'$\tilde{n}$', + 'p_tilde' : r'$\tilde{p}$', + 'p_filt' : r'$

$', + 'T_tilde' : r'$\tilde{T}$', + 'u_tilde' : r'$\tilde{u}^a$', + 'D_u_tilde' : r'$\nabla_{a}\tilde{u}^b$', + 'D_T_tilde' : r'$\nabla_{a}\tilde{T}$', + 'D_n_tilde' : r'$\nabla_{a}\tilde{n}$', + 'D_eps_tilde' : r'$\nabla_{a}\tilde{\varepsilon}$', + 'n_tilde_dot' : r'$\dot{\tilde{n}}$', + 'T_tilde_dot' : r'$\dot{\tilde{T}}$', + 'sD_T_tilde': r'$D_{a}\tilde{T}$', + 'sD_n_tilde': r'$D_{a}\tilde{n}$', + 'shear_tilde' : r'$\tilde{\sigma}^{ab}$', + 'acc_tilde' : r'$\tilde{a}^a$', + 'exp_tilde' : r'$\tilde{\theta}$', + 'Theta_tilde' : r'$\tilde{\Theta}^a$', + 'eta' : r'$\eta$', + 'zeta' : r'$\zeta$', + 'kappa' : r'$\kappa$', + 'Pi_res_sq' : r'$\tilde{\Pi}^2$', + 'pi_res_sq' : r'$\tilde{\pi}_{ab}\tilde{\pi}^{ab}$', + 'shear_sq' : r'$\tilde{\sigma}_{ab}\tilde{\sigma}^{ab}$', + 'Theta_sq': r'$\tilde{\Theta}_a\tilde{\Theta}^a$', + 'q_res_sq': r'$\tilde{q}_a \tilde{q}^a$', + 'det_shear': r'$det(\sigma)$', + 'vort_sq' : r'$\omega_{ab}\omega^{ab}$', + 'acc_mag': r'$|a|$', + 'sD_n_tilde_sq' : r'$D_{a}\tilde{n}D^{a}\tilde{n}$', + 'dot_Dn_Theta' : r'$D_{a}\tilde{n}\Theta^{a}$', + 'Q1' : r'$\tilde{\sigma}_{ab}\tilde{\sigma}^{ab} - \tilde{\omega}_{ab}\tilde{\omega}^{ab}$', + 'Q2' : r'$\tilde{\sigma}_{ab}\tilde{\sigma}^{ab}/\tilde{\omega}_{ab}\tilde{\omega}^{ab}$', + 'weights' : r'$w$'} + + def update_labels_dict(self, entry_dict): + """ + pretty self-explanatory + """ + self.labels_var_dict.update(entry_dict) + + def set_find_obs(self, find_obs): + self.find_obs = find_obs + + def set_filter(self, filter): + self.filter = filter + + def get_all_var_strs(self): + return list(self.meso_vars.keys()) + list(self.deriv_vars.keys()) + list(self.meso_structures.keys()) + \ + list(self.filter_vars.keys()) + + def get_gridpoints(self): + """ + Pretty self-explanatory + """ + return self.domain_vars['Points'] + + def get_interpol_var(self, var, point): + """ + Returns the interpolated variables at the point. + + Parameters + ---------- + var: str corresponding to meso_structure, meso_vars or deriv_vars + + point : list of floats + ordered coordinates: t,x,y + + Return + ------ + Interpolated values/arrays corresponding to variable. + Empty list if none of the variables is not meso_structures/meso_vars or deriv_vars of the model. + + Notes + ----- + Interpolation gives errors when applied to boundary + """ + if var in self.meso_structures: + return interpn(self.domain_vars['Points'], self.meso_structures[var], point, method = self.interp_method)[0] + elif var in self.meso_vars: + return interpn(self.domain_vars['Points'], self.meso_vars[var], point, method = self.interp_method)[0] + elif var in self.deriv_vars: + return interpn(self.domain_vars['Points'], self.deriv_vars[var], point, method = self.interp_method)[0] + elif var in self.filter_vars: + return interpn(self.domain_vars['Points'], self.filter_vars[var], point, method=self.interp_method)[0] + else: + print('Cannot interpolate value of {} using data fromfilter_vars meso_structures/meso_varsderiv_vars/filter_vars. Check!'.format(var)) + + @multimethod + def get_var_gridpoint(self, var: str, h: object, i: object, j: object): + """ + Returns variable corresponding to input 'var' at gridpoint + identified by h,i,j + + Parameters: + ----------- + var: string corresponding to structure, meso_vars or deriv_vars + + h,i,j: int + integers corresponding to position on the grid. + + Returns: + -------- + Values or arrays corresponding to variable evaluated at the closest + grid-point to input 'point'. + + Notes: + ------ + This method is useful e.g. for plotting the raw data. + """ + if var in self.meso_structures: + return self.meso_structures[var][h,i,j] + elif var in self.meso_vars: + return self.meso_vars[var][h,i,j] + elif var in self.deriv_vars: + return self.deriv_vars[var][h,i,j] + elif var in self.filter_vars: + return self.filter_vars[var][h,i,j] + else: + print(f'Cannot get value of {var} at h,i,j from data in meso_vars/meso_structures/deriv_vars/filter_vars') + return None + + @multimethod + def get_var_gridpoint(self, var: str, point: object): + """ + Returns variable corresponding to input 'var' at gridpoint + closest to input 'point'. + + Parameters: + ----------- + vars: string corresponding to structure, meso_vars or deriv_vars + + point: list of 2+1 floats + + Returns: + -------- + Values or arrays corresponding to variable evaluated at the closest + grid-point to input 'point'. + + Notes: + ------ + This method should be used in case using interpolated values becomes + too expensive. + """ + indices = Base.find_nearest_cell(point, self.domain_vars['Points']) + if var in self.meso_structures: + return self.meso_structures[var][tuple(indices)] + elif var in self.meso_vars: + return self.meso_vars[var][tuple(indices)] + elif var in self.deriv_vars: + return self.deriv_vars[var][tuple(indices)] + elif var in self.filter_vars: + return self.filter_vars[var][tuple(indices)] + else: + print(f'Cannot get value of {var} at h,i,j from data in meso_vars/meso_structures/deriv_vars/filter_vars') + return None + + def get_model_name(self): + return 'resHD2D' + + def set_find_obs_method(self, find_obs): + self.find_obs = find_obs + + def setup_meso_grid(self, patch_bdrs, coarse_factor = 1, coarse_time = False): + """ + Builds the meso_model grid using the micro_model grid points contained in + the input patch (defined via 'patch_bdrs'). The method allows for coarse graining + the grid (both in space dirs only or also in time) + Then store the info about the meso grid and set up arrays of definite rank and size + for the quantities needed later. + + Parameters: + ----------- + patch_bdrs: list of lists of two floats, + [[tmin, tmax],[xmin,xmax],[ymin,ymax]] + + coarse_factor: integer + + coarse_time: boolean + If true, coarsening is also applied to the time direction. + Notes: + ------ + If the patch_bdrs are larger than micro_grid, the method will not set-up the meso_grid, + and an error message is printed. This is extra safety measure! + """ + + # Is the patch within the micro_model domain? + conditions = patch_bdrs[0][0] < self.micro_model.domain_vars['tmin'] or \ + patch_bdrs[0][1] > self.micro_model.domain_vars['tmax'] or \ + patch_bdrs[1][0] < self.micro_model.domain_vars['xmin'] or \ + patch_bdrs[1][1] > self.micro_model.domain_vars['xmax'] or \ + patch_bdrs[2][0] < self.micro_model.domain_vars['ymin'] or \ + patch_bdrs[2][1] > self.micro_model.domain_vars['ymax'] + + if conditions: + print('Error: the input region for filtering is larger than micro_model domain!') + return None + + #Find the nearest cell to input patch bdrs + patch_min = [patch_bdrs[0][0], patch_bdrs[1][0], patch_bdrs[2][0]] + patch_max = [patch_bdrs[0][1], patch_bdrs[1][1], patch_bdrs[2][1]] + idx_mins = Base.find_nearest_cell(patch_min, self.micro_model.domain_vars['points']) + idx_maxs = Base.find_nearest_cell(patch_max, self.micro_model.domain_vars['points']) + + # Set meso_grid spacings + self.domain_vars['Dt'] = self.micro_model.domain_vars['dt'] + if coarse_time: + self.domain_vars['Dt'] = self.micro_model.domain_vars['dt'] * coarse_factor + else: + self.domain_vars['Dt'] = self.micro_model.domain_vars['dt'] + self.domain_vars['Dx'] = self.micro_model.domain_vars['dx'] * coarse_factor + self.domain_vars['Dy'] = self.micro_model.domain_vars['dy'] * coarse_factor + + # Building the meso_grid + h, i, j = idx_mins[0], idx_mins[1], idx_mins[2] + while h <= idx_maxs[0]: + t = self.micro_model.domain_vars['t'][h] + self.domain_vars['T'].append(t) + if coarse_time: + h += coarse_factor + else: + h += 1 + while i <= idx_maxs[1]: + x = self.micro_model.domain_vars['x'][i] + self.domain_vars['X'].append(x) + i += coarse_factor + while j <= idx_maxs[2]: + y = self.micro_model.domain_vars['y'][j] + self.domain_vars['Y'].append(y) + j += coarse_factor + + # Saving the info about the meso_grid + self.domain_vars['Points'] = [self.domain_vars['T'], self.domain_vars['X'], self.domain_vars['Y']] + self.domain_vars['Tmin'] = np.amin(self.domain_vars['T']) + self.domain_vars['Xmin'] = np.amin(self.domain_vars['X']) + self.domain_vars['Ymin'] = np.amin(self.domain_vars['Y']) + self.domain_vars['Tmax'] = np.amax(self.domain_vars['T']) + self.domain_vars['Xmax'] = np.amax(self.domain_vars['X']) + self.domain_vars['Ymax'] = np.amax(self.domain_vars['Y']) + self.domain_vars['Nt'] = len(self.domain_vars['T']) + self.domain_vars['Nx'] = len(self.domain_vars['X']) + self.domain_vars['Ny'] = len(self.domain_vars['Y']) + + # Setup arrays for structures + Nt, Nx, Ny = self.domain_vars['Nt'], self.domain_vars['Nx'], self.domain_vars['Ny'] + self.meso_structures['BC'] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1)) + self.meso_structures['SET'] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1, self.spatial_dims+1)) + + # Setup arrays for meso_vars + for str in self.meso_scalars_strs: + self.meso_vars[str] = np.zeros((Nt, Nx, Ny)) + for str in self.meso_vectors_strs: + self.meso_vars[str] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1)) + for str in self.meso_r2tensors_strs: + self.meso_vars[str] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1, self.spatial_dims+1)) + + # Setup arrays for filter_vars + self.filter_vars['U'] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1)) + self.filter_vars['U_errors'] = np.zeros((Nt, Nx, Ny)) + self.filter_vars['U_success'] = dict() + + for h in range(Nt): + for i in range(Nx): + for j in range(Ny): + self.filter_vars['U_success'].update({(h,i,j): False}) + + # Setup arrays for derivatives of the model. + self.deriv_vars['D_u_tilde'] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1, self.spatial_dims+1)) + self.deriv_vars['D_T_tilde'] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1)) + self.deriv_vars['D_eps_tilde'] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1)) + self.deriv_vars['D_n_tilde'] = np.zeros((Nt, Nx, Ny, self.spatial_dims+1)) + + def find_observers(self): + """ + Method to compute filtering observers at grid points built with setup_meso_grid. + The observers found (and relative errors) are saved in the dictionary self.filter_vars. + Set up the entry self.filter_vars['U_success'] as a dictionary with (tuples of) indices + on the meso_grid as keys, and bool as values (true if the observer has been found, false otherwise) + + Notes: + ------ + Requires setup_meso_grid() to be called first. + """ + for h, t in enumerate(self.domain_vars['T']): + for i, x in enumerate(self.domain_vars['X']): + for j, y in enumerate(self.domain_vars['Y']): + point = [t,x,y] + sol = self.find_obs.find_observer(point) + if sol[0]: + self.filter_vars['U'][h,i,j] = sol[1] + self.filter_vars['U_errors'][h,i,j] = sol[2] + self.filter_vars['U_success'].update({(h,i,j) : True}) + + if not sol[0]: + # self.filter_vars['U_success'].update({(h,i,j) : False}) + # No need to update the dictionary as this has been initialized to False everywhere. + print('Careful: obs could not be found at: ', self.domain_vars['Points'][h][i][j]) + + def find_observers_parallel(self, n_cpus): + """ + Method to find observers at all points on meso-grid, parallelized version. + The observers found (and relative errors) are saved in the dictionary self.filter_vars. + Set up the entry self.filter_vars['U_success'] as a dictionary with (tuples of) indices + on the meso_grid as keys, and bool as values (true if the observer has been found, false otherwise) + + Parameters: + ----------- + + n_cpus: int + number of processes to run in parallel + + Notes: + ------ + This method relies on the routine find_obs.find_observers_parallel(). + So meso_class must be constructed passing parallelized class for finding observers. + """ + ts = self.domain_vars['T'] + xs = self.domain_vars['X'] + ys = self.domain_vars['Y'] + + t_idxs = np.arange(len(ts)) + x_idxs = np.arange(len(xs)) + y_idxs = np.arange(len(ys)) + + points = [] + for elem in product(ts,xs,ys): + points.append(list(elem)) + + indices_meso_grid = [] + for elem in product(t_idxs, x_idxs, y_idxs): + indices_meso_grid.append(elem) + + successes, failures = self.find_obs.find_observers_parallel(points, n_cpus) + + for i in range(len(successes[0])): + point_indxs_meso_grid = indices_meso_grid[successes[0][i]] + self.filter_vars['U'][point_indxs_meso_grid] = successes[1][i] + self.filter_vars['U_errors'][point_indxs_meso_grid] = successes[2][i] + self.filter_vars['U_success'].update({(point_indxs_meso_grid): True}) + + if len(failures)!=0: + print('Observers could not be found at the following points:\n') + for i in range(len(failures)): + failed_idxs_meso_grid = indices_meso_grid[failures[i]] + print('{}\n'.format(failed_idxs_meso_grid)) + + def filter_micro_variables(self): + """ + Filter all meso_model structures AND micro pressure within the input ranges. + If no range is provided in one direction, routine will try and filter at all points + on the meso-grid. Note this would require the grid to be set up wisely so to avoid + problems at the boundaries. + + This method relies on filter_var_point implemented separately for the filter, e.g. spatial_box_filter + + Notes: + ------ + Requires setup_meso_grid() to be called first. + Also find_observers() should be called first, although not doing so won't crash it. + """ + for h, t in enumerate(self.domain_vars['T']): + for i, x in enumerate(self.domain_vars['X']): + for j, y in enumerate(self.domain_vars['Y']): + point = [t,x,y] + if self.filter_vars['U_success'][h,i,j]: + obs = self.filter_vars['U'][h,i,j] + for struct in self.meso_structures: + self.meso_structures[struct][h,i,j] = self.filter.filter_var_point(struct, point, obs) + self.meso_vars['p_filt'][h,i,j] = self.filter.filter_var_point('p', point, obs) + else: + print('Could not filter at {}: observer not found.'.format(point)) + + def filter_micro_vars_parallel(self, n_cpus): + """ + Filter all meso_model structures AND micro pressure. + Routine will try and filter at all points on the meso-grid. + Note this would require the grid to be set up wisely so to avoid + problems at the boundaries. + + This method relies on filter_vars_parallel implemented separately for the + filter class, e.g. as in box_filter_parallel + + Parameters: + ----------- + + n_cpus: int + number of processes for parallelization + + Notes: + ------ + Requires setup_meso_grid() to be called first. + Also find_observers() should be called first, although not doing so won't crash it. + """ + ts = self.domain_vars['T'] + xs = self.domain_vars['X'] + ys = self.domain_vars['Y'] + + t_idxs = np.arange(len(ts)) + x_idxs = np.arange(len(xs)) + y_idxs = np.arange(len(ys)) + + points = [] + for elem in product(ts,xs,ys): + points.append(list(elem)) + + indices_meso_grid = [] + for elem in product(t_idxs, x_idxs, y_idxs): + indices_meso_grid.append(elem) + + observers = [] + for elem in product(t_idxs, x_idxs, y_idxs): + if self.filter_vars['U_success'][elem]: + observers.append(self.filter_vars['U'][elem]) + else: + print('Observers are not computed on (parts of) the grid!') + return None + + vars = ['BC', 'SET', 'p'] + points_observers = [] + for i in range(len(points)): + # args_for_filtering_parallel.append([points[i], observers[i], vars]) + points_observers.append([points[i], observers[i]]) + + # positions, filtered_vars = self.filter.filter_vars_parallel(args_for_filtering_parallel, n_cpus) + filtered_vars = dict.fromkeys(vars) + for var in vars: + positions, filtered_vars[var] = self.filter.filter_var_parallel(points_observers, var, n_cpus) + + for i in range(len(positions)): + point_indxs_meso_grid = indices_meso_grid[positions[i]] + self.meso_structures['BC'][point_indxs_meso_grid] = filtered_vars['BC'][i] + self.meso_structures['SET'][point_indxs_meso_grid] = filtered_vars['SET'][i] + self.meso_vars['p_filt'][point_indxs_meso_grid] = filtered_vars['p'][i] + # self.meso_structures['BC'][point_indxs_meso_grid] = filtered_vars[i][0] + # self.meso_structures['SET'][point_indxs_meso_grid] = filtered_vars[i][1] + # self.meso_vars['p_filt'][point_indxs_meso_grid] = filtered_vars[i][2] + + def p_from_EOS(self, eps, n): + """ + Compute pressure from Gamma-law EoS + """ + return (self.coefficients['Gamma']-1)*(eps-n) + + def decompose_structures_gridpoint(self, h, i, j): + """ + Decompose the fluid part of SET as well as Fab at grid point (h,i,j) + + Parameters: + ----------- + h, i, j: integers + the indices on the grid where the decomposition is performed. + + Returns: + -------- + None + + Notes: + ------ + Decomposition of BC, fluid SET and Faraday tensor. The EM part of SET is decomposed later + via non local operations (same story for the charge current). + + """ + # Computing the Favre density and velocity + n_t = np.sqrt(-Base.Mink_dot(self.meso_structures['BC'][h,i,j], self.meso_structures['BC'][h,i,j])) + u_t = np.multiply(1. / n_t, self.meso_structures['BC'][h,i,j]) + T_ab = self.meso_structures['SET'][h,i,j,:,:] # Remember this is rank (2,0) + + # Computing the decomposition at each point + eps_t = np.einsum('i,j,ik,jl,kl', u_t, u_t, self.metric, self.metric, T_ab) + h_ab = np.einsum('ij,jk->ik', self.metric + np.einsum('i,j->ij', u_t, u_t), self.metric) # This is a rank (1,1) tensor, i.e. a real projector. + q_a = np.einsum('ij,jk,kl,l->i',h_ab, T_ab, self.metric, u_t) # There might be missing a minus sign here? + s_ab = np.einsum('ij,kl,jl->ik',h_ab, h_ab, T_ab) + s = np.einsum('ii',s_ab) + p_t = self.p_from_EOS(eps_t, n_t) + + + # Storing the decomposition with appropriate names. + self.meso_vars['n_tilde'][h,i,j] = n_t + self.meso_vars['u_tilde'][h,i,j,:] = u_t + self.meso_vars['eps_tilde'][h,i,j] = eps_t + self.meso_vars['q_res'][h,i,j,:] = q_a + self.meso_vars['pi_res'][h,i,j,:,:] = s_ab - np.multiply(s / self.spatial_dims , self.metric +np.outer(u_t,u_t)) + self.meso_vars['p_tilde'][h,i,j] = p_t + self.meso_vars['Pi_res'][h,i,j] = s - p_t + self.meso_vars['eos_res'][h,i,j] = self.meso_vars['p_filt'][h,i,j] - p_t + self.meso_vars['T_tilde'][h,i,j] = p_t / n_t + + def decompose_structures(self): + """ + Decompose structures at all points on the meso_grid where observers could be found. + + Parameters: + ----------- + t_range: list of two floats + ranges in the t-direction, used for selecting the sublist of points + + x_range, y_range: similar to above. + """ + for h, t in enumerate(self.domain_vars['T']): + for i, x in enumerate(self.domain_vars['X']): + for j, y in enumerate(self.domain_vars['Y']): + point = [t,y,x] + if self.filter_vars['U_success'][h,i,j]: + self.decompose_structures_gridpoint(h,i,j) + else: + print('Structures not decomposed at {}: observer could not be found.'.format(point)) + + @staticmethod + def p_Gamma_law(eps, n, Gamma): + """ + staticmethod used by decompose_structures_task, that is the + parallel version decompose_structures_gridpoint + + Parameters: + ----------- + eps: float + the energy density of the fluid at a point + + n: float + the baryon number density of the fluid at a point + + Gamma: float + Gamma factor of the Gamma law + """ + return (Gamma-1)* (eps-n) + + @staticmethod + def decompose_structures_task(BC, SET , p_filt, h, i, j): + """ + Task to be executed in parallel: decomposing the structures at a point + + Parameters: + ----------- + + BC: np.array (3,) + the baryon current, rank: (1,0) + + SET: np.array (3,3) + the Stress-Energy tensor, rank (2,0) + + p_filt: float + the filtered pressure, scalar + + h,i,j: integers + indices of the corresponding gridpoint on meso-grid + + Returns: + -------- + (list, list): + first list contains the decomposition of structures at point + second list contains indices of point on mesogrid + """ + # As this is staticmethod, no access to self.metric + metric = np.zeros((3,3)) + metric[0,0] = -1. + metric[1,1] = metric[2,2] = 1. + spatial_dims = 2. + + # Computing the Favre density and velocity + n_t = np.sqrt(-Base.Mink_dot(BC, BC)) + u_t = np.multiply(1./ n_t, BC) + + # remember SET is a rank (2,0) tensor + # Computing the SET decomposition at each point + eps_t = np.einsum('i,j,ik,jl,kl', u_t, u_t, metric, metric, SET) + h_ab = np.einsum('ij,jk->ik', metric + np.einsum('i,j->ij', u_t, u_t), metric) # This is a rank (1,1) tensor, i.e. a real projector. + q_a = - np.einsum('ij,jk,kl,l->i', h_ab, SET, metric, u_t) + s_ab = np.einsum('ij,kl,jl->ik', h_ab, h_ab, SET) + s = np.einsum('ij,ji->', s_ab, metric) + # s = np.multiply(1/spatial_dims, s) + s_ab_tracefree = s_ab - np.multiply(s/3, metric + np.einsum('i,j->ij', u_t, u_t)) + + p_t = resHD2D.p_Gamma_law(eps_t, n_t, 4.0/3.0) + # rechange this: this was just to test the impact of EOS residuals onto the modelling of zeta + # Pi_res = s - p_t + Pi_res = s - p_filt + EOS_res = p_filt - p_t + T_t = p_t/n_t + + return [n_t, u_t, eps_t, q_a, s_ab_tracefree, p_t, Pi_res, EOS_res, T_t], [h,i,j] #Uncomment if EoS residual is modelled + # return [n_t, u_t, eps_t, q_a, s_ab_tracefree, p_t, Pi_res, T_t], [h,i,j] + + def decompose_structures_parallel(self, n_cpus): + """ + Routine to decompose structures on the entire grid in + parallel. + + Parameters: + ----------- + n_cpus: int + numbers of processes for parallelization + """ + # Preparing arguments for pool + args_for_pool=[] + for h in range(len(self.domain_vars['T'])): + for i in range(len(self.domain_vars['X'])): + for j in range(len(self.domain_vars['Y'])): + BC = self.meso_structures['BC'][h,i,j] + SET = self.meso_structures['SET'][h,i,j] + p_filt = self.meso_vars['p_filt'][h,i,j] + args_for_pool.append((BC, SET, p_filt, h,i,j)) + + with mp.Pool(processes=n_cpus) as pool: + print('Decomposing structures in parallel with {} processes'.format(pool._processes), flush=True) + for result in pool.starmap(resHD2D.decompose_structures_task, args_for_pool): + h,i,j = result[1] + self.meso_vars['n_tilde'][h,i,j] = result[0][0] + self.meso_vars['u_tilde'][h,i,j,:] = result[0][1] + self.meso_vars['eps_tilde'][h,i,j] = result[0][2] + self.meso_vars['q_res'][h,i,j,:] = result[0][3] + self.meso_vars['pi_res'][h,i,j,:,:] = result[0][4] + self.meso_vars['p_tilde'][h,i,j] = result[0][5] + self.meso_vars['Pi_res'][h,i,j] = result[0][6] + self.meso_vars['eos_res'][h,i,j] = result[0][7] #If EoS is not modelled you should adjust this! + self.meso_vars['T_tilde'][h,i,j] = result[0][8] + + def calculate_derivatives_gridpoint(self, nonlocal_var_str, h, i, j, direction, order = 1): + """ + Calculate partial derivative in the input 'direction' of the variable corresponding to 'nonlocal_var_str' + at the position on the grid identified by indices h,i,j. The order of the differencing scheme + can also be specified, default to 1. + + Parameters: + ----------- + nonlocal_var_str: string + quantity to be taken derivate of, must be in self.nonlocal_vars_strs() + + h, i ,j: integers + indices for point on the meso_grid + + direction: integer < self.spatial_dim + 1 + + order: integer, defatult to 1 + order of the differencing scheme + + Returns: + -------- + Finite-differenced quantity at (h,i,j) + + Notes: + ------ + The method returns the value instead of storing it, so that these can be + rearranged as preferred later, that is in calculate_derivatives() + """ + + if direction > self.spatial_dims: + print('Directions are numbered from 0 up to {}'.format(self.spatial_dims)) + return None + + if order > len(self.differencing): + print('Maximum order implemented is {}: continuing with it.'.format(len(self.differencing))) + order = len(self.differencing) + + # Forward, backward or centered differencing? + k = [h,i,j][direction] + N = len(self.domain_vars['Points'][direction]) + + if k in [l for l in range(order)]: + coefficients = self.differencing[order]['fw']['coefficients'] + stencil = self.differencing[order]['fw']['stencil'] + elif k in [N-1-l for l in range(order)]: + coefficients = self.differencing[order]['bw']['coefficients'] + stencil = self.differencing[order]['bw']['stencil'] + else: + coefficients = self.differencing[order]['cen']['coefficients'] + stencil = self.differencing[order]['cen']['stencil'] + + temp = 0 + for s, sample in enumerate(stencil): + idxs = [h,i,j] + idxs[direction] += sample + temp += np.multiply( coefficients[s] / self.domain_vars['Dx'], self.get_var_gridpoint(nonlocal_var_str, *idxs)) + return temp + + def calculate_derivatives(self): + """ + Compute all the derivatives of the quantities corresponding to nonlocal_vars_strs, for all + gridpoints on the meso-grid. + + Notes: + ------ + The derived quantities are stored as 'tensors' as follows: + 1st three indices refer to the position on the grid + + 4th index refers to the directionality of the derivative + + last indices (1 or 2) correspond to the components of the quantity to be derived + + The index corresponding to the derivative is covariant, i.e. down. + + Example: + + Fab [h,i,j,a,b] : h,i,j grid; a,b, spacetime components + + D_Fab[h,i,j,c,a,b]: h,i,j grid; c direction of derivative; a,b as for Fab + + """ + for h, t in enumerate(self.domain_vars['T']): + for i, x in enumerate(self.domain_vars['X']): + for j, y in enumerate(self.domain_vars['Y']): + point = [t,y,x] + if self.filter_vars['U_success'][h,i,j]: + for dir in range(self.spatial_dims+1): + for str in self.nonlocal_vars_strs: + dstr = 'D_' + str + self.deriv_vars[dstr][h,i,j,dir] = self.calculate_derivatives_gridpoint(str, h, i, j, dir) + else: + print('Derivatives not calculated at {}: observer could not be found.'.format(point)) + + def closure_ingredients_gridpoint(self, h, i, j): + """ + Decompose quantities obatined via non_local operations (i.e. derivatives) as needed + for the closure scheme. This is done at gridpoint (h,i,j) and then relevant values are + returned + + Parameters: + ----------- + h, i, j: integers + the indices of the gridpoint + + Returns: + -------- + list of strings: names of the quantities computed + + list of nd.arrays with quantities computed + + Notes: + ------ + The ingredients for the closure scheme are model specific: it makes sense that the relevant + dictionary is set up not on construction. + + UNDER CONSTRUCTION: THE EM PART NEEDS MORE THINKING + + Rename: closure_ingredients_gridpoint() ?? + Coefficients can be computed here directly, but more general models will require more work. + Do not split this, at least for now + """ + # FAVRE_OBS + u_t = self.meso_vars['u_tilde'][h,i,j] + u_t_cov = np.einsum('ij,j->i', self.metric, u_t) + + # CLOSURE INGREDIENTS: FAVRE OBS DERIVATIVE DECOMPOSITION - WORKING WITH (2,0) + nabla_u = self.deriv_vars['D_u_tilde'][h,i,j] # This is a rank (1,1) tensor + nabla_u = np.einsum('ij,jk->ik', self.metric, nabla_u) #this is a rank (2,0) tensor + acc_t = np.einsum('i,ij', u_t_cov, nabla_u) # vector + + # The following two quantities should be identically zero, but they won't be due to numerical errors. + # Computing them and removing to project velocity gradients + unit_norm_violation = np.einsum('ij,j', nabla_u, u_t_cov) #vector + acc_orthogonality_violation = np.einsum('i,i->', u_t_cov, acc_t) + Daub = nabla_u + np.einsum('i,j->ij', u_t, acc_t) + np.einsum('i,j->ij', unit_norm_violation, u_t) +\ + np.multiply(acc_orthogonality_violation, np.einsum('i,j->ij', u_t, u_t)) #Should be a (2,0) tensor + h_ab = self.metric + np.einsum('i,j->ij', u_t, u_t) + exp_t = np.einsum('ii->',Daub) + shear_t = np.multiply(1/2., Daub + np.einsum('ij->ji', Daub)) - np.multiply( 1/self.spatial_dims * exp_t, h_ab) + vort_t = np.multiply(1/2., Daub - np.einsum('ij->ji', Daub)) + + + # CLOSURE INGREDIENTS: HEAT FLUX + nabla_T = self.deriv_vars['D_T_tilde'][h,i,j] + projector = np.zeros((3,3)) + np.fill_diagonal(projector, 1) + projector += np.einsum('i,j->ij',u_t_cov, u_t) + DaT = np.einsum('ij,j->i', projector, nabla_T) + Theta_tilde = DaT + np.multiply(self.meso_vars['T_tilde'][h,i,j], np.einsum('ij,j->i', self.metric, acc_t)) + + closure_vars_strs = ['shear_tilde', 'exp_tilde', 'acc_tilde', 'Theta_tilde'] + closure_vars = [shear_t, exp_t, acc_t, Theta_tilde] + return closure_vars_strs, closure_vars + + def closure_ingredients(self): + """ + Wrapper of the corresponding gridpoint method. + Set up the dictionary for the closure variables not yet defined. + Loop over the grid and store the decomposed variables. + """ + Nt, Nx, Ny = self.domain_vars['Nt'], self.domain_vars['Nx'], self.domain_vars['Ny'] + for h in range(Nt): + for i in range(Nx): + for j in range(Ny): + if self.filter_vars['U_success'][h,i,j]: + keys, values = self.closure_ingredients_gridpoint(h,i,j) + # try-except block to extend the meso_vars dictionary + for idx, key in enumerate(keys): + try: + self.meso_vars[key] + except KeyError: + print('The key {} does not belong to meso_vars yet, adding it!'.format(key)) + shape = values[idx].shape + self.meso_vars.update({key: np.zeros(([Nt, Nx, Ny] + list(shape)))}) + finally: + self.meso_vars[key][h,i,j] = values[idx] + + @staticmethod + def closure_ingredients_task(u_t, nabla_u, T_t, nabla_T, nabla_n, h, i, j): + """ + Task to decompose Favre obs + Temperature derivatives + These will be used in EL_style_closure to extract the + turbulent effective dissipative coefficients + + Parameters: + ----------- + u_t: np.array (3,) + the Favre-observer, rank: (1,0) + + nabla_u: np.array(3,3) + derivatives of Favre observer, rank: (1,1) + + T_t: float + The temperature from filtered EoS + + nabla_T: np.array (3,0) + Temperature derivatives, rank: (0,1) + + h,i,j: integers + the indices of point on meso-grid + + Returns: + -------- + (list, list, list): + first list contains the strings of the returned quantities + (needed as the meso-vars dictionary is extended appropriately) + + second list contains the corresponding np.arrays + + third list contains the indices on meso-grid + """ + # building blocks: this task is static so no access to self + spatial_dims = 2. + metric = np.zeros((3,3)) + metric[0,0] = -1 + metric[1,1] = metric[2,2] = 1 + + # CLOSURE INGREDIENTS: FAVRE OBS DERIVATIVE DECOMPOSITION - WORKING WITH (2,0) + # The decomposition below is exact if certain algebraic constraints are satisfied, which they won't be + # due to numerical errors. + # However, it appears the only quantity to be corrected is the acceleration + u_t_cov = np.einsum('ij,j->i', metric, u_t) + nabla_u = np.einsum('ij,jk->ik', metric, nabla_u) #this is a rank (2,0) tensor + acc_t = np.einsum('i,ij', u_t_cov, nabla_u) # vector + + acc_orthogonality_violation = np.einsum('i,i->', u_t_cov, acc_t) + acc_t = acc_t + acc_orthogonality_violation * u_t + + projector = np.zeros((3,3)) + np.fill_diagonal(projector, 1) + projector += np.einsum('i,j->ij',u_t, u_t_cov) + h_ab = metric + np.einsum('i,j->ij', u_t, u_t) #Rank (2,0) tensor + + Daub = np.einsum('ij,kl,jl->ik', projector, projector, nabla_u) + exp_t = np.einsum('ij,ji->', Daub, metric) + shear_t = np.multiply(1/2., Daub + np.einsum('ij->ji', Daub)) - np.multiply( exp_t/ spatial_dims, h_ab) + vort_t = np.multiply(1/2., Daub - np.einsum('ij->ji', Daub)) + + # #The following should be used if the violation of the algebraic constraints become too large + # Daub = np.einsum('ij,kl,jl->ik', projector, projector, nabla_u) + # orthogonality_violation_1 = np.einsum('i,ij->j', u_t_cov, Daub) + # orthogonality_violation_2 = np.einsum('ij,j->i', Daub, u_t_cov) + # orthogonality_violation_3 = np.einsum('i,j,ij->', u_t_cov, u_t_cov, Daub) + # Daub = Daub + np.einsum('i,j->ij', u_t, orthogonality_violation_1) + np.einsum('i,j->ij', orthogonality_violation_2, u_t) + \ + # np.multiply(orthogonality_violation_3, np.einsum('i,j->ij', u_t, u_t) ) + # exp_t = np.einsum('ij,ji->', Daub, metric) + # shear_t = np.multiply(1/2., Daub + np.einsum('ij->ji', Daub)) - np.multiply( exp_t/ spatial_dims, h_ab) + # vort_t = np.multiply(1/2., Daub - np.einsum('ij->ji', Daub)) + + + # CLOSURE INGREDIENTS: DERIVATIVES OF N_TILDE AND T_TILDE + projector = np.zeros((3,3)) + np.fill_diagonal(projector, 1) + projector += np.einsum('i,j->ij',u_t_cov, u_t) #This is different from the projector above: look at indices! + + sD_n_tilde = np.einsum('ij,j->i', projector, nabla_n) + sD_T_tilde = np.einsum('ij,j->i', projector, nabla_T) + + n_tilde_dot = np.einsum('i,i->', u_t, nabla_n) + T_tilde_dot = np.einsum('i,i->', u_t, nabla_T) + + Theta_t = sD_T_tilde + np.multiply(T_t, np.einsum('ij,j->i', metric, acc_t)) + + closure_vars_strs = ['shear_tilde', 'exp_tilde', 'acc_tilde', 'vort_tilde','Theta_tilde', 'n_tilde_dot', \ + 'T_tilde_dot', 'sD_n_tilde', 'sD_T_tilde' ] + closure_vars = [shear_t, exp_t, acc_t, vort_t, Theta_t, n_tilde_dot, T_tilde_dot, sD_n_tilde, sD_T_tilde] + return closure_vars_strs, closure_vars, [h,i,j] + + def closure_ingredients_parallel(self, n_cpus): + """ + Routine to compute and store the closure ingredients at all gridpoints in parallel + + Parameters: + ----------- + n_cpus: int + number of processes for parallelization + + Notes: + ------ + worth thinking about merging this with decompose structures? + """ + Nt = self.domain_vars['Nt'] + Nx = self.domain_vars['Nx'] + Ny = self.domain_vars['Ny'] + + args_for_pool = [] + for h in range(Nt): + for i in range(Nx): + for j in range(Ny): + u_t = self.meso_vars['u_tilde'][h,i,j] + nabla_u = self.deriv_vars['D_u_tilde'][h,i,j] + T_t = self.meso_vars['T_tilde'][h,i,j] + nabla_T = self.deriv_vars['D_T_tilde'][h,i,j] + nabla_n = self.deriv_vars['D_n_tilde'][h,i,j] + args_for_pool.append((u_t, nabla_u, T_t, nabla_T, nabla_n, h, i, j)) + + with mp.Pool(processes=n_cpus) as pool: + print('Computing closure ingredients with {} processes'.format(pool._processes), flush=True) + for result in pool.starmap(resHD2D.closure_ingredients_task, args_for_pool): + keys, values, grid_idxs = result + # if self.filter_vars['U_success'][tuple(grid_idxs)]: #this check is in practice always True + for idx, key in enumerate(keys): + try: + self.meso_vars[key] + except KeyError: + print('The key {} does not belong to meso_vars yet, adding it!'.format(key), flush=True) + shape = values[idx].shape + self.meso_vars.update({key : np.zeros(([Nt,Nx,Ny]+ list(shape)))}) + finally: + self.meso_vars[key][tuple(grid_idxs)] = values[idx] + + def EL_style_closure_gridpoint(self, h, i, j): + """ + Compute bulk, shear via scalarization + PA trick, thermal conductivity later. + At a point. + Returns: coefficient(s) at a point. + """ + coefficients_names=[] + coefficients=[] + + # CALCULATING BULK VISCOUS COEFF + zeta = self.meso_vars['Pi_res'][h,i,j] / self.meso_vars['exp_tilde'][h,i,j] + coefficients_names.append('zeta') + coefficients.append(zeta) + + # CALCULATING SHEAR VISCOUS COEFF + pi_res_sq = np.einsum('ij,kl,ik,jl->', self.meso_vars['pi_res'][h,i,j], self.meso_vars['pi_res'][h,i,j], self.metric, self.metric) + shear_sq = np.einsum('ij,kl,ik,jl->', self.meso_vars['shear_tilde'][h,i,j], self.meso_vars['shear_tilde'][h,i,j], self.metric, self.metric) + eta = pi_res_sq/shear_sq + # Compute sign of eta by looking at the PA of pi_res with higher eigenvalue. + # Change shear to the eigenbasis of pi_res (using that the matrix is unitary as pi_res is sym) + pi_eig, pi_eigv = np.linalg.eigh(self.meso_vars['pi_res'][h,i,j]) + transformed_shear = np.einsum('ji,jk,kl', pi_eigv, self.meso_vars['shear_tilde'][h,i,j], pi_eigv) + abs_pi_eig = np.abs(pi_eig) + pos = list(abs_pi_eig).index(np.max(abs_pi_eig)) + eta = eta * np.sign(pi_eig[pos] / transformed_shear[pos,pos]) + coefficients_names.append('eta') + coefficients.append(eta) + + # CALCULATING THE HEAT CONDUCTIVITIY + q_res = self.meso_vars['q_res'][h,i,j] + Theta_t = self.meso_vars['Theta_tilde'][h,i,j] + q_res_sq = np.einsum('i,ij,j', q_res, self.metric, q_res) + Theta_t_sq = np.einsum('i,ij,j', Theta_t, self.metric, Theta_t) + cos_angle = np.einsum('i,ij,j', q_res, self.metric, Theta_t) / (q_res_sq * Theta_t_sq) + sign = np.sign(cos_angle) + kappa = sign * np.sqrt(q_res_sq / Theta_t_sq) + coefficients_names.append('kappa') + coefficients.append(kappa) + + return coefficients_names, coefficients + + def EL_style_closure(self): + """ + Wrapper of EL_style_closure_gridpoint() + """ + Nt, Nx, Ny = self.domain_vars['Nt'], self.domain_vars['Nx'], self.domain_vars['Ny'] + for h in range(Nt): + for i in range(Nx): + for j in range(Ny): + if self.filter_vars['U_success'][h,i,j]: + keys, values = self.EL_style_closure_gridpoint(h,i,j) + # try-except block to extend the meso_vars dictionary with the dissipative coefficients + for idx, key in enumerate(keys): + try: + self.meso_vars[key] + except KeyError: + print('The key {} does not belong to meso_vars yet, adding it!'.format(key)) + shape = values[idx].shape + self.meso_vars.update({key: np.zeros(([Nt, Nx, Ny] + list(shape)))}) + finally: + self.meso_vars[key][h,i,j] = values[idx] + + @staticmethod + def EL_style_closure_task(Pi_res, exp, pi_res, shear, q_res, Theta, h, i, j): + """ + Task for computing the effective dissipative coefficients at all gridpoint + in parallel + + Parameters: + ----------- + Pi_res: float + Pressure residual using meso EoS + + exp: float + the Favre expansion rate + + pi_res: np.array (3,3) + the residual anisotropic stresses (should be trace-free) + + shear: np.array (3,3) + the shear matrix computed from Favre obs derivatives + + q_res: np.array (3,) + the residual heat-flux + + Theta: np.array (3,) + the temperature spatial gradients with acceleration term + + h,i,j: integers + grid indixes corresponding to point on grid + + Return: + ------- + (coeff names, coeff, [h,i,j]) + + """ + coefficients_names=[] + coefficients=[] + + metric = np.zeros((3,3)) + metric[0,0] = -1 + metric[1,1] = metric[2,2] = +1 + + # CALCULATING BULK VISCOUS COEFF + zeta = - Pi_res / exp + coefficients_names.append('zeta') + coefficients.append(zeta) + + # CALCULATING SHEAR VISCOUS COEFF + pi_res_sq = np.einsum('ij,kl,ik,jl->', pi_res, pi_res, metric, metric) + shear_sq = np.einsum('ij,kl,ik,jl->', shear, shear, metric, metric) + eta = np.sqrt(pi_res_sq/shear_sq) + # Compute sign of eta by looking at the PA of pi_res with higher eigenvalue. + # Change shear to the eigenbasis of pi_res (using that the matrix is unitary as pi_res is sym) + pi_eig, pi_eigv = np.linalg.eigh(pi_res) + transformed_shear = np.einsum('ji,jk,kl', pi_eigv, shear, pi_eigv) + abs_pi_eig = np.abs(pi_eig) + pos = list(abs_pi_eig).index(np.max(abs_pi_eig)) + eta = eta * np.sign( - pi_eig[pos] / transformed_shear[pos,pos]) + coefficients_names.append('eta') + coefficients.append(eta) + + # CALCULATING THE HEAT CONDUCTIVITIY + q_res_sq = np.einsum('i,ij,j', q_res, metric, q_res) + Theta_sq = np.einsum('i,ij,j', Theta, metric, Theta) + cos_angle = np.einsum('i,ij,j', q_res, metric, Theta) / (q_res_sq * Theta_sq) + sign = - np.sign(cos_angle) + kappa = sign * np.sqrt(q_res_sq / Theta_sq) + coefficients_names.append('kappa') + coefficients.append(kappa) + + + return coefficients_names, coefficients, [h,i,j] + + def EL_style_closure_parallel(self, n_cpus): + """ + Routine to launch EL_style_closure_task in parallel across multiple gridpoints + + Parameters: + ----------- + n_cpus: int + number of processes for parallelization + + Notes: + ------ + worth thinking about merging this with decompose_structures_task and closure_ingredients_task? + could compute just u_t at all points, so to be able to take derivatives of it + Then since you're accesing a single point, you could decompose, compute ingredients and extract coeff + in one go. Might give you a speed up: the loop for preparning arguments for pool is done once + and not twice/thrice, and the processes are opened/closed half the times. + """ + args_for_pool=[] + + Nt = self.domain_vars['Nt'] + Nx = self.domain_vars['Nx'] + Ny = self.domain_vars['Ny'] + + + for h in range(Nt): + for i in range(Nx): + for j in range(Ny): + Pi_res = self.meso_vars['Pi_res'][h,i,j] + pi_res = self.meso_vars['pi_res'][h,i,j] + q_res = self.meso_vars['q_res'][h,i,j] + exp_t = self.meso_vars['exp_tilde'][h,i,j] + shear_t = self.meso_vars['shear_tilde'][h,i,j] + Theta_t = self.meso_vars['Theta_tilde'][h,i,j] + + args_for_pool.append((Pi_res, exp_t, pi_res, shear_t, q_res, Theta_t, h,i,j)) + + + with mp.Pool(processes=n_cpus) as pool: + print('Computing dissipative coefficients with {} processes'.format(pool._processes), flush=True) + for result in pool.starmap(resHD2D.EL_style_closure_task, args_for_pool): + keys, values, grid_idxs = result + # if self.filter_vars['U_success'][tuple(grid_idxs)]: #this check is in practice always True + for idx, key in enumerate(keys): + try: + self.meso_vars[key] + except KeyError: + print('The key {} does not belong to meso_vars yet, adding it!'.format(key), flush=True) + shape = values[idx].shape + self.meso_vars.update({key : np.zeros(([Nt,Nx,Ny]+ list(shape)))}) + finally: + self.meso_vars[key][tuple(grid_idxs)] = values[idx] + + @staticmethod + def EL_componentwise_task(pi_res, shear, h, i, j): + """ + Task for computing the shear coefficients componentwise + + Parameters: + ----------- + pi_res: nd.array + the anisotropic stress residual at gridpoint h,i,j + + shear: nd.array + the (Favre-observer) shear tensor at gridpoint h,i,j + + Returns: + -------- + list of strs: + the names by which you want to store data in the class instance + + nd.array: + the componentwise values of the coefficient, for now eta only + + list: + the gridpoint indices on the meso-grid + + Notes: + ------ + """ + eta_componentwise = np.zeros(pi_res.shape) + eta_componentwise = - np.divide(pi_res, shear) + coeff_name = 'eta_cw' + + return [coeff_name], [eta_componentwise], [h, i, j] + + def EL_componentwise_parallel(self, n_cpus, store=False): + """ + Wrapper of EL_componentwise_task: launch the task in parallel with n_cpus + + Parameters: + ----------- + n_cpus: int + number of processes for parallelization + + store: bool + If true, the meso_vars dictionary is extended to include these + Otherwise, the dictionary is created and returned + """ + args_for_pool=[] + + Nt = self.domain_vars['Nt'] + Nx = self.domain_vars['Nx'] + Ny = self.domain_vars['Ny'] + + printshit = self.meso_vars['shear_tilde'].shape + print(f'shear_tilde.shape: {printshit}') + printshit = self.meso_vars['pi_res'].shape + print(f'pi_res.shape: {printshit}') + + for h in range(Nt): + for i in range(Nx): + for j in range(Ny): + shear_t = self.meso_vars['shear_tilde'][h,i,j] + pi_res = self.meso_vars['pi_res'][h,i,j] + + args_for_pool.append((pi_res, shear_t, h, i, j)) + + with mp.Pool(processes=n_cpus) as pool: + print('Computing vars for modelling coefficients with {} processes'.format(pool._processes), flush=True) + + for result in pool.starmap(resHD2D.EL_componentwise_task, args_for_pool): + keys, values, grid_idxs = result + + if store: + for idx, key in enumerate(keys): + try: + self.meso_vars[key] + except KeyError: + print('The key {} does not belong to meso_vars yet, adding it!'.format(key), flush=True) + try: + shape = values[idx].shape + except AttributeError: + shape = [] + self.meso_vars.update({key : np.zeros(([Nt,Nx,Ny]+ list(shape)))}) + finally: + self.meso_vars[key][tuple(grid_idxs)] = values[idx] + + else: + for i, key in enumerate(keys): + try: + results_dictionary[key] + except UnboundLocalError: + results_dictionary = dict.fromkeys([key]) + shape = values[i].shape + results_dictionary[key] = np.zeros(([Nt, Nx, Ny] + list(shape))) + except KeyError: + shape = values[i].shape + results_dictionary.update( {key : np.zeros(([Nt, Nx, Ny] + list(shape)))} ) + + finally: + results_dictionary[key][tuple(grid_idxs)] = values[i] + + if not store: + return results_dictionary + + @staticmethod + def modelling_coefficients_task(shear, vort, acc, Theta, sD_n_tilde, Pi_res, pi_res, q_res, h, i, j): + """ + Task (to be used in parallel) to compute various quantities that will be used later + for modelling the extracted closure coefficients. + """ + var_names = [] + vars = [] + + metric = np.zeros((3,3)) + metric[0,0] = -1 + metric[1,1] = metric[2,2] = +1 + + # Computing various invariants of the velocity gradients + + # This is required as the determinant is an invariant (does not change under coordinates changes) + # only when the tensor is written as a rank (1,1) + shear_rank11 = np.einsum('ij,jk->ik', shear, metric) + det_shear = det(shear_rank11) + var_names.append('det_shear') + vars.append(det_shear) + + shear_sq = np.einsum('ij,kl,ik,jl->', shear, shear, metric, metric) + var_names.append('shear_sq') + vars.append(shear_sq) + + vort_sq = np.einsum('ij,kl,ik,jl->', vort, vort, metric, metric) + var_names.append('vort_sq') + vars.append(vort_sq) + + acc_mag = np.sqrt(np.einsum('i,ij,j->', acc, metric, acc)) + var_names.append('acc_mag') + vars.append(acc_mag) + + # Computing quantities related to the Q-criterion + Q1 = shear_sq - vort_sq + var_names.append('Q1') + vars.append(Q1) + + Q2 = shear_sq/vort_sq + var_names.append('Q2') + vars.append(Q2) + + # Computing scalars out of thermo gradients + Theta_sq = np.einsum('i,ij,j', Theta, metric, Theta) + var_names.append('Theta_sq') + vars.append(Theta_sq) + + sD_n_tilde_sq = np.einsum('i,ij,j->', sD_n_tilde, metric, sD_n_tilde) + var_names.append('sD_n_tilde_sq') + vars.append(sD_n_tilde_sq) + + dot_Dn_Theta = np.einsum('i,ij,j->', sD_n_tilde, metric, Theta ) + var_names.append('dot_Dn_Theta') + vars.append(dot_Dn_Theta) + + # Computing squares of residuals and also of Theta_tilde + Pi_res_sq = Pi_res * Pi_res + var_names.append('Pi_res_sq') + vars.append(Pi_res_sq) + + pi_res_sq = np.einsum('ij,kl,ik,jl->', pi_res, pi_res, metric, metric) + var_names.append('pi_res_sq') + vars.append(pi_res_sq) + + q_res_sq = np.einsum('i,ij,j', q_res, metric, q_res) + var_names.append('q_res_sq') + vars.append(q_res_sq) + + return var_names, vars, [h,i,j] + + def modelling_coefficients_parallel(self, n_cpus): + """ + Routine to parallelize the task 'Computing_calibration_vars_task' + """ + args_for_pool=[] + + Nt = self.domain_vars['Nt'] + Nx = self.domain_vars['Nx'] + Ny = self.domain_vars['Ny'] + + for h in range(Nt): + for i in range(Nx): + for j in range(Ny): + shear_t = self.meso_vars['shear_tilde'][h,i,j] + vort_t = self.meso_vars['vort_tilde'][h,i,j] + acc_t = self.meso_vars['acc_tilde'][h,i,j] + Theta_t = self.meso_vars['Theta_tilde'][h,i,j] + sD_n_tilde = self.meso_vars['sD_n_tilde'][h,i,j] + Pi_res = self.meso_vars['Pi_res'][h,i,j] + pi_res = self.meso_vars['pi_res'][h,i,j] + q_res = self.meso_vars['q_res'][h,i,j] + + args_for_pool.append((shear_t, vort_t, acc_t, Theta_t, sD_n_tilde, Pi_res, pi_res, q_res, h, i, j)) + + with mp.Pool(processes=n_cpus) as pool: + print('Computing vars for modelling coefficients with {} processes'.format(pool._processes), flush=True) + + for result in pool.starmap(resHD2D.modelling_coefficients_task, args_for_pool): + keys, values, grid_idxs = result + + for idx, key in enumerate(keys): + try: + self.meso_vars[key] + except KeyError: + print('The key {} does not belong to meso_vars yet, adding it!'.format(key), flush=True) + try: + shape = values[idx].shape + except AttributeError: + shape = [] + self.meso_vars.update({key : np.zeros(([Nt,Nx,Ny]+ list(shape)))}) + finally: + self.meso_vars[key][tuple(grid_idxs)] = values[idx] + + def weights_Q1_skew(self): + """ + Build weights for gridpoints: downplay points where shear is small, but take into + account both positive and negative values of Q1 + """ + # def symlog(array): + # return np.sign(array) * np.log10(np.abs(array)+1) + + Q1 = self.meso_vars['Q1'] + symlog_Q1 = MySymLogPlotting.symlog_var(Q1) + + M_pos = np.amax(symlog_Q1) + m_neg = np.amin(symlog_Q1) + + symlog_Q1_pos = np.ma.masked_where(symlog_Q1 < 0, symlog_Q1, copy=True).compressed() + symlog_Q1_neg = np.ma.masked_where(symlog_Q1 > 0, symlog_Q1, copy=True).compressed() + M_neg = np.amax(symlog_Q1_neg) + m_pos = np.amin(symlog_Q1_pos) + + c_pos = (M_pos + m_pos)/2. + c_neg = (M_neg + m_neg)/2. + + def get_weights(x, c_pos, c_neg): + if x >= 0: + result = (np.tanh(x-c_pos)+1)/2 + else: + result = (-np.tanh(x-c_neg)+1)/2 + return (result+1.)/2. + + Nt = self.domain_vars['Nt'] + Nx = self.domain_vars['Nx'] + Ny = self.domain_vars['Ny'] + + weights = np.zeros((Nt, Nx, Ny)) + for h in range(Nt): + for i in range(Nx): + for j in range(Ny): + weights[h,i,j] = get_weights(symlog_Q1[h,i,j], c_pos, c_neg) + + self.meso_vars.update({'weights' : weights}) + + def weights_Q1_non_neg(self): + """ + Build weights for gridpoints: downplay points where Q1 is negative + """ + # def symlog(array): + # return np.sign(array) * np.log10(np.abs(array)+1) + + Q1 = self.meso_vars['Q1'] + symlog_Q1 = MySymLogPlotting.symlog_var(Q1) + symlog_Q1_pos = np.ma.masked_where(symlog_Q1 < 0, symlog_Q1, copy=True).compressed() + symlog_Q1_neg = np.ma.masked_where(symlog_Q1 > 0, symlog_Q1, copy=True).compressed() + + M_pos = np.amax(symlog_Q1_pos) + m_pos = np.amin(symlog_Q1_pos) + M_neg = np.amax(symlog_Q1_neg) + m_neg = np.amin(symlog_Q1_neg) + + c = (M_pos + m_neg)/2. + a_pos = (M_pos + m_pos)/2. + a_neg = np.abs(M_neg + m_neg)/2. + a = (a_pos + a_neg)/2. + + def get_weights(x, c, a): + result = np.tanh((x-c)/a) + return (result+1.)/2. + + Nt = self.domain_vars['Nt'] + Nx = self.domain_vars['Nx'] + Ny = self.domain_vars['Ny'] + + weights = np.zeros((Nt, Nx, Ny)) + for h in range(Nt): + for i in range(Nx): + for j in range(Ny): + weights[h,i,j] = get_weights(symlog_Q1[h,i,j], c, a) + + self.meso_vars.update({'weights' : weights}) + + def weights_Q2(self): + """ + Build weights for gridpoints based on Q2 + """ + Q2 = np.log10(self.meso_vars['Q2']) + + M = np.amax(Q2) + m = np.amin(Q2) + scale = (M+m)/2 + + def get_weights(x, scale): + result = np.tanh(x/scale) + result = (result +1)/2 + return result + + Nt = self.domain_vars['Nt'] + Nx = self.domain_vars['Nx'] + Ny = self.domain_vars['Ny'] + + weights = np.zeros((Nt, Nx, Ny)) + for h in range(Nt): + for i in range(Nx): + for j in range(Ny): + weights[h,i,j] = get_weights(Q2[h,i,j], scale) + + self.meso_vars.update({'weights' : weights}) + + def residual_weights(self, residual_str): + """ + Build weights based on residual corresponding to input 'residual_str'. + Downplay points where this is small, that is those points where a closure is less required! + """ + + residual = np.log10(self.meso_vars[residual_str]) + + M = np.amax(residual) + m = np.amin(residual) + scale = (M+m)/2 + + def get_weights(x, scale): + result = np.tanh(x/scale) + result = (result +1)/2 + return result + + Nt = self.domain_vars['Nt'] + Nx = self.domain_vars['Nx'] + Ny = self.domain_vars['Ny'] + + weights = np.zeros((Nt, Nx, Ny)) + for h in range(Nt): + for i in range(Nx): + for j in range(Ny): + weights[h,i,j] = get_weights(residual[h,i,j], scale) + + self.meso_vars.update({'weights' : weights}) + + def denominator_weights(self, coeff_denominator_str): + """ + Build weights based on quantity corresponding to 'coeff_denominator_str'. + Downplay points where this is small, that is points where the extracted coefficient are less + trustworthy + """ + + denominator = np.log10(self.meso_vars[coeff_denominator_str]) + + M = np.amax(denominator) + m = np.amin(denominator) + scale = (M+m)/2 + + def get_weights(x, scale): + result = np.tanh(x/scale) + result = (result +1)/2 + return result + + Nt = self.domain_vars['Nt'] + Nx = self.domain_vars['Nx'] + Ny = self.domain_vars['Ny'] + + weights = np.zeros((Nt, Nx, Ny)) + for h in range(Nt): + for i in range(Nx): + for j in range(Ny): + weights[h,i,j] = get_weights(denominator[h,i,j], scale) + + self.meso_vars.update({'weights' : weights}) + + def EL_style_closure_regression(self, CoefficientAnalysis): + """ + Takes in a list of correlation quantities (strings? The regressors needed are computed + previously via closure ingredients) + Pass the dataset to a CoefficientAnalysis instance. + Plot the correlations with the regressors and perform the regression using methods from a Coefficient + Analysis instance. + """ + # FICTITIOUS RANGES AND POINTS TO TEST TRIM_DATA ROUTINE + ranges = [[self.domain_vars['Tmin'], self.domain_vars['Tmax']],\ + [self.domain_vars['Xmin'], self.domain_vars['Xmax']],\ + [self.domain_vars['Ymin'], self.domain_vars['Ymax']]] + points = self.domain_vars['Points'] + + # TESTING SCALAR REGRESSION ROUTINE + # y = self.meso_vars['zeta'] + # X = [self.meso_vars['eps_tilde'], self.meso_vars['n_tilde']] + # stats_result = CoefficientAnalysis.scalar_regression(y, X, ranges, points) + # print(*stats_result) + + # TESTING TENSOR REGRESSION ROUTINE + # y=self.meso_vars['q_res'] + # X=[self.meso_vars['e_tilde'], self.meso_vars['u_tilde']] + # # stats_result = CoefficientAnalysis.tensor_components_regression(y, X, 2,ranges, points, components=[(0,),(2,)]) + # stats_result = CoefficientAnalysis.tensor_components_regression(y, X, 2,ranges, points, add_intercept=False) + # for i in range(len(stats_result)): + # print('{}-th component: \n {} \n\n'.format(i, stats_result[i])) + + # TESTING CORRELATION VISUALIZATION ROUTINES + # x = self.meso_vars['n_tilde'] + # y = self.meso_vars['eps_tilde'] + # labels = ['n_tilde','eps_tilde'] + # g1=CoefficientAnalysis.visualize_correlation(x, y, xlabel=labels[0], ylabel=labels[1]) + # fig=g1.figure + # fig.savefig('Single_correlation_plot.pdf', format='pdf') + + # data=[self.meso_vars['eta'], self.meso_vars['b_tilde'], self.meso_vars['eps_tilde'], self.meso_vars['n_tilde']] + # labels=['eta', 'b_tilde', 'eps_tilde', 'n_tilde'] + # g2=CoefficientAnalysis.visualize_many_correlations(data, labels) + + # data=[self.meso_vars['zeta'], self.meso_vars['b_tilde'], self.meso_vars['eps_tilde'], self.meso_vars['n_tilde']] + # labels=['zeta', 'b_tilde', 'eps_tilde', 'n_tilde'] + # gbis= CoefficientAnalysis.visualize_many_correlations(data, labels) + # # plt.savefig('Many_correlations_plot.pdf', format='pdf') + # plt.show() + + # TESTING THE CHECK REGRESSORS ROUTINE: + # data=[self.meso_vars['b_tilde'], self.meso_vars['eps_tilde'], self.meso_vars['n_tilde']] + # labels=['b_tilde', 'eps_tilde', 'n_tilde'] + # g1=CoefficientAnalysis.visualize_many_correlations(data, labels) + # comps, g2 = CoefficientAnalysis.PCA_find_regressors_subset(data, ranges = ranges, model_points = points) + # g2.fig.suptitle("Correlation between PC (standardized data)") + # g2.fig.subplots_adjust(top=0.94) + # print(comps) + # plt.show() + +if __name__ == '__main__': + + + + ######################################################## + # TESTING SERIAL IMPLEMENTATION + ######################################################## + # FileReader = METHOD_HDF5('/Users/thomas/Dropbox/Work/projects/Filtering/Data/test_res100/') + # micro_model = IdealHD_2D() + # FileReader.read_in_data(micro_model) + # micro_model.setup_structures() + # find_obs = FindObs_drift_root(micro_model, 0.001) + # filter = spatial_box_filter(micro_model, 0.003) + + + # CPU_start_time = time.process_time() + # meso_model = resHD2D(micro_model, find_obs, filter) + + # # print(micro_model.domain_vars['tmin'], micro_model.domain_vars['tmax']) + + # t_range = [1.502, 1.505] + # x_range = [0.29, 0.36] + # y_range = [0.39, 0.46] + + # meso_model.setup_meso_grid([t_range, x_range, y_range]) + # meso_model.find_observers() + # meso_model.filter_micro_variables() + + + # print('Filter stage ended') + # # # meso_model.decompose_structures_gridpoint(1,1,1) + # meso_model.decompose_structures() + + # print('Decomposition stage ended') + # # print(meso_model.calculate_derivative_gridpoint('u_tilde', 0,1,1,0)) + # meso_model.calculate_derivatives() + + # # meso_model.closure_ingredients_gridpoint(1,1,0) + # # meso_model.closure_ingredients() + # # print('Derivatives and Decomposition stage ended') + + # # meso_model.EL_style_closure_gridpoint(1,2,3) + # # meso_model.EL_style_closure() + # # regressor = CoefficientsAnalysis() + # # meso_model.EL_style_closure_regression(regressor) + + # # print('Total time is {}'.format(time.process_time() - CPU_start_time)) + + + # ######################################################## + # # TESTING PARALLEL IMPLEMENTATION: find obs + filter + # ######################################################## + # FileReader = METHOD_HDF5('../Data/test_res100/') + # micro_model = IdealHD_2D() + # FileReader.read_in_data(micro_model) + # micro_model.setup_structures() + + # t_range = [1.502, 1.504] + # x_range = [0.05, 0.95] + # y_range = [0.05, 0.95] + + + # # find obs - serial + # start_time = time.perf_counter() + # find_obs = FindObs_drift_root(micro_model, 0.001) + # filter = spatial_box_filter(micro_model, 0.003) + # meso_model = resHD2D(micro_model, find_obs, filter) + # meso_model.setup_meso_grid([t_range, x_range, y_range]) + + # num_points = meso_model.domain_vars['Nt'] * meso_model.domain_vars['Nx'] * meso_model.domain_vars['Ny'] + # print(f'Testing parallelization with {num_points} points\n') + + # meso_model.find_observers() + # serial_time = time.perf_counter() - start_time + # print('Serial execution time: {}\n'.format(serial_time)) + + + # # fin obs - parallel + # start_time = time.perf_counter() + # find_obs = FindObs_root_parallel(micro_model, 0.001) + # filter = spatial_box_filter(micro_model, 0.003) + # meso_model = resHD2D(micro_model, find_obs, filter) + # meso_model.setup_meso_grid([t_range, x_range, y_range]) + # meso_model.find_observers_parallel() + # parallel_time = time.perf_counter() - start_time + # print('Finished finding observers in parallel, execution time: {}\n'.format(parallel_time)) + # print('Speed-up factor: {}'.format(serial_time/parallel_time)) + + + # # now filtering serial + # start_time = time.perf_counter() + # meso_model.filter_micro_variables() + # serial_time = time.perf_counter() - start_time + # print('Finished filtering in serial, time taken {}\n'.format(serial_time)) + + # # now filtering in parallel + # parallel_filter = box_filter_parallel(micro_model, 0.003) + # meso_model.set_filter(parallel_filter) + # start_time = time.perf_counter() + # meso_model.filter_micro_vars_parallel() + # parallel_time = time.perf_counter() - start_time + # print('Finished filtering in parallel, time taken {}\n'.format(parallel_time)) + # print('Speed-up factor: {}'.format(serial_time/parallel_time)) + + + ######################################################## + # TESTING PARALLEL IMPLEMENTATION: meso routines + ######################################################## + directory = '../Data/test_res100/' + FileReader = METHOD_HDF5(directory) + micro_model = IdealHD_2D() + FileReader.read_in_data(micro_model) + micro_model.setup_structures() + + t_range = [1.502, 1.504] + x_range = [0.05, 0.06] + y_range = [0.05, 0.06] + + # set up meso model and grid + find_obs = FindObs_root_parallel(micro_model, 0.001) + filter = box_filter_parallel(micro_model, 0.003) + meso_model = resHD2D(micro_model, find_obs, filter) + meso_model.setup_meso_grid([t_range, x_range, y_range]) + + num_points = meso_model.domain_vars['Nt'] * meso_model.domain_vars['Nx'] * meso_model.domain_vars['Ny'] + print(f'Testing parallelization with {num_points} points\n') + + # fin obs - parallel + start_time = time.perf_counter() + meso_model.find_observers_parallel(os.cpu_count()) + parallel_time = time.perf_counter() - start_time + print('Finished finding observers in parallel, execution time: {}\n'.format(parallel_time)) + + # now filtering in parallel + start_time = time.perf_counter() + meso_model.filter_micro_vars_parallel(os.cpu_count()) + parallel_time = time.perf_counter() - start_time + print('Finished filtering in parallel, time taken {}\n'.format(parallel_time)) + + # # now testing serial vs parallel decomposition + # start_time = time.perf_counter() + # meso_model.decompose_structures() + # serial_time = time.perf_counter() - start_time + # print('Finished decomposing in serial, time taken {}\n'.format(serial_time)) + + start_time = time.perf_counter() + meso_model.decompose_structures_parallel(os.cpu_count()) + parallel_time = time.perf_counter() - start_time + print('Finished decomposing in parallel, time taken {}\n'.format(parallel_time)) + # print('Speed-up factor: {}'.format(serial_time/parallel_time)) + + \ No newline at end of file diff --git a/master_files/MicroModels.py b/master_files/MicroModels.py new file mode 100644 index 0000000..a8555ac --- /dev/null +++ b/master_files/MicroModels.py @@ -0,0 +1,802 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Mar 31 10:00:02 2023 + +@author: Thomas +""" + +import numpy as np +import math +import time +import os +import multiprocessing as mp +from itertools import product + +from scipy.interpolate import interpn +from multimethod import multimethod + +from FileReaders import * +from system.BaseFunctionality import * + + +# These are the symbols, so be careful when using these to construct vectors! +# levi3D = np.array([[[ np.sign(i-j) * np.sign(j- k) * np.sign(k-i) \ +# for k in range(3)]for j in range(3) ] for i in range(3) ]) + +# levi4D = np.array([[[[ np.sign(i - j) * np.sign(j - k) * np.sign(k - l) * np.sign(i - l) \ +# for l in range(4)] for k in range(4) ] for j in range(4)] for i in range(4)]) + + +class IdealMHD_2D(object): + """ + ADD SHORT DESCRIPTION OF THE CLASS AND ITS METHODS + """ + def __init__(self, interp_method = "linear"): + """ + Sets up the variables and dictionaries, strings correspond to + those used in METHOD + + Parameters + ---------- + interp_method: str + optional method to be used by interpn + """ + self.spatial_dims = 2 + self.interp_method = interp_method + + self.metric = np.zeros((3,3)) + self.metric[0,0] = -1 + self.metric[1,1] = self.metric[2,2] = +1 + + # This is the Levi-Civita symbol, not tensor, so be careful when using it + self.Levi3D = np.array([[[ np.sign(i-j) * np.sign(j- k) * np.sign(k-i) \ + for k in range(3)]for j in range(3) ] for i in range(3) ]) + + #Dictionary for grid: info and points + self.domain_int_strs = ('nt','nx','ny') + self.domain_float_strs = ("tmin","tmax","xmin","xmax","ymin","ymax","dt","dx","dy") + self.domain_array_strs = ("t","x","y","points") + self.domain_vars = dict.fromkeys(self.domain_int_strs+self.domain_float_strs+self.domain_array_strs) + for str in self.domain_vars: + self.domain_vars[str] = [] + + #Dictionary for primitive var + self.prim_strs = ("vx","vy","n","p", "Bz") + self.prim_vars = dict.fromkeys(self.prim_strs) + for str in self.prim_strs: + self.prim_vars[str] = [] + + #Dictionary for auxiliary var + self.aux_strs = ("W","h","bz","bsq", "e") + self.aux_vars = dict.fromkeys(self.aux_strs) + for str in self.aux_strs: + self.aux_vars[str] = [] + + #Dictionary for structures + self.structures_strs = ("BC", "SETfl", "SETem", "Fab") + self.structures = dict.fromkeys(self.structures_strs) + for str in self.structures_strs: + self.structures[str] = [] + + def get_spatial_dims(self): + return self.spatial_dims + + def get_model_name(self): + return 'IdealMHD_2D' + + def get_domain_strs(self): + return self.domain_int_strs + self.domain_float_strs + self.domain_array_strs + + def get_prim_strs(self): + return self.prim_strs + + def get_aux_strs(self): + return self.aux_strs + + def get_structures_strs(self): + return self.structures_strs + + def get_all_var_strs(self): + return self.get_prim_strs() + self.get_aux_strs() + self.get_structures_strs() + + def get_gridpoints(self): + """ + Pretty self-explanatory + """ + return self.domain_vars['points'] + + def get_interpol_var(self, var, point): + """ + Returns the interpolated variables at the point. + + Parameters + ---------- + var: str corresponding to primitive, auxiliary or structre variable + + point : list of floats + ordered coordinates: t,x,y + + Return + ------ + Interpolated values/arrays corresponding to variable. + Empty list if none of the variables is a primitive, auxiliary o structure of the micro_model + + Notes + ----- + Interpolation gives errors when applied to boundary + """ + + if var in self.get_prim_strs(): + return interpn(self.domain_vars['points'], self.prim_vars[var], point, method = self.interp_method)[0] + elif var in self.get_aux_strs(): + return interpn(self.domain_vars['points'], self.aux_vars[var], point, method = self.interp_method)[0] + elif var in self.get_structures_strs(): + return interpn(self.domain_vars['points'], self.structures[var], point, method = self.interp_method)[0] + else: + print(f'{var} is not a primitive, auxiliary variable or structure of the micro_model!!') + + @multimethod + def get_var_gridpoint(self, var: str, h: object, i: object, j: object): + """ + Returns variable corresponding to input 'var' at gridpoint identified by h,i,j + + Parameters: + ----------- + var: str + String corresponding to primitive, auxiliary or structure variable + + h,i,j: int + integers corresponding to position on the grid. + + Returns: + -------- + Values or arrays corresponding to variable evaluated at the closest grid-point to input 'point'. + + Notes: + ------ + This method is useful e.g. for plotting the raw data. + """ + if var in self.get_prim_strs(): + return self.prim_vars[var][h,i,j] + elif var in self.get_aux_strs(): + return self.aux_vars[var][h,i,j] + elif var in self.get_structures_strs(): + return self.structures[var][h,i,j] + else: + print('{} is not a variable of model {}'.format(var, self.get_model_name())) + return None + + @multimethod + def get_var_gridpoint(self, var: str, point: object): + """ + Returns variable corresponding to input 'var' at gridpoint + closest to input 'point'. + + Parameters: + ----------- + vars: string corresponding to primitive, auxiliary or structure variable + + point: list of 2+1 floats + + Returns: + -------- + Values or arrays corresponding to variable evaluated at the closest grid-point to input 'point'. + + Notes: + ------ + This method should be used in case using interpolated values + becomes too expensive. + """ + indices = Base.find_nearest_cell(point, self.domain_vars['points']) + if var in self.get_prim_strs(): + return self.prim_vars[var][tuple(indices)] + elif var in self.get_aux_strs(): + return self.aux_vars[var][tuple(indices)] + elif var in self.get_structures_strs(): + return self.structures[var][tuple(indices)] + else: + print(f"{var} is not a variable of IdealMHD_2D!") + return None + + def setup_structures(self): + """ + Set up the structures (i.e baryon (mass) current BC, Stress-Energy and Faraday tensors + + Notes: + ------ + Structures are built as multi-dim np.arrays, with the first three indices referring + to the grid, while the last one or two refer to space-time components. + + The Faraday tensor is stored as a fully co-variant tensor (both indices down) + The stress-energy tensor is stored as a fully contra-variant tensor (both indices up) + """ + self.structures["BC"] = np.zeros((self.domain_vars['nt'],self.domain_vars['nx'],self.domain_vars['ny'],3)) + self.structures["SETfl"] = np.zeros((self.domain_vars['nt'],self.domain_vars['nx'],self.domain_vars['ny'],3,3)) + self.structures["SETem"] = np.zeros((self.domain_vars['nt'],self.domain_vars['nx'],self.domain_vars['ny'],3,3)) + self.structures["Fab"] = np.zeros((self.domain_vars['nt'],self.domain_vars['nx'],self.domain_vars['ny'],3,3)) + + for h in range(self.domain_vars['nt']): + for i in range(self.domain_vars['nx']): + for j in range(self.domain_vars['ny']): + vel_vec = np.array([self.aux_vars['W'][h,i,j],self.aux_vars['W'][h,i,j] * self.prim_vars['vx'][h,i,j] ,\ + self.aux_vars['W'][h,i,j] * self.prim_vars['vy'][h,i,j]]) + + fibr_b = self.aux_vars['bz'][h,i,j] + + self.structures['BC'][h,i,j,:] = np.multiply(self.prim_vars['n'][h,i,j], vel_vec ) + + self.structures['SETfl'][h,i,j,:,:] = (self.prim_vars["n"][h,i,j] * self.aux_vars['h'][h,i,j]) * np.outer(vel_vec, vel_vec) \ + + self.prim_vars['p'][h,i,j] * self.metric + + self.structures['SETem'][h,i,j:,:] = (fibr_b**2) * np.outer(vel_vec, vel_vec) + (fibr_b/2) * self.metric + + self.structures['Fab'][h,i,j,:,:] = np.tensordot(self.Levi3D, vel_vec, axes = ([2,0])) * fibr_b + + + ################################ + # CORRESPONDING 3+1-d FORMULAE + ################################ + + # vel_vec = np.array([self.aux_vars['W'][h,i,j],self.aux_vars['W'][h,i,j] * self.prim_vars['vx'][h,i,j] ,\ + # self.aux_vars['W'][h,i,j] * self.prim_vars['vy'][h,i,j], self.aux_vars['W'][h,i,j] * self.prim_vars['vz'][h,i,j]]) + + # fibr_b = [0, self.aux_vars['bx'][h,i,j], self.aux_vars['by'][h,i,j], self.aux_vars['bz'][h,i,j]] + + # self.structures['Fab'][h,i,j,:,:] = np.tensordot(np.tensordot(self.Levi4D, vel_vec, axes = ([2,0])), fibr_b, axes= ([2,0])) + + # self.structures['SETem'][h,i,j,:,:] = Base.Mink_dot(fibr_b, fibr_b) * np.outer(vel_vec, vel_vec) +\ + # (Base.Mink_dot(fibr_b, fibr_b)/2) * self.metric -\ + # np.multiply(1/2., np.outer(fibr_b, fibr_b) ) + + + # This might be useful/needed + # self.all_vars = self.prim_vars + # self.all_vars.update(self.aux_vars) + # self.all_vars.update(self.structures) + + self.vars = self.prim_vars + self.vars.update(self.aux_vars) + self.vars.update(self.structures) + + +class IdealHydro_2D(object): + + def __init__(self, interp_method = "linear"): + """ + Sets up the variables and dictionaries, strings correspond to + those used in METHOD + + Parameters + ---------- + interp_method: str + optional method to be used by interpn + """ + self.spatial_dims = 2 + self.interp_method = interp_method + + self.metric = np.zeros((3,3)) + self.metric[0,0] = -1 + self.metric[1,1] = self.metric[2,2] = +1 + + # This is the Levi-Civita symbol, not tensor, so be careful when using it + self.Levi3D = np.array([[[ np.sign(i-j) * np.sign(j- k) * np.sign(k-i) \ + for k in range(3)]for j in range(3) ] for i in range(3) ]) + + #Dictionary for grid: info and points + self.domain_int_strs = ('nt','nx','ny') + self.domain_float_strs = ("tmin","tmax","xmin","xmax","ymin","ymax","dt","dx","dy") + self.domain_array_strs = ("t","x","y","points") + self.domain_vars = dict.fromkeys(self.domain_int_strs+self.domain_float_strs + self.domain_array_strs) + for str in self.domain_vars: + self.domain_vars[str] = [] + + #Dictionary for primitive var + self.prim_strs = ("v1","v2","rho","p","n") + self.prim_vars = dict.fromkeys(self.prim_strs) + for str in self.prim_strs: + self.prim_vars[str] = [] + + #Dictionary for auxiliary var + self.aux_strs = ("W","h","T") + self.aux_vars = dict.fromkeys(self.aux_strs) + for str in self.aux_strs: + self.aux_vars[str] = [] + + #Dictionary for structures + self.structures_strs = ("BC","SET") + self.structures = dict.fromkeys(self.structures_strs) + for str in self.structures_strs: + self.structures[str] = [] + + #Dictionary for all vars + self.var_strs = self.prim_strs + self.aux_strs + self.structures_strs + # self.vars = self.prim_vars + # self.vars.update(self.aux_vars) + # self.vars.update(self.structures) + + self.all_var_strs = self.prim_strs + self.aux_strs + self.structures_strs + + def get_model_name(self): + return 'IdealHydro_2D' + + def get_spatial_dims(self): + return self.spatial_dims + + def get_domain_strs(self): + return self.domain_info_strs + self.domain_points_strs + + def get_prim_strs(self): + return list(self.prim_vars.keys()) + + def get_aux_strs(self): + return list(self.aux_vars.keys()) + + def get_structures_strs(self): + return list(self.structures.keys()) + + def get_all_var_strs(self): + return list(self.prim_vars.keys()) + list(self.aux_vars_vars.keys()) + list(self.structures.keys()) + + def setup_structures(self): + """ + Set up the structures (i.e baryon current, SET) + + Structures are built as multi-dim np.arrays, with the first three indices referring + to the grid, while the last one or two refer to space-time components. + """ + self.structures["BC"] = np.zeros((self.domain_vars['nt'],self.domain_vars['nx'],self.domain_vars['ny'],3)) + self.structures["SET"] = np.zeros((self.domain_vars['nt'],self.domain_vars['nx'],self.domain_vars['ny'],3,3)) + + for h in range(self.domain_vars['nt']): + for i in range(self.domain_vars['nx']): + for j in range(self.domain_vars['ny']): + vel = np.array([self.aux_vars['W'][h,i,j],self.aux_vars['W'][h,i,j] * self.prim_vars['v1'][h,i,j] ,\ + self.aux_vars['W'][h,i,j] * self.prim_vars['v2'][h,i,j]]) + self.structures['BC'][h,i,j,:] = np.multiply(self.prim_vars['n'][h,i,j], vel ) + + + self.structures["SET"][h,i,j,:,:] = (self.prim_vars["rho"][h,i,j] + self.prim_vars["p"][h,i,j]) * \ + np.outer(vel,vel) + self.prim_vars["p"][h,i,j] * self.metric + + self.vars = self.prim_vars + self.vars.update(self.aux_vars) + self.vars.update(self.structures) + + def get_interpol_prim(self, var_names, point): + """ + Returns the interpolated variable at the point + + Parameters + ---------- + vars : list of strings + strings have to be in to prim_vars keys + point : list of floats + ordered coordinates: t,x,y + Return + ------ + list of floats corresponding to string of vars + + Notes + ----- + Interpolation raises a ValueError when out of grid boundaries. + """ + res = [] + + for var_name in var_names: + try: + res.append( interpn(self.domain_vars["points"], self.prim_vars[var_name], point, method = self.interp_method)[0]) #, bounds_error = False) + except KeyError: + print(f"{var_name} does not belong to the primitive variables of the micro_model!") + return res + + def get_interpol_aux(self, var_names, point): + """ + Returns the interpolated variable at the point + + Parameters + ---------- + vars : list of strings + strings have to be in to aux_vars keys + point : list of floats + ordered coordinates: t,x,y + Return + ------ + list of floats corresponding to string of vars + + Notes + ----- + Interpolation gives errors when applied to boundary + """ + + res = [] + for var_name in var_names: + try: + res.append( interpn(self.domain_vars["points"], self.aux_vars[var_name], point, method = self.interp_method)[0]) + except KeyError: + print(f"{var_name} does not belong to the auxiliary variables of the micro_model!") + return res + + def get_interpol_struct(self, var_name, point): + """ + Returns the interpolated structure at the point + + Parameters + ---------- + var : str corresponding to one of the structures + + point : list of floats + ordered coordinates: t,x,y + + Return + ------ + Array with the interpolated values of the var structure + Empty list if var is not a structure in the micro_model + + Notes + ----- + Interpolation gives errors when applied to boundary + """ + res = [] + if var_name == "BC": + res = np.zeros(len(self.structures[var_name][:,0,0,0])) + for a in range(len(self.structures[var_name][:,0,0,0])): + res[a] = interpn(self.domain_vars["points"], self.structures[var_name][a,:,:,:], point, method = self.interp_method)[0] + elif var_name == "SET": + res = np.zeros((len(self.structures[var_name][:,0,0,0,0]),len(self.structures[var][0,:,0,0,0]))) + for a in range(len(self.structures[var_name][:,0,0,0,0])): + for b in range(len(self.structures[var_name][0,:,0,0,0])): + res[a,b] = interpn(self.domain_vars["points"],self.structures[var_name][a,b,:,:,:], point, method = self.interp_method)[0] + else: + print(f"{var} does not belong to the structures in the micro_model") + return res + + def get_interpol_var(self, var, point): + """ + Returns the interpolated variables at the point. + + Parameters + ---------- + vars : str corresponding to primitive, auxiliary or structre variable + + point : list of floats + ordered coordinates: t,x,y + + Return + ------ + Interpolated values/arrays corresponding to variable. + Empty list if none of the variables is a primitive, auxiliary o structure of the micro_model + + Notes + ----- + Interpolation gives errors when applied to boundary + """ + + if var in self.get_prim_strs(): + return interpn(self.domain_vars['points'], self.prim_vars[var], point, method = self.interp_method)[0] + elif var in self.get_aux_strs(): + return interpn(self.domain_vars['points'], self.aux_vars[var], point, method = self.interp_method)[0] + elif var in self.get_structures_strs(): + return interpn(self.domain_vars['points'], self.structures[var], point, method = self.interp_method)[0] + else: + print(f'{var} is not a primitive, auxiliary variable or structure of the micro_model!!') + + + + """ + Returns the interpolated structure at the point + Parameters + ---------- + var : str corresponding to one of the structures + + point : list of floats + ordered coordinates: t,x,y + Return + ------ + Array with the interpolated values of any var + Empty list if var is not a structure in the micro_model + Notes + ----- + Interpolation gives errors when applied to boundary + """ + res = [] + for var_name in var_names: + try: + res.append( interpn(self.domain_vars["points"], self.vars[var_name], point, method = self.interp_method)[0]) + except KeyError: + print(f"{var_name} does not belong to the auxiliary variables of the micro_model!") + return res + + +class IdealHD_2D(object): + """ + CUT AND PAST OF IdealMHD_2D --> removing the magnetic bits + """ + def __init__(self, interp_method = "linear"): + """ + Sets up the variables and dictionaries, strings correspond to + those used in METHOD + + Parameters + ---------- + interp_method: str + optional method to be used by interpn + """ + self.spatial_dims = 2 + self.interp_method = interp_method + + self.metric = np.zeros((3,3)) + self.metric[0,0] = -1 + self.metric[1,1] = self.metric[2,2] = +1 + + #Dictionary for grid: info and points + self.domain_int_strs = ('nt','nx','ny') + self.domain_float_strs = ("tmin","tmax","xmin","xmax","ymin","ymax","dt","dx","dy") + self.domain_array_strs = ("t","x","y","points") + self.domain_vars = dict.fromkeys(self.domain_int_strs+self.domain_float_strs+self.domain_array_strs) + for str in self.domain_vars: + self.domain_vars[str] = [] + + #Dictionary for primitive var + self.prim_strs = ("vx","vy","n","p") + self.prim_vars = dict.fromkeys(self.prim_strs) + for str in self.prim_strs: + self.prim_vars[str] = [] + + #Dictionary for auxiliary var + self.aux_strs = ("W", "h", "e") + self.aux_vars = dict.fromkeys(self.aux_strs) + for str in self.aux_strs: + self.aux_vars[str] = [] + + #Dictionary for structures + self.structures_strs = ("BC", "SET", "bar_vel") + self.structures = dict.fromkeys(self.structures_strs) + for str in self.structures_strs: + self.structures[str] = [] + + self.labels_var_dict = {'BC' : r'$n^{a}$', + 'SET' : r'$T^{ab}$', + 'bar_vel' : r'$u^a$', + 'vx' : r'$v_x$', + 'vy' : r'$v_y$', + 'n' : r'$n$', + 'W' : r'$W$', + 'e' : r'$e$', + 'h' : r'$h$', + 'p' : r'$p$'} + + def upgrade_labels_dict(self, entry_dict): + """ + pretty self-explanatory + """ + self.labels_var_dict.update(entry_dict) + + def get_spatial_dims(self): + return self.spatial_dims + + def get_model_name(self): + return 'Ideal Hydro (2+1d)' + + def get_domain_strs(self): + return self.domain_int_strs + self.domain_float_strs + self.domain_array_strs + + def get_prim_strs(self): + return self.prim_strs + + def get_aux_strs(self): + return self.aux_strs + + def get_structures_strs(self): + return self.structures_strs + + def get_all_var_strs(self): + return self.get_prim_strs() + self.get_aux_strs() + self.get_structures_strs() + + def get_gridpoints(self): + """ + Pretty self-explanatory + """ + return self.domain_vars['points'] + + def get_interpol_var(self, var, point): + """ + Returns the interpolated variables at the point. + + Parameters + ---------- + var: str corresponding to primitive, auxiliary or structre variable + + point : list of floats + ordered coordinates: t,x,y + + Return + ------ + Interpolated values/arrays corresponding to variable. + Empty list if none of the variables is a primitive, auxiliary o structure of the micro_model + + Notes + ----- + Interpolation gives errors when applied to boundary + """ + + if var in self.get_prim_strs(): + return interpn(self.domain_vars['points'], self.prim_vars[var], point, method = self.interp_method)[0] + elif var in self.get_aux_strs(): + return interpn(self.domain_vars['points'], self.aux_vars[var], point, method = self.interp_method)[0] + elif var in self.get_structures_strs(): + return interpn(self.domain_vars['points'], self.structures[var], point, method = self.interp_method)[0] + else: + print(f'{var} is not a primitive, auxiliary variable or structure of the micro_model!!') + + @multimethod + def get_var_gridpoint(self, var: str, h: object, i: object, j: object): + """ + Returns variable corresponding to input 'var' at gridpoint identified by h,i,j + + Parameters: + ----------- + var: str + String corresponding to primitive, auxiliary or structure variable + + h,i,j: int + integers corresponding to position on the grid. + + Returns: + -------- + Values or arrays corresponding to variable evaluated at the closest grid-point to input 'point'. + + Notes: + ------ + This method is useful e.g. for plotting the raw data. + """ + if var in self.get_prim_strs(): + return self.prim_vars[var][h,i,j] + elif var in self.get_aux_strs(): + return self.aux_vars[var][h,i,j] + elif var in self.get_structures_strs(): + return self.structures[var][h,i,j] + else: + print('{} is not a variable of model {}'.format(var, self.get_model_name())) + return None + + @multimethod + def get_var_gridpoint(self, var: str, point: object): + """ + Returns variable corresponding to input 'var' at gridpoint + closest to input 'point'. + + Parameters: + ----------- + vars: string corresponding to primitive, auxiliary or structure variable + + point: list of 2+1 floats + + Returns: + -------- + Values or arrays corresponding to variable evaluated at the closest grid-point to input 'point'. + + Notes: + ------ + This method should be used in case using interpolated values + becomes too expensive. + """ + indices = Base.find_nearest_cell(point, self.domain_vars['points']) + if var in self.get_prim_strs(): + return self.prim_vars[var][tuple(indices)] + elif var in self.get_aux_strs(): + return self.aux_vars[var][tuple(indices)] + elif var in self.get_structures_strs(): + return self.structures[var][tuple(indices)] + else: + print(f"{var} is not a variable of IdealMHD_2D!") + return None + + def setup_structures(self): + """ + Set up the structures (i.e baryon (mass) current BC, Stress-Energy and Faraday tensors + + Notes: + ------ + Structures are built as multi-dim np.arrays, with the first three indices referring + to the grid, while the last one or two refer to space-time components. + + The Faraday tensor is stored as a fully co-variant tensor (both indices down) + The stress-energy tensor is stored as a fully contra-variant tensor (both indices up) + """ + self.structures["BC"] = np.zeros((self.domain_vars['nt'],self.domain_vars['nx'],self.domain_vars['ny'],3)) + self.structures["bar_vel"] = np.zeros((self.domain_vars['nt'],self.domain_vars['nx'],self.domain_vars['ny'],3)) + self.structures["SET"] = np.zeros((self.domain_vars['nt'],self.domain_vars['nx'],self.domain_vars['ny'],3,3)) + + for h in range(self.domain_vars['nt']): + for i in range(self.domain_vars['nx']): + for j in range(self.domain_vars['ny']): + vel_vec = np.array([self.aux_vars['W'][h,i,j],self.aux_vars['W'][h,i,j] * self.prim_vars['vx'][h,i,j] ,\ + self.aux_vars['W'][h,i,j] * self.prim_vars['vy'][h,i,j]]) + + self.structures['bar_vel'][h,i,j,:] = vel_vec + + self.structures['BC'][h,i,j,:] = np.multiply(self.prim_vars['n'][h,i,j], vel_vec ) + + self.structures['SET'][h,i,j,:,:] = (self.prim_vars['n'][h,i,j] * self.aux_vars['h'][h,i,j]) * np.outer(vel_vec, vel_vec) \ + + self.prim_vars['p'][h,i,j] * self.metric + + + + + self.vars = self.prim_vars + self.vars.update(self.aux_vars) + self.vars.update(self.structures) + + #################################################### + # ATTEMPT TO PARALLELIZE SETUP STRUCTURES: NOT WORTHY + # FOR SETUP_STRUCTURES ROUTINE + #################################################### + # # defining it statically means no self is passed on construction, but guarantees + # # the method is the specific for this class! + # @staticmethod + # def setting_up(W, vx, vy, n, h, p, idxs): + # bar_vel = [W, W * vx, W * vy] + # BC = np.multiply(n, bar_vel) + # metric = np.zeros((3,3)) + # metric[0,0] = -1 + # metric[1,1] = metric[2,2] = +1 + # SET = n * h * np.outer(bar_vel, bar_vel) + p * metric + # return bar_vel, BC, SET, idxs + + + # def setup_structures_parallel(self): + # args_to_pass = [] + # for h in range(self.domain_vars['nt']): + # for i in range(self.domain_vars['nx']): + # for j in range(self.domain_vars['ny']): + # args = (self.aux_vars['W'][h,i,j], self.prim_vars['vx'][h,i,j], self.prim_vars['vy'][h,i,j], \ + # self.prim_vars['n'][h,i,j], self.aux_vars['h'][h,i,j], self.prim_vars['p'][h,i,j], (h,i,j)) + # args_to_pass.append(args) + + # # chunksize = [None, 1, len(args_to_pass)/ os.cpu_count()] + # with mp.Manager() as manager: + # print('Running with {} processes\n'.format(os.cpu_count())) + # with manager.Pool(os.cpu_count()) as pool: + # for result in pool.starmap(self.setting_up, args_to_pass): + # bar_vel, BC, SET, grid_idxs = result[0], result[1], result[2], tuple(result[3]) + # self.structures['bar_vel'][tuple(grid_idxs)] = bar_vel + # self.structures['BC'][tuple(grid_idxs)] = BC + # self.structures['SET'][tuple(grid_idxs)] = SET + +# TC +if __name__ == '__main__': + + CPU_start_time = time.process_time() + + FileReader = METHOD_HDF5('../Data/test_res100/') + # FileReader = METHOD_HDF5('../Data/res800/res800_t3') + micro_model = IdealHD_2D() + FileReader.read_in_data(micro_model) + micro_model.setup_structures() + + #################################################### + # Comparing speed of gridpoint vs interpol routines + #################################################### + print('Structure strs: {}'.format(micro_model.get_structures_strs())) + point = [1.502,0.4,0.2] + # vars = ['SETfl', 'BC', 'Fab', 'SETem'] + vars = ['BC', 'bar_vel', 'n'] + for var in vars: + res = micro_model.get_interpol_var(var, point) + res2 = micro_model.get_var_gridpoint(var, point) + print(f'{var}: \n {res} \n\n\n {res2} \n ********** \n ') + + + #################################################### + # TESTING PARALLELIZED SETUP STRUCTURE + #################################################### + # CPU_start_time = time.perf_counter() + # micro_model.setup_structures() + # serial_time= time.perf_counter() - CPU_start_time + # print('Time taken serial: {}\n'.format(serial_time)) + + # CPU_start_time = time.perf_counter() + # micro_model.setup_structures_parallel() + # parallel_time = time.perf_counter() - CPU_start_time + # print('Time taken parallel: {}\n'.format(parallel_time)) + # print('Speed up factor: {}\n'.format(serial_time/parallel_time)) \ No newline at end of file diff --git a/master_files/Visualization.py b/master_files/Visualization.py new file mode 100644 index 0000000..e7ed228 --- /dev/null +++ b/master_files/Visualization.py @@ -0,0 +1,567 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Jun 5 03:14:43 2023 + +@author: marcu +""" + + +import matplotlib.pyplot as plt +from matplotlib import colors +from mpl_toolkits.axes_grid1 import make_axes_locatable +from matplotlib.ticker import LogLocator +import numpy as np +import h5py +from scipy.interpolate import interpn +from system.BaseFunctionality import * + +from MicroModels import * +from MesoModels import * +from Filters import * + +class Plotter_2D(object): + + def __init__(self, screen_size = [11.97, 8.36]): + """ + Parameters: + ----------- + screen_size = list of 2 floats + width and height of the screen: used to rescale plots' size. + """ + self.plot_vars_subplots_dims = {1 : (1,1), + 2 : (1,2), + 3 : (1,3), + 4 : (2,2), + 5 : (2,3), + 6 : (2,3)} + + + self.screen_size = np.array(screen_size) + + # Change to latex font + plt.rc("font",family="serif") + plt.rc("mathtext",fontset="cm") + + def get_var_data(self, model, var_str, t, x_range, y_range, component_indices=(), method= 'raw_data', interp_dims=None): + """ + Retrieves the required data from model to plot a variable defined by + var_str over coordinates t, x_range, y_range, either from the model's + raw data or by interpolating between the model's raw data over the coords. + + Parameters + ---------- + model : Micro or Meso Model + var_str : str + Must match a variable of the model. + t : float + time coordinate (defines the foliation). + x_range : list of 2 floats: x_start and x_end + defines range of x coordinates within foliation. + y_range : list of 2 floats: y_start and y_end + defines range of y coordinates within foliation. + component_indices : tuple + the indices of the component to pick out if the variable is a vector/tensor. + method : str + currently either raw_data or interpolate. + interp_dims : tuple of integers + defines the number of points to interpolate at in x and y directions. + + Returns + ------- + data_to_plot : numpy array of floats + the 2D data to be plotted by plt.imshow() + extent: list of floats + + + + Notes: + ------ + Logic: if method is raw_data, then no interp_dims are needed. + Better to have 'raw_data' and interp_dims = None as default + + data_to_plot is transposed and extent is built to be used with: + origin=lower, extent=L,R,B,T by imshow + + """ + if var_str in model.get_all_var_strs(): + + # Block to check component_indices passed are compatible with shape of var to be plotted. + st1 = len(component_indices) + st2 = len(model.get_var_gridpoint(var_str, 0, 0, 0).shape) + compatible = st1==st2 + if not compatible: + if st2 == 0 : + print('WARNING: {} is a scalar but you passed some indices, ignoring this and moving on.'.format(var_str)) + component_indices = () + elif st2 != 0: + print('WARNING: {} is a tensor but you passed more/fewer indices than required. Retrieving the "first component"!'.format(var_str)) + component_indices = tuple([0 for _ in range(st2)]) + + # extent = [*x_range, *y_range] + + if method == 'interpolate': + compatible = interp_dims != None and len(interp_dims) ==2 + if not compatible: + print('Error: when using (linearly spaced) interpolated data, you must' +\ + 'specify # points in each spatial direction! Exiting.') + return None + + nx, ny = interp_dims[:] + xs, ys = np.linspace(x_range[0], x_range[1], nx), np.linspace(y_range[0], y_range[1], ny) + data_to_plot = np.zeros((nx, ny)) + + points = [t, xs, ys] + # extent = [points[2][0],points[2][-1],points[1][0],points[1][-1]] + extent = [points[1][0],points[1][-1],points[2][0],points[2][-1]] + + for i in range(nx): + for j in range(ny): + point = [t, xs[i], ys[j]] + data_to_plot[i, j] = model.get_interpol_var(var_str, point)[component_indices] + + elif method == 'raw_data': + start_indices = Base.find_nearest_cell([t, x_range[0], y_range[0]], model.get_gridpoints()) + end_indices = Base.find_nearest_cell([t, x_range[1], y_range[1]], model.get_gridpoints()) + + h = start_indices[0] + i_s, i_f = start_indices[1], end_indices[1] + j_s, j_f = start_indices[2], end_indices[2] + + gridpoints = model.get_gridpoints() + points = [gridpoints[0][h], gridpoints[1][i_s:i_f+1], gridpoints[2][j_s:j_f+1]] + # extent = [points[2][0],points[2][-1],points[1][0],points[1][-1]] + extent = [points[1][0],points[1][-1],points[2][0],points[2][-1]] + + data_shape = (i_f - i_s + 1, j_f - j_s + 1) + data_to_plot = np.zeros(data_shape) + + for i in range(i_f - i_s + 1): + for j in range(j_f - j_s + 1): + data_to_plot[i,j] = model.get_var_gridpoint(var_str, h, i + i_s, j + j_s)[component_indices] + + else: + print('Data method is not a valid choice! Must be interpolate or raw_data.') + return None + # return data_to_plot, points + data_to_plot = np.transpose(data_to_plot) + return data_to_plot, extent + + else: + print(f'{var_str} is not a plottable variable of the model!') + return None + + def plot_vars(self, model, var_strs, t, x_range, y_range, components_indices=None, method='raw_data', interp_dims=None, + norms=None, cmaps=None): + """ + Plot variable(s) from model, defined by var_strs, over coordinates + t, x_range, y_range. Either from the model's raw data or by interpolating + between the model's raw data over the coords. + + Parameters + ---------- + model : Micro or Meso Model + var_strs : list of str + Must match entries in the models' 'vars' dictionary. + t : float + time coordinate (defines the foliation). + x_range : list of 2 floats: x_start and x_end + defines range of x coordinates within foliation. + y_range : list of 2 floats: y_start and y_end + defines range of y coordinates within foliation. + components_indices : list of tuple(s) + the indices of the components to pick out if the variables are vectors/tensors. + Can be omitted if all variables are scalars, otherwise must be a list + of tuples matching the length of var_strs that corresponds with each + variable in the list. + method : str + currently either raw_data or interpolate + interp_dims : tuple of integers + defines the number of points to interpolate at in x and y directions. + norms = list of strs + each entry of the list is passed as option to imshow as norm=str + when plotting the corresponding var + + valid choices include all the standard norms like log or symlog, + and 'mysymlog' which is implemented in BaseFunctionality + + cmaps = list of strs + each entry of the list is passed to imshow as cmap=cmaps[i] + when plotting the corresponding var + + valid choices are all the std ones + + Output + ------- + Plots the (2D) data using imshow. Note that the plotting data's coordinates + may not perfectly match the input coordinates if method=raw_data as + nearest-cell data is used where the input coordinates do not coincide + with the model's raw data coordinates. + + """ + n_plots = len(var_strs) + + if norms == None: + norms = [None for _ in range(len(var_strs))] + elif len(var_strs)!= len(norms): + print('The norms provided are not the same number as the variables: setting these to auto') + norms = [None for _ in range(len(var_strs))] + + if cmaps == None: + cmaps = [None for _ in range(len(var_strs))] + elif len(var_strs)!= len(cmaps): + print('The norms provided are not the same number as the variables: setting these to auto') + cmaps = [None for _ in range(len(var_strs))] + + n_rows, n_cols = self.plot_vars_subplots_dims[n_plots] + + # Block to determine adaptively the figsize. + figsizes = {1 : (1/3.,1/3.), + 2 : (2/3.,1/3.), + 3 : (1,1/3.), + 4 : (2/3.,2/3.), + 5 : (1,2/3.), + 6 : (1,2/3.)} + for item in figsizes: + figsizes[item] = tuple(figsizes[item] * self.screen_size) + figsize = figsizes[n_plots] + + + fig, axes = plt.subplots(n_rows,n_cols, figsize=figsize) + if n_plots == 1: + axes = [axes] + else: + axes = axes.flatten() + + if not components_indices: + print('No list of components indices passed: setting this to an empty list.') + components_indices = [ () for _ in range(len(var_strs))] + + for i, (var_str, component_indices, ax) in enumerate(zip(var_strs, components_indices, axes)): + # data_to_plot, points = self.get_var_data(model, var_str, t, x_range, y_range, interp_dims, method, component_indices) + # extent = [points[2][0],points[2][-1],points[1][0],points[1][-1]] + data_to_plot, extent = self.get_var_data(model, var_str, t, x_range, y_range, component_indices, method, interp_dims) + + if norms[i] == 'mysymlog': + ticks, labels, nodes = MySymLogPlotting.get_mysymlog_var_ticks(data_to_plot) + data_to_plot = MySymLogPlotting.symlog_var(data_to_plot) + mynorm = MyThreeNodesNorm(nodes) + im = ax.imshow(data_to_plot, extent=extent, origin='lower', norm=mynorm, cmap=cmaps[i]) + divider = make_axes_locatable(ax) + cax = divider.append_axes('right', size='5%', pad=0.05) + cbar = fig.colorbar(im, cax=cax, orientation='vertical') + cbar.set_ticks(ticks) + cbar.ax.set_yticklabels(labels) + + elif norms[i] != 'mysymlog': + im = ax.imshow(data_to_plot, extent=extent, origin='lower', norm=norms[i], cmap=cmaps[i]) + divider = make_axes_locatable(ax) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + title = var_str + if hasattr(model, 'labels_var_dict'): + if title in model.labels_var_dict.keys(): + title = model.labels_var_dict[title] + if component_indices != (): + title = title + ", {}-component".format(component_indices) + ax.set_title(title) + ax.set_xlabel(r'$x$') + ax.set_ylabel(r'$y$') + + time_for_filename = str(round(t,2)) + fig.suptitle('Snapshot from model {} at time {}'.format(model.get_model_name(), time_for_filename), fontsize = 12) + fig.tight_layout() + # plt.show() + return fig + + def plot_vars_models_comparison(self, models, var_strs, t, x_range, y_range, components_indices=None, method='raw_data',\ + interp_dims=None, diff_plot=False, rel_diff=False, norms=None, cmaps=None): + """ + Plot variables from a number of models. If 2 models are given, a third + plot of the difference (relative or absolute) can be added too. + The method refers to the difference plot(s): for models with different grid spacings, data must + be extracted via interpolation, so setting method = 'interpolate'. + In any other case, should set method = 'raw_data'. + + Parameters + ---------- + models : list of Micro or Meso Models + var_strs : list of lists of strings + each sublist must match entries in the models' 'vars' dictionary. + t : float + time coordinate (defines the foliation). + x_range : list of 2 floats: x_start and x_end + defines range of x coordinates within foliation. + y_range : list of 2 floats: y_start and y_end + defines range of y coordinates within foliation. + component_indices : list of list of tuples + each tuple identifies the indices of the component to pick out if the variable + is a vector/tensor. + method : str + currently either raw_data or interpolate. + interp_dims : tuple of integers + defines the number of points to interpolate at in x and y directions. + diff_plot: bool + Whether to add a column to show difference between models + rel_diff: bool + Whether to plot the absolute or relative difference between models + norms/maps = list of list of strs + these have to be compatible with the final number of rows and columns + in the plot. First index in the list runs over the columns (models and their difference), + second index runs over the rows (vars). + + Output + ------- + Plots the (2D) data using imshow. Note that the plotting data's coordinates + may not perfectly match the input coordinates if method=raw_data as + nearest-cell data is used where the input coordinates do not coincide + with the model's raw data coordinates. + + """ + if len(var_strs) != len(models): + print("I need a list of vars to plot per model. Check!") + return None + num_vars_1st_model = len(var_strs[0]) + for i in range(1, len(var_strs)): + if len(var_strs[i]) != num_vars_1st_model: + print("The number of variables per model must be the same. Exiting.") + return None + + + n_cols = len(models) + if diff_plot: + if len(models)!=2: + print("Can plot the difference between TWO models, not more.") + else: + n_cols+=1 + if rel_diff: + if len(models)!=2: + print("Can plot the difference between TWO models, not more.") + else: + n_cols+=1 + + n_rows = len(var_strs[0]) + if len(var_strs[0])>3: + print("This function is meant to compare up to 3 vars. Plotting the first three.") + n_rows = 3 + + if not components_indices: + print('No list of components indices passed: setting this to an empty list.') + empty_tuple_components = [ () for _ in range(len(var_strs[0]))] + components_indices = [] + for i in range(len(models)): + components_indices.append(empty_tuple_components) + + inv_fig_shape = (n_cols, n_rows) + if not norms or np.array(norms).shape != inv_fig_shape: + print('Norms provided are not compatible with figure, setting these to auto') + norms = [None for _ in range(n_rows)] + norms = [norms for _ in range(n_cols)] + + if not cmaps or np.array(cmaps).shape != inv_fig_shape: + print('Colormaps not compatible with figure, setting these to auto') + cmaps = [None for _ in range(n_rows)] + cmaps = [cmaps for _ in range(n_cols)] + + + figsize = self.screen_size + fig, axes = plt.subplots(n_rows, n_cols, squeeze=False, figsize=figsize) + + for j in range(len(models)): + for i in range(n_rows): + + data_to_plot, extent = self.get_var_data(models[j], var_strs[j][i], t, x_range, y_range, components_indices[j][i], 'raw_data', None) + + if norms[j][i] == 'mysymlog': + ticks, labels, nodes = MySymLogPlotting.get_mysymlog_var_ticks(data_to_plot) + data_to_plot = MySymLogPlotting.symlog_var(data_to_plot) + mynorm = MyThreeNodesNorm(nodes) + im = axes[i,j].imshow(data_to_plot, extent=extent, origin='lower', norm=mynorm, cmap=cmaps[j][i]) + divider = make_axes_locatable(axes[i,j]) + cax = divider.append_axes('right', size='5%', pad=0.05) + cbar = fig.colorbar(im, cax=cax, orientation='vertical') + cbar.set_ticks(ticks) + cbar.ax.set_yticklabels(labels) + + elif norms[j][i] != 'mysymlog': + im = axes[i,j].imshow(data_to_plot, extent=extent, origin='lower', norm=norms[j][i], cmap=cmaps[j][i]) + divider = make_axes_locatable(axes[i,j]) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + # im=axes[i,j].imshow(data_to_plot, extent=extent, norm=norms[j][i], cmap=cmaps[j][i]) + # divider = make_axes_locatable(axes[i,j]) + # cax = divider.append_axes('right', size='5%', pad=0.05) + # fig.colorbar(im, cax=cax, orientation='vertical') + # title = models[j].get_model_name() + "\n"+var_strs[j][i] + + # title = models[j].get_model_name() + '\n' + title = '' + if hasattr(models[j], 'labels_var_dict'): + if var_strs[j][i] in models[j].labels_var_dict.keys(): + title += models[j].labels_var_dict[var_strs[j][i]] + else: + title += var_strs[j][i] + else: + title += var_strs[j][i] + + if components_indices[j][i] != (): + title += " {}-component".format(components_indices[j][i]) + axes[i,j].set_title(title) + axes[i,j].set_xlabel(r'$x$') + axes[i,j].set_ylabel(r'$y$') + + + + if diff_plot and len(models)==2: + try: + for i in range(len(var_strs[0])): + data1, extent1 = self.get_var_data(models[0], var_strs[0][i], t, x_range, y_range, components_indices[0][i], method, interp_dims) + data2, extent2 = self.get_var_data(models[1], var_strs[1][i], t, x_range, y_range, components_indices[1][i], method, interp_dims) + if extent1 != extent2: + print("Cannot plot the difference between the vars: data not aligned.") + continue + data_to_plot = data1 - data2 + + if norms[2][i] == 'mysymlog': + ticks, labels, nodes = MySymLogPlotting.get_mysymlog_var_ticks(data_to_plot) + data_to_plot = MySymLogPlotting.symlog_var(data_to_plot) + mynorm = MyThreeNodesNorm(nodes) + im = axes[i,2].imshow(data_to_plot, extent=extent, origin='lower', norm=mynorm, cmap=cmaps[2][i]) + divider = make_axes_locatable(axes[i,2]) + cax = divider.append_axes('right', size='5%', pad=0.05) + cbar = fig.colorbar(im, cax=cax, orientation='vertical') + cbar.set_ticks(ticks) + cbar.ax.set_yticklabels(labels) + + elif norms[2][i] != 'mysymlog': + im = axes[i,2].imshow(data_to_plot, extent=extent, origin='lower', norm=norms[2][i], cmap=cmaps[2][i]) + divider = make_axes_locatable(axes[i,2]) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + + # im = axes[i,2].imshow(data_to_plot, extent=extent1, norm=norms[2][i], cmap=cmaps[2][i]) + # divider = make_axes_locatable(axes[i,2]) + # cax = divider.append_axes('right', size='5%', pad=0.05) + # fig.colorbar(im, cax=cax, orientation='vertical') + + + axes[i,2].set_title('Models difference') + axes[i,2].set_xlabel(r'$y$') + axes[i,2].set_ylabel(r'$x$') + except ValueError as v: + print(f"Cannot plot the difference between {var_strs} in the two "+\ + f"models. Caught a value error: {v}") + + + if rel_diff and len(models)==2: + try: + for i in range(len(var_strs[0])): + data1, extent1 = self.get_var_data(models[0], var_strs[0][i], t, x_range, y_range, components_indices[0][i], method, interp_dims) + data2, extent2 = self.get_var_data(models[1], var_strs[1][i], t, x_range, y_range, components_indices[1][i], method, interp_dims) + if extent1 != extent2: + print("Cannot plot the difference between the vars: data not aligned.") + continue + ar_mean = (np.abs(data1) + np.abs(data2))/2 + data_to_plot = np.abs(data1 -data2)/ar_mean + if diff_plot: + column = 3 + else: + column = 2 + + + if norms[column][i] == 'mysymlog': + ticks, labels, nodes = MySymLogPlotting.get_mysymlog_var_ticks(data_to_plot) + data_to_plot = MySymLogPlotting.symlog_var(data_to_plot) + mynorm = MyThreeNodesNorm(nodes) + im = axes[i,column].imshow(data_to_plot, extent=extent, origin='lower', norm=mynorm, cmap=cmaps[column][i]) + divider = make_axes_locatable(axes[i,column]) + cax = divider.append_axes('right', size='5%', pad=0.05) + cbar = fig.colorbar(im, cax=cax, orientation='vertical') + cbar.set_ticks(ticks) + cbar.ax.set_yticklabels(labels) + + elif norms[column][i] != 'mysymlog': + im = axes[i,column].imshow(data_to_plot, extent=extent, origin='lower', norm=norms[column][i], cmap=cmaps[column][i]) + divider = make_axes_locatable(axes[i,column]) + cax = divider.append_axes('right', size='5%', pad=0.05) + fig.colorbar(im, cax=cax, orientation='vertical') + + # im = axes[i,column].imshow(data_to_plot, extent=extent1, norm=norms[column][i], cmap=cmaps[column][i]) + # divider = make_axes_locatable(axes[i,column]) + # cax = divider.append_axes('right', size='5%', pad=0.05) + # fig.colorbar(im, cax=cax, orientation='vertical') + + axes[i,column].set_title('Relative difference') + axes[i,column].set_xlabel(r'$y$') + axes[i,column].set_ylabel(r'$x$') + except ValueError as v: + print(f"Cannot plot the difference between {var_strs} in the two "+\ + f"models. Caught a value error: {v}") + + + models_names = [model.get_model_name() for model in models] + suptitle = "Comparing " + for i in range(len(models_names)): + suptitle += models_names[i] + ", " + suptitle += "models." + # fig.suptitle(suptitle) + fig.tight_layout() + # plt.subplot_tool() + return fig + + + +if __name__ == '__main__': + + FileReader = METHOD_HDF5('../Data/test_res100/') + micro_model = IdealMHD_2D() + FileReader.read_in_data(micro_model) + micro_model.setup_structures() + + + visualizer = Plotter_2D([11.97, 8.36]) + print('Finished initializing') + + # TESTING GET_VAR_DATA + ###################### + # var = 'BC' + # components = (0,2) + # data1= visualizer.get_var_data(micro_model, var, 1.502, [0.3, 0.4], [0.3,0.4], component_indices=components)[0] + # data, extent= visualizer.get_var_data(micro_model, var, 1.502, [0.3, 0.4], [0.3,0.4], component_indices=components, method='interpolate', interp_dims=(20,20)) + # print(extent) + + # TESTING PLOT_VARS + ################### + # vars = ['BC', 'vx', 'vy', 'Bz', 'p', 'W'] + # components = [(0,), (), (), (), (), ()] + # model = micro_model + # visualizer.plot_vars(model, vars, 1.502, [0.01, 0.98], [0.01, 0.98], components_indices=components) + # visualizer.plot_vars(model, vars, 1.502, [0.01, 0.98], [0.01, 0.98], method = 'interpolate', interp_dims=(100,100), components_indices=components) + + + # TESTING PLOT_VAR_MODEL_COMPARISON + ################################### + find_obs = FindObs_drift_root(micro_model, 0.001) + filter = spatial_box_filter(micro_model, 0.003) + meso_model = resMHD2D(micro_model, find_obs, filter) + ranges = [0.2, 0.25] + meso_model.setup_meso_grid([[1.501, 1.503],ranges, ranges], coarse_factor=1) + meso_model.find_observers() + meso_model.filter_micro_variables() + + print("Finished filtering") + + vars = [['BC'], ['BC']] + models = [micro_model, meso_model] + components = [[(0,)],[(0,)]] + norms = [['log'], ['log'], ['symlog']] + cmaps=None + # cmaps = [['seismic'], ['seismic'], ['seismic']] + # smaller_ranges = [ranges[0]+0.01, ranges[1]- 0.01] # Needed to avoid interpolation errors at boundaries + # visualizer.plot_var_model_comparison(models, var, 1.502, smaller_ranges, smaller_ranges, \ + # method='interpolate', interp_dims=(30,30), component_indices=component) + visualizer.plot_vars_models_comparison(models, vars, 1.502, ranges, ranges, components_indices=components, diff_plot=True, rel_diff = False, + norms=norms, cmaps=cmaps) + plt.show() diff --git a/master_files/system/BaseFunctionality.py b/master_files/system/BaseFunctionality.py new file mode 100644 index 0000000..58771b6 --- /dev/null +++ b/master_files/system/BaseFunctionality.py @@ -0,0 +1,398 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Nov 2 17:21:02 2022 + +@author: mjh1n20 +""" + +# from multiprocessing import Process, Pool +import numpy as np +# from timeit import default_timer as timer +import cProfile, pstats, io +import math +import matplotlib as mpl +import matplotlib.pyplot as plt +from mpl_toolkits.axes_grid1 import make_axes_locatable +import random + +class Base(object): + @staticmethod + def Mink_dot(vec1, vec2): + """ + Parameters: + ----------- + vec1, vec2 : list of floats (or np.arrays) + + Return: + ------- + mink-dot (cartesian) in 1+n dim + """ + if len(vec1) != len(vec2): + print("The two vectors passed to Mink_dot are not of same dimension!") + + dot = -vec1[0]*vec2[0] + for i in range(1,len(vec1)): + dot += vec1[i] * vec2[i] + return dot + + @staticmethod + def get_rel_vel(spatial_vels): + """ + Build unit vectors starting from spatial components + Needed as this will enter the minimization procedure + + Parameters: + ---------- + spatial_vels: list of floats + + Returns: + -------- + list of floats: the d+1 vector, normalized wrt Mink metric + """ + W = 1 / np.sqrt(1-np.sum(spatial_vels**2)) + return W * np.insert(spatial_vels,0,1.0) + + @staticmethod + def project_tensor(vector1_wrt, vector2_wrt, to_project): + """ + """ + return np.inner(vector1_wrt,np.inner(vector2_wrt,to_project)) + + + @staticmethod + def orthogonal_projector(u, metric): + """ + Returns: + -------- + Orthogonal projector wrt vector u + + Notes: + ------ + The vector u must be time-like + """ + return metric + np.outer(u,u) + + + """ + A pair of functions that work in conjuction (thank you stack overflow). + find_nearest returns the closest value to 'value' in 'array', + find_nearest_cell then takes this closest value and returns its indices. + Should now work for any dimensional data. + """ + @staticmethod + def find_nearest(array, value): + """ + Returns closest value to input 'value' in 'array' + + Parameters: + ----------- + array: np.array of shape (n,) + + value: float + + Returns: + -------- + float + + Note: + ----- + To be used together with find_nearest_cell. + """ + idx = np.searchsorted(array, value, side="left") + if idx > 0 and (idx == len(array) or math.fabs(value - array[idx-1]) < math.fabs(value - array[idx])): + return array[idx-1] + else: + return array[idx] + + @staticmethod + def find_nearest_cell(point, points): + """ + Use find nearest to find closest value in a list of input 'points' to + input 'point'. + + Parameters: + ----------- + point: list of d+1 float + + points: list of lists of d+1 floats + + Returns: + -------- + List of d+1 indices corresponding to closest value to point in points + """ + if len(points) != len(point): + print("find_nearest_cell: The length of the coordinate vector\ + does not match the length of the coordinates.") + positions = [] + for dim in range(len(point)): + positions.append(Base.find_nearest(points[dim], point[dim])) + return [np.where(points[i] == positions[i])[0][0] for i in range(len(positions))] + + def profile(self, fnc): + """A decorator that uses cProfile to profile a function""" + def inner(*args, **kwargs): + pr = cProfile.Profile() + pr.enable() + retval = fnc(*args, **kwargs) + pr.disable() + s = io.StringIO() + sortby = 'cumulative' + ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + ps.print_stats() + print(s.getvalue()) + return retval + return inner + +class MySymLogPlotting(object): + + @staticmethod + def symlog_num(num): + """ + Return the symlog of a number + + Parameters: + ----------- + num: float + + Returns: + -------- + The symlog of the input num (separate copy) + """ + if np.abs(num) +1. == 1.0: + result = num + else: + result = np.sign(num) * np.log10(np.abs(num)+1.) + return result + + @staticmethod + def inverse_symlog_num(num): + if num > 0: + return 1 + 10 ** num + elif num < 0: + return 1- 10 ** (- num) + else: + return 0 + + @staticmethod + def symlog_var(var): + """ + Return the symlog of an array. + + Parameters: + ----------- + var: np.array of any shape + + Returns: + -------- + The symlog of the input var (separate copy) + """ + count_zeros=0 + temp = np.empty_like(var) + for index in np.ndindex(var.shape): + value = var[index] + if value == 0: + count_zeros +=1 + else: + temp[index] = MySymLogPlotting.symlog_num(value) + if count_zeros >= 1: + print('Careful: there are {} zeros in the data'.format(count_zeros)) + return temp + + @staticmethod + def get_mysymlog_var_ticks(var): + """ + Method to automatize the computation of the ticks and nodes for a variable. + Nodes are then to be used within the class MyThreeNodesNorm. + Ticks and labels are for the colorbar of the plot of input 'var'. + + Parameters: + ----------- + var: np.array + This HAS TO take both positive and negative values + + Returns: + -------- + ticks: list + list of tick points to be used by the colorbar + + ticks_labels: list + list of corresponding labels for the colorbar + + nodes: list of len=5 + the extrame and the three central nodes to be used by MyThreeNodesNorm + + Notes: + ------ + The ticks/nodes and labels are computed like this: start from negative values, identify min + and max values of the negative part of input 'var' to identify relevant ticks and nodes. + Then add a zero (tick and node) and proceed to the positive values. + """ + ticks = [] + ticks_labels = [] + nodes = [] + + + pos_var = np.ma.masked_where(var <0., var, copy=True).compressed() + pos_var_small = np.ma.masked_where(pos_var >=1., pos_var, copy=True).compressed() + pos_var_large = np.ma.masked_where(pos_var <1., pos_var, copy=True).compressed() + + neg_var = np.ma.masked_where(var >0., var, copy=True).compressed() + neg_var_small = np.ma.masked_where(neg_var <=-1., neg_var, copy=True).compressed() + neg_var_large = np.ma.masked_where(neg_var >-1., neg_var, copy=True).compressed() + + # Working out nodes, ticks and ticks_labels for the negative range + if len(neg_var_large) >0: + # print('There are negative large values', flush=True) + vmin = np.amin(neg_var_large) + new_nodes = [MySymLogPlotting.symlog_num(vmin)] + new_ticks = new_nodes + new_ticks_labels = [r'$-10^{%d}$'%(int(np.log10(-vmin)))] + + nodes += new_nodes + ticks += new_ticks + ticks_labels += new_ticks_labels + + + if len(neg_var_small) == 0: + # print('Actually: only negative large values', flush=True) + vmax = np.amax(neg_var_large) + new_nodes = [MySymLogPlotting.symlog_num(vmax)] + new_ticks = new_nodes + new_ticks_labels = [r'$-10^{%d}$'%(int(np.log10(-vmax)))] + + nodes += new_nodes + ticks += new_ticks + ticks_labels += new_ticks_labels + + else: + # print('And also negative small values', flush=True) + vmin = np.amin(neg_var_small) + vmax = np.amax(neg_var_small) + + new_nodes = [MySymLogPlotting.symlog_num(vmax)] + # new_ticks = [symlog_num(vmin), symlog_num(vmax)] + new_ticks = [MySymLogPlotting.symlog_num(vmax)] + # new_ticks_labels = [r'$-10^{%d}$'%(int(d)) for d in np.log10([-vmin,-vmax])] + new_ticks_labels = [r'$-10^{%d}$'%(int(d)) for d in np.log10([-vmax])] + + ticks += new_ticks + ticks_labels += new_ticks_labels + nodes += new_nodes + + else: # len(neg_var_large)==0: + # print('Only negative small values', flush=True) + vmin = np.amin(neg_var_small) + vmax = np.amax(neg_var_small) + + # print(vmin, vmax, "\n") + new_nodes = [MySymLogPlotting.symlog_num(vmin), MySymLogPlotting.symlog_num(vmax)] + # print(new_nodes) + new_ticks = new_nodes + new_ticks_labels = [r'$-10^{%d}$'%(int(d)) for d in np.log10([-vmin,-vmax])] + + ticks += new_ticks + ticks_labels += new_ticks_labels + nodes += new_nodes + + + nodes += [0.] + ticks += [0.] + ticks_labels += ['0'] + + # Working out the remaining nodes, ticks and ticks_labels for the positive range + + if len(pos_var_large)==0: + # print('Only positive small values', flush=True) + vmin = np.amin(pos_var_small) + vmax = np.amax(pos_var_small) + + # print(vmin, vmax) + new_nodes = [MySymLogPlotting.symlog_num(vmin), MySymLogPlotting.symlog_num(vmax)] + # print(new_nodes) + new_ticks = new_nodes + new_ticks_labels = [r'$10^{%d}$'%(int(d)) for d in np.log10([vmin,vmax])] + + ticks += new_ticks + ticks_labels += new_ticks_labels + nodes += new_nodes + + else: # len(pos_var_large) > 0: + # print('There are positive large values', flush=True) + + if len(pos_var_small) >0: + # print('And also positive small values', flush=True) + vmin = np.amin(pos_var_small) + vmax = np.amax(pos_var_small) + + # new_nodes = [symlog_num(vmax)] + new_nodes = [MySymLogPlotting.symlog_num(vmin)] + # new_ticks = [symlog_num(vmin), symlog_num(vmax)] + new_ticks = [MySymLogPlotting.symlog_num(vmin)] + # new_ticks_labels = [r'$10^{%d}$'%(int(d)) for d in np.log10([vmin,vmax])] + new_ticks_labels = [r'$10^{%d}$'%(int(d)) for d in np.log10([vmin])] + + ticks += new_ticks + ticks_labels += new_ticks_labels + nodes += new_nodes + + vmax = np.amax(pos_var_large) + new_nodes = [MySymLogPlotting.symlog_num(vmax)] + new_ticks = new_nodes + new_ticks_labels = [r'$10^{%d}$'%(int(np.log10(vmax)))] + + ticks += new_ticks + ticks_labels += new_ticks_labels + nodes += new_nodes + + + else: # len(pos_var_small) ==0: + vmin = np.amin(pos_var_large) + vmax = np.amax(pos_var_large) + + new_nodes = [MySymLogPlotting.symlog_num(vmin), MySymLogPlotting.symlog_num(vmax)] + new_ticks = new_nodes + new_ticks_labels = [r'$10^{%d}$'%(int(d)) for d in np.log10([vmin,vmax])] + + ticks += new_ticks + ticks_labels += new_ticks_labels + nodes += new_nodes + + return ticks, ticks_labels, nodes + +class MyThreeNodesNorm(mpl.colors.Normalize): + """ + Sub-classing colors.Normalize: the norm has three inner nodes plus the extrema. + Within each segment (delimited by a node or extrema) you have linear interpolation. + + Should be used when plotting quantities that are both positive and negative, and you + want to highlight 1) where a critical value (middle_node) is 2) the closest values some + variable takes to its left and right + """ + def __init__(self, nodes, clip=False): + """ + Parameters: + + nodes: array of five numbers in strictly ascending order (the nodes) + """ + if len(nodes)!=5: + raise ValueError('The class MyThreeNodesNorm requires 5 nodes: the extrema and the three central') + + for i in range(len(nodes)-1): + if nodes[i+1] <=nodes[i]: + raise ValueError('nodes must be in monotonically ascending order!') + + super().__init__(nodes[0], nodes[4], clip) + self.first_node = nodes[1] + self.central_node = nodes[2] + self.third_node = nodes[3] + + def __call__(self, value, clip=None): + x = [self.vmin, self.first_node, self.central_node, self.third_node, self.vmax] + y = [0, 0.4, 0.5, 0.6, 1.] + return np.ma.masked_array(np.interp(value, x, y, + left=-np.inf, right=np.inf)) + + def inverse(self, value): + y = [self.vmin, self.first_node, self.central_node, self.third_node, self.vmax] + x = [0, 0.4, 0.5, 0.6, 1.] + return np.interp(value, x, y, left=-np.inf, right=np.inf) diff --git a/system/BaseFunctionality.py b/system/BaseFunctionality.py deleted file mode 100644 index c48f439..0000000 --- a/system/BaseFunctionality.py +++ /dev/null @@ -1,106 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Nov 2 17:21:02 2022 - -@author: mjh1n20 -""" - -from multiprocessing import Process, Pool -import os -import numpy as np -#import matplotlib.pyplot as plt -import pickle -from timeit import default_timer as timer -import h5py -from scipy.interpolate import interpn -from scipy.optimize import root, minimize -#from mpl_toolkits.mplot3d import Axes3D -from scipy.integrate import solve_ivp, quad -import cProfile, pstats, io -import math - -class Base(object): - - @staticmethod - def Mink_dot(vec1,vec2): - """ - Parameters: - ----------- - vec1, vec2 : list of floats (or np.arrays) - - Return: - ------- - mink-dot (cartesian) in 1+n dim - """ - if len(vec1) != len(vec2): - print("The two vectors passed to Mink_dot are not of same dimension!") - - dot = -vec1[0]*vec2[0] - for i in range(1,len(vec1)): - dot += vec1[i] * vec2[i] - return dot - - @staticmethod - def get_rel_vel(spatial_vels): - """ - Build unit vectors starting from spatial components - Needed as this will enter the minimization procedure - - Parameters: - ---------- - spatial_vels: list of floats - - Returns: - -------- - list of floats: the d+1 vector, normalized wrt Mink metric - """ - W = 1 / np.sqrt(1-np.sum(spatial_vels**2)) - return W * np.insert(spatial_vels,0,1.0) - - @staticmethod - def project_tensor(vector1_wrt, vector2_wrt, to_project): - return np.inner(vector1_wrt,np.inner(vector2_wrt,to_project)) - - @staticmethod - def orthogonal_projector(u, metric): - return metric + np.outer(u,u) - - - """ - A pair of functions that work in conjuction (thank you stack overflow). - find_nearest returns the closest value to 'value' in 'array', - find_nearest_cell then takes this closest value and returns its indices. - Should now work for any dimensional data. - """ - @staticmethod - def find_nearest(array, value): - idx = np.searchsorted(array, value, side="left") - if idx > 0 and (idx == len(array) or math.fabs(value - array[idx-1]) < math.fabs(value - array[idx])): - return array[idx-1] - else: - return array[idx] - - @staticmethod - def find_nearest_cell(point, points): - if len(points) != len(point): - print("find_nearest_cell: The length of the coordinate vector\ - does not match the length of the coordinates.") - positions = [] - for dim in range(len(point)): - positions.append(Base.find_nearest(points[dim], point[dim])) - return [np.where(points[i] == positions[i])[0][0] for i in range(len(positions))] - - def profile(self, fnc): - """A decorator that uses cProfile to profile a function""" - def inner(*args, **kwargs): - pr = cProfile.Profile() - pr.enable() - retval = fnc(*args, **kwargs) - pr.disable() - s = io.StringIO() - sortby = 'cumulative' - ps = pstats.Stats(pr, stream=s).sort_stats(sortby) - ps.print_stats() - print(s.getvalue()) - return retval - return inner diff --git a/system/__pycache__/BaseFunctionality.cpython-36.pyc b/system/__pycache__/BaseFunctionality.cpython-36.pyc deleted file mode 100644 index f323591..0000000 Binary files a/system/__pycache__/BaseFunctionality.cpython-36.pyc and /dev/null differ