From b4aa88dfc69cdd0e1bb7c0d9e77cdf65e128a381 Mon Sep 17 00:00:00 2001 From: David Seus <david.seus@ians.uni-stuttgart.de> Date: Fri, 16 Aug 2019 14:32:08 +0200 Subject: [PATCH] fix wrong assignment of gli_dofs on interface facets --- .gitignore | 2 + LDDsimulation/LDDsimulation.py | 527 +++++++++++++++++------- LDDsimulation/boundary_and_interface.py | 39 +- LDDsimulation/domainPatch.py | 388 +++++++++++------ LDDsimulation/helpers.py | 283 +++++-------- LDDsimulation/solutionFile.py | 2 +- 6 files changed, 781 insertions(+), 460 deletions(-) diff --git a/.gitignore b/.gitignore index d66cabb..eb49951 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,8 @@ *.h5 *.dep *.csv +*.ipynb +core # Ignoriere Bilder und Graphiken sowie Videos und Musik *.png diff --git a/LDDsimulation/LDDsimulation.py b/LDDsimulation/LDDsimulation.py index 71b503d..455b2e7 100644 --- a/LDDsimulation/LDDsimulation.py +++ b/LDDsimulation/LDDsimulation.py @@ -1,10 +1,12 @@ -""" -LDD simulation class +"""LDD simulation class. + +Main class of the LDD simulation. The heart and soul of the code. """ import os import dolfin as df import numpy as np +import numpy.linalg as la import typing as tp import mshr import domainPatch as dp @@ -19,9 +21,8 @@ from termcolor import colored from solutionFile import SolutionFile - class LDDsimulation(object): - """ Main Class of LDD simulation + """Main Class of LDD simulation. LDDsimulation is the main class for the LDD simulation. It contains methods for setting up and running the LDD simulation for different meshes and domains. @@ -54,14 +55,16 @@ class LDDsimulation(object): # # To be able to run DG in parallel # df.parameters["ghost_mode"] = "shared_facet" # df.parameters["ghost_mode"] = "none" + df.parameters["mesh_partitioner"] = "ParMETIS" # Form compiler options df.parameters["form_compiler"]["optimize"] = True df.parameters["form_compiler"]["cpp_optimize"] = True # 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.INFO) + # df.set_log_level(df.LogLevel.DEBUG) # CRITICAL = 50, // errors that may lead to data corruption and suchlike # ERROR = 40, // things that go boom @@ -69,7 +72,7 @@ class LDDsimulation(object): # INFO = 20, // information of general interest # PROGRESS = 16, // what's happening (broadly) # TRACE = 13, // what's happening (in detail) - # DBG = 10 // sundry + # DEBUG = 10, // sundry hlp.print_once("Set default parameter...\n") self.restart_file = None @@ -92,7 +95,7 @@ class LDDsimulation(object): # global marker for subdomains and interfaces. Is set by self.init_meshes_and_markers() self.domain_marker = None # List of interfaces of class Interface. This is populated by self.init_interfaces - self.interface = [] + self.interface = None # The global interface marker. Gets initialised by self.init_interfaces(). self.interface_marker = None # List of list of indices indicating subdomains adjacent to interfaces. @@ -103,7 +106,7 @@ class LDDsimulation(object): ## Private variables # maximal number of L-iterations that the LDD solver uses. - self._max_iter_num = 100 + self._max_iter_num = 500 # TODO rewrite this with regard to the mesh sizes # self.calc_tol = self.tol # list of timesteps that get analyed. Gets initiated by self._init_analyse_timesteps @@ -145,8 +148,8 @@ 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 Algebra Backends:") + df.list_linear_algebra_backends() # # print("\nLinear Solver Methods:") df.list_linear_solver_methods() print("\n") @@ -155,24 +158,37 @@ class LDDsimulation(object): # # print("\nPeconditioners for Krylov Solvers:") df.list_krylov_solver_preconditioners() - # df.LinearSolver_default_parameters() + df.info(df.parameters, True) - self.solver_type_is_Kryov = False + self.solver_type_is_Krylov = True ### Define the linear solver to be used. - self.solver = 'superlu' #'gmres'#'bicgstab' # biconjugate gradient stabilized method - self.preconditioner = 'default'#jacobi#'hypre_amg' #'ilu'#'hypre_amg' # incomplete LU factorization - # dictionary of solver parametrs. This is passed to self._init_subdomains, - # where for each subdomain a sovler object of type self.solver is created - # with these parameters. - self.solver_parameters = { - 'nonzero_initial_guess': True, - 'absolute_tolerance': 1E-14, - 'relative_tolerance': 1E-12, - 'maximum_iterations': 1000, - 'report': False - } + if self.solver_type_is_Krylov: + self.solver = 'bicgstab'#'superlu' #'gmres'#'bicgstab' # biconjugate gradient stabilized method + self.preconditioner = 'jacobi' #'hypre_amg' 'default' #'ilu'#'hypre_amg' # incomplete LU factorization + # dictionary of solver parametrs. This is passed to self._init_subdomains, + # where for each subdomain a sovler object of type self.solver is created + # with these parameters. + self.solver_parameters = { + 'nonzero_initial_guess': True, + 'absolute_tolerance': 1E-14, + 'relative_tolerance': 1E-12, + 'maximum_iterations': 1000, + 'report': False, + 'monitor_convergence': False + } + else: + self.solver = 'superlu' #'gmres'#'bicgstab' # biconjugate gradient stabilized method + self.preconditioner = 'default' #'hypre_amg' 'default' #'ilu'#'hypre_amg' # incomplete LU factorization + # dictionary of solver parametrs. This is passed to self._init_subdomains, + # where for each subdomain a sovler object of type self.solver is created + # with these parameters. + self.solver_parameters = { + 'report' : True, + 'symmetric': False, + 'verbose': True + } self.debug_solver = debug self.LDDsolver_tol = LDDsolver_tol @@ -182,6 +198,7 @@ class LDDsimulation(object): # done or not. def set_parameters(self,# + use_case: str, output_dir: str,# subdomain_def_points: tp.List[tp.List[df.Point]],# # this is actually an array of bools @@ -212,6 +229,9 @@ class LDDsimulation(object): )-> None: """ set parameters of an instance of class LDDsimulation""" + self.use_case = use_case + if include_gravity: + self.use_case = self.use_case + "-with-gravity" self.output_dir = output_dir self.subdomain_def_points = subdomain_def_points self.isRichards = isRichards @@ -277,7 +297,10 @@ class LDDsimulation(object): self.write_exact_solution_to_xdmf() self._initialised = True - def run(self, analyse_solver_at_timesteps: tp.List[float] = None): + + def run(self, + analyse_solver_at_timesteps: tp.List[float] = None, + analyse_condition: bool = False): """ run the simulation This method implements the time stepping, calls the acutal solvers and writes @@ -309,24 +332,31 @@ class LDDsimulation(object): df.info(colored("Start time stepping","green")) # write initial values to file. - self.timestep_num = int(0) + self.timestep_num = int(1) # note that at this point of the code self.t = self.starttime as set in # the beginning. while self.timestep_num <= self.number_of_timesteps: + print("LDD simulation use case: {}".format(self.use_case)) 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): print("analyising timestep ...") - self.LDDsolver(debug = self.debug_solver, analyse_timestep = True) + self.LDDsolver(debug=self.debug_solver, + analyse_timestep=True, + analyse_condition=analyse_condition) else: - self.LDDsolver(debug = self.debug_solver, analyse_timestep = False) + self.LDDsolver(debug=self.debug_solver, + analyse_timestep=False, + analyse_condition=False) self.t += self.timestep_size - self.timestep_num += 1 # write solutions to files. for subdom_ind, subdomain in self.subdomain.items(): + # for phase in subdomain.has_phases: + # print(f"calculated pressure of {phase}phase:") + # print(subdomain.pressure[phase].vector()[:]) subdomain.write_solution_to_xdmf(# file = self.solution_file[subdom_ind], # time = self.t,# @@ -336,11 +366,29 @@ class LDDsimulation(object): # 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) + 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 + ) if norm_pa_exact > self.tol: relative_L2_errornorm.update( {phase: error_calculated/norm_pa_exact} @@ -357,9 +405,10 @@ class LDDsimulation(object): errors = relative_L2_errornorm, ) + self.timestep_num += 1 - - df.info("Finished") + print("LDD simulation use case: {}".format(self.use_case)) + df.info("Finished calculation") df.info("Elapsed time:"+str(timer.elapsed()[0])) timer.stop() # r_cons.close() @@ -370,12 +419,15 @@ class LDDsimulation(object): # with open(self.output_dir+"timings.txt", "w") as timefile: # timefile.write(df.timings) - df.dump_timings_to_xml(self.output_dir+"timings.xml",df.TimingClear.keep) - + df.dump_timings_to_xml(self.output_dir+"timings.xml", df.TimingClear.keep) + # df.info(colored("start post processing calculations ...\n", "yellow")) + # self.post_processing() + # df.info(colored("finished post processing calculations \nAll right. I'm Done.", "green")) def LDDsolver(self, time: float = None, # debug: bool = False, # - analyse_timestep: bool = False): + analyse_timestep: bool = False, + analyse_condition: bool = False): """ calulate the solution on all patches for the next timestep The heart of the simulation. The function LDDsolver implements the LDD @@ -408,7 +460,7 @@ class LDDsimulation(object): iteration = 0 # gobal stopping criterion for the iteration. all_subdomains_are_done = False - while iteration <= self._max_iter_num and not all_subdomains_are_done: + while iteration < self._max_iter_num and not all_subdomains_are_done: iteration += 1 # we need to loop over the subdomains and solve an L-scheme type step # on each subdomain. here it should be possible to parallelise in @@ -417,44 +469,62 @@ class LDDsimulation(object): # set subdomain iteration to current iteration subdomain.iteration_number = iteration # solve the problem on subdomain - self.Lsolver_step(subdomain_index = sd_index,# - debug = debug, + condition_nums = self.Lsolver_step( + subdomain_index=sd_index,# + debug=debug, + analyse_condition=analyse_condition ) # bypass subsequent error calculation for the first iteration, becauses # it is zero anyways. - if iteration > 1: - subsequent_iter_error = dict() - for phase in subdomain.has_phases: - error = df.Function(subdomain.function_space["pressure"][phase]) - error.assign(subdomain.pressure[phase] - subdomain.pressure_prev_iter[phase]) - error_calculated = df.norm(error, 'L2') - subsequent_iter_error.update( - {phase: error_calculated} - ) - if debug: - print(f"time = {time}: subsequent error on subdomain {sd_index} for {phase} phase in iteration {iteration} = ", subsequent_iter_error[phase]) + #if iteration >= 1: + subsequent_iter_error = dict() + if debug: + print("LDD simulation use case: {}".format(self.use_case)) + for phase in subdomain.has_phases: + error = df.Function(subdomain.function_space["pressure"][phase]) + error.assign(subdomain.pressure[phase] - subdomain.pressure_prev_iter[phase]) + error_calculated = df.norm(error, 'L2') + subsequent_iter_error.update( + {phase: error_calculated} + ) + if debug: + print(f"time = {time} (at timestep {self.timestep_num}): subsequent error on subdomain {sd_index} for {phase} phase in iteration {iteration} = ", subsequent_iter_error[phase]) - # calculate the maximum over phase of subsequent errors for the - # stopping criterion. - max_subsequent_error[sd_index-1] = max(subsequent_iter_error.values()) + # calculate the maximum over phase of subsequent errors for the + # stopping criterion. + max_subsequent_error[sd_index-1] = max(subsequent_iter_error.values()) - if analyse_timestep: - # save the calculated L2 errors to csv file for plotting - # with pgfplots - subsequent_error_filename = self.output_dir\ + if analyse_timestep: + # save the calculated L2 errors to csv file for plotting + # with pgfplots + subsequent_error_filename = self.output_dir\ + +self.output_filename_parameter_part[sd_index]\ + +"subsequent_iteration_errors" +"_at_time"+\ + "{number}".format(number=self.timestep_num) +".csv" #"{number:.{digits}f}".format(number=time, digits=4) + self.write_subsequent_errors_to_csv( + filename = subsequent_error_filename, # + subdomain_index = sd_index, + errors = subsequent_iter_error + ) + if analyse_condition: + # save the calculated condition numbers of the assembled + # matrices a separate file for monitoring + condition_number_filename = self.output_dir\ +self.output_filename_parameter_part[sd_index]\ - +"subsequent_iteration_errors" +"_at_time"+\ - "{number:.{digits}f}".format(number=time, digits=4) +".csv" - self.write_subsequent_errors_to_csv( - filename = subsequent_error_filename, # + +"condition_numbers" +"_at_timestep"+\ + "{number}".format(number=self.timestep_num) +".csv" + self.write_condition_numbers_to_csv( + filename = condition_number_filename, # subdomain_index = sd_index, - errors = subsequent_iter_error, - time = time) + condition_number = condition_nums + ) # prepare next iteration # write the newly calculated solution to the inteface dictionaries # of the subdomain for communication. - subdomain.write_pressure_to_interfaces() + if self.interface_def_points is not None: + subdomain.write_pressure_to_interfaces() + if analyse_timestep: subdomain.write_solution_to_xdmf(# file = solution_over_iteration_within_timestep[sd_index], # @@ -469,22 +539,23 @@ 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}: max subsequent error of both subdomain in iteration {iteration} = ", 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.") - all_subdomains_are_done = True + #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) + 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.") + all_subdomains_are_done = True # end iteration while loop. + def prepare_LDDsolver(self, time: float = None, # debug: bool = False, # analyse_timestep: bool = False) -> tp.Dict[int, tp.Type[SolutionFile]]: @@ -545,7 +616,9 @@ class LDDsimulation(object): # reset all interface[has_interface].current_iteration[ind] = 0, for # index has_interface in subdomain.has_interface - subdomain.null_all_interface_iteration_numbers() + if self.interface_def_points is not None: + subdomain.null_all_interface_iteration_numbers() + if subdomain.outer_boundary is not None: self.update_DirichletBC_dictionary(time = time, # subdomain_index = ind, @@ -572,14 +645,17 @@ class LDDsimulation(object): # method works properly. The current pressure dictionaries should already # be populated with the current solution, sinces the solver writes the pressure # to interfaces after each iteration. - subdomain.write_pressure_to_interfaces() - subdomain.write_pressure_to_interfaces(previous_iter=True) - subdomain.calc_gl0_term() + if self.interface_def_points is not None: + subdomain.write_pressure_to_interfaces() + subdomain.write_pressure_to_interfaces(previous_iter=True) + subdomain.calc_gl0_term() return solution_over_iteration_within_timestep + def Lsolver_step(self, subdomain_index: int,# - debug: bool = False) -> None: + analyse_condition: bool = False, + debug: bool = False) -> tp.Dict[str, float]: """ L-scheme solver iteration step for an object of class subdomain Lsolver_step implements L-scheme solver iteration step for an subdomain @@ -596,7 +672,9 @@ class LDDsimulation(object): """ subdomain = self.subdomain[subdomain_index] iteration = subdomain.iteration_number - + condition_number = None + if analyse_condition: + condition_number = dict() for phase in subdomain.has_phases: # extract L-scheme form and rhs (without gli term) from subdomain. governing_problem = subdomain.governing_problem(phase = phase) @@ -606,11 +684,10 @@ class LDDsimulation(object): form_assembled = df.assemble(form) rhs_assembled = df.assemble(rhs) - if debug and subdomain.mesh.num_cells() < 36: - # print(f"phase = {phase}: form_assembled:\n", form_assembled.array()) - # print(f"phase = {phase}: rhs:\n", rhs_assembled.get_local()) - print(f"phase = {phase}: gli_term:\n", gli_term_assembled) - print(f"phase = {phase}: rhs_assembled:\n", rhs_assembled.get_local()) + if analyse_condition: + condition_number.update( + {phase: la.cond(form_assembled.array())} + ) # apply outer Dirichlet boundary conditions if present. if subdomain.outer_boundary is not None: @@ -633,6 +710,8 @@ class LDDsimulation(object): if debug and subdomain.mesh.num_cells() < 36: print("\npressure after solver:\n", subdomain.pressure[phase].vector().get_local()) + return condition_number + def _init_solution_files(self): """ set up solution files for saving the solution of the LDD scheme for @@ -641,6 +720,7 @@ class LDDsimulation(object): # set up solution file names for each subdomain. self.output_filename_parameter_part = dict() self.solution_file = dict() + self.postprocessed_solution_file = dict() for subdom_ind, subdomain in self.subdomain.items(): tau = subdomain.timestep_size L = subdomain.L @@ -653,7 +733,6 @@ class LDDsimulation(object): + "_gravitiy_" + "{}".format(self.include_gravity) name3 = "_lambda" + "{}".format(Lam) + "_Lw" + "{}".format(L["wetting"]) filename_param_part = name1 + name2 + name3 - filename = self.output_dir + filename_param_part + "_solution" +".xdmf" else: Lam_w = subdomain.lambda_param['wetting'] Lam_nw = subdomain.lambda_param['nonwetting'] @@ -665,7 +744,9 @@ class LDDsimulation(object): +"_lambda_nw" + "{}".format(Lam_nw)\ +"_Lw" + "{}".format(L["wetting"])\ +"_Lnw" + "{}".format(L["nonwetting"]) - filename = self.output_dir + filename_param_part + "_solution" +".xdmf" + + filename = self.output_dir + filename_param_part + "_solution" +".xdmf" + post_filename = self.output_dir + filename_param_part + "_solution_post" +".xdmf" # create solution files and populate dictionary. self.output_filename_parameter_part.update( {subdom_ind: filename_param_part} @@ -673,6 +754,10 @@ class LDDsimulation(object): self.solution_file.update( # {subdom_ind: SolutionFile(self._mpi_communicator, filename)} ) + # self.postprocessed_solution_file.update( + # {subdom_ind: SolutionFile(self._mpi_communicator, post_filename)} + # ) + def write_exact_solution_to_xdmf(self): """ @@ -682,14 +767,26 @@ class LDDsimulation(object): for subdom_ind, subdomain in self.subdomain.items(): t = copy.copy(self.starttime) file = self.solution_file[subdom_ind] - for timestep in range(0,self.number_of_timesteps+1): + + for timestep in range(self.number_of_timesteps+1): + exact_pressure = dict() for phase in subdomain.has_phases: pa_exact = subdomain.pressure_exact[phase] pa_exact.t = t - tmp_exact_pressure = df.interpolate(pa_exact, subdomain.function_space["pressure"][phase]) - tmp_exact_pressure.rename("exact_pressure_"+"{}".format(phase), "exactpressure_"+"{}".format(phase)) - file.write(tmp_exact_pressure, t) - + 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) + + exact_capillary_pressure = df.Function(subdomain.function_space["pressure"]['wetting']) + if self.isRichards: + exact_capillary_pressure.assign(-exact_pressure['wetting']) + else: + pc_temp = exact_pressure["nonwetting"].vector()[:] - exact_pressure["wetting"].vector()[:] + exact_capillary_pressure.vector()[:] = pc_temp + exact_capillary_pressure.rename("pc_exact", "pc_exact") + file.write(exact_capillary_pressure, t) t += self.timestep_size @@ -697,12 +794,8 @@ class LDDsimulation(object): filename: str, # subdomain_index: int,# errors: tp.Dict[str, float],# - time: float = None) -> None: + ) -> 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] - if time is None: - time = self.t subdomain = self.subdomain[subdomain_index] iteration = subdomain.iteration_number @@ -727,6 +820,37 @@ class LDDsimulation(object): else: self.errors.to_csv(filename, sep='\t', encoding='utf-8', index=False) + + def write_condition_numbers_to_csv(self,# + filename: str, # + subdomain_index: int,# + condition_number: tp.Dict[str, float],# + ) -> None: + """ write subsequent errors to csv file for plotting with pgfplots""" + subdomain = self.subdomain[subdomain_index] + iteration = subdomain.iteration_number + + if subdomain.isRichards: + condition = pd.DataFrame({ "iteration": iteration, + #"th. initial mass": df.assemble(rho*df.dx), \ + #"energy": self.total_energy(), \ + "wetting": condition_number["wetting"]}, index=[iteration]) + + else: + condition = pd.DataFrame({"iteration":iteration, #\ + #"th. initial mass": df.assemble(rho*df.dx), \ + #"energy": self.total_energy(), \ + "wetting": condition_number["wetting"], + "nonwetting": condition_number["nonwetting"]}, index=[iteration]) + # if self._rank == 0: + # check if file exists + if os.path.isfile(filename) == True: + with open(filename, 'a') as f: + condition.to_csv(f, header=False, sep='\t', encoding='utf-8', index=False) + else: + condition.to_csv(filename, sep='\t', encoding='utf-8', index=False) + + def write_errornorms_to_csv(self,# filename: str, # subdomain_index: int,# @@ -761,6 +885,28 @@ class LDDsimulation(object): else: self.errors.to_csv(filename, sep='\t', encoding='utf-8', index=False) + + def write_function_to_xdmf(self, + function: df.Function, + file: tp.Type[SolutionFile], # + data_set_name: str, + time: float = None,# + ): + """ + Write function to disk file file. It is expected, that file is a df.XDMFFile + + PARAMETERS + function df.Function dolfin function containing the + information to write to the file. + file str File name of df.XDMFFile to write to + data_set_name str name of the data set + time float time at which to write the solution. + + """ + function.rename("{}".format(data_set_name), "{}".format(data_set_name)) + file.write(function, time) + + ## Private methods def _init_meshes_and_markers(self, subdomain_def_points: tp.List[tp.List[df.Point]] = None,# mesh_resolution: float = None, # @@ -806,29 +952,39 @@ class LDDsimulation(object): ### generate global mesh and subdomain meshes. Also count number of subdomains. # define the global simulation domain polygon first. domain = mshr.Polygon(subdomain_def_points[0]) - for num, subdomain_vertices in enumerate(subdomain_def_points[1:], 1): - subdomain = mshr.Polygon(subdomain_vertices) - domain.set_subdomain(num, subdomain) - # count number of subdomains for further for loops - self._number_of_subdomains = num + if self.interface_def_points is None: + # in this case we don't have any interface and no domain decomposi- + # tion going on. + self._number_of_subdomains = 1 + else: + for num, subdomain_vertices in enumerate(subdomain_def_points[1:], 1): + subdomain = mshr.Polygon(subdomain_vertices) + domain.set_subdomain(num, subdomain) + # count number of subdomains for further for loops + self._number_of_subdomains = num # create global mesh. Submeshes still need to be extracted. mesh = mshr.generate_mesh(domain, mesh_resolution) + if self.write2file['meshes_and_markers']: filepath = self.output_dir+"global_mesh.pvd" df.File(filepath) << mesh filepath = self.output_dir+"global_mesh.xml.gz" df.File(filepath) << mesh + self.mesh_subdomain.append(mesh) # define domainmarker on global mesh and set marker values to domain # number as assigned in the above for loop. self.domain_marker = df.MeshFunction('size_t', mesh, mesh.topology().dim(), mesh.domains()) - # extract subdomain meshes - for num in range(1, self._number_of_subdomains+1): - # if MPI.size(mpi_comm_world()) == 1: - self.mesh_subdomain.append(df.SubMesh(mesh, self.domain_marker, num)) - # else: - # self.mesh_subdomain.append(create_submesh(mesh, self.domain_marker, num)) + if self.interface_def_points is None: + self.domain_marker.set_all(0) + else: + # extract subdomain meshes + for num in range(1, self._number_of_subdomains+1): + # if MPI.size(mpi_comm_world()) == 1: + self.mesh_subdomain.append(df.SubMesh(mesh, self.domain_marker, num)) + # else: + # self.mesh_subdomain.append(create_submesh(mesh, self.domain_marker, num)) @@ -884,21 +1040,25 @@ class LDDsimulation(object): # define global interface marker. This is a mesh function on edges of the mesh. self.interface_marker = df.MeshFunction('size_t', mesh, mesh.topology().dim()-1) self.interface_marker.set_all(0) - for glob_interface_num, vertices in enumerate(interface_def_points): - self.interface.append(bi.Interface(vertices=vertices, # - tol=self.tol,#mesh.hmin()/100, - internal=True,# - adjacent_subdomains = adjacent_subdomains[glob_interface_num],# - isRichards = self.isRichards,# - global_index = glob_interface_num, - ) - )# - # marker value gets internally set to global_index+1. - # The reason is that we want to mark outer boundaries with the value 0. - marker_value = self.interface[glob_interface_num].marker_value - self.interface[glob_interface_num].mark(self.interface_marker, marker_value) - self.interface[glob_interface_num].init_facet_dictionaries(self.interface_marker, marker_value, subdomain_index=0) - # print(self.interface[glob_interface_num]) + # if self.interface_def_points is None, i.e. we have no interface we do + # nothing, since self.init() defines self.interface = None + if self.interface_def_points is not None: + self.interface = [] + for glob_interface_num, vertices in enumerate(interface_def_points): + self.interface.append(bi.Interface(vertices=vertices, # + tol=self.tol,#mesh.hmin()/100, + internal=True,# + adjacent_subdomains = adjacent_subdomains[glob_interface_num],# + isRichards = self.isRichards,# + global_index = glob_interface_num, + ) + )# + # marker value gets internally set to global_index+1. + # The reason is that we want to mark outer boundaries with the value 0. + marker_value = self.interface[glob_interface_num].marker_value + self.interface[glob_interface_num].mark(self.interface_marker, marker_value) + self.interface[glob_interface_num].init_facet_dictionaries(self.interface_marker, marker_value, subdomain_index=0) + # print(self.interface[glob_interface_num]) if self.write2file['meshes_and_markers']: filepath = self.output_dir+"interface_marker_global.pvd" @@ -916,7 +1076,10 @@ class LDDsimulation(object): # The list is_richards has the same length as the nuber of subdomains. # Therefor it is used in the subdomain initialisation loop. for subdom_num, isR in self.isRichards.items(): - interface_list = self._get_interfaces(subdom_num) + if self.interface_def_points is None: + interface_list = None + else: + interface_list = self._get_interfaces(subdom_num) # print(f"has_interface list for subdomain {subdom_num}:", interface_list) if self.densities is None: subdom_densities = None @@ -946,22 +1109,23 @@ class LDDsimulation(object): ) # setup the linear solvers and set the solver parameters. # df.LinearSolver(self.solver) - if self.solver_type_is_Kryov: + if self.solver_type_is_Krylov: self.subdomain[subdom_num].linear_solver = df.KrylovSolver(self.solver, self.preconditioner) - # we use the previous iteration as an initial guess for the linear solver. - solver_param = self.subdomain[subdom_num].linear_solver.parameters - df.info(solver_param, True) - solver_param['nonzero_initial_guess'] = self.solver_parameters['nonzero_initial_guess'] - solver_param['absolute_tolerance'] = self.solver_parameters['absolute_tolerance'] - solver_param['relative_tolerance'] = self.solver_parameters['relative_tolerance'] - solver_param['maximum_iterations'] = self.solver_parameters['maximum_iterations'] - solver_param['report'] = self.solver_parameters['report'] 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}") + + # assign solver parameters set at the beginning of the LDDsimulation class. + solver_param = self.subdomain[subdom_num].linear_solver.parameters + df.info(solver_param, False) + for parameter_name in self.solver_parameters.keys(): + solver_param[parameter_name] = self.solver_parameters[parameter_name] - # ## print out set solver parameters - for parameter, value in self.subdomain[subdom_num].linear_solver.parameters.items(): - print(f"parameter: {parameter} = {value}") + # # ## print out set solver parameters + # for parameter, value in self.subdomain[subdom_num].linear_solver.parameters.items(): + # print(f"parameter: {parameter} = {value}") if self.write2file['meshes_and_markers']: filepath = self.output_dir+f"interface_marker_subdomain{subdom_num}.pvd" @@ -1030,12 +1194,30 @@ class LDDsimulation(object): subdomain.pressure[phase].assign(pa0_interpolated) subdomain.pressure_prev_iter[phase].assign(pa0_interpolated) subdomain.pressure_prev_timestep[phase].assign(pa0_interpolated) + # write zeros to the abs_diff variables + + 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[num], # + time=self.starttime,# + data_set_name=data_set_name + ) # populate interface dictionaries with pressure values. The calc_gli_term # of each subdomain reads from the interface.pressure_values_prev # and from interface.pressure_values dictionary so both need to be populated. - subdomain.write_pressure_to_interfaces() - subdomain.write_pressure_to_interfaces(previous_iter = True) + if self.interface_def_points is not None: + subdomain.write_pressure_to_interfaces() + subdomain.write_pressure_to_interfaces(previous_iter = True) + subdomain.write_solution_to_xdmf(# file = self.solution_file[num], # time = self.starttime,# @@ -1109,7 +1291,7 @@ class LDDsimulation(object): interpolation_degree = self.interpolation_degree if time is None: - time = self.starttime + time = self.t subdomain = self.subdomain[subdomain_index] # in case time == self.starttime, the expression has already been # assembled by self._init_DirichletBC_dictionary. @@ -1128,6 +1310,65 @@ class LDDsimulation(object): ) + # def post_processing(self): + # """post processing of the simulation. + # calculate + # - pc_exact and pc_num + # - absolute differences to exact solution if present. + # - relative errors to exact solution if present + # """ + # for subdom_ind, subdomain in self.subdomain.items(): + # self._mesh = df.Mesh() + # solution_file = self.solution_file[subdom_ind] + # post_solution_file = self.postprocessed_solution_file[subdom_ind] + # pressure = dict() + # # the internal time series counter in df.XDMFFile starts at 0 and + # # refers to the first saved value. This corresponds to the initial + # # values. We then calculated self.number_of_timesteps many timesteps. + # t = self.starttime + # for internal_timestep in range(self.number_of_timesteps+1): + # for phase in subdomain.has_phases: + # pressure.update( + # {phase: df.Function(subdomain.function_space["pressure"][phase])} + # ) + # phase_pressure_string = "pressure_"+"{}".format(phase) + # solution_file.read_checkpoint( + # u=pressure[phase], + # name=phase_pressure_string, + # counter=internal_timestep + # ) + # pressure[phase].rename("pressure_"+"{}".format(phase), "pressure_"+"{}".format(phase)) + # post_solution_file.write(pressure[phase], t) + # t += self.timestep_size + # print(f"read pressure of {phase}phase:") + # print(pressure[phase].vector()[:]) + # solution_file.close() + # # # 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() + # # 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) + # # if norm_pa_exact > self.tol: + # # relative_L2_errornorm.update( + # # {phase: error_calculated/norm_pa_exact} + # # ) + # # else: + # # relative_L2_errornorm.update( + # # {phase: error_calculated} + # # ) + # # 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, + # # ) + + def _init_exact_solution_expression(self): """ initialise dolfin expressions for exact solution and populate self.exact_solution_expr dictionary. This is used to calculate errornorms @@ -1195,7 +1436,7 @@ class LDDsimulation(object): if self.number_of_timesteps_to_analyse == 0: self.analyse_timesteps = None elif self.number_of_timesteps_to_analyse == 1: - self.analyse_timesteps = [0] + 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 @@ -1205,7 +1446,7 @@ class LDDsimulation(object): 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(0, self.number_of_timesteps, self.number_of_timesteps_to_analyse, dtype=int) + 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 diff --git a/LDDsimulation/boundary_and_interface.py b/LDDsimulation/boundary_and_interface.py index b50d921..9204d77 100644 --- a/LDDsimulation/boundary_and_interface.py +++ b/LDDsimulation/boundary_and_interface.py @@ -333,7 +333,8 @@ class Interface(BoundaryPart): interface_marker_value: int, debug: bool = False) -> tp.Dict[int, tp.Dict[int, np.ndarray]]: """ for a given function space function_space, calculate the dof indices - and coordinates along the interface. + and coordinates along the interface. The pair {global_dof_index: dof_coordinate} + is saved as value in a dictionary which has global facet indices as keys. # Parameters function_space: #type df.functionSpace the function space of whitch @@ -362,9 +363,21 @@ class Interface(BoundaryPart): for mesh_entity in mesh_entity_iterator: # this is the facet index relative to the Mesh # that interface_marker is defined on. - facet_index = mesh_entity.index() + interface_facet_index = mesh_entity.index() + for interface_facet in df.facets(mesh_entity): + # we only need the x,y coordinates. vertex.point()[:] returns + # the array and cannot be sliced. the result can be indexed, hence, + # vertex.point()[:][0:2] + # facet_midpoint = interface_facet.midpoint()[:][0:2] + # print(f"facet: {interface_facet}") + # print("facet coordinates: ") + facet_points = [] + for vertex in df.vertices(interface_facet): + # print(f"vertex: {vertex} with coordinates {vertex.point()[:][0:2]}") + facet_points.append(vertex.point()) + - dofs_and_coords.update({facet_index: dict()}) + dofs_and_coords.update({interface_facet_index: dict()}) # because mesh_entity doesn't have a method to access coordinates we # need to cast it into an acutal facet object. for cell in df.cells(mesh_entity): @@ -373,12 +386,15 @@ class Interface(BoundaryPart): # returns an iterator object. facet_cell = cell facet_cell_index = facet_cell.index() + # cell_facets_entity = df.facets(cell) + # cell_coordinates = cell.get_vertex_coordinates() # the cell is a triangle and for each dof numbered locally for that - # cell, we have the coordinates in a numpy array. + # cell, we have the coordinates in a numpy array. The numbering is the + # numbering local to the cell. facet_cell_dof_coordinates = element.tabulate_dof_coordinates(facet_cell) # the cell dof map gives to each local (to the cell) numbering of the - # dofs the index of the dof realitve to the global dof numbering of + # dofs the index of the dofs realitve to the global dof numbering of # space, i.e. the index to access the dof with function.vector()[index]. facet_cell_dofmap = dofmap.cell_dofs(facet_cell_index) @@ -387,9 +403,9 @@ class Interface(BoundaryPart): # cell is a triangle and only has one facet that belongs to # the interface. we want to store only dofs that lie on that particular # facet - if self._is_on_boundary_part(dof_coord): + if self._is_on_line_segment(facet_points[0], facet_points[1], dof_coord): global_dof_ind = facet_cell_dofmap[local_dof_ind] - dofs_and_coords[facet_index].update({global_dof_ind: dof_coord}) + dofs_and_coords[interface_facet_index].update({global_dof_ind: dof_coord}) if debug: @@ -482,10 +498,11 @@ class Interface(BoundaryPart): for local_facet_index, local_facet_vertex in subdom_facet2coord.items(): for global_facet_index, global_facet_vertex in global_facet2coord.items(): # vertices are not necessarily in the same order - vertices_are_equal = np.array_equal(local_facet_vertex[0], global_facet_vertex[0]) - vertices_are_equal += np.array_equal(local_facet_vertex[0], global_facet_vertex[1]) - vertices_are_equal += np.array_equal(local_facet_vertex[1], global_facet_vertex[0]) - vertices_are_equal += np.array_equal(local_facet_vertex[1], global_facet_vertex[1]) + # np.array_equal(local_facet_vertex[0], global_facet_vertex[0]) + vertices_are_equal = np.allclose(local_facet_vertex[0], global_facet_vertex[0], rtol=1e-10, atol=1e-14) + vertices_are_equal += np.allclose(local_facet_vertex[0], global_facet_vertex[1], rtol=1e-10, atol=1e-14) + vertices_are_equal += np.allclose(local_facet_vertex[1], global_facet_vertex[0], rtol=1e-10, atol=1e-14) + vertices_are_equal += np.allclose(local_facet_vertex[1], global_facet_vertex[1], rtol=1e-10, atol=1e-14) both_vertices_are_equal = (vertices_are_equal == 2) if both_vertices_are_equal: self.local_to_global_facet_map[subdomain_index].update( diff --git a/LDDsimulation/domainPatch.py b/LDDsimulation/domainPatch.py index acdb82f..0b11785 100644 --- a/LDDsimulation/domainPatch.py +++ b/LDDsimulation/domainPatch.py @@ -209,25 +209,25 @@ class DomainPatch(df.SubDomain): self._init_dof_maps() self._init_markers() self._init_measures() - self._calc_interface_dof_indices_and_coordinates() - self._init_interface_values() - # self._calc_dof_indices_of_interfaces() - # self._calc_interface_has_common_dof_indices() - # self._calc_normal_vectors_norms_for_common_dofs() + if self.include_gravity: self.gravity_term = dict() self._calc_gravity_expressions() else: self.gravity_term = None - # Dictionary of FEM function holding the same dofs gli_assembled above - # but as a df.Function. This is needed for writing out the gli terms when - # analyising the convergence of a timestep. - self.gli_function = dict() - for phase in self.has_phases: - self.gli_function.update( - {phase: df.Function(self.function_space["gli"][phase])} - ) + if self.has_interface is not None: + self._calc_interface_dof_indices_and_coordinates() + self._calc_corresponding_dof_indices() + self._init_interface_values() + # Dictionary of FEM function holding the same dofs gli_assembled above + # but as a df.Function. This is needed for writing out the gli terms when + # analyising the convergence of a timestep. + self.gli_function = dict() + for phase in self.has_phases: + self.gli_function.update( + {phase: df.Function(self.function_space["gli"][phase])} + ) # END constructor @@ -238,16 +238,18 @@ class DomainPatch(df.SubDomain): model = "two-phase" if self.isRichards: model = "Richards" - - interface_string = ["\nMy interfaces are:"] - for glob_if_index in self.has_interface: - interface = self.interface[glob_if_index] - interface_string.append(interface.__str__()) - # common_int_dof_str = ".\n Dofs common with other interfaces:"\ - # +"{}".format([(ind, dof) for ind, dof in self._interface_has_common_dof_indices[glob_if_index].items()]) - # interface_string.append(common_int_dof_str) - # - interface_string = " ".join(interface_string) + if self.has_interface is not None: + interface_string = ["\nI have interfaces:"] + for glob_if_index in self.has_interface: + interface = self.interface[glob_if_index] + interface_string.append(interface.__str__()) + # common_int_dof_str = ".\n Dofs common with other interfaces:"\ + # +"{}".format([(ind, dof) for ind, dof in self._interface_has_common_dof_indices[glob_if_index].items()]) + # interface_string.append(common_int_dof_str) + # + interface_string = " ".join(interface_string) + else: + interface_string ="\nI have no interfaces" if self.outer_boundary is None: outer_boundary_str = "no outer boundaries" @@ -255,7 +257,6 @@ class DomainPatch(df.SubDomain): outer_boundary_str = f"outer boundaries: {[ind for ind in self.outer_boundary_def_points.keys()]}.\n" string = f"\nI'm subdomain{self.subdomain_index} and assume {model} model."\ - +f"\nI have the interfaces: {self.has_interface} and "\ + outer_boundary_str\ + interface_string return string @@ -273,22 +274,23 @@ class DomainPatch(df.SubDomain): interface.pressure_values_prev """ subdomain = self.subdomain_index - for ind in self.has_interface: - interface = self.interface[ind] - for phase in self.has_phases: - p_dof_coordinates = self.interface_dof_indices_and_coordinates["pressure"][ind][phase] - local2global_facet = interface.local_to_global_facet_map[subdomain] - for subdomain_facet_index, facet_dof_coord_dict in p_dof_coordinates.items(): - global_facet_index = local2global_facet[subdomain_facet_index] - # set dictionary to save the values to. - if previous_iter: - save_dict = interface.pressure_values_prev[subdomain][phase][global_facet_index] - else: - save_dict = interface.pressure_values[subdomain][phase][global_facet_index] + if self.has_interface is not None: + for ind in self.has_interface: + interface = self.interface[ind] + for phase in self.has_phases: + p_dof_coordinates = self.interface_dof_indices_and_coordinates["pressure"][ind][phase] + local2global_facet = interface.local_to_global_facet_map[subdomain] + for subdomain_facet_index, facet_dof_coord_dict in p_dof_coordinates.items(): + global_facet_index = local2global_facet[subdomain_facet_index] + # set dictionary to save the values to. + if previous_iter: + save_dict = interface.pressure_values_prev[subdomain][phase][global_facet_index] + else: + save_dict = interface.pressure_values[subdomain][phase][global_facet_index] - for dof_index, dof_coord in facet_dof_coord_dict.items(): - dof_coord_tupel = (dof_coord[0], dof_coord[1]) - save_dict.update({dof_coord_tupel: self.pressure[phase].vector()[dof_index]}) + for dof_index, dof_coord in facet_dof_coord_dict.items(): + dof_coord_tupel = (dof_coord[0], dof_coord[1]) + save_dict.update({dof_coord_tupel: self.pressure[phase].vector()[dof_index]}) def governing_problem(self, phase: str) -> tp.Dict[str, fl.Form]: @@ -341,17 +343,19 @@ class DomainPatch(df.SubDomain): # and by interface_form terms if the neighbour of the current patch # assumes Richards model if phase == "wetting": - # gather interface forms - interface_forms = [] - gli_forms = [] - # the marker values of the interfacese are global interfaces index + 1 - for interface in self.has_interface: - # print(f"subdomain{self.subdomain_index} has interfaces: {interface}\n") - marker_value = self.interface[interface].marker_value - # interface_forms += (dt*Lambda_a*pa*phi_a)*ds(interface) - interface_forms.append((dt*Lambda_a*pa*phi_a)*ds(marker_value)) - gli = self.calc_gli_term(interface_index=interface, phase=phase) - gli_forms.append((dt*gli*phi_a)*ds(marker_value)) + if self.has_interface is not None: + # gather interface forms + interface_forms = [] + gli_forms = [] + # the marker values of the interfacese are global interfaces index + 1 + for interface in self.has_interface: + # print(f"subdomain{self.subdomain_index} has interfaces: {interface}\n") + marker_value = self.interface[interface].marker_value + # interface_forms += (dt*Lambda_a*pa*phi_a)*ds(interface) + interface_forms.append((dt*Lambda_a*pa*phi_a)*ds(marker_value)) + gli = self.calc_gli_term(interface_index=interface, phase=phase) + gli_forms.append((dt*gli*phi_a)*ds(marker_value)) + # form2 is different for wetting and nonwetting form2 = dt/mu_a*df.dot(ka(S(pc_prev_iter))*df.grad(pa), df.grad(phi_a))*dx rhs2 = -(porosity*(S(pc_prev_iter) - S(pc_prev_timestep))*phi_a)*dx @@ -360,41 +364,50 @@ class DomainPatch(df.SubDomain): rhs_gravity = dt/mu_a*df.dot(ka(S(pc_prev_iter))*df.grad(z_a), df.grad(phi_a))*dx elif phase == "nonwetting": - # gather interface forms - interface_forms = [] - gli_forms = [] - for interface in self.has_interface: - marker_value = self.interface[interface].marker_value - if self.interface[interface].neighbour_isRichards[self.subdomain_index]: - # if the neighbour of our subdomain (with index self.subdomain_index) - # assumes a Richards model, we assume constant normalised atmospheric pressure - # and no interface term is practically appearing. - # interface_forms += (df.Constant(0)*phi_a)*ds(interface) - # pass - interface_forms.append((0*pa*phi_a)*ds(marker_value)) - gli_forms.append((0*phi_a)*ds(marker_value)) - else: - # interface_forms += (dt*Lambda_a*pa*phi_a)*ds(interface) - interface_forms.append((dt*Lambda_a*pa*phi_a)*ds(marker_value)) - gli = self.calc_gli_term(interface_index=interface, phase=phase) - gli_forms.append((dt*gli*phi_a)*ds(marker_value)) + if self.has_interface is not None: + # gather interface forms + interface_forms = [] + gli_forms = [] + for interface in self.has_interface: + marker_value = self.interface[interface].marker_value + if self.interface[interface].neighbour_isRichards[self.subdomain_index]: + # if the neighbour of our subdomain (with index self.subdomain_index) + # assumes a Richards model, we assume constant normalised atmospheric pressure + # and no interface term is practically appearing. + # interface_forms += (df.Constant(0)*phi_a)*ds(interface) + # pass + interface_forms.append((0*pa*phi_a)*ds(marker_value)) + gli_forms.append((0*phi_a)*ds(marker_value)) + else: + # interface_forms += (dt*Lambda_a*pa*phi_a)*ds(interface) + interface_forms.append((dt*Lambda_a*pa*phi_a)*ds(marker_value)) + gli = self.calc_gli_term(interface_index=interface, phase=phase) + gli_forms.append((dt*gli*phi_a)*ds(marker_value)) + form2 = dt/mu_a*df.dot(ka(1 - S(pc_prev_iter))*df.grad(pa), df.grad(phi_a))*dx # the non wetting phase has a + before instead of a - before the bracket rhs2 = (porosity*(S(pc_prev_iter) - S(pc_prev_timestep))*phi_a)*dx if self.include_gravity: - # z_a included the minus sign already, see self._calc_gravity_expressions + # z_a included the minus sign, see self._calc_gravity_expressions rhs_gravity = dt/mu_a*df.dot(ka(1 - S(pc_prev_iter))*df.grad(z_a), df.grad(phi_a))*dx else: raise RuntimeError('missmatch for input parameter phase.', 'Expected either phase == wetting or phase == nonwetting ') return None - form = form1 + form2 + sum(interface_forms) - if self.include_gravity: - # z_a included the minus sign already, see self._calc_gravity_expressions - rhs = rhs1 + rhs2 + dt*source_a*phi_a*dx - sum(gli_forms) - rhs_gravity + if self.has_interface is not None: + form = form1 + form2 + sum(interface_forms) + if self.include_gravity: + # z_a included the minus sign already, see self._calc_gravity_expressions + rhs = rhs1 + rhs2 + dt*source_a*phi_a*dx - sum(gli_forms) + rhs_gravity + else: + rhs = rhs1 + rhs2 + dt*source_a*phi_a*dx - sum(gli_forms) else: - rhs = rhs1 + rhs2 + dt*source_a*phi_a*dx - sum(gli_forms) - + form = form1 + form2 + if self.include_gravity: + # z_a included the minus sign already, see self._calc_gravity_expressions + rhs = rhs1 + rhs2 + dt*source_a*phi_a*dx + rhs_gravity + else: + rhs = rhs1 + rhs2 + dt*source_a*phi_a*dx form_and_rhs = {# 'form': form,# 'rhs' : rhs @@ -455,52 +468,43 @@ class DomainPatch(df.SubDomain): # to calculate a gl0 term. # TODO: add intrinsic permeabilty!!!! if self.include_gravity: - # z_a included the minus sign already, see self._calc_gravity_expressions - flux = -1/mu_a*ka(S(pc_prev))*df.grad(pa_prev + z_a) + # z_a included the minus sign, see self._calc_gravity_expressions + flux = -1/mu_a*ka(S(pc_prev))*df.grad(pa_prev - z_a) else: flux = -1/mu_a*ka(S(pc_prev))*df.grad(pa_prev) else: # phase == 'nonwetting' # we need anothre flux in this case if self.include_gravity: - flux = -1/mu_a*ka(1-S(pc_prev))*df.grad(pa_prev + z_a) + flux = -1/mu_a*ka(1-S(pc_prev))*df.grad(pa_prev - z_a) else: flux = -1/mu_a*ka(1-S(pc_prev))*df.grad(pa_prev) flux_function = df.project(flux, self.function_space["flux"][phase]) flux_x, flux_y = flux_function.split() gl0 = df.Function(self.function_space["gli"][phase]) - + # # get dictionaries of global dof indices and coordinates along + # # the interface. These dictionaries have the facet indices of + # # facets belonging to the interface as keys (with respect to + # # the submesh numbering) and the dictionary + # # containing pairs {global_dof_index: dof_coordinate} for all + # # dofs along the facet as values. gli_dof_coordinates = self.interface_dof_indices_and_coordinates["gli"][interf_ind][phase] - p_dof_coordinates = self.interface_dof_indices_and_coordinates["pressure"][interf_ind][phase] - flux_dof_coordinates = self.interface_dof_indices_and_coordinates["flux"][interf_ind][phase] - + # p_dof_coordinates = self.interface_dof_indices_and_coordinates["pressure"][interf_ind][phase] + # flux_dof_coordinates = self.interface_dof_indices_and_coordinates["flux"][interf_ind][phase] + # # the attributes local and global for facet numbering mean + # # the following: local facet index is the index of the facet + # # with respect to the numbering of the submesh belonging to + # # the subdomain. global facet index refers to the facet numbering + # # of the mesh of the whole computational domain. local2global_facet = interface.local_to_global_facet_map[subdomain] + corresponding_dof_index = self.interface_corresponding_dof_index[interf_ind][phase] for local_facet_ind, normal in interface.outer_normals[subdomain].items(): gli_dofs_along_facet = gli_dof_coordinates[local_facet_ind] - p_dofs_along_facet = p_dof_coordinates[local_facet_ind] - flux_x_dofs_along_facet = flux_dof_coordinates["x"][local_facet_ind] - flux_y_dofs_along_facet = flux_dof_coordinates["y"][local_facet_ind] - - # for each gli dof with index gli_dof_ind, we need the - # dof indices for the flux and the pressure corresponding - # to the coordinates of the dof, in order to calculate gl0 - # dof wise. for gli_dof_index, gli_dof_coord in gli_dofs_along_facet.items(): - flux_x_dof_index = -1 - for flux_x_dof_ind, dof_coord in flux_x_dofs_along_facet.items(): - if np.array_equal(dof_coord, gli_dof_coord): - flux_x_dof_index = flux_x_dof_ind - - flux_y_dof_index = -1 - for flux_y_dof_ind, dof_coord in flux_y_dofs_along_facet.items(): - if np.array_equal(dof_coord, gli_dof_coord): - flux_y_dof_index = flux_y_dof_ind - - p_dof_index = -1 - for p_dof_ind, dof_coord in p_dofs_along_facet.items(): - if np.array_equal(dof_coord, gli_dof_coord): - p_dof_index = p_dof_ind + flux_x_dof_index = corresponding_dof_index[local_facet_ind][gli_dof_index]["flux_x"] + flux_y_dof_index = corresponding_dof_index[local_facet_ind][gli_dof_index]["flux_y"] + p_dof_index = corresponding_dof_index[local_facet_ind][gli_dof_index]["pressure"] gl0.vector()[gli_dof_index] = flux_x.vector()[flux_x_dof_index]*normal[0] \ + flux_y.vector()[flux_y_dof_index]*normal[1] \ @@ -587,11 +591,11 @@ class DomainPatch(df.SubDomain): "and iteration = " + "{} on subdomain".format(iteration)+ "{}".format(subdomain)\ + "You suck! Revisite DomainPatch.calc_gli_term() and LDDsimulation.LDDsolver ASAP.") - dt = self.timestep_size + # dt = self.timestep_size Lambda = self.lambda_param[phase] gli_interface_dofs = self.interface_dof_indices_and_coordinates["gli"][interface_index][phase] - p_interface_dofs = self.interface_dof_indices_and_coordinates["pressure"][interface_index][phase] + # p_interface_dofs = self.interface_dof_indices_and_coordinates["pressure"][interface_index][phase] local2global_facet = interface.local_to_global_facet_map[subdomain] for local_facet_index, gli_facet_dofs2coordinates in gli_interface_dofs.items(): global_facet_index = local2global_facet[local_facet_index] @@ -599,20 +603,42 @@ class DomainPatch(df.SubDomain): gli_prev = interface.gli_term_prev[neighbour][phase][global_facet_index] p_prev = pressure_prev[global_facet_index] gli_save_dict = gli_save_dictionary[global_facet_index] + # print(f"\non subdomain{self.subdomain_index}") + # print(f"on facet with glob_index: {global_facet_index} we have gli_dofs") + # for gli_dof_index, gli_dof_coord in gli_facet_dofs2coordinates.items(): # find the right previous pressure dof of the interface # dictionary to use for the calculation. - p_previous_dof = -9999999.9999 + # print(f"coordinates of gli_dof with index {gli_dof_index} I try to match is: {gli_dof_coord}" ) + p_previous_dof = None for p_dof_coord_tupel, p_prev_dof in p_prev.items(): p_coord = np.array([p_dof_coord_tupel[0], p_dof_coord_tupel[1]]) - if np.array_equal(gli_dof_coord, p_coord): + # print(f"coordinates of p_dof I test this against: {p_coord}") + a_close_b = np.allclose(gli_dof_coord, p_coord, rtol=1e-10, atol=1e-14) + b_close_a = np.allclose(p_coord, gli_dof_coord, rtol=1e-10, atol=1e-14) + # print(f"type(gli_dof_coord) = {type(gli_dof_coord)}") + # print(f"np.allclose({gli_dof_coord}, {p_coord}, rtol=1e-10, atol=1e-14): {a_close_b }") + # print(f"np.allclose({p_coord}, {gli_dof_coord}, rtol=1e-10, atol=1e-14): {b_close_a}") + if a_close_b and b_close_a: p_previous_dof = p_prev_dof + print("\n") + if p_previous_dof is None: + raise RuntimeError(f"didn't find p_previous_dof corresponding to dict key ({gli_dof_coord})", + "something is wrong") # same with the gli_previous_dof - gli_previous_dof = -9999999.9999 + gli_previous_dof = None for gli_prev_coord_tupel, gli_prev_dof in gli_prev.items(): gli_prev_coord = np.array([gli_prev_coord_tupel[0], gli_prev_coord_tupel[1]]) - if np.array_equal(gli_dof_coord, gli_prev_coord): + # due to the warning in the documentation of np.allclose + # that np.allclose is not symetric, we test if both + # np.allclose(a,b) and np.allclose(b,a) are true. + a_close_b = np.allclose(gli_dof_coord, gli_prev_coord, rtol=1e-10, atol=1e-14) + b_close_a = np.allclose(gli_prev_coord, gli_dof_coord, rtol=1e-10, atol=1e-14) + if a_close_b and b_close_a: gli_previous_dof = gli_prev_dof + if gli_previous_dof is None: + raise RuntimeError(f"didn't find gli_previous_dof corresponding to dict key ({gli_dof_coord})", + "something is wrong") gli_value = -2*Lambda*p_previous_dof - gli_previous_dof gli.vector()[gli_dof_index] = gli_value @@ -641,8 +667,11 @@ class DomainPatch(df.SubDomain): of the subdomain. """ subdomain = self.subdomain_index - for index in self.has_interface: - self.interface[index].current_iteration[subdomain] = 0 + if self.has_interface is not None: + for index in self.has_interface: + self.interface[index].current_iteration[subdomain] = 0 + else: + pass #### PRIVATE METHODS def _init_function_space(self) -> None: @@ -681,13 +710,13 @@ class DomainPatch(df.SubDomain): """ calculate dof maps and the vertex to parent map. This method sets the class Variables - self.parent_mesh_index self.dof2vertex self.vertex2dof """ mesh_data = self.mesh.data() - # local to parent vertex index map - self.parent_mesh_index = mesh_data.array('parent_vertex_indices',0) + # # local to parent vertex index map + # if self.has_interface is not None: + # self.parent_mesh_index = mesh_data.array('parent_vertex_indices',0) # print(f"on subdomain{self.subdomain_index} parent_vertex_indices: {self.parent_mesh_index}") self.dof2vertex = dict() self.vertex2dof = dict() @@ -720,6 +749,7 @@ class DomainPatch(df.SubDomain): {"y": self.function_space["flux"][phase].sub(1).dofmap()} ) + def _init_markers(self) -> None: """ define boundary markers @@ -732,17 +762,18 @@ class DomainPatch(df.SubDomain): self.outer_boundary_marker.set_all(0) self.interface_marker = df.MeshFunction('size_t', self.mesh, self.mesh.topology().dim()-1) self.interface_marker.set_all(0) - for glob_index in self.has_interface: - # the marker value is set to the same as the global marker value, - # stored in self.interfaces[glob_index].marker_value. - marker_value = self.interface[glob_index].marker_value - # each interface gets marked with the global interface index. - self.interface[glob_index].mark(self.interface_marker, marker_value) - # init facet_to_vertex_coordinates_dictionray for subdoaain mesh - self.interface[glob_index].init_facet_dictionaries( - interface_marker=self.interface_marker, - interface_marker_value=marker_value, - subdomain_index=self.subdomain_index) + if self.has_interface is not None: + for glob_index in self.has_interface: + # the marker value is set to the same as the global marker value, + # stored in self.interfaces[glob_index].marker_value. + marker_value = self.interface[glob_index].marker_value + # each interface gets marked with the global interface index. + self.interface[glob_index].mark(self.interface_marker, marker_value) + # init facet_to_vertex_coordinates_dictionray for subdoaain mesh + self.interface[glob_index].init_facet_dictionaries( + interface_marker=self.interface_marker, + interface_marker_value=marker_value, + subdomain_index=self.subdomain_index) # create outerBoundary objects and mark outer boundaries with 1 # in case there is no outer boundary, self.outer_boundary_def_points is # None. @@ -766,6 +797,7 @@ class DomainPatch(df.SubDomain): self.outer_boundary_marker, boundary_marker_value ) + def _init_measures(self): """ define measures for the governing form @@ -814,6 +846,91 @@ class DomainPatch(df.SubDomain): ) + def _calc_corresponding_dof_indices(self): + """ calculate dictionary which for each interface and each phase holds + for each facet index, the dof indices of the pressures and flux components + corresponding to a given dof index of the gli function. + This gets used by self.calc_gl0_term + """ + self.interface_corresponding_dof_index = dict() + subdomain = self.subdomain_index + for interf_ind in self.has_interface: + interface = self.interface[interf_ind] + self.interface_corresponding_dof_index.update( + {interf_ind: dict()} + ) + for phase in self.has_phases: + self.interface_corresponding_dof_index[interf_ind].update( + {phase: dict()} + ) + # get dictionaries of global dof indices and coordinates along + # the interface. These dictionaries have the facet indices of + # facets belonging to the interface as keys (with respect to + # the submesh numbering) and the dictionary + # containing pairs {global_dof_index: dof_coordinate} for all + # dofs along the facet as values. + gli_dof_coordinates = self.interface_dof_indices_and_coordinates["gli"][interf_ind][phase] + p_dof_coordinates = self.interface_dof_indices_and_coordinates["pressure"][interf_ind][phase] + flux_dof_coordinates = self.interface_dof_indices_and_coordinates["flux"][interf_ind][phase] + # the attributes local and global for facet numbering mean + # the following: local facet index is the index of the facet + # with respect to the numbering of the submesh belonging to + # the subdomain. global facet index refers to the facet numbering + # of the mesh of the whole computational domain. + # local2global_facet = interface.local_to_global_facet_map[subdomain] + for local_facet_ind in interface.outer_normals[subdomain].keys(): + self.interface_corresponding_dof_index[interf_ind][phase].update( + {local_facet_ind: dict()} + ) + gli_dofs_along_facet = gli_dof_coordinates[local_facet_ind] + p_dofs_along_facet = p_dof_coordinates[local_facet_ind] + flux_x_dofs_along_facet = flux_dof_coordinates["x"][local_facet_ind] + flux_y_dofs_along_facet = flux_dof_coordinates["y"][local_facet_ind] + + # for each gli dof with index gli_dof_ind, we need the + # dof indices for the flux and the pressure corresponding + # to the coordinates of the dof, in order to calculate gl0 + # dof wise. + for gli_dof_index, gli_dof_coord in gli_dofs_along_facet.items(): + self.interface_corresponding_dof_index[interf_ind][phase][local_facet_ind].update( + {gli_dof_index: {"flux_x": None, + "flux_x": None, + "pressure": None}} + ) + for flux_x_dof_ind, dof_coord in flux_x_dofs_along_facet.items(): + a_close_b = np.allclose(gli_dof_coord, dof_coord, rtol=1e-10, atol=1e-14) + b_close_a = np.allclose(dof_coord, gli_dof_coord, rtol=1e-10, atol=1e-14) + if a_close_b and b_close_a: + self.interface_corresponding_dof_index[interf_ind][phase][local_facet_ind][gli_dof_index].update( + {"flux_x": flux_x_dof_ind} + ) + if self.interface_corresponding_dof_index[interf_ind][phase][local_facet_ind][gli_dof_index]["flux_x"] is None: + raise RuntimeError(f"didn't find flux_x_dof_ind corresponding to dict key ({gli_dof_coord})", + "something is wrong") + + for flux_y_dof_ind, dof_coord in flux_y_dofs_along_facet.items(): + a_close_b = np.allclose(gli_dof_coord, dof_coord, rtol=1e-10, atol=1e-14) + b_close_a = np.allclose(dof_coord, gli_dof_coord, rtol=1e-10, atol=1e-14) + if a_close_b and b_close_a: + self.interface_corresponding_dof_index[interf_ind][phase][local_facet_ind][gli_dof_index].update( + {"flux_y": flux_y_dof_ind} + ) + if self.interface_corresponding_dof_index[interf_ind][phase][local_facet_ind][gli_dof_index]["flux_y"] is None: + raise RuntimeError(f"didn't find flux_y_dof_ind corresponding to dict key ({gli_dof_coord})", + "something is wrong") + + for p_dof_ind, dof_coord in p_dofs_along_facet.items(): + a_close_b = np.allclose(gli_dof_coord, dof_coord, rtol=1e-10, atol=1e-14) + b_close_a = np.allclose(dof_coord, gli_dof_coord, rtol=1e-10, atol=1e-14) + if a_close_b and b_close_a: + self.interface_corresponding_dof_index[interf_ind][phase][local_facet_ind][gli_dof_index].update( + {"pressure": p_dof_ind} + ) + if self.interface_corresponding_dof_index[interf_ind][phase][local_facet_ind][gli_dof_index]["pressure"] is None: + raise RuntimeError(f"didn't find p_dof_ind corresponding to dict key ({gli_dof_coord})", + "something is wrong") + + def _init_interface_values(self): """ allocate the dictionaries for each interface used to store the interface values for communication. @@ -860,17 +977,24 @@ class DomainPatch(df.SubDomain): write_iter_for_fixed_time bool flag to toggle wether pairs of (solution, iteration) for fixed t should be written instead of (solution, t) - subsequent_errors df.Funtion FEM Function passed by the solver - containing self.pressure - self.pressure_prev_iter """ t = time - for phase in self.has_phases: - if not write_iter_for_fixed_time: + if not write_iter_for_fixed_time: + for phase in self.has_phases: self.pressure[phase].rename("pressure_"+"{}".format(phase), "pressure_"+"{}".format(phase)) file.write(self.pressure[phase], t) + capillary_pressure = df.Function(self.function_space["pressure"]["wetting"]) + if self.isRichards: + capillary_pressure.assign(-self.pressure["wetting"]) else: + pc_temp = self.pressure["nonwetting"].vector()[:] - self.pressure["wetting"].vector()[:] + capillary_pressure.vector()[:] = pc_temp + capillary_pressure.rename("pc_num", "pc_num") + file.write(capillary_pressure, t) + else: + for phase in self.has_phases: i = self.iteration_number self.pressure[phase].rename("pressure_"+"{}".format(phase), # "pressure(t="+"{}".format(t)+")_"+"{}".format(phase) @@ -883,9 +1007,5 @@ class DomainPatch(df.SubDomain): "pressure(t="+"{}".format(t)+")_"+"{}".format(phase) ) file.write(error, i) - # self.gli_function[phase].vector().set_local(self.gli_assembled[phase]) - # self.gli_function[phase].rename("gli_function_"+"{}".format(phase),# - # "all_gli_terms(t="+"{}".format(t)+")_"+"{}".format(phase)) - # file.write(self.gli_function[phase], i) # END OF CLASS DomainPatch diff --git a/LDDsimulation/helpers.py b/LDDsimulation/helpers.py index 95c6168..765630c 100644 --- a/LDDsimulation/helpers.py +++ b/LDDsimulation/helpers.py @@ -8,6 +8,9 @@ import sys import matplotlib import matplotlib.pyplot as plt import numpy as np +import typing as tp +import sympy as sym + # from termcolor import colored # from colormap import Colormap #import fenicstools as ft @@ -18,174 +21,112 @@ def print_once(*args,**kwargs): print(*args,**kwargs) sys.stdout.flush() -# def fit_IC(mesh,u): -# """ -# Fit given data u on mesh to certain functions. -# Useful in the case of a single droplet. -# Returns the fitted data as class for IC. -# """ -# -# # get mesh coordiantes -# xx = mesh.coordinates()[:,0] -# yy = mesh.coordinates()[:,1] -# nx = np.size(xx) -# ny = np.size(yy) -# -# # get function values -# rho_data = np.zeros(nx) -# phase_field_data = np.zeros(nx) -# mu_data = np.zeros(nx) -# -# for i in range(0,nx): -# rho_data[i] = u.sub(0)(xx[i],yy[i]) -# mu_data[i] = u.sub(4)(xx[i],yy[i]) -# phase_field_data[i] = u.sub(2)(xx[i],yy[i]) -# -# # define functions to fit -# def rho(x,a,b,cx,cy,dx,dy): -# val = a*np.tanh(b*np.sqrt((x[0]-cx)**2+(x[1]-cy)**2)+dx) + dy -# return val -# -# def phase_field(x,a,b,cx,cy,dx,dy): -# val = a*np.tanh(b*np.sqrt((x[0]-cx)**2+(x[1]-cy)**2)+dx) + dy -# return val -# -# def mu(x,a,b,cx,cy,dx,dy): -# val = a*np.tanh(b*np.sqrt((x[0]-cx)**2+(x[1]-cy)**2)+dx) + dy -# return val -# -# xx1 = np.concatenate((xx[:,np.newaxis],yy[:,np.newaxis]),axis= 1) -# # fit data to function -# popt_rho = curve_fit(rho, xx1.T , rho_data, p0=[1./np.pi,-10,.5,.3,1.,.5]) -# [a,b,cx,cy,dx,dy] = popt_rho[0] -# # print(a,b,cx,cy,dx,dy) -# -# popt_phase_field = curve_fit(phase_field, xx1.T , phase_field_data, p0=[1./np.pi,-10,.5,.3,1.,.5]) -# [a1,b1,cx1,cy1,dx1,dy1] = popt_phase_field[0] -# # print(a1,b1,cx1,cy1,dx1,dy1) -# -# popt_mu = curve_fit(mu, xx1.T , mu_data, p0=[1./np.pi,-10,.5,.3,1.,.5]) -# [a2,b2,cx2,cy2,dx2,dy2] = popt_mu[0] -# # print(a2,b2,cx2,cy2,dx2,dy2) -# -# # plot result -# # res = 500; -# # [X,Y] = np.meshgrid(np.linspace(0.,1.,res),np.linspace(0.,1.,res)) -# # Z = np.zeros((res,res)) -# # for i in range(0,res): -# # for j in range(0,res): -# # Z[i,j] = phase_field([X[i,j],Y[i,j]],a1,b1,cx1,cy1,d1) -# # plt.imshow(Z,cmap = "viridis", origin ="lower") -# # plt.colorbar() -# # plt.show() -# -# class fitted_IC(Expression): -# def eval(self, values, x): -# # rho -# values[0] = rho(x,a,b,cx,cy,dx,dy) -# -# # velocity -# for i in range(2): -# values[i + 1] = 0. -# # c -# values[2 + 1] = phase_field(x,a1,b1,cx1,cy1,dx1,dy1) -# # tau -# values[2 + 2] = 0.0 -# # mu -# values[2 + 3] = mu(x,a2,b2,cx2,cy2,dx2,dy2) -# # sigma -# for i in range(2): -# values[2 + 4 + i] = 0. -# -# def value_shape(self): -# return (4 + 2*2,) -# -# return fitted_IC -# -# -# def tensor_jump(v, n): -# """ -# DG jump operator for vector valued functions -# """ -# return df.outer(v("+"), n("+")) + df.outer(v("-"), n("-")) -# -# def print_constants(self,file=None): -# """ -# Print the names and values of all df.Constant attributes. -# """ -# for key in self.__dict__.keys(): -# attribute = self.__dict__[key] -# if type(attribute) is type(df.Constant(0.)): -# print_once(attribute.name() + " = " + str(attribute.values()),file=file) -# for key in self.__dict__["_EOS"].__dict__: -# attribute = self.__dict__["_EOS"].__dict__[key] -# if type(attribute) is type(df.Constant(0.)): -# print_once(attribute.name() + " = " + str(attribute.values()),file=file) -# -# def get_interface_thickness(solution,ME,xmin=-.5,xmax=1.5,ymin=0.,ymax=1.,ny=1000): -# """ -# Compute interface thickness from simulation result. -# The interface thickness is here defined as the region where 0.1 < c < 0.9. -# Parameter: solution, function space ME -# Meshrelated: xmin,xmax,ymin,ymax -# Number of generated samples: ny -# """ -# xmid = .5*(xmin+xmax) -# yy = np.linspace(ymin,ymax,ny) -# xx = xmid*np.ones_like(yy) -# # evaluate phasefield along following points -# zz = np.column_stack((xx,yy)) -# p = ft.Probes(zz.flatten(), ME.sub(2).collapse()) -# p(solution.sub(2)) -# # values collected on root process -# values= p.array(root=0) -# if df.MPI.rank(df.MPI.comm_world) == 0: -# try: -# # get interface_start -# interface_start = np.min(np.where(abs(values-0.1)<5.e-3)) -# # get interface_end -# interface_end = np.min(np.where(abs(values-0.9)<5.e-3)) -# # compute interface_thickness -# interface_thickness = abs(yy[interface_end]-yy[interface_start]) -# return interface_thickness -# except: -# print_once(colored("Phasefield values out of range.","red")) -# return -1 -# -# -# def plot_solution(u): -# """ Plot solution for poster or the like...""" -# # Define "CD"-Colormap -# red = [0.12549019607843137, 0.3843137254901961, 0.6431372549019608, 0.9019607843137255] -# blue = [0.6823529411764706, 0.788235294117647, 0.8941176470588236, 1.0] -# green = [0.33725490196078434, 0.5254901960784314, 0.7137254901960784, 0.9019607843137255] -# c = Colormap() -# mycmap = c.cmap( {'red':red[::-1], 'green':green[::-1], 'blue':blue[::-1]}) -# matplotlib.cm.register_cmap(name='mycmap', cmap=mycmap) -# # plt.rcParams['image.cmap'] = 'mycmap' -# plt.rcParams['image.cmap'] = 'RdYlBu' -# matplotlib.rcParams.update({'font.size': 17}) -# matplotlib.rc('font',**{'family':'sans-serif','sans-serif':['Helvetica']}) -# matplotlib.rc('text', usetex=True) -# -# cnt = plot(u,mode="contourf",levels=256,antialiased = True,linestyles=None) -# # fixes to remove white contour lines -# cnt = plot(u,mode="contourf",levels=256,antialiased = True,linestyles=None) -# cnt = plot(u,mode="contourf",levels=256,antialiased = True,linestyles=None) -# -# for c in cnt.collections: -# c.set_edgecolor("face") -# -# plt.xlim(0.45, 0.8) -# plt.ylim(0.2, 0.55) -# plt.axis("off") -# plt.annotate("$+$ phase",color='w',xy=(0.5, .45)) -# plt.annotate("$-$ phase",xy=(0.7, .3)) -# plt.annotate("$\Omega$",xy=(0.75, .5),fontsize=25) -# plt.annotate("$\gamma$",xy=(0.647, .358),fontsize=25) -# plt.annotate("",xy=(0.62,0.375),xytext=(0.66,0.33),arrowprops=dict(arrowstyle="<->", -# connectionstyle="arc3",facecolor='black',linewidth=1)) -# #plt.show() -# plt.tight_layout() -# plt.savefig('diffuse_simulation.pdf',bbox_inches='tight') -# return 0 + +def generate_exact_solution_expressions( + symbols: tp.Dict[int, int], + isRichards: tp.Dict[int, bool], + symbolic_pressure: tp.Dict[int, tp.Dict[str, str]], + symbolic_capillary_pressure: tp.Dict[int, str], + viscosity: tp.Dict[int, tp.List[float]],# + porosity: tp.Dict[int, tp.Dict[str, float]],# + relative_permeability: tp.Dict[int, tp.Dict[str, tp.Callable[...,None]] ],# + relative_permeability_prime: tp.Dict[int, tp.Dict[str, tp.Callable[...,None]] ], + saturation_pressure_relationship: tp.Dict[int, tp.Callable[...,None]] = None,# + saturation_pressure_relationship_prime: tp.Dict[int, tp.Callable[...,None]] = None,# + symbolic_S_pc_relationship: tp.Dict[int, tp.Callable[...,None]] = None,# + symbolic_S_pc_relationship_prime: tp.Dict[int, tp.Callable[...,None]] = None,# + densities: tp.Dict[int, tp.Dict[str, float]] = None,# + gravity_acceleration: float = 9.81, + include_gravity: bool = False, + ) -> tp.Dict[str, tp.Dict[int, tp.Dict[str, str]] ]: + """ Generate exact solution, source and initial condition code from symbolic expressions""" + + output = { + 'source': dict(), + 'initial_condition': dict(), + 'exact_solution': dict() + } + x = symbols["x"] + y = symbols["y"] + t = symbols["t"] + # turn above symbolic code into exact solution for dolphin and + # construct the rhs that matches the above exact solution. + dtS = dict() + div_flux = dict() + if saturation_pressure_relationship is not None: + S_pc_sym = saturation_pressure_relationship + S_pc_sym_prime = saturation_pressure_relationship_prime + for subdomain, isR in isRichards.items(): + dtS.update({subdomain: dict()}) + div_flux.update({subdomain: dict()}) + output['source'].update({subdomain: dict()}) + output['exact_solution'].update({subdomain: dict()}) + output['initial_condition'].update({subdomain: dict()}) + if isR: + subdomain_has_phases = ["wetting"] + else: + subdomain_has_phases = ["wetting", "nonwetting"] + + # conditional for S_pc_prime + pc = symbolic_capillary_pressure[subdomain] + dtpc = sym.diff(pc, t, 1) + dxpc = sym.diff(pc, x, 1) + dypc = sym.diff(pc, y, 1) + if saturation_pressure_relationship is not None: + S = sym.Piecewise((S_pc_sym[subdomain](pc), pc > 0), (1, True)) + dS = sym.Piecewise((S_pc_sym_prime[subdomain](pc), pc > 0), (0, True)) + else: + S = symbolic_S_pc_relationship[subdomain] + dS = symbolic_S_pc_relationship_prime[subdomain] + for phase in subdomain_has_phases: + # Turn above symbolic expression for exact solution into c code + output['exact_solution'][subdomain].update( + {phase: sym.printing.ccode(symbolic_pressure[subdomain][phase])} + ) + # save the c code for initial conditions + output['initial_condition'][subdomain].update( + {phase: sym.printing.ccode(symbolic_pressure[subdomain][phase].subs(t, 0))} + ) + if phase == "nonwetting": + dtS[subdomain].update( + {phase: -porosity[subdomain]*dS*dtpc} + ) + else: + dtS[subdomain].update( + {phase: porosity[subdomain]*dS*dtpc} + ) + pa = symbolic_pressure[subdomain][phase] + dxpa = sym.diff(pa, x, 1) + dxdxpa = sym.diff(pa, x, 2) + dypa = sym.diff(pa, y, 1) + dydypa = sym.diff(pa, y, 2) + mu = viscosity[subdomain][phase] + ka = relative_permeability[subdomain][phase] + dka = relative_permeability_prime[subdomain][phase] + rho = densities[subdomain][phase] + g = gravity_acceleration + + if phase == "nonwetting": + # x part of div(flux) for nonwetting + dxdxflux = -1/mu*dka(1-S)*dS*dxpc*dxpa + 1/mu*dxdxpa*ka(1-S) + # y part of div(flux) for nonwetting + if include_gravity: + dydyflux = -1/mu*dka(1-S)*dS*dypc*(dypa + rho*g) + 1/mu*dydypa*ka(1-S) + else: + dydyflux = -1/mu*dka(1-S)*dS*dypc*dypa + 1/mu*dydypa*ka(1-S) + else: + # x part of div(flux) for wetting + dxdxflux = 1/mu*dka(S)*dS*dxpc*dxpa + 1/mu*dxdxpa*ka(S) + # y part of div(flux) for wetting + if include_gravity: + dydyflux = 1/mu*dka(S)*dS*dypc*(dypa + rho*g) + 1/mu*dydypa*ka(S) + else: + dydyflux = 1/mu*dka(S)*dS*dypc*(dypa) + 1/mu*dydypa*ka(S) + + div_flux[subdomain].update({phase: dxdxflux + dydyflux}) + contructed_rhs = dtS[subdomain][phase] - div_flux[subdomain][phase] + output['source'][subdomain].update( + {phase: sym.printing.ccode(contructed_rhs)} + ) + + return output diff --git a/LDDsimulation/solutionFile.py b/LDDsimulation/solutionFile.py index 7ae0a95..e71450f 100644 --- a/LDDsimulation/solutionFile.py +++ b/LDDsimulation/solutionFile.py @@ -9,5 +9,5 @@ class SolutionFile(df.XDMFFile): df.XDMFFile.__init__(self, mpicomm, filepath) self.parameters["functions_share_mesh"] = True - self.parameters["flush_output"] = False + self.parameters["flush_output"] = True self.path = filepath # Mimic the file path attribute from a `file` returned by `open` -- GitLab