diff --git a/LDDsimulation/LDDsimulation.py b/LDDsimulation/LDDsimulation.py index f9e7794580c0ab461dce1147d12b5119c56115cb..e9d60574be1fd1fb5d4e4996e8844cd4d4c85ab1 100644 --- a/LDDsimulation/LDDsimulation.py +++ b/LDDsimulation/LDDsimulation.py @@ -67,8 +67,8 @@ class LDDsimulation(object): # df.parameters['std_out_all_processes'] = False - # df.set_log_level(df.LogLevel.WARNING) - df.set_log_level(df.LogLevel.INFO) + df.set_log_level(df.LogLevel.WARNING) + # df.set_log_level(df.LogLevel.INFO) # df.set_log_level(df.LogLevel.DEBUG) # CRITICAL = 50, // errors that may lead to data corruption and suchlike @@ -155,19 +155,19 @@ class LDDsimulation(object): # subdomain. This gets populated by self._init_solution_files() self.solution_file = None ########## SOLVER DEFINITION AND PARAMETERS ######## - print("\nLinear Algebra Backends:") - df.list_linear_algebra_backends() - # # print("\nLinear Solver Methods:") - df.list_linear_solver_methods() - print("\n") - df.list_krylov_solver_methods() - print("\n") - # # print("\nPeconditioners for Krylov Solvers:") - df.list_krylov_solver_preconditioners() + # print("\nLinear Algebra Backends:") + # df.list_linear_algebra_backends() + # # # print("\nLinear Solver Methods:") + # df.list_linear_solver_methods() + # print("\n") + # df.list_krylov_solver_methods() + # print("\n") + # # # print("\nPeconditioners for Krylov Solvers:") + # df.list_krylov_solver_preconditioners() - df.info(df.parameters, True) + # df.info(df.parameters, True) self.solver_type_is_Krylov = True ### Define the linear solver to be used. @@ -240,6 +240,8 @@ class LDDsimulation(object): self.use_case = use_case if include_gravity: self.use_case = self.use_case + "-with-gravity" + if self.mesh_study: + self.use_case = "mesh study (meshres"+ "{}".format(mesh_resolution) + ") for case: " + self.use_case self.output_dir = output_dir self.subdomain_def_points = subdomain_def_points self.isRichards = isRichards @@ -259,10 +261,16 @@ class LDDsimulation(object): self.number_of_timesteps = number_of_timesteps self.number_of_timesteps_to_analyse = number_of_timesteps_to_analyse # define array of timesteps at which to plot stuff - self.timesteps_to_plot = np.arange(0, self.number_of_timesteps, plot_timestep_every) + self.timestep_numbers_to_plot = np.arange(0, self.number_of_timesteps, plot_timestep_every) # we are now missing the last timestep. - np.append(self.timesteps_to_plot, self.number_of_timesteps) + self.timestep_numbers_to_plot = np.append(self.timestep_numbers_to_plot, self.number_of_timesteps) + hlp.print_once("The following timesteps will be used for plotting:") + # print(self.timestep_numbers_to_plot) self.timestep_size = timestep_size + # stores the timesteps tn that will be used to write out the solution. + # these are the timesteps corresponding to the timestep numbers in + # self.timestep_numbers_to_plot + self.timesteps_to_plot = self.starttime + self.timestep_size*self.timestep_numbers_to_plot self.sources = sources self.initial_conditions = initial_conditions self.dirichletBC_expression_strings = dirichletBC_expression_strings @@ -357,8 +365,9 @@ class LDDsimulation(object): print(f"entering timestep ", "{}".format(self.timestep_num), "at time t = "+ "{number:.{digits}f}".format(number = self.t, digits = 4)) - # check if the solver should beanalised or not. - if analyse_solver_at_timesteps is not None and np.isin(self.timestep_num, analyse_solver_at_timesteps, assume_unique=True): + + # check if the solver should be analised or not. + if np.isin(self.timestep_num, analyse_solver_at_timesteps, assume_unique=True): print("analyising timestep ...") self.LDDsolver(debug=self.debug_solver, analyse_timestep=True, @@ -369,71 +378,19 @@ class LDDsimulation(object): analyse_condition=False) self.t += self.timestep_size - # write solutions to files. - for subdom_ind in self.isRichards.keys(): - subdomain = self.subdomain[subdom_ind] - # for phase in subdomain.has_phases: - # print(f"calculated pressure of {phase}phase:") - # print(subdomain.pressure[phase].vector()[:]) - if np.isin(self.timestep_num, self.timesteps_to_plot, assume_unique=True) and self.write2file['solutions']: - subdomain.write_solution_to_xdmf(# - file = self.solution_file[subdom_ind], # - time = self.t,# - write_iter_for_fixed_time = False, - ) - # if we have an exact solution, write out the |u -uh|_L2 to see the - # absolute error. - if subdomain.pressure_exact is not None: - relative_L2_errornorm = dict() - pressure_exact = dict() - for phase in subdomain.has_phases: - pa_exact = subdomain.pressure_exact[phase] - pa_exact.t = self.t - norm_pa_exact = df.norm(pa_exact, norm_type='L2', mesh=subdomain.mesh) - error_calculated = df.errornorm(pa_exact, subdomain.pressure[phase], 'L2', degree_rise=6) - self.output[subdom_ind]['errornorm'][phase]['L1'] += self.timestep_size*error_calculated - self.output[subdom_ind]['errornorm'][phase]['L2'] += self.timestep_size*error_calculated**2 - if error_calculated > self.output[subdom_ind]['errornorm'][phase]['Linf']: - self.output[subdom_ind]['errornorm'][phase]['Linf'] = error_calculated - - if norm_pa_exact > self.tol: - relative_L2_errornorm.update( - {phase: error_calculated/norm_pa_exact} - ) - else: - relative_L2_errornorm.update( - {phase: error_calculated} - ) - - if np.isin(self.timestep_num, self.timesteps_to_plot, assume_unique=True): - errornorm_filename = self.output_dir+self.output_filename_parameter_part[subdom_ind]+\ - "_L2_errornorms_over_time" +".csv" - self.write_errornorms_to_csv( - filename = errornorm_filename, # - subdomain_index = subdom_ind, - errors = relative_L2_errornorm, - ) - if np.isin(self.timestep_num, self.timesteps_to_plot, assume_unique=True) and self.write2file['absolute_differences']: - pressure_exact.update( - {phase: df.interpolate(pa_exact, subdomain.function_space["pressure"][phase])} - ) - absolute_difference = df.Function(subdomain.function_space["pressure"][phase]) - pressure_difference = pressure_exact[phase].vector()[:] - subdomain.pressure[phase].vector()[:] - abs_diff_tmp = np.fabs(pressure_difference) - absolute_difference.vector()[:] = abs_diff_tmp - if phase == "wetting": - data_set_name="abs_diff_w" - else: - data_set_name="abs_diff_nw" - self.write_function_to_xdmf(# - function=absolute_difference, - file=self.solution_file[subdom_ind], # - time=self.t,# - data_set_name=data_set_name - ) + # calculate spacetime errornorms for self.output. + space_errornorm = self._spacetime_errornorm_calc_step() + # write solutions to files. It is paramount, that self.timestep_num + # be only updated after calling self.write_data_to_disc() + self._write_data_to_disc(space_errornorm) self.timestep_num += 1 - self.output[subdom_ind]['errornorm'][phase]['L2'] = np.sqrt(self.output[subdom_ind]['errornorm'][phase]['L2']) + + # the L2 space_time errornorm needs to be square rooted. + for subdom_ind in self.isRichards.keys(): + subdomain = self.subdomain[subdom_ind] + for phase in subdomain.has_phases: + self.output[subdom_ind]['errornorm'][phase]['L2'] = np.sqrt(self.output[subdom_ind]['errornorm'][phase]['L2']) print("LDD simulation use case: {}".format(self.use_case)) df.info("Finished calculation") @@ -453,6 +410,7 @@ class LDDsimulation(object): # df.info(colored("finished post processing calculations \nAll right. I'm Done.", "green")) return self.output + def LDDsolver(self, time: float = None, # debug: bool = False, # analyse_timestep: bool = False, @@ -554,7 +512,7 @@ class LDDsimulation(object): # of the subdomain for communication. subdomain.write_pressure_to_interfaces() - if analyse_timestep and self.write_to_file['L_iterations_per_timestep']: + if analyse_timestep and self.write2file['L_iterations_per_timestep']: subdomain.write_solution_to_xdmf(# file = solution_over_iteration_within_timestep[sd_index], # time = time,# @@ -567,17 +525,10 @@ class LDDsimulation(object): ) # end loop over subdomains ##### stopping criterion for the solver. - # only check if error criterion has been met after at least one iteration. - #if iteration >= 1: - if debug: - # print(f" total_subsequent_error in iter {iteration} = {total_subsequent_iter_error }\n") - print(f"the maximum distance between any to points in a cell mesh.hmax() on subdomain{sd_index} is h={subdomain.mesh.hmax()}") - #print(f"the minimal distance between any to points in a cell mesh.hmin() on subdomain{sd_index} is h={subdomain.mesh.hmin()}") - print(f"the error criterion is tol = {error_criterion}") - total_subsequent_error = np.amax(max_subsequent_error) if debug: - print(f"time = {time} (at timestep {self.timestep_num}):: max subsequent error of both subdomain in iteration {iteration} = ", total_subsequent_error) + print(f"the error criterion is tol = {error_criterion}") + print(f"time = {time} (at timestep {self.timestep_num}):: max subsequent error of both subdomains in iteration {iteration} is: ", total_subsequent_error) if total_subsequent_error < error_criterion: # if debug: print(f"all phases on all subdomains reached a subsequent error = {total_subsequent_error} < tol = {error_criterion} after {iteration} iterations.") @@ -744,6 +695,130 @@ class LDDsimulation(object): return condition_number + def _spacetime_errornorm_calc_step(self)-> tp.Dict[int, tp.Dict[str, float]]: + """one step in integrating the spacetime errornorm + + OUTPUT + error_out tp.Dict[int, tp.Dict[str, float]] dictionary containing + the L2-errornorms in space for the timestep. + this gets passed on to self.write_data_to_disc() + """ + + error_out = dict() + for subdom_ind in self.isRichards.keys(): + error_out.update({subdom_ind: dict()}) + subdomain = self.subdomain[subdom_ind] + # if we have an exact solution, write out several errors and + # errornorms to be used later in paraview or latex plots. + if subdomain.pressure_exact is not None: + for phase in subdomain.has_phases: + pa_exact = subdomain.pressure_exact[phase] + pa_exact.t = self.t + error_calculated = df.errornorm(pa_exact, subdomain.pressure[phase], 'L2', degree_rise=6) + self.output[subdom_ind]['errornorm'][phase]['L1'] += self.timestep_size*error_calculated + self.output[subdom_ind]['errornorm'][phase]['L2'] += self.timestep_size*error_calculated**2 + if error_calculated > self.output[subdom_ind]['errornorm'][phase]['Linf']: + self.output[subdom_ind]['errornorm'][phase]['Linf'] = error_calculated + + # if we are at a timestep at which to write shit out, + # calculate the relative errornorm + if np.isin(self.timestep_num, self.timestep_numbers_to_plot, assume_unique=True): + norm_pa_exact = df.norm(pa_exact, norm_type='L2', mesh=subdomain.mesh) + if norm_pa_exact > self.tol: + error_out[subdom_ind].update( + {phase: error_calculated/norm_pa_exact} + ) + else: + error_out[subdom_ind].update( + {phase: error_calculated} + ) + else: + error_out[subdom_ind].update({phase: None}) + else: + # error_out.update({subdom_ind: None}) + error_outupdate({subdom_ind: None}) + return error_out + + + def _write_data_to_disc(self, space_errornorm: tp.Dict[int, tp.Dict[str, float]]): + """function that takes care of writing out different data to disc after + a timestep done by the self.run() function. What is written out at which + timesteps, is determined by by self.write2file aswell as + self.timestep_numbers_to_plot. + + INPUT + space_errornorm tp.Dict[int, tp.Dict[str, float]]) dictionary of + L2 space errornorms calculated by + self._spacetime_errornorm_calc_step() + """ + # we only write data of timesteps to disc, that are stored in + # self.timestep_numbers_to_plot. + if np.isin(self.timestep_num, self.timestep_numbers_to_plot, assume_unique=True): + # We need to determin the corresponding + # timestep value in self.timesteps_to_plot to write data out at the + # same values for timestep values tn. This is necessary, because we + # used self.timesteps_to_plot to plot the exact solutions. + # Counting up the time via self.t += self.timestep_size in the timesteping + # loop, yields to slightly other values for t due to rounding errors. sigh. + plot_index = np.argwhere(self.timestep_numbers_to_plot==self.timestep_num)[0][0] + plot_t = self.timesteps_to_plot[plot_index] + + for subdom_ind in self.isRichards.keys(): + subdomain = self.subdomain[subdom_ind] + # write out solution, if self.write2file['solutions'] flag says so. + if self.write2file['solutions']: + subdomain.write_solution_to_xdmf(# + file = self.solution_file[subdom_ind], # + time = plot_t,# + write_iter_for_fixed_time = False, + ) + # if we have an exact solution, write out several errors and + # errornorms to be used later in paraview or latex plots. + if subdomain.pressure_exact is not None: + # write out the absolute differences for convenience in paraview + if self.write2file['absolute_differences']: + pressure_exact = dict() + for phase in subdomain.has_phases: + pa_exact = subdomain.pressure_exact[phase] + pa_exact.t = plot_t + pressure_exact.update( + {phase: df.interpolate(pa_exact, subdomain.function_space["pressure"][phase])} + ) + absolute_difference = df.Function(subdomain.function_space["pressure"][phase]) + pressure_difference = pressure_exact[phase].vector()[:] - subdomain.pressure[phase].vector()[:] + abs_diff_tmp = np.fabs(pressure_difference) + absolute_difference.vector()[:] = abs_diff_tmp + if phase == "wetting": + data_set_name="abs_diff_w" + else: + data_set_name="abs_diff_nw" + self.write_function_to_xdmf(# + function=absolute_difference, + file=self.solution_file[subdom_ind], # + time=plot_t,# + data_set_name=data_set_name + ) + + if self.write2file['space_errornorms']: + relative_L2_errornorm = space_errornorm[subdom_ind] + errornorm_filename = self.output_dir+self.output_filename_parameter_part[subdom_ind]+\ + "_L2_errornorms_over_time" +".csv" + self.write_errornorms_to_csv( + filename = errornorm_filename, # + subdomain_index = subdom_ind, + errors = relative_L2_errornorm, + time=plot_t, + timestep_num=self.timestep_num + ) + else: + pass + pass + else: + # in this case we are at a timestep that is not bound to be written + # to disc. + pass + + def _init_solution_files(self): """ set up solution files for saving the solution of the LDD scheme for each subdomain. @@ -795,19 +870,19 @@ class LDDsimulation(object): evaluate the exact solution of the simulation (in case there is one) at all timesteps and write it to the solution file. """ + # print(f" solution will be printed at times t = {self.timesteps_to_plot}\n") for subdom_ind, subdomain in self.subdomain.items(): - t = copy.copy(self.starttime) file = self.solution_file[subdom_ind] for timestep in self.timesteps_to_plot: exact_pressure = dict() for phase in subdomain.has_phases: pa_exact = subdomain.pressure_exact[phase] - pa_exact.t = t + pa_exact.t = timestep exact_pressure.update( {phase: df.interpolate(pa_exact, subdomain.function_space["pressure"][phase])} ) exact_pressure[phase].rename("exact_pressure_"+"{}".format(phase), "exact_pressure_"+"{}".format(phase)) - file.write(exact_pressure[phase], t) + file.write(exact_pressure[phase], timestep) exact_capillary_pressure = df.Function(subdomain.function_space["pressure"]['wetting']) if subdomain.isRichards: @@ -816,8 +891,7 @@ class LDDsimulation(object): pc_temp = exact_pressure["nonwetting"].vector().get_local() - exact_pressure["wetting"].vector().get_local() exact_capillary_pressure.vector().set_local(pc_temp) exact_capillary_pressure.rename("pc_exact", "pc_exact") - file.write(exact_capillary_pressure, t) - t += self.timestep_size + file.write(exact_capillary_pressure, timestep) def write_subsequent_errors_to_csv(self,# @@ -885,28 +959,24 @@ class LDDsimulation(object): filename: str, # subdomain_index: int,# errors: tp.Dict[str, float],# + time: float, + timestep_num: int ) -> None: """ write subsequent errors to csv file for plotting with pgfplots""" - # in case no time is given, use the time of the class. - subdomain = self.subdomain[subdomain_index] - time = self.t - timestep = self.timestep_num - subdomain = self.subdomain[subdomain_index] - # iteration = subdomain.iteration_number if subdomain.isRichards: - self.errors = pd.DataFrame({ "time": self.t, - "timestep": self.timestep_num, + self.errors = pd.DataFrame({ "time": time, + "timestep": timestep_num, #"th. initial mass": df.assemble(rho*df.dx), \ #"energy": self.total_energy(), \ - "wetting": errors["wetting"]}, index=[timestep]) + "wetting": errors["wetting"]}, index=[timestep_num]) else: - self.errors = pd.DataFrame({ "time": self.t, - "timestep": self.timestep_num, + self.errors = pd.DataFrame({ "time": time, + "timestep": timestep_num, "wetting": errors["wetting"], - "nonwetting": errors["nonwetting"]}, index=[timestep]) + "nonwetting": errors["nonwetting"]}, index=[timestep_num]) # if self._rank == 0: # check if file exists if os.path.isfile(filename) == True: @@ -1144,13 +1214,13 @@ class LDDsimulation(object): self.subdomain[subdom_num].linear_solver = df.KrylovSolver(self.solver, self.preconditioner) else: self.subdomain[subdom_num].linear_solver = df.LUSolver() - print("solver method:") - for parameter, value in self.subdomain[subdom_num].linear_solver.parameters.items(): - print(f"\'{parameter}\': {value}") + # print("solver method:") + # for parameter, value in self.subdomain[subdom_num].linear_solver.parameters.items(): + # print(f"\'{parameter}\': {value}") # assign solver parameters set at the beginning of the LDDsimulation class. solver_param = self.subdomain[subdom_num].linear_solver.parameters - df.info(solver_param, True) + # df.info(solver_param, True) for parameter_name in self.solver_parameters.keys(): solver_param[parameter_name] = self.solver_parameters[parameter_name] @@ -1164,7 +1234,7 @@ class LDDsimulation(object): filepath = self.output_dir+f"outer_boundary_marker_subdomain{subdom_num}.pvd" df.File(filepath) << self.subdomain[subdom_num].outer_boundary_marker - print(self.subdomain[subdom_num]) + # print(self.subdomain[subdom_num]) def _get_interfaces(self, subdomain_index: int, # @@ -1454,34 +1524,17 @@ class LDDsimulation(object): def _init_analyse_timesteps(self): - """ determin timesteps to anayise - - by setting - """ - # We want to write out csv files containing subsequent L2 errors of iterations for - # number_of_timesteps_to_analyse many timesteps. We want to do this with - # analyse_timesteps = np.linspace(starttime, number_of_timesteps, number_of_timesteps_to_analyse) - # see below. Therefor, we need to check if (number_of_timesteps_to_analyse-1) is - # a divisor of number_of_timesteps, + """ determin timesteps to anayise """ if self.number_of_timesteps_to_analyse == 0: self.analyse_timesteps = None - elif self.number_of_timesteps_to_analyse == 1: - self.analyse_timesteps = [1] else: - if not self.number_of_timesteps%(self.number_of_timesteps_to_analyse-1) == 0: - # if number_of_timesteps%(number_of_timesteps_to_analyse-1) is not 0, we - # round the division error to the nearest integer and redefine number_of_timesteps - print(f"since number_of_timesteps/number_of_timesteps_to_analyse = {self.number_of_timesteps/self.number_of_timesteps_to_analyse}", - "is not an integer,\n") - new_analysing_interval = int(round(self.number_of_timesteps/(self.number_of_timesteps_to_analyse-1))) - self.number_of_timesteps = new_analysing_interval*(self.number_of_timesteps_to_analyse) - print(f"I'm resetting number_of_timesteps = {self.number_of_timesteps}\n") self.analyse_timesteps = np.linspace(1, self.number_of_timesteps, self.number_of_timesteps_to_analyse, dtype=int) - print(f"the following timesteps will be analysed: {self.analyse_timesteps}") - self.endtime = self.number_of_timesteps*self.timestep_size + + self.endtime = self.starttime + self.number_of_timesteps*self.timestep_size # time_interval = [starttime, Tmax] - print(f"The simulation interval is [{self.starttime}, {self.endtime}]") + print(f"The simulation interval is [{self.starttime}, {self.endtime}]\n") + print(f"the following timesteps will be analysed: {self.analyse_timesteps}\n") def _init_simulation_output(self):