diff --git a/LDDsimulation/LDDsimulation.py b/LDDsimulation/LDDsimulation.py index 33901e69e77ee5f0b8abf806063a4c298a5a9c3c..87fbf67557192ac4b43bb93760cb9b0ad78a9d9d 100644 --- a/LDDsimulation/LDDsimulation.py +++ b/LDDsimulation/LDDsimulation.py @@ -103,7 +103,7 @@ class LDDsimulation(object): ## Private variables # maximal number of L-iterations that the LDD solver uses. - self._max_iter_num = 1000 + self._max_iter_num = 2000 # 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 diff --git a/LDDsimulation/boundary_and_interface.py b/LDDsimulation/boundary_and_interface.py index 48f6ea7f6127059fd081c287fc8122a7fd6b1129..c8bca3c925f8f6bfd9a496b26f3977f0f7486ca4 100644 --- a/LDDsimulation/boundary_and_interface.py +++ b/LDDsimulation/boundary_and_interface.py @@ -604,24 +604,23 @@ class Interface(BoundaryPart): # of edges on the boundary. We need the number of nodes, however. number_of_interface_vertices = sum(interface_marker.array() == interface_marker_value) + 1 - print(f"interface marker array",interface_marker.array() == interface_marker_value) - print(f"facets marked by interface marker", interface_marker.array()) - print(f"interface{self.global_index} has coordinates {self.coordinates(interface_marker, interface_marker_value)}") - # for cell in interface_marker[interface_marker.array() == interface_marker_value]: - # print(cell.get_cell_data()) + # print(f"interface marker array",interface_marker.array() == interface_marker_value) + # print(f"facets marked by interface marker", interface_marker.array()) + # print(f"interface{self.global_index} has coordinates {self.coordinates(interface_marker, interface_marker_value)}") + # print(f"\nDetermined number of interface vertices as {number_of_interface_vertices}") # we need one mesh_dimension + 1 columns to store the the index of the node. vertex_indices = np.zeros(shape = number_of_interface_vertices, dtype=int) # print(f"allocated array for vertex_indices\n", vertex_indices) # interface_vertex_number = 0 # mesh_vertex_index = 0 - print(f"\n we are one interface{self.global_index}", - f" we determined {number_of_interface_vertices} interface vertices.") + # print(f"\n we are one interface{self.global_index}", + # f" we determined {number_of_interface_vertices} interface vertices.") for vert_num, x in enumerate(mesh_coordinates): if self._is_on_boundary_part(x): # print(f"Vertex {x} with index {vert_num} is on interface") # print(f"interface_vertex_number = {interface_vertex_number}") - print(f"dfPoint = ({x}) is on interface{self.global_index}") + # print(f"dfPoint = ({x}) is on interface{self.global_index}") vertex_indices[interface_vertex_number] = vert_num interface_vertex_number += 1 diff --git a/LDDsimulation/domainPatch.py b/LDDsimulation/domainPatch.py index 5bc83173312fabc14e89d3208c218d9424408d28..d82a5ee46e29ed035977a18ac695ba0f182f3828 100644 --- a/LDDsimulation/domainPatch.py +++ b/LDDsimulation/domainPatch.py @@ -251,9 +251,15 @@ class DomainPatch(df.SubDomain): interface_string.append(common_int_dof_str) interface_string = " ".join(interface_string) + + if self.outer_boundary is None: + outer_boundary_str = "no outer boundaries" + else: + 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 "\ - +f"outer boundaries: {[ind for ind in self.outer_boundary_def_points.keys()]}.\n"\ + + outer_boundary_str\ + interface_string return string @@ -408,9 +414,9 @@ class DomainPatch(df.SubDomain): for interf_ind in self.has_interface: interface = self.interface[interf_ind] - # neighbour index - neighbour = interface.neighbour[subdomain] - neighbour_iter_num = interface.current_iteration[neighbour] + # # neighbour index + # neighbour = interface.neighbour[subdomain] + # neighbour_iter_num = interface.current_iteration[neighbour] ds = self.ds(interface.marker_value) # needed for the read_gli_dofs() functions interface_dofs = self._dof_indices_of_interface[interf_ind] @@ -420,6 +426,7 @@ class DomainPatch(df.SubDomain): # two different interfaces. dofs_in_common_with_other_interfaces = self._interface_has_common_dof_indices[interf_ind] if debug: + neighbour = interface.neighbour[subdomain] print(f"On subdomain{subdomain}, we have:\n", f"Interface{interf_ind}, Neighbour index = {neighbour}\n", f"dofs on interface:\n{interface_dofs['wetting']}\n", @@ -508,9 +515,7 @@ class DomainPatch(df.SubDomain): for phase in self.has_phases: for interf_ind in self.has_interface: interface = self.interface[interf_ind] - neighbour = interface.neighbour[subdomain] - neighbour_iter_num = interface.current_iteration[neighbour] - # needed for the read_gli_dofs() functions + # # needed for the read_gli_dofs() functions interface_dofs = self._dof_indices_of_interface[interf_ind] if debug: print(f"Interface{interf_ind}.gli_term_prev[{subdomain}][{phase}]=\n",interface.gli_term_prev[subdomain][phase]) diff --git a/RR-2-patch-symmetric-analytic-soltion/RR-2-patch-symmetric.py b/RR-2-patch-symmetric-analytic-soltion/RR-2-patch-symmetric.py new file mode 100755 index 0000000000000000000000000000000000000000..4edff0ffde6437266823b756c08da12ea49a1e20 --- /dev/null +++ b/RR-2-patch-symmetric-analytic-soltion/RR-2-patch-symmetric.py @@ -0,0 +1,233 @@ +#!/usr/bin/python3 +import dolfin as df +import mshr +import numpy as np +import sympy as sym +import typing as tp +import domainPatch as dp +import LDDsimulation as ldd +import functools as ft +#import ufl as ufl + +##### Domain and Interface #### +# global simulation domain domain +sub_domain0_vertices = [df.Point(0.0,0.0), # + df.Point(1.0,0.0),# + df.Point(1.0,1.0),# + df.Point(0.0,1.0)] +# interface between subdomain1 and subdomain2 +interface12_vertices = [df.Point(0.0, 0.5), + df.Point(1.0, 0.5) ] +# subdomain1. +sub_domain1_vertices = [interface12_vertices[0], + interface12_vertices[1], + df.Point(1.0,1.0), + df.Point(0.0,1.0) ] + +# vertex coordinates of the outer boundaries. If it can not be specified as a +# polygon, use an entry per boundary polygon. This information is used for defining +# the Dirichlet boundary conditions. If a domain is completely internal, the +# dictionary entry should be 0: None +subdomain1_outer_boundary_verts = { + 0: [interface12_vertices[0], # + df.Point(0.0,1.0), # + df.Point(1.0,1.0), # + interface12_vertices[1]] +} +# subdomain2 +sub_domain2_vertices = [df.Point(0.0,0.0), + df.Point(1.0,0.0), + interface12_vertices[1], + interface12_vertices[0] ] + +subdomain2_outer_boundary_verts = { + 0: [interface12_vertices[1], # + df.Point(1.0,0.0), # + df.Point(0.0,0.0), # + interface12_vertices[0]] +} +# subdomain2_outer_boundary_verts = { +# 0: [interface12_vertices[0], df.Point(0.0,0.0)],# +# 1: [df.Point(0.0,0.0), df.Point(1.0,0.0)], # +# 2: [df.Point(1.0,0.0), interface12_vertices[1]] +# } +# subdomain2_outer_boundary_verts = { +# 0: None +# } + +# list of subdomains given by the boundary polygon vertices. +# Subdomains are given as a list of dolfin points forming +# a closed polygon, such that mshr.Polygon(subdomain_def_points[i]) can be used +# to create the subdomain. subdomain_def_points[0] contains the +# vertices of the global simulation domain and subdomain_def_points[i] contains the +# vertices of the subdomain i. +subdomain_def_points = [sub_domain0_vertices,# + sub_domain1_vertices,# + sub_domain2_vertices] +# in the below list, index 0 corresponds to the 12 interface which has index 1 +interface_def_points = [interface12_vertices] + +# if a subdomain has no outer boundary write None instead, i.e. +# i: None +# if i is the index of the inner subdomain. +outer_boundary_def_points = { + # subdomain number + 1 : subdomain1_outer_boundary_verts, + 2 : subdomain2_outer_boundary_verts +} + +# adjacent_subdomains[i] contains the indices of the subdomains sharing the +# interface i (i.e. given by interface_def_points[i]). +adjacent_subdomains = [[1,2]] +isRichards = { + 1: True, # + 2: True + } + +##################### MESH ##################################################### +mesh_resolution = 11 +##################### TIME ##################################################### +timestep_size = 4*0.0001 +number_of_timesteps = 3000 +# decide how many timesteps you want analysed. Analysed means, that we write out +# subsequent errors of the L-iteration within the timestep. +number_of_timesteps_to_analyse = 11 +starttime = 0 + + +viscosity = {# +# subdom_num : viscosity + 1 : {'wetting' :1}, # + 2 : {'wetting' :1} +} + +porosity = {# +# subdom_num : porosity + 1 : 1,# + 2 : 1 +} + +L = {# +# subdom_num : subdomain L for L-scheme + 1 : {'wetting' :0.25},# + 2 : {'wetting' :0.25} +} + +lambda_param = {# +# subdom_num : lambda parameter for the L-scheme + 1 : {'wetting' :40},# + 2 : {'wetting' :40} +} + +## relative permeabilty functions on subdomain 1 +def rel_perm1(s): + # relative permeabilty on subdomain1 + return s**2 + +_rel_perm1 = ft.partial(rel_perm1) + +subdomain1_rel_perm = { + 'wetting': _rel_perm1,# + 'nonwetting': None +} +## relative permeabilty functions on subdomain 2 +def rel_perm2(s): + # relative permeabilty on subdomain2 + return s**3 +_rel_perm2 = ft.partial(rel_perm2) + +subdomain2_rel_perm = { + 'wetting': _rel_perm2,# + 'nonwetting': None +} + +## dictionary of relative permeabilties on all domains. +relative_permeability = {# + 1: subdomain1_rel_perm, + 2: subdomain2_rel_perm +} + +# this function needs to be monotonically decreasing in the capillary_pressure. +# since in the richards case pc = -pw, this becomes as a function of pw a monotonically +# INCREASING function like in our Richards-Richards paper. However, since we unify +# the treatment in the code for Richards and two-phase, we need the same requierment +# for both cases, two-phase and Richards. +def saturation(capillary_pressure, subdomain_index): + # inverse capillary pressure-saturation-relationship + return df.conditional(capillary_pressure > 0, 1/((1 + capillary_pressure)**(1/(subdomain_index + 1))), 1) + +sat_pressure_relationship = {# + 1: ft.partial(saturation, subdomain_index = 1),# + 2: ft.partial(saturation, subdomain_index = 2) +} + +source_expression = { + 1: {'wetting': '4.0/pow(1 + x[0]*x[0] + (x[1]-0.5)*(x[1]-0.5), 2) - t/sqrt( pow(1 + t*t, 3)*(1 + x[0]*x[0] + (x[1]-0.5)*(x[1]-0.5)) )'}, + 2: {'wetting': '2.0*(1-x[0]*x[0])/pow(1 + x[0]*x[0], 2) - 2*t/(3*pow( pow(1 + t*t, 4)*(1 + x[0]*x[0]), 1/3))'} +} + +initial_condition = { + 1: {'wetting': '-(x[0]*x[0] + (x[1]-0.5)*(x[1]-0.5))'},# + 2: {'wetting': '-x[0]*x[0]'} +} + +exact_solution = { + 1: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0] + (x[1]-0.5)*(x[1]-0.5))'},# + 2: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0])'} +} + +# similary to the outer boundary dictionary, if a patch has no outer boundary +# None should be written instead of an expression. This is a bit of a brainfuck: +# dirichletBC[ind] gives a dictionary of the outer boundaries of subdomain ind. +# Since a domain patch can have several disjoint outer boundary parts, the expressions +# need to get an enumaration index which starts at 0. So dirichletBC[ind][j] is +# the dictionary of outer dirichlet conditions of subdomain ind and boundary part j. +# finally, dirichletBC[ind][j]['wetting'] and dirichletBC[ind][j]['nonwetting'] return +# the actual expression needed for the dirichlet condition for both phases if present. +dirichletBC = { +#subdomain index: {outer boudary part index: {phase: expression}} + 1: { 0: {'wetting': exact_solution[1]['wetting']}}, + 2: { 0: {'wetting': exact_solution[2]['wetting']}} +} + +# def saturation(pressure, subdomain_index): +# # inverse capillary pressure-saturation-relationship +# return df.conditional(pressure < 0, 1/((1 - pressure)**(1/(subdomain_index + 1))), 1) +# +# sa + +write_to_file = { + 'meshes_and_markers': True, + 'L_iterations': True +} + +# initialise LDD simulation class +simulation = ldd.LDDsimulation(tol = 1E-14, debug = False) +simulation.set_parameters(output_dir = "./output/",# + subdomain_def_points = subdomain_def_points,# + isRichards = isRichards,# + interface_def_points = interface_def_points,# + outer_boundary_def_points = outer_boundary_def_points,# + adjacent_subdomains = adjacent_subdomains,# + mesh_resolution = mesh_resolution,# + viscosity = viscosity,# + porosity = porosity,# + L = L,# + lambda_param = lambda_param,# + relative_permeability = relative_permeability,# + saturation = sat_pressure_relationship,# + starttime = starttime,# + number_of_timesteps = number_of_timesteps, + number_of_timesteps_to_analyse = number_of_timesteps_to_analyse, + timestep_size = timestep_size,# + sources = source_expression,# + initial_conditions = initial_condition,# + dirichletBC_expression_strings = dirichletBC,# + exact_solution = exact_solution,# + write2file = write_to_file,# + ) + +simulation.initialise() +simulation.run() +# simulation.LDDsolver(time = 0, debug = True, analyse_timestep = True) +# df.info(parameters, True) diff --git a/RR-multi-patch-plus-gravity-const-solution/RR-multi-patch-with-gravity-constant-solution.py b/RR-multi-patch-plus-gravity-const-solution/RR-multi-patch-with-gravity-constant-solution.py new file mode 100755 index 0000000000000000000000000000000000000000..14f1e7ca6c25f36f2e9878ddf38a6344df48c239 --- /dev/null +++ b/RR-multi-patch-plus-gravity-const-solution/RR-multi-patch-with-gravity-constant-solution.py @@ -0,0 +1,468 @@ +#!/usr/bin/python3 +import dolfin as df +# import mshr +# import numpy as np +import sympy as sym +# import typing as tp +# import domainPatch as dp +import LDDsimulation as ldd +import functools as ft +# import ufl as ufl + +# init sympy session +sym.init_printing() + +# ----------------------------------------------------------------------------# +# ------------------- MESH ---------------------------------------------------# +# ----------------------------------------------------------------------------# +mesh_resolution = 30 +# ----------------------------------------:-------------------------------------# +# ------------------- TIME ---------------------------------------------------# +# ----------------------------------------------------------------------------# +timestep_size = 0.01 +number_of_timesteps = 10 +# decide how many timesteps you want analysed. Analysed means, that we write +# out subsequent errors of the L-iteration within the timestep. +number_of_timesteps_to_analyse = 2 +starttime = 0 + + +# ----------------------------------------------------------------------------# +# ------------------- Domain and Interface -----------------------------------# +# ----------------------------------------------------------------------------# +# global simulation domain domain +sub_domain0_vertices = [df.Point(-1.0, -1.0), + df.Point(1.0, -1.0), + df.Point(1.0, 1.0), + df.Point(-1.0, 1.0)] +# interfaces +interface12_vertices = [df.Point(0.0, 0.0), + df.Point(1.0, 0.0)] + +interface14_vertices = [df.Point(0.0, 0.0), + df.Point(0.0, 1.0)] + +interface23_vertices = [df.Point(0.0, 0.0), + df.Point(0.0, -1.0)] + +interface34_vertices = [df.Point(-1.0, 0.0), + df.Point(0.0, 0.0)] +# subdomain1. +sub_domain1_vertices = [interface12_vertices[0], + interface12_vertices[1], + sub_domain0_vertices[2], + df.Point(0.0, 1.0)] + +# vertex coordinates of the outer boundaries. If it can not be specified as a +# polygon, use an entry per boundary polygon. This information is used for +# defining the Dirichlet boundary conditions. If a domain is completely inter- +# nal, the dictionary entry should be 0: None +subdomain1_outer_boundary_verts = { + 0: [interface12_vertices[1], + sub_domain0_vertices[2], + df.Point(0.0, 1.0)] +} +# subdomain2 +sub_domain2_vertices = [interface23_vertices[1], + sub_domain0_vertices[1], + interface12_vertices[1], + interface12_vertices[0]] + +subdomain2_outer_boundary_verts = { + 0: [df.Point(0.0, -1.0), + sub_domain0_vertices[1], + interface12_vertices[1]] +} +sub_domain3_vertices = [interface34_vertices[0], + sub_domain0_vertices[0], + interface23_vertices[1], + interface23_vertices[0]] + +subdomain3_outer_boundary_verts = { + 0: [interface34_vertices[0], + sub_domain0_vertices[0], + interface23_vertices[1]] +} + +sub_domain4_vertices = [interface34_vertices[0], + interface34_vertices[1], + interface14_vertices[1], + sub_domain0_vertices[3]] + +subdomain4_outer_boundary_verts = { + 0: [interface14_vertices[1], + sub_domain0_vertices[3], + interface34_vertices[0]] +} + +# list of subdomains given by the boundary polygon vertices. +# Subdomains are given as a list of dolfin points forming +# a closed polygon, such that mshr.Polygon(subdomain_def_points[i]) can be used +# to create the subdomain. subdomain_def_points[0] contains the +# vertices of the global simulation domain and subdomain_def_points[i] contains +# the vertices of the subdomain i. +subdomain_def_points = [sub_domain0_vertices, + sub_domain1_vertices, + sub_domain2_vertices, + sub_domain3_vertices, + sub_domain4_vertices] +# in the below list, index 0 corresponds to the 12 interface which has global +# marker value 1 +interface_def_points = [interface12_vertices, + interface14_vertices, + interface23_vertices, + interface34_vertices] + +# adjacent_subdomains[i] contains the indices of the subdomains sharing the +# interface i (i.e. given by interface_def_points[i]). +adjacent_subdomains = [[1, 2], [1, 4], [2, 3], [3, 4]] + +# if a subdomain has no outer boundary write None instead, i.e. +# i: None +# if i is the index of the inner subdomain. +outer_boundary_def_points = { + # subdomain number + 1: subdomain1_outer_boundary_verts, + 2: subdomain2_outer_boundary_verts, + 3: subdomain3_outer_boundary_verts, + 4: subdomain4_outer_boundary_verts +} + +isRichards = { + 1: True, + 2: True, + 3: True, + 4: True + } + + +# Dict of the form: { subdom_num : viscosity } +viscosity = { + 1: {'wetting': 1}, + 2: {'wetting': 1}, + 3: {'wetting': 1}, + 4: {'wetting': 1} +} + +# Dict of the form: { subdom_num : density } +densities = { + 1: {'wetting': 1}, + 2: {'wetting': 1}, + 3: {'wetting': 1}, + 4: {'wetting': 1} +} + +gravity_acceleration = 9.81 +# Dict of the form: { subdom_num : porosity } +porosity = { + 1: 1, + 2: 1, + 3: 1, + 4: 1 +} + +# subdom_num : subdomain L for L-scheme +L = { + 1: {'wetting': 0.5}, + 2: {'wetting': 0.5}, + 3: {'wetting': 0.5}, + 4: {'wetting': 0.5} +} + +lamdal_w = 30 +# subdom_num : lambda parameter for the L-scheme +lambda_param = { + 1: {'wetting': lamdal_w}, + 2: {'wetting': lamdal_w}, + 3: {'wetting': lamdal_w}, + 4: {'wetting': lamdal_w} +} + + +# relative permeabilty functions on subdomain 1 +def rel_perm1(s): + # relative permeabilty on subdomain1 + return s**2 + + +_rel_perm1 = ft.partial(rel_perm1) +subdomain1_rel_perm = { + 'wetting': _rel_perm1, + 'nonwetting': None +} + + +# relative permeabilty functions on subdomain 2 +def rel_perm2(s): + # relative permeabilty on subdomain2 + return s**3 + + +_rel_perm2 = ft.partial(rel_perm2) + +subdomain2_rel_perm = { + 'wetting': _rel_perm2, + 'nonwetting': None +} + +_rel_perm3 = ft.partial(rel_perm2) +subdomain3_rel_perm = subdomain2_rel_perm.copy() + +_rel_perm4 = ft.partial(rel_perm1) +subdomain4_rel_perm = subdomain1_rel_perm.copy() + +# dictionary of relative permeabilties on all domains. +relative_permeability = { + 1: subdomain1_rel_perm, + 2: subdomain2_rel_perm, + 3: subdomain3_rel_perm, + 4: subdomain4_rel_perm +} + + +# definition of the derivatives of the relative permeabilities +# relative permeabilty functions on subdomain 1 +def rel_perm1_prime(s): + # relative permeabilty on subdomain1 + return 2*s + + +_rel_perm1_prime = ft.partial(rel_perm1_prime) +subdomain1_rel_perm_prime = { + 'wetting': _rel_perm1_prime, + 'nonwetting': None +} + + +# relative permeabilty functions on subdomain 2 +def rel_perm2_prime(s): + # relative permeabilty on subdomain2 + return 3*s**2 + + +_rel_perm2_prime = ft.partial(rel_perm2_prime) + +subdomain2_rel_perm_prime = { + 'wetting': _rel_perm2_prime, + 'nonwetting': None +} + +# _rel_perm3_prime = ft.partial(rel_perm2_prime) +subdomain3_rel_perm_prime = subdomain2_rel_perm_prime.copy() + +# _rel_perm4_prime = ft.partial(rel_perm1_prime) +subdomain4_rel_perm_prime = subdomain1_rel_perm_prime.copy() + +# dictionary of relative permeabilties on all domains. +ka_prime = { + 1: subdomain1_rel_perm_prime, + 2: subdomain2_rel_perm_prime, + 3: subdomain3_rel_perm_prime, + 4: subdomain4_rel_perm_prime +} + + +# this function needs to be monotonically decreasing in the capillary_pressure. +# since in the richards case pc=-pw, this becomes as a function of pw a mono +# tonically INCREASING function like in our Richards-Richards paper. However +# since we unify the treatment in the code for Richards and two-phase, we need +# the same requierment +# for both cases, two-phase and Richards. +def saturation(pc, index): + # inverse capillary pressure-saturation-relationship + return df.conditional(pc > 0, 1/((1 + pc)**(1/(index + 1))), 1) + + +def saturation_sym(pc, index): + # inverse capillary pressure-saturation-relationship + return 1/((1 + pc)**(1/(index + 1))) + + +# derivative of S-pc relationship with respect to pc. This is needed for the +# construction of a analytic solution. +def saturation_sym_prime(pc, index): + # inverse capillary pressure-saturation-relationship + return -1/((index+1)*(1 + pc)**((index+2)/(index+1))) + + +# note that the conditional definition of S-pc in the nonsymbolic part will be +# incorporated in the construction of the exact solution below. +S_pc_sym = { + 1: ft.partial(saturation_sym, index=1), + 2: ft.partial(saturation_sym, index=2), + 3: ft.partial(saturation_sym, index=2), + 4: ft.partial(saturation_sym, index=1) +} + +S_pc_sym_prime = { + 1: ft.partial(saturation_sym_prime, index=1), + 2: ft.partial(saturation_sym_prime, index=2), + 3: ft.partial(saturation_sym_prime, index=2), + 4: ft.partial(saturation_sym_prime, index=1) +} + +sat_pressure_relationship = { + 1: ft.partial(saturation, index=1), + 2: ft.partial(saturation, index=2), + 3: ft.partial(saturation, index=2), + 4: ft.partial(saturation, index=1) +} + +############################################# +# Manufacture source expressions with sympy # +############################################# +x, y = sym.symbols('x[0], x[1]') # needed by UFL +t = sym.symbols('t', positive=True) + +p_e_sym = { + 1: {'wetting': -3 + 0*t}, + 2: {'wetting': -3 + 0*t}, + 3: {'wetting': -3 + 0*t}, + 4: {'wetting': -3 + 0*t} +} + +pc_e_sym = { + 1: -1*p_e_sym[1]['wetting'], + 2: -1*p_e_sym[2]['wetting'], + 3: -1*p_e_sym[3]['wetting'], + 4: -1*p_e_sym[4]['wetting'] +} + +# construction of the rhs that matches the above exact solution. +dtS = dict() +div_flux = dict() +source_expression = dict() +exact_solution = dict() +initial_condition = dict() +for subdomain, isR in isRichards.items(): + dtS.update({subdomain: dict()}) + div_flux.update({subdomain: dict()}) + source_expression.update({subdomain: dict()}) + exact_solution.update({subdomain: dict()}) + initial_condition.update({subdomain: dict()}) + if isR: + subdomain_has_phases = ["wetting"] + else: + subdomain_has_phases = ["wetting", "nonwetting"] + + # conditional for S_pc_prime + pc = pc_e_sym[subdomain] + dtpc = sym.diff(pc, t, 1) + dxpc = sym.diff(pc, x, 1) + dypc = sym.diff(pc, y, 1) + 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)) + for phase in subdomain_has_phases: + # Turn above symbolic expression for exact solution into c code + exact_solution[subdomain].update( + {phase: sym.printing.ccode(p_e_sym[subdomain][phase])} + ) + # save the c code for initial conditions + initial_condition[subdomain].update( + {phase: sym.printing.ccode(p_e_sym[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 = p_e_sym[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 = ka_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 + dydyflux = -1/mu*dka(1-S)*dS*dypc*(dypa - rho*g) \ + + 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 + dydyflux = 1/mu*dka(S)*dS*dypc*(dypa - rho*g) + 1/mu*dydypa*ka(S) + div_flux[subdomain].update({phase: dxdxflux + dydyflux}) + contructed_rhs = dtS[subdomain][phase] - div_flux[subdomain][phase] + source_expression[subdomain].update( + {phase: sym.printing.ccode(contructed_rhs)} + ) + # print(f"source_expression[{subdomain}][{phase}] =", source_expression[subdomain][phase]) + +# Dictionary of dirichlet boundary conditions. +dirichletBC = dict() +# similarly to the outer boundary dictionary, if a patch has no outer boundary +# None should be written instead of an expression. +# This is a bit of a brainfuck: +# dirichletBC[ind] gives a dictionary of the outer boundaries of subdomain ind. +# Since a domain patch can have several disjoint outer boundary parts, the +# expressions need to get an enumaration index which starts at 0. +# So dirichletBC[ind][j] is the dictionary of outer dirichlet conditions of +# subdomain ind and boundary part j. +# Finally, dirichletBC[ind][j]['wetting'] and dirichletBC[ind][j]['nonwetting'] +# return the actual expression needed for the dirichlet condition for both +# phases if present. + +# subdomain index: {outer boudary part index: {phase: expression}} +for subdomain in isRichards.keys(): + # if subdomain has no outer boundary, outer_boundary_def_points[subdomain] is None + if outer_boundary_def_points[subdomain] is None: + dirichletBC.update({subdomain: None}) + else: + dirichletBC.update({subdomain: dict()}) + # set the dirichlet conditions to be the same code as exact solution on + # the subdomain. + for outer_boundary_ind in outer_boundary_def_points[subdomain].keys(): + dirichletBC[subdomain].update( + {outer_boundary_ind: exact_solution[subdomain]} + ) + + + +write_to_file = { + 'meshes_and_markers': True, + 'L_iterations': True +} + +# initialise LDD simulation class +simulation = ldd.LDDsimulation(tol=1E-14, debug=True, LDDsolver_tol=1E-9) +simulation.set_parameters(output_dir="./output/", + subdomain_def_points=subdomain_def_points, + isRichards=isRichards, + interface_def_points=interface_def_points, + outer_boundary_def_points=outer_boundary_def_points, + adjacent_subdomains=adjacent_subdomains, + mesh_resolution=mesh_resolution, + viscosity=viscosity, + porosity=porosity, + L=L, + lambda_param=lambda_param, + relative_permeability=relative_permeability, + saturation=sat_pressure_relationship, + starttime=starttime, + number_of_timesteps=number_of_timesteps, + number_of_timesteps_to_analyse=number_of_timesteps_to_analyse, + timestep_size=timestep_size, + sources=source_expression, + initial_conditions=initial_condition, + dirichletBC_expression_strings=dirichletBC, + exact_solution=exact_solution, + densities=densities, + include_gravity=True, + write2file=write_to_file, + ) + +simulation.initialise() +# print(simulation.__dict__) +simulation.run() +# simulation.LDDsolver(time=0, debug=True, analyse_timestep=True) +# df.info(parameters, True) diff --git a/RR-multi-patch-plus-gravity/RR-multi-patch-with-gravity.py b/RR-multi-patch-plus-gravity/RR-multi-patch-with-gravity.py index 16d3d63d8f45f1dce24b35ba11b2d5eda34ebb70..b6e7bcb69f803c8882de204f053d57142f87ed1a 100755 --- a/RR-multi-patch-plus-gravity/RR-multi-patch-with-gravity.py +++ b/RR-multi-patch-plus-gravity/RR-multi-patch-with-gravity.py @@ -15,15 +15,15 @@ sym.init_printing() # ----------------------------------------------------------------------------# # ------------------- MESH ---------------------------------------------------# # ----------------------------------------------------------------------------# -mesh_resolution = 3 +mesh_resolution = 50 # ----------------------------------------:-------------------------------------# # ------------------- TIME ---------------------------------------------------# # ----------------------------------------------------------------------------# timestep_size = 0.01 -number_of_timesteps = 1 +number_of_timesteps = 500 # decide how many timesteps you want analysed. Analysed means, that we write # out subsequent errors of the L-iteration within the timestep. -number_of_timesteps_to_analyse = 0 +number_of_timesteps_to_analyse = 11 starttime = 0 @@ -163,13 +163,13 @@ porosity = { # subdom_num : subdomain L for L-scheme L = { - 1: {'wetting': 0.25}, - 2: {'wetting': 0.25}, - 3: {'wetting': 0.25}, - 4: {'wetting': 0.25} + 1: {'wetting': 0.5}, + 2: {'wetting': 0.5}, + 3: {'wetting': 0.5}, + 4: {'wetting': 0.5} } -lamdal_w = 23 +lamdal_w = 30 # subdom_num : lambda parameter for the L-scheme lambda_param = { 1: {'wetting': lamdal_w}, @@ -363,11 +363,22 @@ for subdomain, isR in isRichards.items(): 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)) for phase in subdomain_has_phases: - if phase == "nonwetting": - dS = -dS - dtS[subdomain].update( - {phase: porosity[subdomain]*dS*dtpc} + # Turn above symbolic expression for exact solution into c code + exact_solution[subdomain].update( + {phase: sym.printing.ccode(p_e_sym[subdomain][phase])} + ) + # save the c code for initial conditions + initial_condition[subdomain].update( + {phase: sym.printing.ccode(p_e_sym[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 = p_e_sym[subdomain][phase] dxpa = sym.diff(pa, x, 1) dxdxpa = sym.diff(pa, x, 2) @@ -380,14 +391,18 @@ for subdomain, isR in isRichards.items(): g = gravity_acceleration if phase == "nonwetting": - dxdxflux = 1/mu*dka(1-S)*dS*dxpc*dxpa - 1/mu*dxdxpa*ka(1-S) - dydyflux = 1/mu*dka(1-S)*dS*dypc*(dypa - rho*g) \ - - 1/mu*dydypa*ka(1-S) + # 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 + dydyflux = -1/mu*dka(1-S)*dS*dypc*(dypa - rho*g) \ + + 1/mu*dydypa*ka(1-S) else: - dxdxflux = -1/mu*dka(S)*dS*dxpc*dxpa - 1/mu*dxdxpa*ka(S) - dydyflux = -1/mu*dka(S)*dS*dypc*(dypa - rho*g) - 1/mu*dydypa*ka(S) + # 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 + dydyflux = 1/mu*dka(S)*dS*dypc*(dypa - rho*g) + 1/mu*dydypa*ka(S) div_flux[subdomain].update({phase: dxdxflux + dydyflux}) - contructed_rhs = dtS[subdomain][phase] + div_flux[subdomain][phase] + contructed_rhs = dtS[subdomain][phase] - div_flux[subdomain][phase] source_expression[subdomain].update( {phase: sym.printing.ccode(contructed_rhs)} ) diff --git a/RR-multi-patch-with-inner-patch-const-solution/RR-multi-patch-with-inner-patch-constant-solution.py b/RR-multi-patch-with-inner-patch-const-solution/RR-multi-patch-with-inner-patch-constant-solution.py new file mode 100755 index 0000000000000000000000000000000000000000..f73abbc4038783cbc6b5f7348012d24dfaa7a472 --- /dev/null +++ b/RR-multi-patch-with-inner-patch-const-solution/RR-multi-patch-with-inner-patch-constant-solution.py @@ -0,0 +1,603 @@ +#!/usr/bin/python3 +import dolfin as df +# import mshr +# import numpy as np +import sympy as sym +# import typing as tp +# import domainPatch as dp +import LDDsimulation as ldd +import functools as ft +# import ufl as ufl + +# init sympy session +sym.init_printing() + +# ----------------------------------------------------------------------------# +# ------------------- MESH ---------------------------------------------------# +# ----------------------------------------------------------------------------# +mesh_resolution = 30 +# ----------------------------------------:-------------------------------------# +# ------------------- TIME ---------------------------------------------------# +# ----------------------------------------------------------------------------# +timestep_size = 0.01 +number_of_timesteps = 10 +# decide how many timesteps you want analysed. Analysed means, that we write +# out subsequent errors of the L-iteration within the timestep. +number_of_timesteps_to_analyse = 2 +starttime = 0 + + +# ----------------------------------------------------------------------------# +# ------------------- Domain and Interface -----------------------------------# +# ----------------------------------------------------------------------------# +# global simulation domain domain +sub_domain0_vertices = [df.Point(-1.0, -1.0), + df.Point(1.0, -1.0), + df.Point(1.0, 1.0), + df.Point(-1.0, 1.0)] + +# interfaces + +interface23_vertices = [df.Point(0.0, -0.6), + df.Point(0.7, 0.0)] + +interface12_vertices = [interface23_vertices[1], + df.Point(1.0, 0.0)] + +interface13_vertices = [df.Point(0.0, 0.0), + interface23_vertices[1]] + +interface15_vertices = [df.Point(0.0, 0.0), + df.Point(0.0, 1.0)] + +interface34_vertices = [df.Point(0.0, 0.0), + interface23_vertices[0]] + +interface24_vertices = [interface23_vertices[0], + df.Point(0.0, -1.0)] + +interface45_vertices = [df.Point(-1.0, 0.0), + df.Point(0.0, 0.0)] +# subdomain1. +sub_domain1_vertices = [interface23_vertices[0], + interface23_vertices[1], + interface12_vertices[1], + sub_domain0_vertices[2], + df.Point(0.0, 1.0)] + +# vertex coordinates of the outer boundaries. If it can not be specified as a +# polygon, use an entry per boundary polygon. This information is used for +# defining the Dirichlet boundary conditions. If a domain is completely inter- +# nal, the dictionary entry should be 0: None +subdomain1_outer_boundary_verts = { + 0: [interface12_vertices[1], + sub_domain0_vertices[2], + df.Point(0.0, 1.0)] +} +# subdomain2 +sub_domain2_vertices = [interface24_vertices[1], + sub_domain0_vertices[1], + interface12_vertices[1], + interface23_vertices[1], + interface23_vertices[0]] + +subdomain2_outer_boundary_verts = { + 0: [interface24_vertices[1], + sub_domain0_vertices[1], + interface12_vertices[1]] +} + +sub_domain3_vertices = [interface23_vertices[0], + interface23_vertices[1], + interface13_vertices[0]] + +subdomain3_outer_boundary_verts = None + + +sub_domain4_vertices = [sub_domain0_vertices[0], + interface24_vertices[1], + interface34_vertices[1], + interface34_vertices[0], + interface45_vertices[0]] + +subdomain4_outer_boundary_verts = { + 0: [interface45_vertices[0], + sub_domain0_vertices[0], + interface24_vertices[1]] +} + +sub_domain5_vertices = [interface45_vertices[0], + interface15_vertices[0], + interface15_vertices[1], + sub_domain0_vertices[3]] + +subdomain5_outer_boundary_verts = { + 0: [interface15_vertices[1], + sub_domain0_vertices[3], + interface45_vertices[0]] +} + +# list of subdomains given by the boundary polygon vertices. +# Subdomains are given as a list of dolfin points forming +# a closed polygon, such that mshr.Polygon(subdomain_def_points[i]) can be used +# to create the subdomain. subdomain_def_points[0] contains the +# vertices of the global simulation domain and subdomain_def_points[i] contains +# the vertices of the subdomain i. +subdomain_def_points = [sub_domain0_vertices, + sub_domain1_vertices, + sub_domain2_vertices, + sub_domain3_vertices, + sub_domain4_vertices, + sub_domain5_vertices] +# in the below list, index 0 corresponds to the 12 interface which has global +# marker value 1 +interface_def_points = [interface13_vertices, + interface12_vertices, + interface23_vertices, + interface24_vertices, + interface34_vertices, + interface45_vertices, + interface15_vertices,] + +# adjacent_subdomains[i] contains the indices of the subdomains sharing the +# interface i (i.e. given by interface_def_points[i]). +adjacent_subdomains = [[1, 3], [1, 2], [2, 3], [2, 4], [3, 4], [4, 5], [1, 5]] + +# if a subdomain has no outer boundary write None instead, i.e. +# i: None +# if i is the index of the inner subdomain. +outer_boundary_def_points = { + # subdomain number + 1: subdomain1_outer_boundary_verts, + 2: subdomain2_outer_boundary_verts, + 3: subdomain3_outer_boundary_verts, + 4: subdomain4_outer_boundary_verts, + 5: subdomain5_outer_boundary_verts +} + +isRichards = { + 1: True, + 2: True, + 3: True, + 4: True, + 5: True + } + + +# Dict of the form: { subdom_num : viscosity } +viscosity = { + 1: {'wetting': 1}, + 2: {'wetting': 1}, + 3: {'wetting': 1}, + 4: {'wetting': 1}, + 5: {'wetting': 1} +} + +# Dict of the form: { subdom_num : density } +densities = { + 1: {'wetting': 1}, + 2: {'wetting': 1}, + 3: {'wetting': 1}, + 4: {'wetting': 1}, + 5: {'wetting': 1} +} + +gravity_acceleration = 9.81 +# Dict of the form: { subdom_num : porosity } +porosity = { + 1: 1, + 2: 1, + 3: 1, + 4: 1, + 5: 1 +} + +# subdom_num : subdomain L for L-scheme +L = { + 1: {'wetting': 0.6}, + 2: {'wetting': 0.6}, + 3: {'wetting': 0.6}, + 4: {'wetting': 0.6}, + 5: {'wetting': 0.6} +} + +lamdal_w = 32 + +# subdom_num : lambda parameter for the L-scheme +lambda_param = { + 1: {'wetting': lamdal_w}, + 2: {'wetting': lamdal_w}, + 3: {'wetting': lamdal_w}, + 4: {'wetting': lamdal_w}, + 5: {'wetting': lamdal_w} +} + + +# relative permeabilty functions on subdomain 1 +def rel_perm1(s): + # relative permeabilty on subdomain1 + return s**2 + + +_rel_perm1 = ft.partial(rel_perm1) +subdomain1_rel_perm = { + 'wetting': _rel_perm1, + 'nonwetting': None +} + + +# relative permeabilty functions on subdomain 2 +def rel_perm2(s): + # relative permeabilty on subdomain2 + return s**3 + + +_rel_perm2 = ft.partial(rel_perm2) + +subdomain2_rel_perm = { + 'wetting': _rel_perm2, + 'nonwetting': None +} + +# _rel_perm3 = ft.partial(rel_perm2) +subdomain3_rel_perm = subdomain2_rel_perm.copy() + +# _rel_perm4 = ft.partial(rel_perm2) +subdomain4_rel_perm = subdomain2_rel_perm.copy() + +# _rel_perm5 = ft.partial(rel_perm1) +subdomain5_rel_perm = subdomain1_rel_perm.copy() + + +# dictionary of relative permeabilties on all domains. +relative_permeability = { + 1: subdomain1_rel_perm, + 2: subdomain2_rel_perm, + 3: subdomain3_rel_perm, + 4: subdomain4_rel_perm, + 5: subdomain5_rel_perm +} + + +# definition of the derivatives of the relative permeabilities +# relative permeabilty functions on subdomain 1 +def rel_perm1_prime(s): + # relative permeabilty on subdomain1 + return 2*s + + +_rel_perm1_prime = ft.partial(rel_perm1_prime) +subdomain1_rel_perm_prime = { + 'wetting': _rel_perm1_prime, + 'nonwetting': None +} + + +# relative permeabilty functions on subdomain 2 +def rel_perm2_prime(s): + # relative permeabilty on subdomain2 + return 3*s**2 + + +_rel_perm2_prime = ft.partial(rel_perm2_prime) + +subdomain2_rel_perm_prime = { + 'wetting': _rel_perm2_prime, + 'nonwetting': None +} + +# _rel_perm3_prime = ft.partial(rel_perm2_prime) +subdomain3_rel_perm_prime = subdomain2_rel_perm_prime.copy() + +# _rel_perm4_prime = ft.partial(rel_perm1_prime) +subdomain4_rel_perm_prime = subdomain2_rel_perm_prime.copy() +subdomain5_rel_perm_prime = subdomain1_rel_perm_prime.copy() + +# dictionary of relative permeabilties on all domains. +ka_prime = { + 1: subdomain1_rel_perm_prime, + 2: subdomain2_rel_perm_prime, + 3: subdomain3_rel_perm_prime, + 4: subdomain4_rel_perm_prime, + 5: subdomain5_rel_perm_prime +} + + +# this function needs to be monotonically decreasing in the capillary_pressure. +# since in the richards case pc=-pw, this becomes as a function of pw a mono +# tonically INCREASING function like in our Richards-Richards paper. However +# since we unify the treatment in the code for Richards and two-phase, we need +# the same requierment +# for both cases, two-phase and Richards. +def saturation(pc, index): + # inverse capillary pressure-saturation-relationship + return df.conditional(pc > 0, 1/((1 + pc)**(1/(index + 1))), 1) + + +def saturation_sym(pc, index): + # inverse capillary pressure-saturation-relationship + return 1/((1 + pc)**(1/(index + 1))) + + +# derivative of S-pc relationship with respect to pc. This is needed for the +# construction of a analytic solution. +def saturation_sym_prime(pc, index): + # inverse capillary pressure-saturation-relationship + return -1/((index+1)*(1 + pc)**((index+2)/(index+1))) + + +# note that the conditional definition of S-pc in the nonsymbolic part will be +# incorporated in the construction of the exact solution below. +S_pc_sym = { + 1: ft.partial(saturation_sym, index=1), + 2: ft.partial(saturation_sym, index=2), + 3: ft.partial(saturation_sym, index=2), + 4: ft.partial(saturation_sym, index=2), + 5: ft.partial(saturation_sym, index=1) +} + +S_pc_sym_prime = { + 1: ft.partial(saturation_sym_prime, index=1), + 2: ft.partial(saturation_sym_prime, index=2), + 3: ft.partial(saturation_sym_prime, index=2), + 4: ft.partial(saturation_sym_prime, index=2), + 5: ft.partial(saturation_sym_prime, index=1) +} + +sat_pressure_relationship = { + 1: ft.partial(saturation, index=1), + 2: ft.partial(saturation, index=2), + 3: ft.partial(saturation, index=2), + 4: ft.partial(saturation, index=2), + 5: ft.partial(saturation, index=1) +} + +# exact_solution = { +# 1: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0] + x[1]*x[1])'}, +# 2: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0])'}, +# 3: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0])'}, +# 4: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0])'}, +# 5: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0] + x[1]*x[1])'} +# } +# +# initial_condition = { +# 1: {'wetting': '-(x[0]*x[0] + x[1]*x[1])'}, +# 2: {'wetting': '-x[0]*x[0]'}, +# 3: {'wetting': '-x[0]*x[0]'}, +# 4: {'wetting': '-x[0]*x[0]'}, +# 5: {'wetting': '-(x[0]*x[0] + x[1]*x[1])'} +# } + +############################################# +# Manufacture source expressions with sympy # +############################################# +x, y = sym.symbols('x[0], x[1]') # needed by UFL +t = sym.symbols('t', positive=True) + +p_e_sym = { + 1: {'wetting': -3 + 0*t}, + 2: {'wetting': -3 + 0*t}, + 3: {'wetting': -3 + 0*t}, + 4: {'wetting': -3 + 0*t}, + 5: {'wetting': -3 + 0*t} +} + +pc_e_sym = { + 1: -1*p_e_sym[1]['wetting'], + 2: -1*p_e_sym[2]['wetting'], + 3: -1*p_e_sym[3]['wetting'], + 4: -1*p_e_sym[4]['wetting'], + 5: -1*p_e_sym[5]['wetting'] +} + +# turn above symbolic code into exact solution for dolphin and +# construct the rhs that matches the above exact solution. +dtS = dict() +div_flux = dict() +source_expression = dict() +exact_solution = dict() +initial_condition = dict() +for subdomain, isR in isRichards.items(): + dtS.update({subdomain: dict()}) + div_flux.update({subdomain: dict()}) + source_expression.update({subdomain: dict()}) + exact_solution.update({subdomain: dict()}) + initial_condition.update({subdomain: dict()}) + if isR: + subdomain_has_phases = ["wetting"] + else: + subdomain_has_phases = ["wetting", "nonwetting"] + + # conditional for S_pc_prime + pc = pc_e_sym[subdomain] + dtpc = sym.diff(pc, t, 1) + dxpc = sym.diff(pc, x, 1) + dypc = sym.diff(pc, y, 1) + 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)) + for phase in subdomain_has_phases: + # Turn above symbolic expression for exact solution into c code + exact_solution[subdomain].update( + {phase: sym.printing.ccode(p_e_sym[subdomain][phase])} + ) + # save the c code for initial conditions + initial_condition[subdomain].update( + {phase: sym.printing.ccode(p_e_sym[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 = p_e_sym[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 = ka_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 + dydyflux = -1/mu*dka(1-S)*dS*dypc*(dypa - rho*g) \ + + 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 + dydyflux = 1/mu*dka(S)*dS*dypc*(dypa - rho*g) + 1/mu*dydypa*ka(S) + div_flux[subdomain].update({phase: dxdxflux + dydyflux}) + contructed_rhs = dtS[subdomain][phase] - div_flux[subdomain][phase] + source_expression[subdomain].update( + {phase: sym.printing.ccode(contructed_rhs)} + ) + # print(f"source_expression[{subdomain}][{phase}] =", source_expression[subdomain][phase]) + +# Dictionary of dirichlet boundary conditions. +dirichletBC = dict() +# similarly to the outer boundary dictionary, if a patch has no outer boundary +# None should be written instead of an expression. +# This is a bit of a brainfuck: +# dirichletBC[ind] gives a dictionary of the outer boundaries of subdomain ind. +# Since a domain patch can have several disjoint outer boundary parts, the +# expressions need to get an enumaration index which starts at 0. +# So dirichletBC[ind][j] is the dictionary of outer dirichlet conditions of +# subdomain ind and boundary part j. +# Finally, dirichletBC[ind][j]['wetting'] and dirichletBC[ind][j]['nonwetting'] +# return the actual expression needed for the dirichlet condition for both +# phases if present. + +# subdomain index: {outer boudary part index: {phase: expression}} +for subdomain in isRichards.keys(): + # if subdomain has no outer boundary, outer_boundary_def_points[subdomain] is None + if outer_boundary_def_points[subdomain] is None: + dirichletBC.update({subdomain: None}) + else: + dirichletBC.update({subdomain: dict()}) + # set the dirichlet conditions to be the same code as exact solution on + # the subdomain. + for outer_boundary_ind in outer_boundary_def_points[subdomain].keys(): + dirichletBC[subdomain].update( + {outer_boundary_ind: exact_solution[subdomain]} + ) + + +# # construction of the rhs that matches the above exact solution. +# dtS = dict() +# div_flux = dict() +# source_expression = dict() +# for subdomain, isR in isRichards.items(): +# dtS.update({subdomain: dict()}) +# div_flux.update({subdomain: dict()}) +# source_expression.update({subdomain: dict()}) +# if isR: +# subdomain_has_phases = ["wetting"] +# else: +# subdomain_has_phases = ["wetting", "nonwetting"] +# +# # conditional for S_pc_prime +# pc = pc_e_sym[subdomain] +# dtpc = sym.diff(pc, t, 1) +# dxpc = sym.diff(pc, x, 1) +# dypc = sym.diff(pc, y, 1) +# 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)) +# for phase in subdomain_has_phases: +# if phase == "nonwetting": +# dS = -dS +# dtS[subdomain].update( +# {phase: porosity[subdomain]*dS*dtpc} +# ) +# pa = p_e_sym[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 = ka_prime[subdomain][phase] +# rho = densities[subdomain][phase] +# g = gravity_acceleration +# +# if phase == "nonwetting": +# dxdxflux = 1/mu*dka(1-S)*dS*dxpc*dxpa - 1/mu*dxdxpa*ka(1-S) +# dydyflux = 1/mu*dka(1-S)*dS*dypc*(dypa - rho*g) \ +# - 1/mu*dydypa*ka(1-S) +# else: +# dxdxflux = -1/mu*dka(S)*dS*dxpc*dxpa - 1/mu*dxdxpa*ka(S) +# dydyflux = -1/mu*dka(S)*dS*dypc*(dypa - rho*g) - 1/mu*dydypa*ka(S) +# div_flux[subdomain].update({phase: dxdxflux + dydyflux}) +# contructed_rhs = dtS[subdomain][phase] + div_flux[subdomain][phase] +# source_expression[subdomain].update( +# {phase: sym.printing.ccode(contructed_rhs)} +# ) +# print(f"source_expression[{subdomain}][{phase}] =", +# source_expression[subdomain][phase]) + +# similarly to the outer boundary dictionary, if a patch has no outer boundary +# None should be written instead of an expression. +# This is a bit of a brainfuck: +# dirichletBC[ind] gives a dictionary of the outer boundaries of subdomain ind. +# Since a domain patch can have several disjoint outer boundary parts, the +# expressions need to get an enumaration index which starts at 0. +# So dirichletBC[ind][j] is the dictionary of outer dirichlet conditions of +# subdomain ind and boundary part j. +# Finally, dirichletBC[ind][j]['wetting'] and dirichletBC[ind][j]['nonwetting'] +# return the actual expression needed for the dirichlet condition for both +# phases if present. + +# subdomain index: {outer boudary part index: {phase: expression}} +# dirichletBC = { +# 1: {0: {'wetting': exact_solution[1]['wetting']}}, +# 2: {0: {'wetting': exact_solution[2]['wetting']}}, +# 3: {0: {'wetting': exact_solution[3]['wetting']}}, +# 4: {0: {'wetting': exact_solution[4]['wetting']}}, +# 5: {0: {'wetting': exact_solution[5]['wetting']}} +# } + +write_to_file = { + 'meshes_and_markers': True, + 'L_iterations': True +} + +# initialise LDD simulation class +simulation = ldd.LDDsimulation(tol=1E-14, debug=True, LDDsolver_tol=1E-9) +simulation.set_parameters(output_dir="./output/", + subdomain_def_points=subdomain_def_points, + isRichards=isRichards, + interface_def_points=interface_def_points, + outer_boundary_def_points=outer_boundary_def_points, + adjacent_subdomains=adjacent_subdomains, + mesh_resolution=mesh_resolution, + viscosity=viscosity, + porosity=porosity, + L=L, + lambda_param=lambda_param, + relative_permeability=relative_permeability, + saturation=sat_pressure_relationship, + starttime=starttime, + number_of_timesteps=number_of_timesteps, + number_of_timesteps_to_analyse=number_of_timesteps_to_analyse, + timestep_size=timestep_size, + sources=source_expression, + initial_conditions=initial_condition, + dirichletBC_expression_strings=dirichletBC, + exact_solution=exact_solution, + densities=densities, + include_gravity=True, + write2file=write_to_file, + ) + +simulation.initialise() +# print(simulation.__dict__) +simulation.run() +# simulation.LDDsolver(time=0, debug=True, analyse_timestep=True) +# df.info(parameters, True) diff --git a/RR-multi-patch-with-inner-patch/RR-multi-patch-with-inner-patch.py b/RR-multi-patch-with-inner-patch/RR-multi-patch-with-inner-patch.py new file mode 100755 index 0000000000000000000000000000000000000000..bbc1cb765265440a0c18f972702b5ad10bd7fcbb --- /dev/null +++ b/RR-multi-patch-with-inner-patch/RR-multi-patch-with-inner-patch.py @@ -0,0 +1,603 @@ +#!/usr/bin/python3 +import dolfin as df +# import mshr +# import numpy as np +import sympy as sym +# import typing as tp +# import domainPatch as dp +import LDDsimulation as ldd +import functools as ft +# import ufl as ufl + +# init sympy session +sym.init_printing() + +# ----------------------------------------------------------------------------# +# ------------------- MESH ---------------------------------------------------# +# ----------------------------------------------------------------------------# +mesh_resolution = 50 +# ----------------------------------------:-------------------------------------# +# ------------------- TIME ---------------------------------------------------# +# ----------------------------------------------------------------------------# +timestep_size = 0.01 +number_of_timesteps = 500 +# decide how many timesteps you want analysed. Analysed means, that we write +# out subsequent errors of the L-iteration within the timestep. +number_of_timesteps_to_analyse = 11 +starttime = 0 + + +# ----------------------------------------------------------------------------# +# ------------------- Domain and Interface -----------------------------------# +# ----------------------------------------------------------------------------# +# global simulation domain domain +sub_domain0_vertices = [df.Point(-1.0, -1.0), + df.Point(1.0, -1.0), + df.Point(1.0, 1.0), + df.Point(-1.0, 1.0)] + +# interfaces + +interface23_vertices = [df.Point(0.0, -0.6), + df.Point(0.7, 0.0)] + +interface12_vertices = [interface23_vertices[1], + df.Point(1.0, 0.0)] + +interface13_vertices = [df.Point(0.0, 0.0), + interface23_vertices[1]] + +interface15_vertices = [df.Point(0.0, 0.0), + df.Point(0.0, 1.0)] + +interface34_vertices = [df.Point(0.0, 0.0), + interface23_vertices[0]] + +interface24_vertices = [interface23_vertices[0], + df.Point(0.0, -1.0)] + +interface45_vertices = [df.Point(-1.0, 0.0), + df.Point(0.0, 0.0)] +# subdomain1. +sub_domain1_vertices = [interface23_vertices[0], + interface23_vertices[1], + interface12_vertices[1], + sub_domain0_vertices[2], + df.Point(0.0, 1.0)] + +# vertex coordinates of the outer boundaries. If it can not be specified as a +# polygon, use an entry per boundary polygon. This information is used for +# defining the Dirichlet boundary conditions. If a domain is completely inter- +# nal, the dictionary entry should be 0: None +subdomain1_outer_boundary_verts = { + 0: [interface12_vertices[1], + sub_domain0_vertices[2], + df.Point(0.0, 1.0)] +} +# subdomain2 +sub_domain2_vertices = [interface24_vertices[1], + sub_domain0_vertices[1], + interface12_vertices[1], + interface23_vertices[1], + interface23_vertices[0]] + +subdomain2_outer_boundary_verts = { + 0: [interface24_vertices[1], + sub_domain0_vertices[1], + interface12_vertices[1]] +} + +sub_domain3_vertices = [interface23_vertices[0], + interface23_vertices[1], + interface13_vertices[0]] + +subdomain3_outer_boundary_verts = None + + +sub_domain4_vertices = [sub_domain0_vertices[0], + interface24_vertices[1], + interface34_vertices[1], + interface34_vertices[0], + interface45_vertices[0]] + +subdomain4_outer_boundary_verts = { + 0: [interface45_vertices[0], + sub_domain0_vertices[0], + interface24_vertices[1]] +} + +sub_domain5_vertices = [interface45_vertices[0], + interface15_vertices[0], + interface15_vertices[1], + sub_domain0_vertices[3]] + +subdomain5_outer_boundary_verts = { + 0: [interface15_vertices[1], + sub_domain0_vertices[3], + interface45_vertices[0]] +} + +# list of subdomains given by the boundary polygon vertices. +# Subdomains are given as a list of dolfin points forming +# a closed polygon, such that mshr.Polygon(subdomain_def_points[i]) can be used +# to create the subdomain. subdomain_def_points[0] contains the +# vertices of the global simulation domain and subdomain_def_points[i] contains +# the vertices of the subdomain i. +subdomain_def_points = [sub_domain0_vertices, + sub_domain1_vertices, + sub_domain2_vertices, + sub_domain3_vertices, + sub_domain4_vertices, + sub_domain5_vertices] +# in the below list, index 0 corresponds to the 12 interface which has global +# marker value 1 +interface_def_points = [interface13_vertices, + interface12_vertices, + interface23_vertices, + interface24_vertices, + interface34_vertices, + interface45_vertices, + interface15_vertices,] + +# adjacent_subdomains[i] contains the indices of the subdomains sharing the +# interface i (i.e. given by interface_def_points[i]). +adjacent_subdomains = [[1, 3], [1, 2], [2, 3], [2, 4], [3, 4], [4, 5], [1, 5]] + +# if a subdomain has no outer boundary write None instead, i.e. +# i: None +# if i is the index of the inner subdomain. +outer_boundary_def_points = { + # subdomain number + 1: subdomain1_outer_boundary_verts, + 2: subdomain2_outer_boundary_verts, + 3: subdomain3_outer_boundary_verts, + 4: subdomain4_outer_boundary_verts, + 5: subdomain5_outer_boundary_verts +} + +isRichards = { + 1: True, + 2: True, + 3: True, + 4: True, + 5: True + } + + +# Dict of the form: { subdom_num : viscosity } +viscosity = { + 1: {'wetting': 1}, + 2: {'wetting': 1}, + 3: {'wetting': 1}, + 4: {'wetting': 1}, + 5: {'wetting': 1} +} + +# Dict of the form: { subdom_num : density } +densities = { + 1: {'wetting': 1}, + 2: {'wetting': 1}, + 3: {'wetting': 1}, + 4: {'wetting': 1}, + 5: {'wetting': 1} +} + +gravity_acceleration = 9.81 +# Dict of the form: { subdom_num : porosity } +porosity = { + 1: 1, + 2: 1, + 3: 1, + 4: 1, + 5: 1 +} + +# subdom_num : subdomain L for L-scheme +L = { + 1: {'wetting': 0.6}, + 2: {'wetting': 0.6}, + 3: {'wetting': 0.6}, + 4: {'wetting': 0.6}, + 5: {'wetting': 0.6} +} + +lamdal_w = 32 + +# subdom_num : lambda parameter for the L-scheme +lambda_param = { + 1: {'wetting': lamdal_w}, + 2: {'wetting': lamdal_w}, + 3: {'wetting': lamdal_w}, + 4: {'wetting': lamdal_w}, + 5: {'wetting': lamdal_w} +} + + +# relative permeabilty functions on subdomain 1 +def rel_perm1(s): + # relative permeabilty on subdomain1 + return s**2 + + +_rel_perm1 = ft.partial(rel_perm1) +subdomain1_rel_perm = { + 'wetting': _rel_perm1, + 'nonwetting': None +} + + +# relative permeabilty functions on subdomain 2 +def rel_perm2(s): + # relative permeabilty on subdomain2 + return s**3 + + +_rel_perm2 = ft.partial(rel_perm2) + +subdomain2_rel_perm = { + 'wetting': _rel_perm2, + 'nonwetting': None +} + +# _rel_perm3 = ft.partial(rel_perm2) +subdomain3_rel_perm = subdomain2_rel_perm.copy() + +# _rel_perm4 = ft.partial(rel_perm2) +subdomain4_rel_perm = subdomain2_rel_perm.copy() + +# _rel_perm5 = ft.partial(rel_perm1) +subdomain5_rel_perm = subdomain1_rel_perm.copy() + + +# dictionary of relative permeabilties on all domains. +relative_permeability = { + 1: subdomain1_rel_perm, + 2: subdomain2_rel_perm, + 3: subdomain3_rel_perm, + 4: subdomain4_rel_perm, + 5: subdomain5_rel_perm +} + + +# definition of the derivatives of the relative permeabilities +# relative permeabilty functions on subdomain 1 +def rel_perm1_prime(s): + # relative permeabilty on subdomain1 + return 2*s + + +_rel_perm1_prime = ft.partial(rel_perm1_prime) +subdomain1_rel_perm_prime = { + 'wetting': _rel_perm1_prime, + 'nonwetting': None +} + + +# relative permeabilty functions on subdomain 2 +def rel_perm2_prime(s): + # relative permeabilty on subdomain2 + return 3*s**2 + + +_rel_perm2_prime = ft.partial(rel_perm2_prime) + +subdomain2_rel_perm_prime = { + 'wetting': _rel_perm2_prime, + 'nonwetting': None +} + +# _rel_perm3_prime = ft.partial(rel_perm2_prime) +subdomain3_rel_perm_prime = subdomain2_rel_perm_prime.copy() + +# _rel_perm4_prime = ft.partial(rel_perm1_prime) +subdomain4_rel_perm_prime = subdomain2_rel_perm_prime.copy() +subdomain5_rel_perm_prime = subdomain1_rel_perm_prime.copy() + +# dictionary of relative permeabilties on all domains. +ka_prime = { + 1: subdomain1_rel_perm_prime, + 2: subdomain2_rel_perm_prime, + 3: subdomain3_rel_perm_prime, + 4: subdomain4_rel_perm_prime, + 5: subdomain5_rel_perm_prime +} + + +# this function needs to be monotonically decreasing in the capillary_pressure. +# since in the richards case pc=-pw, this becomes as a function of pw a mono +# tonically INCREASING function like in our Richards-Richards paper. However +# since we unify the treatment in the code for Richards and two-phase, we need +# the same requierment +# for both cases, two-phase and Richards. +def saturation(pc, index): + # inverse capillary pressure-saturation-relationship + return df.conditional(pc > 0, 1/((1 + pc)**(1/(index + 1))), 1) + + +def saturation_sym(pc, index): + # inverse capillary pressure-saturation-relationship + return 1/((1 + pc)**(1/(index + 1))) + + +# derivative of S-pc relationship with respect to pc. This is needed for the +# construction of a analytic solution. +def saturation_sym_prime(pc, index): + # inverse capillary pressure-saturation-relationship + return -1/((index+1)*(1 + pc)**((index+2)/(index+1))) + + +# note that the conditional definition of S-pc in the nonsymbolic part will be +# incorporated in the construction of the exact solution below. +S_pc_sym = { + 1: ft.partial(saturation_sym, index=1), + 2: ft.partial(saturation_sym, index=2), + 3: ft.partial(saturation_sym, index=2), + 4: ft.partial(saturation_sym, index=2), + 5: ft.partial(saturation_sym, index=1) +} + +S_pc_sym_prime = { + 1: ft.partial(saturation_sym_prime, index=1), + 2: ft.partial(saturation_sym_prime, index=2), + 3: ft.partial(saturation_sym_prime, index=2), + 4: ft.partial(saturation_sym_prime, index=2), + 5: ft.partial(saturation_sym_prime, index=1) +} + +sat_pressure_relationship = { + 1: ft.partial(saturation, index=1), + 2: ft.partial(saturation, index=2), + 3: ft.partial(saturation, index=2), + 4: ft.partial(saturation, index=2), + 5: ft.partial(saturation, index=1) +} + +# exact_solution = { +# 1: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0] + x[1]*x[1])'}, +# 2: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0])'}, +# 3: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0])'}, +# 4: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0])'}, +# 5: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0] + x[1]*x[1])'} +# } +# +# initial_condition = { +# 1: {'wetting': '-(x[0]*x[0] + x[1]*x[1])'}, +# 2: {'wetting': '-x[0]*x[0]'}, +# 3: {'wetting': '-x[0]*x[0]'}, +# 4: {'wetting': '-x[0]*x[0]'}, +# 5: {'wetting': '-(x[0]*x[0] + x[1]*x[1])'} +# } + +############################################# +# Manufacture source expressions with sympy # +############################################# +x, y = sym.symbols('x[0], x[1]') # needed by UFL +t = sym.symbols('t', positive=True) + +p_e_sym = { + 1: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + x*x + y*y)}, + 2: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + x*x)}, + 3: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + x*x)}, + 4: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + x*x)}, + 5: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + x*x + y*y)} +} + +pc_e_sym = { + 1: -1*p_e_sym[1]['wetting'], + 2: -1*p_e_sym[2]['wetting'], + 3: -1*p_e_sym[3]['wetting'], + 4: -1*p_e_sym[4]['wetting'], + 5: -1*p_e_sym[5]['wetting'] +} + +# turn above symbolic code into exact solution for dolphin and +# construct the rhs that matches the above exact solution. +dtS = dict() +div_flux = dict() +source_expression = dict() +exact_solution = dict() +initial_condition = dict() +for subdomain, isR in isRichards.items(): + dtS.update({subdomain: dict()}) + div_flux.update({subdomain: dict()}) + source_expression.update({subdomain: dict()}) + exact_solution.update({subdomain: dict()}) + initial_condition.update({subdomain: dict()}) + if isR: + subdomain_has_phases = ["wetting"] + else: + subdomain_has_phases = ["wetting", "nonwetting"] + + # conditional for S_pc_prime + pc = pc_e_sym[subdomain] + dtpc = sym.diff(pc, t, 1) + dxpc = sym.diff(pc, x, 1) + dypc = sym.diff(pc, y, 1) + 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)) + for phase in subdomain_has_phases: + # Turn above symbolic expression for exact solution into c code + exact_solution[subdomain].update( + {phase: sym.printing.ccode(p_e_sym[subdomain][phase])} + ) + # save the c code for initial conditions + initial_condition[subdomain].update( + {phase: sym.printing.ccode(p_e_sym[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 = p_e_sym[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 = ka_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 + dydyflux = -1/mu*dka(1-S)*dS*dypc*(dypa - rho*g) \ + + 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 + dydyflux = 1/mu*dka(S)*dS*dypc*(dypa - rho*g) + 1/mu*dydypa*ka(S) + div_flux[subdomain].update({phase: dxdxflux + dydyflux}) + contructed_rhs = dtS[subdomain][phase] - div_flux[subdomain][phase] + source_expression[subdomain].update( + {phase: sym.printing.ccode(contructed_rhs)} + ) + # print(f"source_expression[{subdomain}][{phase}] =", source_expression[subdomain][phase]) + +# Dictionary of dirichlet boundary conditions. +dirichletBC = dict() +# similarly to the outer boundary dictionary, if a patch has no outer boundary +# None should be written instead of an expression. +# This is a bit of a brainfuck: +# dirichletBC[ind] gives a dictionary of the outer boundaries of subdomain ind. +# Since a domain patch can have several disjoint outer boundary parts, the +# expressions need to get an enumaration index which starts at 0. +# So dirichletBC[ind][j] is the dictionary of outer dirichlet conditions of +# subdomain ind and boundary part j. +# Finally, dirichletBC[ind][j]['wetting'] and dirichletBC[ind][j]['nonwetting'] +# return the actual expression needed for the dirichlet condition for both +# phases if present. + +# subdomain index: {outer boudary part index: {phase: expression}} +for subdomain in isRichards.keys(): + # if subdomain has no outer boundary, outer_boundary_def_points[subdomain] is None + if outer_boundary_def_points[subdomain] is None: + dirichletBC.update({subdomain: None}) + else: + dirichletBC.update({subdomain: dict()}) + # set the dirichlet conditions to be the same code as exact solution on + # the subdomain. + for outer_boundary_ind in outer_boundary_def_points[subdomain].keys(): + dirichletBC[subdomain].update( + {outer_boundary_ind: exact_solution[subdomain]} + ) + + +# # construction of the rhs that matches the above exact solution. +# dtS = dict() +# div_flux = dict() +# source_expression = dict() +# for subdomain, isR in isRichards.items(): +# dtS.update({subdomain: dict()}) +# div_flux.update({subdomain: dict()}) +# source_expression.update({subdomain: dict()}) +# if isR: +# subdomain_has_phases = ["wetting"] +# else: +# subdomain_has_phases = ["wetting", "nonwetting"] +# +# # conditional for S_pc_prime +# pc = pc_e_sym[subdomain] +# dtpc = sym.diff(pc, t, 1) +# dxpc = sym.diff(pc, x, 1) +# dypc = sym.diff(pc, y, 1) +# 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)) +# for phase in subdomain_has_phases: +# if phase == "nonwetting": +# dS = -dS +# dtS[subdomain].update( +# {phase: porosity[subdomain]*dS*dtpc} +# ) +# pa = p_e_sym[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 = ka_prime[subdomain][phase] +# rho = densities[subdomain][phase] +# g = gravity_acceleration +# +# if phase == "nonwetting": +# dxdxflux = 1/mu*dka(1-S)*dS*dxpc*dxpa - 1/mu*dxdxpa*ka(1-S) +# dydyflux = 1/mu*dka(1-S)*dS*dypc*(dypa - rho*g) \ +# - 1/mu*dydypa*ka(1-S) +# else: +# dxdxflux = -1/mu*dka(S)*dS*dxpc*dxpa - 1/mu*dxdxpa*ka(S) +# dydyflux = -1/mu*dka(S)*dS*dypc*(dypa - rho*g) - 1/mu*dydypa*ka(S) +# div_flux[subdomain].update({phase: dxdxflux + dydyflux}) +# contructed_rhs = dtS[subdomain][phase] + div_flux[subdomain][phase] +# source_expression[subdomain].update( +# {phase: sym.printing.ccode(contructed_rhs)} +# ) +# print(f"source_expression[{subdomain}][{phase}] =", +# source_expression[subdomain][phase]) + +# similarly to the outer boundary dictionary, if a patch has no outer boundary +# None should be written instead of an expression. +# This is a bit of a brainfuck: +# dirichletBC[ind] gives a dictionary of the outer boundaries of subdomain ind. +# Since a domain patch can have several disjoint outer boundary parts, the +# expressions need to get an enumaration index which starts at 0. +# So dirichletBC[ind][j] is the dictionary of outer dirichlet conditions of +# subdomain ind and boundary part j. +# Finally, dirichletBC[ind][j]['wetting'] and dirichletBC[ind][j]['nonwetting'] +# return the actual expression needed for the dirichlet condition for both +# phases if present. + +# subdomain index: {outer boudary part index: {phase: expression}} +# dirichletBC = { +# 1: {0: {'wetting': exact_solution[1]['wetting']}}, +# 2: {0: {'wetting': exact_solution[2]['wetting']}}, +# 3: {0: {'wetting': exact_solution[3]['wetting']}}, +# 4: {0: {'wetting': exact_solution[4]['wetting']}}, +# 5: {0: {'wetting': exact_solution[5]['wetting']}} +# } + +write_to_file = { + 'meshes_and_markers': True, + 'L_iterations': True +} + +# initialise LDD simulation class +simulation = ldd.LDDsimulation(tol=1E-14, debug=False, LDDsolver_tol=1E-9) +simulation.set_parameters(output_dir="./output/", + subdomain_def_points=subdomain_def_points, + isRichards=isRichards, + interface_def_points=interface_def_points, + outer_boundary_def_points=outer_boundary_def_points, + adjacent_subdomains=adjacent_subdomains, + mesh_resolution=mesh_resolution, + viscosity=viscosity, + porosity=porosity, + L=L, + lambda_param=lambda_param, + relative_permeability=relative_permeability, + saturation=sat_pressure_relationship, + starttime=starttime, + number_of_timesteps=number_of_timesteps, + number_of_timesteps_to_analyse=number_of_timesteps_to_analyse, + timestep_size=timestep_size, + sources=source_expression, + initial_conditions=initial_condition, + dirichletBC_expression_strings=dirichletBC, + exact_solution=exact_solution, + densities=densities, + include_gravity=True, + write2file=write_to_file, + ) + +simulation.initialise() +# print(simulation.__dict__) +simulation.run() +# simulation.LDDsolver(time=0, debug=True, analyse_timestep=True) +# df.info(parameters, True) diff --git a/TP-TP-layered-soil-case-const-solution/TP-TP-layered_soil-const-solution.py b/TP-TP-layered-soil-case-const-solution/TP-TP-layered_soil-const-solution.py new file mode 100755 index 0000000000000000000000000000000000000000..c7ff9e4a2712e3eadecb2e8bc1cfddac837ebea4 --- /dev/null +++ b/TP-TP-layered-soil-case-const-solution/TP-TP-layered_soil-const-solution.py @@ -0,0 +1,554 @@ +#!/usr/bin/python3 +"""This program sets up a domain together with a decomposition into subdomains +modelling layered soil. This is used for our LDD article with tp-tp and tp-r +coupling. + +Along with the subdomains and the mesh domain markers are set upself. +The resulting mesh is saved into files for later use. +""" + +#!/usr/bin/python3 +import dolfin as df +import mshr +import numpy as np +import sympy as sym +import typing as tp +import functools as ft +import domainPatch as dp +import LDDsimulation as ldd + +# init sympy session +sym.init_printing() + +# ----------------------------------------------------------------------------# +# ------------------- MESH ---------------------------------------------------# +# ----------------------------------------------------------------------------# +mesh_resolution = 30 +# ----------------------------------------:-------------------------------------# +# ------------------- TIME ---------------------------------------------------# +# ----------------------------------------------------------------------------# +timestep_size = 0.001 +number_of_timesteps = 100 +# decide how many timesteps you want analysed. Analysed means, that we write +# out subsequent errors of the L-iteration within the timestep. +number_of_timesteps_to_analyse = 10 +starttime = 0 + +l_param_w = 60 +l_param_nw = 60 + +# global domain +subdomain0_vertices = [df.Point(0.0,0.0), # + df.Point(13.0,0.0),# + df.Point(13.0,8.0),# + df.Point(0.0,8.0)] + +interface12_vertices = [df.Point(0.0, 7.0), + df.Point(9.0, 7.0), + df.Point(10.5, 7.5), + df.Point(12.0, 7.0), + df.Point(13.0, 6.5)] +# subdomain1. +subdomain1_vertices = [interface12_vertices[0], + interface12_vertices[1], + interface12_vertices[2], + interface12_vertices[3], + interface12_vertices[4], # southern boundary, 12 interface + subdomain0_vertices[2], # eastern boundary, outer boundary + subdomain0_vertices[3]] # northern boundary, outer on_boundary + +# vertex coordinates of the outer boundaries. If it can not be specified as a +# polygon, use an entry per boundary polygon. This information is used for defining +# the Dirichlet boundary conditions. If a domain is completely internal, the +# dictionary entry should be 0: None +subdomain1_outer_boundary_verts = { + 0: [interface12_vertices[4], # + subdomain0_vertices[2], # eastern boundary, outer boundary + subdomain0_vertices[3], + interface12_vertices[0]] +} + + +# interface23 +interface23_vertices = [df.Point(0.0, 5.0), + df.Point(3.0, 5.0), + # df.Point(6.5, 4.5), + df.Point(6.5, 5.0), + df.Point(9.5, 5.0), + # df.Point(11.5, 3.5), + # df.Point(13.0, 3) + df.Point(11.5, 5.0), + df.Point(13.0, 5.0) + ] + +#subdomain1 +subdomain2_vertices = [interface23_vertices[0], + interface23_vertices[1], + interface23_vertices[2], + interface23_vertices[3], + interface23_vertices[4], + interface23_vertices[5], # southern boundary, 23 interface + subdomain1_vertices[4], # eastern boundary, outer boundary + subdomain1_vertices[3], + subdomain1_vertices[2], + subdomain1_vertices[1], + subdomain1_vertices[0] ] # northern boundary, 12 interface + +subdomain2_outer_boundary_verts = { + 0: [interface23_vertices[5], + subdomain1_vertices[4]], + 1: [subdomain1_vertices[0], + interface23_vertices[0]] +} + + +# interface34 +interface34_vertices = [df.Point(0.0, 2.0), + df.Point(4.0, 2.0), + df.Point(9.0, 2.5), + df.Point(10.5, 2.0), + df.Point(13.0, 1.5)] + +# subdomain3 +subdomain3_vertices = [interface34_vertices[0], + interface34_vertices[1], + interface34_vertices[2], + interface34_vertices[3], + interface34_vertices[4], # southern boundary, 34 interface + subdomain2_vertices[5], # eastern boundary, outer boundary + subdomain2_vertices[4], + subdomain2_vertices[3], + subdomain2_vertices[2], + subdomain2_vertices[1], + subdomain2_vertices[0] ] # northern boundary, 23 interface + +subdomain3_outer_boundary_verts = { + 0: [interface34_vertices[4], + subdomain2_vertices[5]], + 1: [subdomain2_vertices[0], + interface34_vertices[0]] +} + +# subdomain4 +subdomain4_vertices = [subdomain0_vertices[0], + subdomain0_vertices[1], # southern boundary, outer boundary + subdomain3_vertices[4],# eastern boundary, outer boundary + subdomain3_vertices[3], + subdomain3_vertices[2], + subdomain3_vertices[1], + subdomain3_vertices[0] ] # northern boundary, 34 interface + +subdomain4_outer_boundary_verts = { + 0: [subdomain4_vertices[6], + subdomain4_vertices[0], + subdomain4_vertices[1], + subdomain4_vertices[2]] +} + + +subdomain_def_points = [subdomain0_vertices,# + subdomain1_vertices,# + subdomain2_vertices,# + subdomain3_vertices,# + subdomain4_vertices + ] + + +# interface_vertices introduces a global numbering of interfaces. +interface_def_points = [interface12_vertices, interface23_vertices, interface34_vertices] +adjacent_subdomains = [[1,2], [2,3], [3,4]] + +# if a subdomain has no outer boundary write None instead, i.e. +# i: None +# if i is the index of the inner subdomain. +outer_boundary_def_points = { + # subdomain number + 1: subdomain1_outer_boundary_verts, + 2: subdomain2_outer_boundary_verts, + 3: subdomain3_outer_boundary_verts, + 4: subdomain4_outer_boundary_verts +} + +isRichards = { + 1: False, + 2: False, + 3: False, + 4: False + } + +# isRichards = { +# 1: True, +# 2: True, +# 3: True, +# 4: True +# } + +# Dict of the form: { subdom_num : viscosity } +viscosity = { + 1: {'wetting' :1, + 'nonwetting': 1/50}, + 2: {'wetting' :1, + 'nonwetting': 1/50}, + 3: {'wetting' :1, + 'nonwetting': 1/50}, + 4: {'wetting' :1, + 'nonwetting': 1/50}, +} + +# Dict of the form: { subdom_num : density } +densities = { + 1: {'wetting': 997, + 'nonwetting': 1.225}, + 2: {'wetting': 997, + 'nonwetting': 1.225}, + 3: {'wetting': 997, + 'nonwetting': 1.225}, + 4: {'wetting': 997, + 'nonwetting': 1.225} +} + +gravity_acceleration = 9.81 +# porosities taken from +# https://www.geotechdata.info/parameter/soil-porosity.html +# Dict of the form: { subdom_num : porosity } +porosity = { + 1: 0.2, # Clayey gravels, clayey sandy gravels + 2: 0.22, # Silty gravels, silty sandy gravels + 3: 0.37, # Clayey sands + 4: 0.2 # Silty or sandy clay +} + +# subdom_num : subdomain L for L-scheme +L = { + 1: {'wetting' :0.4, + 'nonwetting': 0.4}, + 2: {'wetting' :0.4, + 'nonwetting': 0.4}, + 3: {'wetting' :0.4, + 'nonwetting': 0.4}, + 4: {'wetting' :0.4, + 'nonwetting': 0.4} +} + +# subdom_num : lambda parameter for the L-scheme +lambda_param = { + 1: {'wetting': l_param_w, + 'nonwetting': l_param_nw},# + 2: {'wetting': l_param_w, + 'nonwetting': l_param_nw},# + 3: {'wetting': l_param_w, + 'nonwetting': l_param_nw},# + 4: {'wetting': l_param_w, + 'nonwetting': l_param_nw},# +} + + +## relative permeabilty functions on subdomain 1 +def rel_perm1w(s): + # relative permeabilty wetting on subdomain1 + return s**2 + + +def rel_perm1nw(s): + # relative permeabilty nonwetting on subdomain1 + return (1-s)**2 + + +## relative permeabilty functions on subdomain 2 +def rel_perm2w(s): + # relative permeabilty wetting on subdomain2 + return s**3 + + +def rel_perm2nw(s): + # relative permeabilty nonwetting on subdosym.cos(0.8*t - (0.8*x + 1/7*y))main2 + return (1-s)**2 + + +_rel_perm1w = ft.partial(rel_perm1w) +_rel_perm1nw = ft.partial(rel_perm1nw) +_rel_perm2w = ft.partial(rel_perm2w) +_rel_perm2nw = ft.partial(rel_perm2nw) + +subdomain1_rel_perm = { + 'wetting': _rel_perm1w,# + 'nonwetting': _rel_perm1nw +} + +subdomain2_rel_perm = { + 'wetting': _rel_perm2w,# + 'nonwetting': _rel_perm2nw +} + +# _rel_perm3 = ft.partial(rel_perm2) +# subdomain3_rel_perm = subdomain2_rel_perm.copy() +# +# _rel_perm4 = ft.partial(rel_perm1) +# subdomain4_rel_perm = subdomain1_rel_perm.copy() + +# dictionary of relative permeabilties on all domains. +relative_permeability = { + 1: subdomain1_rel_perm, + 2: subdomain1_rel_perm, + 3: subdomain2_rel_perm, + 4: subdomain2_rel_perm +} + +# definition of the derivatives of the relative permeabilities +# relative permeabilty functions on subdomain 1 +def rel_perm1w_prime(s): + # relative permeabilty on subdomain1 + return 2*s + +def rel_perm1nw_prime(s): + # relative permeabilty on subdomain1 + return 2*(1-s) + +# definition of the derivatives of the relative permeabilities +# relative permeabilty functions on subdomain 1 +def rel_perm2w_prime(s): + # relative permeabilty on subdomain1 + return 3*s**2 + +def rel_perm2nw_prime(s): + # relative permeabilty on subdomain1 + return 2*(1-s) + +_rel_perm1w_prime = ft.partial(rel_perm1w_prime) +_rel_perm1nw_prime = ft.partial(rel_perm1nw_prime) +_rel_perm2w_prime = ft.partial(rel_perm2w_prime) +_rel_perm2nw_prime = ft.partial(rel_perm2nw_prime) + +subdomain1_rel_perm_prime = { + 'wetting': _rel_perm1w_prime, + 'nonwetting': _rel_perm1nw_prime +} + + +subdomain2_rel_perm_prime = { + 'wetting': _rel_perm2w_prime, + 'nonwetting': _rel_perm2nw_prime +} + +# dictionary of relative permeabilties on all domains. +ka_prime = { + 1: subdomain1_rel_perm_prime, + 2: subdomain1_rel_perm_prime, + 3: subdomain2_rel_perm_prime, + 4: subdomain2_rel_perm_prime +} + + + +# S-pc-relation ship. We use the van Genuchten approach, i.e. pc = 1/alpha*(S^{-1/m} -1)^1/n, where +# we set alpha = 0, assume m = 1-1/n (see Helmig) and assume that residual saturation is Sw +# this function needs to be monotonically decreasing in the capillary pressure pc. +# since in the richards case pc=-pw, this becomes as a function of pw a mono +# tonically INCREASING function like in our Richards-Richards paper. However +# since we unify the treatment in the code for Richards and two-phase, we need +# the same requierment +# for both cases, two-phase and Richards. +def saturation(pc, n_index, alpha): + # inverse capillary pressure-saturation-relationship + return df.conditional(pc > 0, 1/((1 + (alpha*pc)**n_index)**((n_index - 1)/n_index)), 1) + +# S-pc-relation ship. We use the van Genuchten approach, i.e. pc = 1/alpha*(S^{-1/m} -1)^1/n, where +# we set alpha = 0, assume m = 1-1/n (see Helmig) and assume that residual saturation is Sw +def saturation_sym(pc, n_index, alpha): + # inverse capillary pressure-saturation-relationship + #df.conditional(pc > 0, + return 1/((1 + (alpha*pc)**n_index)**((n_index - 1)/n_index)) + + +# derivative of S-pc relationship with respect to pc. This is needed for the +# construction of a analytic solution. +def saturation_sym_prime(pc, n_index, alpha): + # inverse capillary pressure-saturation-relationship + return -(alpha*(n_index - 1)*(alpha*pc)**(n_index - 1)) / ( (1 + (alpha*pc)**n_index)**((2*n_index - 1)/n_index) ) + + +# note that the conditional definition of S-pc in the nonsymbolic part will be +# incorporated in the construction of the exact solution below. +S_pc_sym = { + 1: ft.partial(saturation_sym, n_index=3, alpha=0.001), + 2: ft.partial(saturation_sym, n_index=3, alpha=0.001), + 3: ft.partial(saturation_sym, n_index=6, alpha=0.001), + 4: ft.partial(saturation_sym, n_index=6, alpha=0.001) +} + +S_pc_sym_prime = { + 1: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), + 2: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), + 3: ft.partial(saturation_sym_prime, n_index=6, alpha=0.001), + 4: ft.partial(saturation_sym_prime, n_index=6, alpha=0.001) +} + +sat_pressure_relationship = { + 1: ft.partial(saturation, n_index=3, alpha=0.001), + 2: ft.partial(saturation, n_index=3, alpha=0.001), + 3: ft.partial(saturation, n_index=6, alpha=0.001), + 4: ft.partial(saturation, n_index=6, alpha=0.001) +} + + +############################################# +# Manufacture source expressions with sympy # +############################################# +x, y = sym.symbols('x[0], x[1]') # needed by UFL +t = sym.symbols('t', positive=True) + +p_e_sym = { + 1: {'wetting': -3 + 0*t, + 'nonwetting': -1+ 0*t}, + 2: {'wetting': -3 + 0*t, + 'nonwetting': -1+ 0*t}, + 3: {'wetting': -3 + 0*t, + 'nonwetting': -1+ 0*t}, + 4: {'wetting': -3 + 0*t, + 'nonwetting': -1+ 0*t}, +} + +pc_e_sym = { + 1: p_e_sym[1]['nonwetting'] - p_e_sym[1]['wetting'], + 2: p_e_sym[2]['nonwetting'] - p_e_sym[2]['wetting'], + 3: p_e_sym[3]['nonwetting'] - p_e_sym[3]['wetting'], + 4: p_e_sym[4]['nonwetting'] - p_e_sym[4]['wetting'] +} + +# turn above symbolic code into exact solution for dolphin and +# construct the rhs that matches the above exact solution. +dtS = dict() +div_flux = dict() +source_expression = dict() +exact_solution = dict() +initial_condition = dict() +for subdomain, isR in isRichards.items(): + dtS.update({subdomain: dict()}) + div_flux.update({subdomain: dict()}) + source_expression.update({subdomain: dict()}) + exact_solution.update({subdomain: dict()}) + initial_condition.update({subdomain: dict()}) + if isR: + subdomain_has_phases = ["wetting"] + else: + subdomain_has_phases = ["wetting", "nonwetting"] + + # conditional for S_pc_prime + pc = pc_e_sym[subdomain] + dtpc = sym.diff(pc, t, 1) + dxpc = sym.diff(pc, x, 1) + dypc = sym.diff(pc, y, 1) + 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)) + for phase in subdomain_has_phases: + # Turn above symbolic expression for exact solution into c code + exact_solution[subdomain].update( + {phase: sym.printing.ccode(p_e_sym[subdomain][phase])} + ) + # save the c code for initial conditions + initial_condition[subdomain].update( + {phase: sym.printing.ccode(p_e_sym[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 = p_e_sym[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 = ka_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 + dydyflux = -1/mu*dka(1-S)*dS*dypc*(dypa - rho*g) \ + + 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 + dydyflux = 1/mu*dka(S)*dS*dypc*(dypa - rho*g) + 1/mu*dydypa*ka(S) + div_flux[subdomain].update({phase: dxdxflux + dydyflux}) + contructed_rhs = dtS[subdomain][phase] - div_flux[subdomain][phase] + source_expression[subdomain].update( + {phase: sym.printing.ccode(contructed_rhs)} + ) + # print(f"source_expression[{subdomain}][{phase}] =", source_expression[subdomain][phase]) + +# Dictionary of dirichlet boundary conditions. +dirichletBC = dict() +# similarly to the outer boundary dictionary, if a patch has no outer boundary +# None should be written instead of an expression. +# This is a bit of a brainfuck: +# dirichletBC[ind] gives a dictionary of the outer boundaries of subdomain ind. +# Since a domain patch can have several disjoint outer boundary parts, the +# expressions need to get an enumaration index which starts at 0. +# So dirichletBC[ind][j] is the dictionary of outer dirichlet conditions of +# subdomain ind and boundary part j. +# Finally, dirichletBC[ind][j]['wetting'] and dirichletBC[ind][j]['nonwetting'] +# return the actual expression needed for the dirichlet condition for both +# phases if present. + +# subdomain index: {outer boudary part index: {phase: expression}} +for subdomain in isRichards.keys(): + # if subdomain has no outer boundary, outer_boundary_def_points[subdomain] is None + if outer_boundary_def_points[subdomain] is None: + dirichletBC.update({subdomain: None}) + else: + dirichletBC.update({subdomain: dict()}) + # set the dirichlet conditions to be the same code as exact solution on + # the subdomain. + for outer_boundary_ind in outer_boundary_def_points[subdomain].keys(): + dirichletBC[subdomain].update( + {outer_boundary_ind: exact_solution[subdomain]} + ) + +write_to_file = { + 'meshes_and_markers': True, + 'L_iterations': True +} + +# initialise LDD simulation class +simulation = ldd.LDDsimulation(tol=1E-14, debug=False, LDDsolver_tol=1E-7) +simulation.set_parameters(output_dir="./output/", + subdomain_def_points=subdomain_def_points, + isRichards=isRichards, + interface_def_points=interface_def_points, + outer_boundary_def_points=outer_boundary_def_points, + adjacent_subdomains=adjacent_subdomains, + mesh_resolution=mesh_resolution, + viscosity=viscosity, + porosity=porosity, + L=L, + lambda_param=lambda_param, + relative_permeability=relative_permeability, + saturation=sat_pressure_relationship, + starttime=starttime, + number_of_timesteps=number_of_timesteps, + number_of_timesteps_to_analyse=number_of_timesteps_to_analyse, + timestep_size=timestep_size, + sources=source_expression, + initial_conditions=initial_condition, + dirichletBC_expression_strings=dirichletBC, + exact_solution=exact_solution, + densities=densities, + include_gravity=True, + write2file=write_to_file, + ) + +simulation.initialise() +# print(simulation.__dict__) +simulation.run() +# simulation.LDDsolver(time=0, debug=True, analyse_timestep=True) +# df.info(parameters, True) diff --git a/TP-TP-layered-soil-case-with-inner-patch-constant-solution/TP-TP-layered_soil_with_inner_patch_const_solution.py b/TP-TP-layered-soil-case-with-inner-patch-constant-solution/TP-TP-layered_soil_with_inner_patch_const_solution.py new file mode 100755 index 0000000000000000000000000000000000000000..04505ac40c5e608f599fd791c836970349e3ca5d --- /dev/null +++ b/TP-TP-layered-soil-case-with-inner-patch-constant-solution/TP-TP-layered_soil_with_inner_patch_const_solution.py @@ -0,0 +1,775 @@ +#!/usr/bin/python3 +"""This program sets up a domain together with a decomposition into subdomains +modelling layered soil. This is used for our LDD article with tp-tp and tp-r +coupling. + +Along with the subdomains and the mesh domain markers are set upself. +The resulting mesh is saved into files for later use. +""" + +#!/usr/bin/python3 +import dolfin as df +import mshr +import numpy as np +import sympy as sym +import typing as tp +import functools as ft +import domainPatch as dp +import LDDsimulation as ldd + +# init sympy session +sym.init_printing() + +# ----------------------------------------------------------------------------# +# ------------------- MESH ---------------------------------------------------# +# ----------------------------------------------------------------------------# +mesh_resolution = 20 +# ----------------------------------------:-----------------------------------# +# ------------------- TIME ---------------------------------------------------# +# ----------------------------------------------------------------------------# +timestep_size = 0.0001 +number_of_timesteps = 10 +# decide how many timesteps you want analysed. Analysed means, that we write +# out subsequent errors of the L-iteration within the timestep. +number_of_timesteps_to_analyse = 4 +starttime = 0 + +Lw = 0.3 +Lnw = 0.4 + +l_param_w = 40 +l_param_nw = 40 + +# global domain +subdomain0_vertices = [df.Point(0.0,0.0), # + df.Point(13.0,0.0),# + df.Point(13.0,8.0),# + df.Point(0.0,8.0)] + +interface12_vertices = [df.Point(0.0, 7.0), + df.Point(9.0, 7.0), + df.Point(10.5, 7.5), + df.Point(12.0, 7.0), + df.Point(13.0, 6.5)] + + +# interface23 +interface23_vertices = [df.Point(0.0, 5.0), + df.Point(3.0, 5.0), + # df.Point(6.5, 4.5), + df.Point(6.5, 5.0)] + +interface24_vertices = [df.Point(6.5, 5.0), + df.Point(9.5, 5.0), + # df.Point(11.5, 3.5), + # df.Point(13.0, 3) + df.Point(11.5, 5.0) + ] + +interface25_vertices = [df.Point(11.5, 5.0), + df.Point(13.0, 5.0) + ] + + +interface32_vertices = [interface23_vertices[2], + interface23_vertices[1], + interface23_vertices[0]] + +interface34_vertices = [df.Point(4.0, 2.0), + df.Point(4.7, 3.0), + interface23_vertices[2]] +# interface36 +interface36_vertices = [df.Point(0.0, 2.0), + df.Point(4.0, 2.0)] + + +interface46_vertices = [df.Point(4.0, 2.0), + df.Point(9.0, 2.5)] + +interface45_vertices = [df.Point(9.0, 2.5), + df.Point(10.0, 3.0), + interface25_vertices[0] + ] + +interface56_vertices = [df.Point(9.0, 2.5), + df.Point(10.5, 2.0), + df.Point(13.0, 1.5)] + + +# interface_vertices introduces a global numbering of interfaces. +interface_def_points = [interface12_vertices, + interface23_vertices, + interface24_vertices, + interface25_vertices, + interface34_vertices, + interface36_vertices, + interface45_vertices, + interface46_vertices, + interface56_vertices, + ] +adjacent_subdomains = [[1,2], + [2,3], + [2,4], + [2,5], + [3,4], + [3,6], + [4,5], + [4,6], + [5,6] + ] + +# subdomain1. +subdomain1_vertices = [interface12_vertices[0], + interface12_vertices[1], + interface12_vertices[2], + interface12_vertices[3], + interface12_vertices[4], # southern boundary, 12 interface + subdomain0_vertices[2], # eastern boundary, outer boundary + subdomain0_vertices[3]] # northern boundary, outer on_boundary + +# vertex coordinates of the outer boundaries. If it can not be specified as a +# polygon, use an entry per boundary polygon. This information is used for defining +# the Dirichlet boundary conditions. If a domain is completely internal, the +# dictionary entry should be 0: None +subdomain1_outer_boundary_verts = { + 0: [interface12_vertices[4], # + subdomain0_vertices[2], # eastern boundary, outer boundary + subdomain0_vertices[3], + interface12_vertices[0]] +} + +#subdomain1 +subdomain2_vertices = [interface23_vertices[0], + interface23_vertices[1], + interface23_vertices[2], + interface24_vertices[1], + interface24_vertices[2], + interface25_vertices[1], # southern boundary, 23 interface + subdomain1_vertices[4], # eastern boundary, outer boundary + subdomain1_vertices[3], + subdomain1_vertices[2], + subdomain1_vertices[1], + subdomain1_vertices[0] ] # northern boundary, 12 interface + +subdomain2_outer_boundary_verts = { + 0: [interface25_vertices[1], + subdomain1_vertices[4]], + 1: [subdomain1_vertices[0], + interface23_vertices[0]] +} + + +subdomain3_vertices = [interface36_vertices[0], + interface36_vertices[1], + # interface34_vertices[0], + interface34_vertices[1], + interface34_vertices[2], + # interface32_vertices[0], + interface32_vertices[1], + interface32_vertices[2] + ] + +subdomain3_outer_boundary_verts = { + 0: [subdomain2_vertices[0], + subdomain3_vertices[0]] +} + + +# subdomain3 +subdomain4_vertices = [interface46_vertices[0], + interface46_vertices[1], + df.Point(10.0, 3.0), + interface24_vertices[2], + interface24_vertices[1], + interface24_vertices[0], + interface34_vertices[1] + ] + +subdomain4_outer_boundary_verts = None + +subdomain5_vertices = [interface56_vertices[0], + interface56_vertices[1], + interface56_vertices[2], + interface25_vertices[1], + interface25_vertices[0], + interface45_vertices[1], + interface45_vertices[0] +] + +subdomain5_outer_boundary_verts = { + 0: [subdomain5_vertices[2], + subdomain5_vertices[3]] +} + + + +subdomain6_vertices = [subdomain0_vertices[0], + subdomain0_vertices[1], # southern boundary, outer boundary + interface56_vertices[2], + interface56_vertices[1], + interface56_vertices[0], + interface36_vertices[1], + interface36_vertices[0] + ] + +subdomain6_outer_boundary_verts = { + 0: [subdomain4_vertices[6], + subdomain4_vertices[0], + subdomain4_vertices[1], + subdomain4_vertices[2]] +} + + +subdomain_def_points = [subdomain0_vertices,# + subdomain1_vertices,# + subdomain2_vertices,# + subdomain3_vertices,# + subdomain4_vertices, + subdomain5_vertices, + subdomain6_vertices + ] + + +# if a subdomain has no outer boundary write None instead, i.e. +# i: None +# if i is the index of the inner subdomain. +outer_boundary_def_points = { + # subdomain number + 1: subdomain1_outer_boundary_verts, + 2: subdomain2_outer_boundary_verts, + 3: subdomain3_outer_boundary_verts, + 4: subdomain4_outer_boundary_verts, + 5: subdomain5_outer_boundary_verts, + 6: subdomain6_outer_boundary_verts +} + +# isRichards = { +# 1: False, +# 2: False, +# 3: False, +# 4: False, +# 5: False, +# 6: False +# } + +isRichards = { + 1: True, + 2: True, + 3: True, + 4: True, + 5: True, + 6: True + } + +visc = {'wetting': 1, + 'nonwetting': 1/50} +dens = {'wetting': 1, + 'nonwetting': 1} +poro = 1 +number_of_subdomains = 0 +viscosity = dict() +densities = dict() +porosity = dict() +Ldict = {'wetting': Lw, + 'nonwetting': Lnw} +Lambda = {'wetting': l_param_w, + 'nonwetting': l_param_nw} +L = dict() +lambda_param = dict() +for subdomain, isR in isRichards.items(): + number_of_subdomains += 1 + viscosity.update({subdomain: dict()}) + densities.update({subdomain: dict()}) + L.update({subdomain: dict()}) + lambda_param.update({subdomain: dict()}) + porosity.update({subdomain: poro}) + subdom_has_phase = ['wetting'] + if not isR: + subdom_has_phase = ['wetting', 'nonwetting'] + for phase in subdom_has_phase: + viscosity[subdomain].update({phase: visc[phase]}) + densities[subdomain].update({phase: dens[phase]}) + L[subdomain].update({phase: Ldict[phase]}) + lambda_param[subdomain].update({phase: Lambda[phase]}) + +# Dict of the form: { subdom_num : viscosity } +# viscosity = { +# 1: {'wetting' :1, +# 'nonwetting': 1/50}, +# 2: {'wetting' :1, +# 'nonwetting': 1/50}, +# 3: {'wetting' :1, +# 'nonwetting': 1/50}, +# 4: {'wetting' :1, +# 'nonwetting': 1/50}, +# 5: {'wetting' :1, +# 'nonwetting': 1/50}, +# 6: {'wetting' :1, +# 'nonwetting': 1/50}, +# } + +# # Dict of the form: { subdom_num : density } +# densities = { +# 1: {'wetting': 1, #997 +# 'nonwetting': 1}, #1}, #1.225}, +# 2: {'wetting': 1, #997 +# 'nonwetting': 1}, #1.225}, +# 3: {'wetting': 1, #997 +# 'nonwetting': 1}, #1.225}, +# 4: {'wetting': 1, #997 +# 'nonwetting': 1}, #1.225} +# 5: {'wetting': 1, #997 +# 'nonwetting': 1}, #1.225}, +# 6: {'wetting': 1, #997 +# 'nonwetting': 1} #1.225} +# } + +gravity_acceleration = 9.81 +# porosities taken from +# https://www.geotechdata.info/parameter/soil-porosity.html +# Dict of the form: { subdom_num : porosity } +# porosity = { +# 1: 1, #0.2, # Clayey gravels, clayey sandy gravels +# 2: 1, #0.22, # Silty gravels, silty sandy gravels +# 3: 1, #0.37, # Clayey sands +# 4: 1, #0.2 # Silty or sandy clay +# 5: 1, # +# 6: 1, # +# } + +# # subdom_num : subdomain L for L-scheme +# L = { +# 1: {'wetting' :Lw, +# 'nonwetting': Lnw}, +# 2: {'wetting' :Lw, +# 'nonwetting': Lnw}, +# 3: {'wetting' :Lw, +# 'nonwetting': Lnw}, +# 4: {'wetting' :Lw, +# 'nonwetting': Lnw}, +# 5: {'wetting' :Lw, +# 'nonwetting': Lnw}, +# 6: {'wetting' :Lw, +# 'nonwetting': Lnw} +# } +# +# # subdom_num : lambda parameter for the L-scheme +# lambda_param = { +# 1: {'wetting': l_param_w, +# 'nonwetting': l_param_nw},# +# 2: {'wetting': l_param_w, +# 'nonwetting': l_param_nw},# +# 3: {'wetting': l_param_w, +# 'nonwetting': l_param_nw},# +# 4: {'wetting': l_param_w, +# 'nonwetting': l_param_nw},# +# 5: {'wetting': l_param_w, +# 'nonwetting': l_param_nw},# +# 6: {'wetting': l_param_w, +# 'nonwetting': l_param_nw},# +# } + + +## relative permeabilty functions on subdomain 1 +def rel_perm1w(s): + # relative permeabilty wetting on subdomain1 + return s**2 + + +def rel_perm1nw(s): + # relative permeabilty nonwetting on subdomain1 + return (1-s)**2 + + +# ## relative permeabilty functions on subdomain 2 +# def rel_perm2w(s): +# # relative permeabilty wetting on subdomain2 +# return s**3 +# +# +# def rel_perm2nw(s): +# # relative permeabilty nonwetting on subdosym.cos(0.8*t - (0.8*x + 1/7*y))main2 +# return (1-s)**2 + + +_rel_perm1w = ft.partial(rel_perm1w) +_rel_perm1nw = ft.partial(rel_perm1nw) +# _rel_perm2w = ft.partial(rel_perm2w) +# _rel_perm2nw = ft.partial(rel_perm2nw) + +subdomain1_rel_perm = { + 'wetting': _rel_perm1w,# + 'nonwetting': _rel_perm1nw +} + +# subdomain2_rel_perm = { +# 'wetting': _rel_perm2w,# +# 'nonwetting': _rel_perm2nw +# } + +# _rel_perm3 = ft.partial(rel_perm2) +# subdomain3_rel_perm = subdomain2_rel_perm.copy() +# +# _rel_perm4 = ft.partial(rel_perm1) +# subdomain4_rel_perm = subdomain1_rel_perm.copy() + +# dictionary of relative permeabilties on all domains. +relative_permeability = { + 1: subdomain1_rel_perm, + 2: subdomain1_rel_perm, + 3: subdomain1_rel_perm, + 4: subdomain1_rel_perm, + 5: subdomain1_rel_perm, + 6: subdomain1_rel_perm, +} + +# definition of the derivatives of the relative permeabilities +# relative permeabilty functions on subdomain 1 +def rel_perm1w_prime(s): + # relative permeabilty on subdomain1 + return 2*s + +def rel_perm1nw_prime(s): + # relative permeabilty on subdomain1 + return 2*(1-s) + +# # definition of the derivatives of the relative permeabilities +# # relative permeabilty functions on subdomain 1 +# def rel_perm2w_prime(s): +# # relative permeabilty on subdomain1 +# return 3*s**2 +# +# def rel_perm2nw_prime(s): +# # relative permeabilty on subdomain1 +# return 2*(l_param_w1-s) + +_rel_perm1w_prime = ft.partial(rel_perm1w_prime) +_rel_perm1nw_prime = ft.partial(rel_perm1nw_prime) +# _rel_perm2w_prime = ft.partial(rel_perm2w_prime) +# _rel_perm2nw_prime = ft.partial(rel_perm2nw_prime) + +subdomain1_rel_perm_prime = { + 'wetting': _rel_perm1w_prime, + 'nonwetting': _rel_perm1nw_prime +} + + +# subdomain2_rel_perm_prime = { +# 'wetting': _rel_perm2w_prime, +# 'nonwetting': _rel_perm2nw_prime +# } + +# dictionary of relative permeabilties on all domains. +ka_prime = { + 1: subdomain1_rel_perm_prime, + 2: subdomain1_rel_perm_prime, + 3: subdomain1_rel_perm_prime, + 4: subdomain1_rel_perm_prime, + 5: subdomain1_rel_perm_prime, + 6: subdomain1_rel_perm_prime, +} + + + +# S-pc-relation ship. We use the van Genuchten approach, i.e. pc = 1/alpha*(S^{-1/m} -1)^1/n, where +# we set alpha = 0, assume m = 1-1/n (see Helmig) and assume that residual saturation is Sw +# this function needs to be monotonically decreasing in the capillary pressure pc. +# since in the richards case pc=-pw, this becomes as a function of pw a mono +# tonically INCREASING function like in our Richards-Richards paper. However +# since we unify the treatment in the code for Richards and two-phase, we need +# the same requierment +# for both cases, two-phase and Richards. +# def saturation(pc, n_index, alpha): +# # inverse capillary pressure-saturation-relationship +# return df.conditional(pc > 0, 1/((1 + (alpha*pc)**n_index)**((n_index - 1)/n_index)), 1) +# +# # S-pc-relation ship. We use the van Genuchten approach, i.e. pc = 1/alpha*(S^{-1/m} -1)^1/n, where +# # we set alpha = 0, assume m = 1-1/n (see Helmig) and assume that residual saturation is Sw +# def saturation_sym(pc, n_index, alpha): +# # inverse capillary pressure-saturation-relationship +# #df.conditional(pc > 0, +# return 1/((1 + (alpha*pc)**n_index)**((n_index - 1)/n_index)) +# +# +# # derivative of S-pc relationship with respect to pc. This is needed for the +# # construction of a analytic solution. +# def saturation_sym_prime(pc, n_index, alpha): +# # inverse capillary pressure-saturation-relationship +# return -(alpha*(n_index - 1)*(alpha*pc)**(n_index - 1)) / ( (1 + (alpha*pc)**n_index)**((2*n_index - 1)/n_index) ) +# +# derivative of S-pc relationship with respect to pc. This is needed for the +# construction of a analytic solution. + +# +# # note that the conditional definition of S-pc in the nonsymbolic part will be +# # incorporated in the construction of the exact solution below. +# S_pc_sym = { +# 1: ft.partial(saturation_sym, n_index=3, alpha=0.001), +# 2: ft.partial(saturation_sym, n_index=3, alpha=0.001), +# 3: ft.partial(saturation_sym, n_index=3, alpha=0.001), +# 4: ft.partial(saturation_sym, n_index=3, alpha=0.001), +# 5: ft.partial(saturation_sym, n_index=3, alpha=0.001), +# 6: ft.partial(saturation_sym, n_index=3, alpha=0.001) +# } +# +# S_pc_sym_prime = { +# 1: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), +# 2: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), +# 3: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), +# 4: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), +# 5: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), +# 6: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001) +# } +# +# sat_pressure_relationship = { +# 1: ft.partial(saturation, n_index=3, alpha=0.001), +# 2: ft.partial(saturation, n_index=3, alpha=0.001), +# 3: ft.partial(saturation, n_index=3, alpha=0.001), +# 4: ft.partial(saturation, n_index=3, alpha=0.001), +# 5: ft.partial(saturation, n_index=3, alpha=0.001), +# 6: ft.partial(saturation, n_index=3, alpha=0.001) +# } + +def saturation(pc, n_index): + # inverse capillary pressure-saturation-relationship + return df.conditional(pc > 0, 1/((1 + pc)**(1/(n_index + 1))), 1) + + +def saturation_sym(pc, n_index): + # inverse capillary pressure-saturation-relationship + return 1/((1 + pc)**(1/(n_index + 1))) + +def saturation_sym_prime(pc, n_index): + # inverse capillary pressure-saturation-relationship + return -1/((n_index+1)*(1 + pc)**((n_index+2)/(n_index+1))) + + +S_pc_sym = { + 1: ft.partial(saturation_sym, n_index=1), + 2: ft.partial(saturation_sym, n_index=1), + 3: ft.partial(saturation_sym, n_index=2), + 4: ft.partial(saturation_sym, n_index=2), + 5: ft.partial(saturation_sym, n_index=2), + 6: ft.partial(saturation_sym, n_index=2) +} + +S_pc_sym_prime = { + 1: ft.partial(saturation_sym_prime, n_index=1), + 2: ft.partial(saturation_sym_prime, n_index=1), + 3: ft.partial(saturation_sym_prime, n_index=2), + 4: ft.partial(saturation_sym_prime, n_index=2), + 5: ft.partial(saturation_sym_prime, n_index=2), + 6: ft.partial(saturation_sym_prime, n_index=2) +} + +sat_pressure_relationship = { + 1: ft.partial(saturation, n_index=1), + 2: ft.partial(saturation, n_index=1), + 3: ft.partial(saturation, n_index=2), + 4: ft.partial(saturation, n_index=2), + 5: ft.partial(saturation, n_index=2), + 6: ft.partial(saturation, n_index=2) +} + + +############################################# +# Manufacture source expressions with sympy # +############################################# +x, y = sym.symbols('x[0], x[1]') # needed by UFL +t = sym.symbols('t', positive=True) + +p_dict = {'wetting': -3 + 0*t, + 'nonwetting': -2 + 0*t} +p_e_sym = dict() +pc_e_sym = dict() +for subdomain, isR in isRichards.items(): + p_e_sym.update({subdomain: dict()}) + subdom_has_phase = ['wetting'] + if not isR: + subdom_has_phase = ['wetting', 'nonwetting'] + for phase in subdom_has_phase: + p_e_sym[subdomain].update({phase: p_dict[phase]}) + + if isR: + pc_e_sym.update({subdomain: -p_e_sym[subdomain]['wetting']}) + else: + pc_e_sym.update({subdomain: p_e_sym[subdomain]['nonwetting'] - p_e_sym[subdomain]['wetting']}) + + +# p_e_sym = { +# 1: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + (x-6.5)*(x-6.5) + (y-5.0)*(y-5.0)), +# 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0)) }, +# 2: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + (x-6.5)*(x-6.5) + (y-5.0)*(y-5.0)), +# 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0)) }, +# 3: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + (x-6.5)*(x-6.5)), +# 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0)) }, +# 4: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + (x-6.5)*(x-6.5)), +# 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0)) }, +# 5: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + (x-6.5)*(x-6.5)), +# 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0)) }, +# 6: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + (x-6.5)*(x-6.5)), +# 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0)) }, +# # 2: {'wetting': 1.0 - (1.0 + t*t)*(10.0 + x*x + (y-5.0)*(y-5.0)), +# # 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0))}, +# # 3: {'wetting': 1.0 - (1.0 + t*t)*(10.0 + x*x + (y-5.0)*(y-5.0)*3*sym.sin(-2*t+2*x)*sym.sin(1/2*y-1.2*t)), +# # 'nonwetting': - 2 - t*(1 + x**2)**2 -sym.sqrt(2+t**2)}, +# # 4: {'wetting': 1.0 - (1.0 + t*t)*(10.0 + x*x + (y-5.0)*(y-5.0)*3*sym.sin(-2*t+2*x)*sym.sin(1/2*y-1.2*t)), +# # 'nonwetting': - 2 - t*(1 + x**2)**2 -sym.sqrt(2+t**2)} +# } + +# pc_e_sym = { +# 1: p_e_sym[1]['nonwetting'] - p_e_sym[1]['wetting'], +# 2: p_e_sym[2]['nonwetting'] - p_e_sym[2]['wetting'], +# 3: p_e_sym[3]['nonwetting'] - p_e_sym[3]['wetting'], +# 4: p_e_sym[4]['nonwetting'] - p_e_sym[4]['wetting'], +# 5: p_e_sym[5]['nonwetting'] - p_e_sym[5]['wetting'], +# 6: p_e_sym[5]['nonwetting'] - p_e_sym[6]['wetting'] +# } + +# pc_e_sym = { +# 1: -p_e_sym[1]['wetting'], +# 2: -p_e_sym[2]['wetting'], +# 3: -p_e_sym[3]['wetting'], +# 4: -p_e_sym[4]['wetting'], +# 5: -p_e_sym[5]['wetting'], +# 6: -p_e_sym[6]['wetting'] +# } + + +# turn above symbolic code into exact solution for dolphin and +# construct the rhs that matches the above exact solution. +dtS = dict() +div_flux = dict() +source_expression = dict() +exact_solution = dict() +initial_condition = dict() +for subdomain, isR in isRichards.items(): + dtS.update({subdomain: dict()}) + div_flux.update({subdomain: dict()}) + source_expression.update({subdomain: dict()}) + exact_solution.update({subdomain: dict()}) + initial_condition.update({subdomain: dict()}) + if isR: + subdomain_has_phases = ["wetting"] + else: + subdomain_has_phases = ["wetting", "nonwetting"] + + # conditional for S_pc_prime + pc = pc_e_sym[subdomain] + dtpc = sym.diff(pc, t, 1) + dxpc = sym.diff(pc, x, 1) + dypc = sym.diff(pc, y, 1) + 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)) + for phase in subdomain_has_phases: + # Turn above symbolic expression for exact solution into c code + exact_solution[subdomain].update( + {phase: sym.printing.ccode(p_e_sym[subdomain][phase])} + ) + # save the c code for initial conditions + initial_condition[subdomain].update( + {phase: sym.printing.ccode(p_e_sym[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 = p_e_sym[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 = ka_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 + dydyflux = -1/mu*dka(1-S)*dS*dypc*(dypa - rho*g) \ + + 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 + dydyflux = 1/mu*dka(S)*dS*dypc*(dypa - rho*g) + 1/mu*dydypa*ka(S) + div_flux[subdomain].update({phase: dxdxflux + dydyflux}) + contructed_rhs = dtS[subdomain][phase] - div_flux[subdomain][phase] + source_expression[subdomain].update( + {phase: sym.printing.ccode(contructed_rhs)} + ) + # print(f"source_expression[{subdomain}][{phase}] =", source_expression[subdomain][phase]) + +# Dictionary of dirichlet boundary conditions. +dirichletBC = dict() +# similarly to the outer boundary dictionary, if a patch has no outer boundary +# None should be written instead of an expression. +# This is a bit of a brainfuck: +# dirichletBC[ind] gives a dictionary of the outer boundaries of subdomain ind. +# Since a domain patch can have several disjoint outer boundary parts, the +# expressions need to get an enumaration index which starts at 0. +# So dirichletBC[ind][j] is the dictionary of outer dirichlet conditions of +# subdomain ind and boundary part j. +# Finally, dirichletBC[ind][j]['wetting'] and dirichletBC[ind][j]['nonwetting'] +# return the actual expression needed for the dirichlet condition for both +# phases if present. + +# subdomain index: {outer boudary part index: {phase: expression}} +for subdomain in isRichards.keys(): + # if subdomain has no outer boundary, outer_boundary_def_points[subdomain] is None + if outer_boundary_def_points[subdomain] is None: + dirichletBC.update({subdomain: None}) + else: + dirichletBC.update({subdomain: dict()}) + # set the dirichlet conditions to be the same code as exact solution on + # the subdomain. + for outer_boundary_ind in outer_boundary_def_points[subdomain].keys(): + dirichletBC[subdomain].update( + {outer_boundary_ind: exact_solution[subdomain]} + ) + +write_to_file = { + 'meshes_and_markers': True, + 'L_iterations': True +} + +# initialise LDD simulation class +simulation = ldd.LDDsimulation(tol=1E-14, debug=True, LDDsolver_tol=5E-4) +simulation.set_parameters(output_dir="./output/", + subdomain_def_points=subdomain_def_points, + isRichards=isRichards, + interface_def_points=interface_def_points, + outer_boundary_def_points=outer_boundary_def_points, + adjacent_subdomains=adjacent_subdomains, + mesh_resolution=mesh_resolution, + viscosity=viscosity, + porosity=porosity, + L=L, + lambda_param=lambda_param, + relative_permeability=relative_permeability, + saturation=sat_pressure_relationship, + starttime=starttime, + number_of_timesteps=number_of_timesteps, + number_of_timesteps_to_analyse=number_of_timesteps_to_analyse, + timestep_size=timestep_size, + sources=source_expression, + initial_conditions=initial_condition, + dirichletBC_expression_strings=dirichletBC, + exact_solution=exact_solution, + densities=densities, + include_gravity=True, + write2file=write_to_file, + ) + +simulation.initialise() +# print(simulation.__dict__) +simulation.run() +# simulation.LDDsolver(time=0, debug=True, analyse_timestep=True) +# df.info(parameters, True) diff --git a/TP-TP-layered-soil-case-with-inner-patch/TP-TP-layered_soil_with_inner_patch.py b/TP-TP-layered-soil-case-with-inner-patch/TP-TP-layered_soil_with_inner_patch.py index b7294621b4e7e42270a38bc6f352972e65b8d1c7..abc1414152f8d165ee7df6f1bb10a771337e8e61 100755 --- a/TP-TP-layered-soil-case-with-inner-patch/TP-TP-layered_soil_with_inner_patch.py +++ b/TP-TP-layered-soil-case-with-inner-patch/TP-TP-layered_soil_with_inner_patch.py @@ -23,20 +23,22 @@ sym.init_printing() # ----------------------------------------------------------------------------# # ------------------- MESH ---------------------------------------------------# # ----------------------------------------------------------------------------# -mesh_resolution = 5 +mesh_resolution = 50 # ----------------------------------------:-----------------------------------# # ------------------- TIME ---------------------------------------------------# # ----------------------------------------------------------------------------# -timestep_size = 0.003 -number_of_timesteps = 300 +timestep_size = 0.0001 +number_of_timesteps = 10 # decide how many timesteps you want analysed. Analysed means, that we write # out subsequent errors of the L-iteration within the timestep. -number_of_timesteps_to_analyse = 11 +number_of_timesteps_to_analyse = 4 starttime = 0 -l_param_w = 80 -l_param_nw = 120 +Lw = 0.5 +Lnw = 0.4 +l_param_w = 30 +l_param_nw = 40 # global domain subdomain0_vertices = [df.Point(0.0,0.0), # df.Point(13.0,0.0),# @@ -48,6 +50,73 @@ interface12_vertices = [df.Point(0.0, 7.0), df.Point(10.5, 7.5), df.Point(12.0, 7.0), df.Point(13.0, 6.5)] + + +# interface23 +interface23_vertices = [df.Point(0.0, 5.0), + df.Point(3.0, 5.0), + # df.Point(6.5, 4.5), + df.Point(6.5, 5.0)] + +interface24_vertices = [df.Point(6.5, 5.0), + df.Point(9.5, 5.0), + # df.Point(11.5, 3.5), + # df.Point(13.0, 3) + df.Point(11.5, 5.0) + ] + +interface25_vertices = [df.Point(11.5, 5.0), + df.Point(13.0, 5.0) + ] + + +interface32_vertices = [interface23_vertices[2], + interface23_vertices[1], + interface23_vertices[0]] + +interface34_vertices = [df.Point(4.0, 2.0), + df.Point(4.7, 3.0), + interface23_vertices[2]] +# interface36 +interface36_vertices = [df.Point(0.0, 2.0), + df.Point(4.0, 2.0)] + + +interface46_vertices = [df.Point(4.0, 2.0), + df.Point(9.0, 2.5)] + +interface45_vertices = [df.Point(9.0, 2.5), + df.Point(10.0, 3.0), + interface25_vertices[0] + ] + +interface56_vertices = [df.Point(9.0, 2.5), + df.Point(10.5, 2.0), + df.Point(13.0, 1.5)] + + +# interface_vertices introduces a global numbering of interfaces. +interface_def_points = [interface12_vertices, + interface23_vertices, + interface24_vertices, + interface25_vertices, + interface34_vertices, + interface36_vertices, + interface45_vertices, + interface46_vertices, + interface56_vertices, + ] +adjacent_subdomains = [[1,2], + [2,3], + [2,4], + [2,5], + [3,4], + [3,6], + [4,5], + [4,6], + [5,6] + ] + # subdomain1. subdomain1_vertices = [interface12_vertices[0], interface12_vertices[1], @@ -68,26 +137,13 @@ subdomain1_outer_boundary_verts = { interface12_vertices[0]] } - -# interface23 -interface23_vertices = [df.Point(0.0, 5.0), - df.Point(3.0, 5.0), - # df.Point(6.5, 4.5), - df.Point(6.5, 5.0), - df.Point(9.5, 5.0), - # df.Point(11.5, 3.5), - # df.Point(13.0, 3) - df.Point(11.5, 5.0), - df.Point(13.0, 5.0) - ] - #subdomain1 subdomain2_vertices = [interface23_vertices[0], interface23_vertices[1], interface23_vertices[2], - interface23_vertices[3], - interface23_vertices[4], - interface23_vertices[5], # southern boundary, 23 interface + interface24_vertices[1], + interface24_vertices[2], + interface25_vertices[1], # southern boundary, 23 interface subdomain1_vertices[4], # eastern boundary, outer boundary subdomain1_vertices[3], subdomain1_vertices[2], @@ -95,75 +151,67 @@ subdomain2_vertices = [interface23_vertices[0], subdomain1_vertices[0] ] # northern boundary, 12 interface subdomain2_outer_boundary_verts = { - 0: [interface23_vertices[5], + 0: [interface25_vertices[1], subdomain1_vertices[4]], 1: [subdomain1_vertices[0], interface23_vertices[0]] } - -interface32_vertices = [interface23_vertices[2], - interface23_vertices[1], - interface23_vertices[0]] - -interface34_vertices = [df.Point(4.0, 2.0), - df.Point(4.7, 3.0), - interface23_vertices[2]] -# interface36 -interface36_vertices = [df.Point(0.0, 2.0), - df.Point(4.0, 2.0)] - subdomain3_vertices = [interface36_vertices[0], interface36_vertices[1], - interface34_vertices[0], + # interface34_vertices[0], interface34_vertices[1], - interface34_vertices[2] - interface32_vertices[0], + interface34_vertices[2], + # interface32_vertices[0], interface32_vertices[1], interface32_vertices[2] ] -interface46_vertices = [df.Point(4.0, 2.0), - df.Point(9.0, 2.5)] +subdomain3_outer_boundary_verts = { + 0: [subdomain2_vertices[0], + subdomain3_vertices[0]] +} -interface46_vertices = [df.Point(9.0, 2.5), - df.Point(10.5, 2.0), - df.Point(13.0, 1.5)] # subdomain3 -subdomain3_vertices = [interface34_vertices[0], - interface34_vertices[1], - interface34_vertices[2], - interface34_vertices[3], - interface34_vertices[4], # southern boundary, 34 interface - subdomain2_vertices[5], # eastern boundary, outer boundary - subdomain2_vertices[4], - subdomain2_vertices[3], - subdomain2_vertices[2], - subdomain2_vertices[1], - subdomain2_vertices[0] ] # northern boundary, 23 interface - +subdomain4_vertices = [interface46_vertices[0], + interface46_vertices[1], + df.Point(10.0, 3.0), + interface24_vertices[2], + interface24_vertices[1], + interface24_vertices[0], + interface34_vertices[1] + ] +subdomain4_outer_boundary_verts = None +subdomain5_vertices = [interface56_vertices[0], + interface56_vertices[1], + interface56_vertices[2], + interface25_vertices[1], + interface25_vertices[0], + interface45_vertices[1], + interface45_vertices[0] +] -subdomain3_outer_boundary_verts = { - 0: [interface34_vertices[4], - subdomain2_vertices[5]], - 1: [subdomain2_vertices[0], - interface34_vertices[0]] +subdomain5_outer_boundary_verts = { + 0: [subdomain5_vertices[2], + subdomain5_vertices[3]] } -# subdomain4 -subdomain4_vertices = [subdomain0_vertices[0], - subdomain0_vertices[1], # southern boundary, outer boundary - subdomain3_vertices[4],# eastern boundary, outer boundary - subdomain3_vertices[3], - subdomain3_vertices[2], - subdomain3_vertices[1], - subdomain3_vertices[0] ] # northern boundary, 34 interface -subdomain4_outer_boundary_verts = { + +subdomain6_vertices = [subdomain0_vertices[0], + subdomain0_vertices[1], # southern boundary, outer boundary + interface56_vertices[2], + interface56_vertices[1], + interface56_vertices[0], + interface36_vertices[1], + interface36_vertices[0] + ] + +subdomain6_outer_boundary_verts = { 0: [subdomain4_vertices[6], subdomain4_vertices[0], subdomain4_vertices[1], @@ -175,14 +223,12 @@ subdomain_def_points = [subdomain0_vertices,# subdomain1_vertices,# subdomain2_vertices,# subdomain3_vertices,# - subdomain4_vertices + subdomain4_vertices, + subdomain5_vertices, + subdomain6_vertices ] -# interface_vertices introduces a global numbering of interfaces. -interface_def_points = [interface12_vertices, interface23_vertices, interface34_vertices] -adjacent_subdomains = [[1,2], [2,3], [3,4]] - # if a subdomain has no outer boundary write None instead, i.e. # i: None # if i is the index of the inner subdomain. @@ -191,14 +237,27 @@ outer_boundary_def_points = { 1: subdomain1_outer_boundary_verts, 2: subdomain2_outer_boundary_verts, 3: subdomain3_outer_boundary_verts, - 4: subdomain4_outer_boundary_verts + 4: subdomain4_outer_boundary_verts, + 5: subdomain5_outer_boundary_verts, + 6: subdomain6_outer_boundary_verts } +# isRichards = { +# 1: False, +# 2: False, +# 3: False, +# 4: False, +# 5: False, +# 6: False +# } + isRichards = { - 1: False, - 2: False, - 3: False, - 4: False + 1: True, + 2: True, + 3: True, + 4: True, + 5: True, + 6: True } # Dict of the form: { subdom_num : viscosity } @@ -211,18 +270,26 @@ viscosity = { 'nonwetting': 1/50}, 4: {'wetting' :1, 'nonwetting': 1/50}, + 5: {'wetting' :1, + 'nonwetting': 1/50}, + 6: {'wetting' :1, + 'nonwetting': 1/50}, } # Dict of the form: { subdom_num : density } densities = { - 1: {'wetting': 997, - 'nonwetting': 1.225}, - 2: {'wetting': 997, - 'nonwetting': 1.225}, - 3: {'wetting': 997, - 'nonwetting': 1.225}, - 4: {'wetting': 997, - 'nonwetting': 1.225} + 1: {'wetting': 1, #997 + 'nonwetting': 1}, #1}, #1.225}, + 2: {'wetting': 1, #997 + 'nonwetting': 1}, #1.225}, + 3: {'wetting': 1, #997 + 'nonwetting': 1}, #1.225}, + 4: {'wetting': 1, #997 + 'nonwetting': 1}, #1.225} + 5: {'wetting': 1, #997 + 'nonwetting': 1}, #1.225}, + 6: {'wetting': 1, #997 + 'nonwetting': 1} #1.225} } gravity_acceleration = 9.81 @@ -230,22 +297,28 @@ gravity_acceleration = 9.81 # https://www.geotechdata.info/parameter/soil-porosity.html # Dict of the form: { subdom_num : porosity } porosity = { - 1: 0.2, # Clayey gravels, clayey sandy gravels - 2: 0.22, # Silty gravels, silty sandy gravels - 3: 0.37, # Clayey sands - 4: 0.2 # Silty or sandy clay + 1: 1, #0.2, # Clayey gravels, clayey sandy gravels + 2: 1, #0.22, # Silty gravels, silty sandy gravels + 3: 1, #0.37, # Clayey sands + 4: 1, #0.2 # Silty or sandy clay + 5: 1, # + 6: 1, # } # subdom_num : subdomain L for L-scheme L = { - 1: {'wetting' :0.3, - 'nonwetting': 0.25}, - 2: {'wetting' :0.3, - 'nonwetting': 0.25}, - 3: {'wetting' :0.3, - 'nonwetting': 0.25}, - 4: {'wetting' :0.3, - 'nonwetting': 0.25} + 1: {'wetting' :Lw, + 'nonwetting': Lnw}, + 2: {'wetting' :Lw, + 'nonwetting': Lnw}, + 3: {'wetting' :Lw, + 'nonwetting': Lnw}, + 4: {'wetting' :Lw, + 'nonwetting': Lnw}, + 5: {'wetting' :Lw, + 'nonwetting': Lnw}, + 6: {'wetting' :Lw, + 'nonwetting': Lnw} } # subdom_num : lambda parameter for the L-scheme @@ -258,6 +331,10 @@ lambda_param = { 'nonwetting': l_param_nw},# 4: {'wetting': l_param_w, 'nonwetting': l_param_nw},# + 5: {'wetting': l_param_w, + 'nonwetting': l_param_nw},# + 6: {'wetting': l_param_w, + 'nonwetting': l_param_nw},# } @@ -272,31 +349,31 @@ def rel_perm1nw(s): return (1-s)**2 -## relative permeabilty functions on subdomain 2 -def rel_perm2w(s): - # relative permeabilty wetting on subdomain2 - return s**3 - - -def rel_perm2nw(s): - # relative permeabilty nonwetting on subdosym.cos(0.8*t - (0.8*x + 1/7*y))main2 - return (1-s)**2 +# ## relative permeabilty functions on subdomain 2 +# def rel_perm2w(s): +# # relative permeabilty wetting on subdomain2 +# return s**3 +# +# +# def rel_perm2nw(s): +# # relative permeabilty nonwetting on subdosym.cos(0.8*t - (0.8*x + 1/7*y))main2 +# return (1-s)**2 _rel_perm1w = ft.partial(rel_perm1w) _rel_perm1nw = ft.partial(rel_perm1nw) -_rel_perm2w = ft.partial(rel_perm2w) -_rel_perm2nw = ft.partial(rel_perm2nw) +# _rel_perm2w = ft.partial(rel_perm2w) +# _rel_perm2nw = ft.partial(rel_perm2nw) subdomain1_rel_perm = { 'wetting': _rel_perm1w,# 'nonwetting': _rel_perm1nw } -subdomain2_rel_perm = { - 'wetting': _rel_perm2w,# - 'nonwetting': _rel_perm2nw -} +# subdomain2_rel_perm = { +# 'wetting': _rel_perm2w,# +# 'nonwetting': _rel_perm2nw +# } # _rel_perm3 = ft.partial(rel_perm2) # subdomain3_rel_perm = subdomain2_rel_perm.copy() @@ -308,8 +385,10 @@ subdomain2_rel_perm = { relative_permeability = { 1: subdomain1_rel_perm, 2: subdomain1_rel_perm, - 3: subdomain2_rel_perm, - 4: subdomain2_rel_perm + 3: subdomain1_rel_perm, + 4: subdomain1_rel_perm, + 5: subdomain1_rel_perm, + 6: subdomain1_rel_perm, } # definition of the derivatives of the relative permeabilities @@ -322,20 +401,20 @@ def rel_perm1nw_prime(s): # relative permeabilty on subdomain1 return 2*(1-s) -# definition of the derivatives of the relative permeabilities -# relative permeabilty functions on subdomain 1 -def rel_perm2w_prime(s): - # relative permeabilty on subdomain1 - return 3*s**2 - -def rel_perm2nw_prime(s): - # relative permeabilty on subdomain1 - return 2*(1-s) +# # definition of the derivatives of the relative permeabilities +# # relative permeabilty functions on subdomain 1 +# def rel_perm2w_prime(s): +# # relative permeabilty on subdomain1 +# return 3*s**2 +# +# def rel_perm2nw_prime(s): +# # relative permeabilty on subdomain1 +# return 2*(l_param_w1-s) _rel_perm1w_prime = ft.partial(rel_perm1w_prime) _rel_perm1nw_prime = ft.partial(rel_perm1nw_prime) -_rel_perm2w_prime = ft.partial(rel_perm2w_prime) -_rel_perm2nw_prime = ft.partial(rel_perm2nw_prime) +# _rel_perm2w_prime = ft.partial(rel_perm2w_prime) +# _rel_perm2nw_prime = ft.partial(rel_perm2nw_prime) subdomain1_rel_perm_prime = { 'wetting': _rel_perm1w_prime, @@ -343,17 +422,19 @@ subdomain1_rel_perm_prime = { } -subdomain2_rel_perm_prime = { - 'wetting': _rel_perm2w_prime, - 'nonwetting': _rel_perm2nw_prime -} +# subdomain2_rel_perm_prime = { +# 'wetting': _rel_perm2w_prime, +# 'nonwetting': _rel_perm2nw_prime +# } # dictionary of relative permeabilties on all domains. ka_prime = { 1: subdomain1_rel_perm_prime, 2: subdomain1_rel_perm_prime, - 3: subdomain2_rel_perm_prime, - 4: subdomain2_rel_perm_prime + 3: subdomain1_rel_perm_prime, + 4: subdomain1_rel_perm_prime, + 5: subdomain1_rel_perm_prime, + 6: subdomain1_rel_perm_prime, } @@ -366,46 +447,96 @@ ka_prime = { # since we unify the treatment in the code for Richards and two-phase, we need # the same requierment # for both cases, two-phase and Richards. -def saturation(pc, n_index, alpha): - # inverse capillary pressure-saturation-relationship - return df.conditional(pc > 0, 1/((1 + (alpha*pc)**n_index)**((n_index - 1)/n_index)), 1) +# def saturation(pc, n_index, alpha): +# # inverse capillary pressure-saturation-relationship +# return df.conditional(pc > 0, 1/((1 + (alpha*pc)**n_index)**((n_index - 1)/n_index)), 1) +# +# # S-pc-relation ship. We use the van Genuchten approach, i.e. pc = 1/alpha*(S^{-1/m} -1)^1/n, where +# # we set alpha = 0, assume m = 1-1/n (see Helmig) and assume that residual saturation is Sw +# def saturation_sym(pc, n_index, alpha): +# # inverse capillary pressure-saturation-relationship +# #df.conditional(pc > 0, +# return 1/((1 + (alpha*pc)**n_index)**((n_index - 1)/n_index)) +# +# +# # derivative of S-pc relationship with respect to pc. This is needed for the +# # construction of a analytic solution. +# def saturation_sym_prime(pc, n_index, alpha): +# # inverse capillary pressure-saturation-relationship +# return -(alpha*(n_index - 1)*(alpha*pc)**(n_index - 1)) / ( (1 + (alpha*pc)**n_index)**((2*n_index - 1)/n_index) ) +# +# derivative of S-pc relationship with respect to pc. This is needed for the +# construction of a analytic solution. -# S-pc-relation ship. We use the van Genuchten approach, i.e. pc = 1/alpha*(S^{-1/m} -1)^1/n, where -# we set alpha = 0, assume m = 1-1/n (see Helmig) and assume that residual saturation is Sw -def saturation_sym(pc, n_index, alpha): +# +# # note that the conditional definition of S-pc in the nonsymbolic part will be +# # incorporated in the construction of the exact solution below. +# S_pc_sym = { +# 1: ft.partial(saturation_sym, n_index=3, alpha=0.001), +# 2: ft.partial(saturation_sym, n_index=3, alpha=0.001), +# 3: ft.partial(saturation_sym, n_index=3, alpha=0.001), +# 4: ft.partial(saturation_sym, n_index=3, alpha=0.001), +# 5: ft.partial(saturation_sym, n_index=3, alpha=0.001), +# 6: ft.partial(saturation_sym, n_index=3, alpha=0.001) +# } +# +# S_pc_sym_prime = { +# 1: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), +# 2: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), +# 3: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), +# 4: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), +# 5: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), +# 6: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001) +# } +# +# sat_pressure_relationship = { +# 1: ft.partial(saturation, n_index=3, alpha=0.001), +# 2: ft.partial(saturation, n_index=3, alpha=0.001), +# 3: ft.partial(saturation, n_index=3, alpha=0.001), +# 4: ft.partial(saturation, n_index=3, alpha=0.001), +# 5: ft.partial(saturation, n_index=3, alpha=0.001), +# 6: ft.partial(saturation, n_index=3, alpha=0.001) +# } + +def saturation(pc, n_index): # inverse capillary pressure-saturation-relationship - #df.conditional(pc > 0, - return 1/((1 + (alpha*pc)**n_index)**((n_index - 1)/n_index)) + return df.conditional(pc > 0, 1/((1 + pc)**(1/(n_index + 1))), 1) -# derivative of S-pc relationship with respect to pc. This is needed for the -# construction of a analytic solution. -def saturation_sym_prime(pc, n_index, alpha): +def saturation_sym(pc, n_index): + # inverse capillary pressure-saturation-relationship + return 1/((1 + pc)**(1/(n_index + 1))) + +def saturation_sym_prime(pc, n_index): # inverse capillary pressure-saturation-relationship - return -(alpha*(n_index - 1)*(alpha*pc)**(n_index - 1)) / ( (1 + (alpha*pc)**n_index)**((2*n_index - 1)/n_index) ) + return -1/((n_index+1)*(1 + pc)**((n_index+2)/(n_index+1))) -# note that the conditional definition of S-pc in the nonsymbolic part will be -# incorporated in the construction of the exact solution below. S_pc_sym = { - 1: ft.partial(saturation_sym, n_index=3, alpha=0.001), - 2: ft.partial(saturation_sym, n_index=3, alpha=0.001), - 3: ft.partial(saturation_sym, n_index=6, alpha=0.001), - 4: ft.partial(saturation_sym, n_index=6, alpha=0.001) + 1: ft.partial(saturation_sym, n_index=1), + 2: ft.partial(saturation_sym, n_index=1), + 3: ft.partial(saturation_sym, n_index=2), + 4: ft.partial(saturation_sym, n_index=2), + 5: ft.partial(saturation_sym, n_index=2), + 6: ft.partial(saturation_sym, n_index=2) } S_pc_sym_prime = { - 1: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), - 2: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), - 3: ft.partial(saturation_sym_prime, n_index=6, alpha=0.001), - 4: ft.partial(saturation_sym_prime, n_index=6, alpha=0.001) + 1: ft.partial(saturation_sym_prime, n_index=1), + 2: ft.partial(saturation_sym_prime, n_index=1), + 3: ft.partial(saturation_sym_prime, n_index=2), + 4: ft.partial(saturation_sym_prime, n_index=2), + 5: ft.partial(saturation_sym_prime, n_index=2), + 6: ft.partial(saturation_sym_prime, n_index=2) } sat_pressure_relationship = { - 1: ft.partial(saturation, n_index=3, alpha=0.001), - 2: ft.partial(saturation, n_index=3, alpha=0.001), - 3: ft.partial(saturation, n_index=6, alpha=0.001), - 4: ft.partial(saturation, n_index=6, alpha=0.001) + 1: ft.partial(saturation, n_index=1), + 2: ft.partial(saturation, n_index=1), + 3: ft.partial(saturation, n_index=2), + 4: ft.partial(saturation, n_index=2), + 5: ft.partial(saturation, n_index=2), + 6: ft.partial(saturation, n_index=2) } @@ -416,23 +547,45 @@ x, y = sym.symbols('x[0], x[1]') # needed by UFL t = sym.symbols('t', positive=True) p_e_sym = { - 1: {'wetting': 1.0 - (1.0 + t*t)*(10.0 + x*x + (y-5.0)*(y-5.0)), + 1: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + (x-6.5)*(x-6.5) + (y-5.0)*(y-5.0)), + 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0)) }, + 2: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + (x-6.5)*(x-6.5) + (y-5.0)*(y-5.0)), + 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0)) }, + 3: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + (x-6.5)*(x-6.5)), + 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0)) }, + 4: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + (x-6.5)*(x-6.5)), + 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0)) }, + 5: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + (x-6.5)*(x-6.5)), 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0)) }, - 2: {'wetting': 1.0 - (1.0 + t*t)*(10.0 + x*x + (y-5.0)*(y-5.0)), - 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0))}, - 3: {'wetting': 1.0 - (1.0 + t*t)*(10.0 + x*x + (y-5.0)*(y-5.0)*3*sym.sin(-2*t+2*x)*sym.sin(1/2*y-1.2*t)), - 'nonwetting': - 2 - t*(1 + x**2)**2 -sym.sqrt(2+t**2)}, - 4: {'wetting': 1.0 - (1.0 + t*t)*(10.0 + x*x + (y-5.0)*(y-5.0)*3*sym.sin(-2*t+2*x)*sym.sin(1/2*y-1.2*t)), - 'nonwetting': - 2 - t*(1 + x**2)**2 -sym.sqrt(2+t**2)} + 6: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + (x-6.5)*(x-6.5)), + 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0)) }, + # 2: {'wetting': 1.0 - (1.0 + t*t)*(10.0 + x*x + (y-5.0)*(y-5.0)), + # 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0))}, + # 3: {'wetting': 1.0 - (1.0 + t*t)*(10.0 + x*x + (y-5.0)*(y-5.0)*3*sym.sin(-2*t+2*x)*sym.sin(1/2*y-1.2*t)), + # 'nonwetting': - 2 - t*(1 + x**2)**2 -sym.sqrt(2+t**2)}, + # 4: {'wetting': 1.0 - (1.0 + t*t)*(10.0 + x*x + (y-5.0)*(y-5.0)*3*sym.sin(-2*t+2*x)*sym.sin(1/2*y-1.2*t)), + # 'nonwetting': - 2 - t*(1 + x**2)**2 -sym.sqrt(2+t**2)} } +# pc_e_sym = { +# 1: p_e_sym[1]['nonwetting'] - p_e_sym[1]['wetting'], +# 2: p_e_sym[2]['nonwetting'] - p_e_sym[2]['wetting'], +# 3: p_e_sym[3]['nonwetting'] - p_e_sym[3]['wetting'], +# 4: p_e_sym[4]['nonwetting'] - p_e_sym[4]['wetting'], +# 5: p_e_sym[5]['nonwetting'] - p_e_sym[5]['wetting'], +# 6: p_e_sym[5]['nonwetting'] - p_e_sym[6]['wetting'] +# } + pc_e_sym = { - 1: p_e_sym[1]['nonwetting'] - p_e_sym[1]['wetting'], - 2: p_e_sym[2]['nonwetting'] - p_e_sym[2]['wetting'], - 3: p_e_sym[3]['nonwetting'] - p_e_sym[3]['wetting'], - 4: p_e_sym[4]['nonwetting'] - p_e_sym[4]['wetting'] + 1: -p_e_sym[1]['wetting'], + 2: -p_e_sym[2]['wetting'], + 3: -p_e_sym[3]['wetting'], + 4: -p_e_sym[4]['wetting'], + 5: -p_e_sym[5]['wetting'], + 6: -p_e_sym[6]['wetting'] } + # turn above symbolic code into exact solution for dolphin and # construct the rhs that matches the above exact solution. dtS = dict() @@ -468,10 +621,13 @@ for subdomain, isR in isRichards.items(): {phase: sym.printing.ccode(p_e_sym[subdomain][phase].subs(t, 0))} ) if phase == "nonwetting": - dS = -dS - dtS[subdomain].update( - {phase: porosity[subdomain]*dS*dtpc} - ) + dtS[subdomain].update( + {phase: -porosity[subdomain]*dS*dtpc} + ) + else: + dtS[subdomain].update( + {phase: porosity[subdomain]*dS*dtpc} + ) pa = p_e_sym[subdomain][phase] dxpa = sym.diff(pa, x, 1) dxdxpa = sym.diff(pa, x, 2) @@ -535,7 +691,7 @@ write_to_file = { } # initialise LDD simulation class -simulation = ldd.LDDsimulation(tol=1E-14, debug=True, LDDsolver_tol=1E-7) +simulation = ldd.LDDsimulation(tol=1E-14, debug=True, LDDsolver_tol=1E-6) simulation.set_parameters(output_dir="./output/", subdomain_def_points=subdomain_def_points, isRichards=isRichards, diff --git a/TP-TP-layered-soil-case/TP-TP-layered_soil.py b/TP-TP-layered-soil-case/TP-TP-layered_soil.py index 2cfac5c1df658a4b5817832aa0d54a1a67158884..9cb30dc97384ed7d812fc1d29ea745cf55b46302 100755 --- a/TP-TP-layered-soil-case/TP-TP-layered_soil.py +++ b/TP-TP-layered-soil-case/TP-TP-layered_soil.py @@ -23,19 +23,19 @@ sym.init_printing() # ----------------------------------------------------------------------------# # ------------------- MESH ---------------------------------------------------# # ----------------------------------------------------------------------------# -mesh_resolution = 20 +mesh_resolution = 30 # ----------------------------------------:-------------------------------------# # ------------------- TIME ---------------------------------------------------# # ----------------------------------------------------------------------------# -timestep_size = 0.003 -number_of_timesteps = 300 +timestep_size = 0.0001 +number_of_timesteps = 5 # decide how many timesteps you want analysed. Analysed means, that we write # out subsequent errors of the L-iteration within the timestep. -number_of_timesteps_to_analyse = 11 +number_of_timesteps_to_analyse = 0 starttime = 0 -l_param_w = 80 -l_param_nw = 120 +l_param_w = 60 +l_param_nw = 60 # global domain subdomain0_vertices = [df.Point(0.0,0.0), # @@ -176,6 +176,13 @@ isRichards = { 4: False } +# isRichards = { +# 1: True, +# 2: True, +# 3: True, +# 4: True +# } + # Dict of the form: { subdom_num : viscosity } viscosity = { 1: {'wetting' :1, @@ -213,14 +220,14 @@ porosity = { # subdom_num : subdomain L for L-scheme L = { - 1: {'wetting' :0.3, - 'nonwetting': 0.25}, - 2: {'wetting' :0.3, - 'nonwetting': 0.25}, - 3: {'wetting' :0.3, - 'nonwetting': 0.25}, - 4: {'wetting' :0.3, - 'nonwetting': 0.25} + 1: {'wetting' :0.4, + 'nonwetting': 0.4}, + 2: {'wetting' :0.4, + 'nonwetting': 0.4}, + 3: {'wetting' :0.4, + 'nonwetting': 0.4}, + 4: {'wetting' :0.4, + 'nonwetting': 0.4} } # subdom_num : lambda parameter for the L-scheme @@ -395,9 +402,9 @@ p_e_sym = { 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0)) }, 2: {'wetting': 1.0 - (1.0 + t*t)*(10.0 + x*x + (y-5.0)*(y-5.0)), 'nonwetting': - 2 - t*(1 + (y-5.0) + x**2)**2 -sym.sqrt(2+t**2)*(1 + (y-5.0))}, - 3: {'wetting': 1.0 - (1.0 + t*t)*(10.0 + x*x + (y-5.0)*(y-5.0)*3*sym.sin(-2*t+2*x)*sym.sin(1/2*y-1.2*t)), + 3: {'wetting': 1.0 - (1.0 + t*t)*(10.0 + x*x + (y-5.0)*(y-5.0)) - (y-5.0)*(y-5.0)*3*sym.sin(-2*t+2*x)*sym.sin(1/2*y-1.2*t), 'nonwetting': - 2 - t*(1 + x**2)**2 -sym.sqrt(2+t**2)}, - 4: {'wetting': 1.0 - (1.0 + t*t)*(10.0 + x*x + (y-5.0)*(y-5.0)*3*sym.sin(-2*t+2*x)*sym.sin(1/2*y-1.2*t)), + 4: {'wetting': 1.0 - (1.0 + t*t)*(10.0 + x*x + (y-5.0)*(y-5.0)) - (y-5.0)*(y-5.0)*3*sym.sin(-2*t+2*x)*sym.sin(1/2*y-1.2*t), 'nonwetting': - 2 - t*(1 + x**2)**2 -sym.sqrt(2+t**2)} } @@ -443,10 +450,13 @@ for subdomain, isR in isRichards.items(): {phase: sym.printing.ccode(p_e_sym[subdomain][phase].subs(t, 0))} ) if phase == "nonwetting": - dS = -dS - dtS[subdomain].update( - {phase: porosity[subdomain]*dS*dtpc} - ) + dtS[subdomain].update( + {phase: -porosity[subdomain]*dS*dtpc} + ) + else: + dtS[subdomain].update( + {phase: porosity[subdomain]*dS*dtpc} + ) pa = p_e_sym[subdomain][phase] dxpa = sym.diff(pa, x, 1) dxdxpa = sym.diff(pa, x, 2) diff --git a/TP-TP-patch-test-case/TP-TP-2-patch-test.py b/TP-TP-patch-test-case/TP-TP-2-patch-test.py index 222ab56bdf1c783c62177dcb786a512a1ba8ccad..c998d3b5a3262b3b74f5a83f355584c88376aa78 100755 --- a/TP-TP-patch-test-case/TP-TP-2-patch-test.py +++ b/TP-TP-patch-test-case/TP-TP-2-patch-test.py @@ -119,7 +119,7 @@ L = {# 'nonwetting': 0.25} } -l_param = 40 +l_param = 20 lambda_param = {# # subdom_num : lambda parameter for the L-scheme 1 : {'wetting' :l_param, @@ -165,146 +165,428 @@ relative_permeability = {# 2: subdomain2_rel_perm } -# S-pc-relation ship. We use the van Genuchten approach, i.e. pc = 1/alpha*(S^{-1/m} -1)^1/n, where -# we set alpha = 0, assume m = 1-1/n (see Helmig) and assume that residual saturation is Sw -def saturation(capillary_pressure, n_index, alpha): + +# definition of the derivatives of the relative permeabilities +# relative permeabilty functions on subdomain 1 +def rel_perm1w_prime(s): + # relative permeabilty on subdomain1 + return 2*s + +def rel_perm1nw_prime(s): + # relative permeabilty on subdomain1 + return 2*(1-s) + +# # definition of the derivatives of the relative permeabilities +# # relative permeabilty functions on subdomain 1 +# def rel_perm2w_prime(s): +# # relative permeabilty on subdomain1 +# return 3*s**2 +# +# def rel_perm2nw_prime(s): +# # relative permeabilty on subdomain1 +# return 2*(l_param_w1-s) + +_rel_perm1w_prime = ft.partial(rel_perm1w_prime) +_rel_perm1nw_prime = ft.partial(rel_perm1nw_prime) +# _rel_perm2w_prime = ft.partial(rel_perm2w_prime) +# _rel_perm2nw_prime = ft.partial(rel_perm2nw_prime) + +subdomain1_rel_perm_prime = { + 'wetting': _rel_perm1w_prime, + 'nonwetting': _rel_perm1nw_prime +} + + +# subdomain2_rel_perm_prime = { +# 'wetting': _rel_perm2w_prime, +# 'nonwetting': _rel_perm2nw_prime +# } + +# dictionary of relative permeabilties on all domains. +ka_prime = { + 1: subdomain1_rel_perm_prime, + 2: subdomain1_rel_perm_prime, +} + + + + +def saturation(pc, n_index, alpha): # inverse capillary pressure-saturation-relationship - return df.conditional(capillary_pressure > 0, 1/((1 + (alpha*capillary_pressure)**n_index)**((n_index - 1)/n_index)), 1) + return df.conditional(pc > 0, 1/((1 + (alpha*pc)**n_index)**((n_index - 1)/n_index)), 1) # S-pc-relation ship. We use the van Genuchten approach, i.e. pc = 1/alpha*(S^{-1/m} -1)^1/n, where # we set alpha = 0, assume m = 1-1/n (see Helmig) and assume that residual saturation is Sw -def saturation_sym(capillary_pressure, n_index, alpha): +def saturation_sym(pc, n_index, alpha): # inverse capillary pressure-saturation-relationship - #df.conditional(capillary_pressure > 0, - return 1/((1 + (alpha*capillary_pressure)**n_index)**((n_index - 1)/n_index)) + #df.conditional(pc > 0, + return 1/((1 + (alpha*pc)**n_index)**((n_index - 1)/n_index)) + -S_pc_rel = {# - 1: ft.partial(saturation_sym, n_index = 3, alpha=0.001),# n= 3 stands for non-uniform porous media - 2: ft.partial(saturation_sym, n_index = 6, alpha=0.001) # n=6 stands for uniform porous media matrix (siehe Helmig) +# derivative of S-pc relationship with respect to pc. This is needed for the +# construction of a analytic solution. +def saturation_sym_prime(pc, n_index, alpha): + # inverse capillary pressure-saturation-relationship + return -(alpha*(n_index - 1)*(alpha*pc)**(n_index - 1)) / ( (1 + (alpha*pc)**n_index)**((2*n_index - 1)/n_index) ) + +# note that the conditional definition of S-pc in the nonsymbolic part will be +# incorporated in the construction of the exact solution below. +S_pc_sym = { + 1: ft.partial(saturation_sym, n_index=3, alpha=0.001), + 2: ft.partial(saturation_sym, n_index=3, alpha=0.001), + # 3: ft.partial(saturation_sym, n_index=3, alpha=0.001), + # 4: ft.partial(saturation_sym, n_index=3, alpha=0.001), + # 5: ft.partial(saturation_sym, n_index=3, alpha=0.001), + # 6: ft.partial(saturation_sym, n_index=3, alpha=0.001) } -S_pc_rel_sym = {# - 1: ft.partial(saturation_sym, n_index = sym.Symbol('n'), alpha = sym.Symbol('a')),# n= 3 stands for non-uniform porous media - 2: ft.partial(saturation_sym, n_index = sym.Symbol('n'), alpha = sym.Symbol('a')) # n=6 stands for uniform porous media matrix (siehe Helmig) +S_pc_sym_prime = { + 1: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), + 2: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), + # 3: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), + # 4: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), + # 5: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), + # 6: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001) } -#### Manufacture source expressions with sympy -############################################################################### -## subdomain1 -x, y = sym.symbols('x[0], x[1]') # needed by UFL -t = sym.symbols('t', positive=True) -#f = -sym.diff(u, x, 2) - sym.diff(u, y, 2) # -Laplace(u) -#f = sym.simplify(f) # simplify f -p1_w = 1 - (1+t**2)*(1 + x**2 + (y-0.5)**2) -p1_nw = t*(1-(y-0.5) - x**2)**2 - sym.sqrt(2+t**2)*(1-(y-0.5)) - -#dtS1_w = sym.diff(S_pc_rel_sym[1](p1_nw - p1_w), t, 1) -#dtS1_nw = -sym.diff(S_pc_rel_sym[1](p1_nw - p1_w), t, 1) -dtS1_w = porosity[1]*sym.diff(sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ), t, 1) -dtS1_nw = -porosity[1]*sym.diff(sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ), t, 1) -print("dtS1_w = ", dtS1_w, "\n") -print("dtS1_nw = ", dtS1_nw, "\n") - -#dxdxflux1_w = -sym.diff(relative_permeability[1]['wetting'](S_pc_rel_sym[1](p1_nw - p1_w))*sym.diff(p1_w, x, 1), x, 1) -#dydyflux1_w = -sym.diff(relative_permeability[1]['wetting'](S_pc_rel_sym[1](p1_nw - p1_w))*sym.diff(p1_w, y, 1), y, 1) -dxdxflux1_w = -1/viscosity[1]['wetting']*sym.diff(relative_permeability[1]['wetting'](sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ))*sym.diff(p1_w, x, 1), x, 1) -dydyflux1_w = -1/viscosity[1]['wetting']*sym.diff(relative_permeability[1]['wetting'](sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ))*sym.diff(p1_w, y, 1), y, 1) - -rhs1_w = dtS1_w + dxdxflux1_w + dydyflux1_w -rhs1_w = sym.printing.ccode(rhs1_w) -print("rhs_w = ", rhs1_w, "\n") -#rhs_w = sym.expand(rhs_w) -#print("rhs_w", rhs_w, "\n") -#rhs_w = sym.collect(rhs_w, x) -#print("rhs_w", rhs_w, "\n") - -#dxdxflux1_nw = -sym.diff(relative_permeability[1]['nonwetting'](S_pc_rel_sym[1](p1_nw - p1_w))*sym.diff(p1_nw, x, 1), x, 1) -#dydyflux1_nw = -sym.diff(relative_permeability[1]['nonwetting'](S_pc_rel_sym[1](p1_nw - p1_w))*sym.diff(p1_nw, y, 1), y, 1) -dxdxflux1_nw = -1/viscosity[1]['nonwetting']*sym.diff(relative_permeability[1]['nonwetting'](1-sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ))*sym.diff(p1_nw, x, 1), x, 1) -dydyflux1_nw = -1/viscosity[1]['nonwetting']*sym.diff(relative_permeability[1]['nonwetting'](1-sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ))*sym.diff(p1_nw, y, 1), y, 1) - -rhs1_nw = dtS1_nw + dxdxflux1_nw + dydyflux1_nw -rhs1_nw = sym.printing.ccode(rhs1_nw) -print("rhs_nw = ", rhs1_nw, "\n") - -## subdomain2 -p2_w = 1 - (1+t**2)*(1 + x**2) -p2_nw = t*(1- x**2)**2 - sym.sqrt(2+t**2)*(1-(y-0.5)) - -#dtS2_w = sym.diff(S_pc_rel_sym[2](p2_nw - p2_w), t, 1) -#dtS2_nw = -sym.diff(S_pc_rel_sym[2](p2_nw - p2_w), t, 1) -dtS2_w = porosity[2]*sym.diff(sym.Piecewise((sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ), (p2_nw - p2_w) > 0), (1, True) ), t, 1) -dtS2_nw = -porosity[2]*sym.diff(sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ), t, 1) -print("dtS2_w = ", dtS2_w, "\n") -print("dtS2_nw = ", dtS2_nw, "\n") - -#dxdxflux2_w = -sym.diff(relative_permeability[2]['wetting'](S_pc_rel_sym[2](p2_nw - p2_w))*sym.diff(p2_w, x, 1), x, 1) -#dydyflux2_w = -sym.diff(relative_permeability[2]['wetting'](S_pc_rel_sym[2](p2_nw - p2_w))*sym.diff(p2_w, y, 1), y, 1) -dxdxflux2_w = -1/viscosity[2]['wetting']*sym.diff(relative_permeability[2]['wetting'](sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ))*sym.diff(p2_w, x, 1), x, 1) -dydyflux2_w = -1/viscosity[2]['wetting']*sym.diff(relative_permeability[2]['wetting'](sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ))*sym.diff(p2_w, y, 1), y, 1) - -rhs2_w = dtS2_w + dxdxflux2_w + dydyflux2_w -rhs2_w = sym.printing.ccode(rhs2_w) -print("rhs2_w = ", rhs2_w, "\n") -#rhs_w = sym.expand(rhs_w) -#print("rhs_w", rhs_w, "\n") -#rhs_w = sym.collect(rhs_w, x) -#print("rhs_w", rhs_w, "\n") - -#dxdxflux2_nw = -sym.diff(relative_permeability[2]['nonwetting'](S_pc_rel_sym[2](p2_nw - p2_w))*sym.diff(p2_nw, x, 1), x, 1) -#dydyflux2_nw = -sym.diff(relative_permeability[2]['nonwetting'](S_pc_rel_sym[2](p2_nw - p2_w))*sym.diff(p2_nw, y, 1), y, 1) -dxdxflux2_nw = -1/viscosity[2]['nonwetting']*sym.diff(relative_permeability[2]['nonwetting'](1-sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ))*sym.diff(p2_nw, x, 1), x, 1) -dydyflux2_nw = -1/viscosity[2]['nonwetting']*sym.diff(relative_permeability[2]['nonwetting'](1-sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ))*sym.diff(p2_nw, y, 1), y, 1) - -rhs2_nw = dtS2_nw + dxdxflux2_nw + dydyflux2_nw -rhs2_nw = sym.printing.ccode(rhs2_nw) -print("rhs2_nw = ", rhs2_nw, "\n") - - -############################################################################### - -source_expression = { - 1: {'wetting': rhs1_w, - 'nonwetting': rhs1_nw}, - 2: {'wetting': rhs2_w, - 'nonwetting': rhs2_nw} +sat_pressure_relationship = { + 1: ft.partial(saturation, n_index=3, alpha=0.001), + 2: ft.partial(saturation, n_index=3, alpha=0.001), + # 3: ft.partial(saturation, n_index=3, alpha=0.001), + # 4: ft.partial(saturation, n_index=3, alpha=0.001), + # 5: ft.partial(saturation, n_index=3, alpha=0.001), + # 6: ft.partial(saturation, n_index=3, alpha=0.001) } -p1_w_00 = p1_w.subs(t, 0) -p1_nw_00 = p1_nw.subs(t, 0) -p2_w_00 = p2_w.subs(t, 0) -p2_nw_00 = p2_nw.subs(t, 0) -# p1_w_00 = sym.printing.ccode(p1_w_00) - -initial_condition = { - 1: {'wetting': sym.printing.ccode(p1_w_00), - 'nonwetting': sym.printing.ccode(p1_nw_00)},# - 2: {'wetting': sym.printing.ccode(p2_w_00), - 'nonwetting': sym.printing.ccode(p2_nw_00)} + +# S-pc-relation ship. We use the van Genuchten approach, i.e. pc = 1/alpha*(S^{-1/m} -1)^1/n, where +# we set alpha = 0, assume m = 1-1/n (see Helmig) and assume that residual saturation is Sw +# def saturation(pc, n_index, alpha): +# # inverse capillary pressure-saturation-relationship +# return df.conditional(pc > 0, 1/((1 + (alpha*pc)**n_index)**((n_index - 1)/n_index)), 1) +# +# # S-pc-relation ship. We use the van Genuchten approach, i.e. pc = 1/alpha*(S^{-1/m} -1)^1/n, where +# # we set alpha = 0, assume m = 1-1/n (see Helmig) and assume that residual saturation is Sw +# def saturation_sym(pc, n_index, alpha): +# # inverse capillary pressure-saturation-relationship +# #df.conditional(capillary_pressure > 0, +# return 1/((1 + (alpha*pc)**n_index)**((n_index - 1)/n_index)) +# +# S_pc_rel = {# +# 1: ft.partial(saturation_sym, n_index = 3, alpha=0.001),# n= 3 stands for non-uniform porous media +# 2: ft.partial(saturation_sym, n_index = 6, alpha=0.001) # n=6 stands for uniform porous media matrix (siehe Helmig) +# } + +# S_pc_rel_sym = {# +# 1: ft.partial(saturation_sym, n_index = sym.Symbol('n'), alpha = sym.Symbol('a')),# n= 3 stands for non-uniform porous media +# 2: ft.partial(saturation_sym, n_index = sym.Symbol('n'), alpha = sym.Symbol('a')) # n=6 stands for uniform porous media matrix (siehe Helmig) +# } + + +# # this function needs to be monotonically decreasing in the capillary_pressure. +# # since in the richards case pc=-pw, this becomes as a function of pw a mono +# # tonically INCREASING function like in our Richards-Richards paper. However +# # since we unify the treatment in the code for Richards and two-phase, we need +# # the same requierment +# # for both cases, two-phase and Richards. +# def saturation(pc, index): +# # inverse capillary pressure-saturation-relationship +# return df.conditional(pc > 0, 1/((1 + pc)**(1/(index + 1))), 1) +# +# +# def saturation_sym(pc, index): +# # inverse capillary pressure-saturation-relationship +# return 1/((1 + pc)**(1/(index + 1))) +# +# +# # derivative of S-pc relationship with respect to pc. This is needed for the +# # construction of a analytic solution. +# def saturation_sym_prime(pc, index): +# # inverse capillary pressure-saturation-relationship +# return -1/((index+1)*(1 + pc)**((index+2)/(index+1))) +# +# +# # note that the conditional definition of S-pc in the nonsymbolic part will be +# # incorporated in the construction of the exact solution below. +# S_pc_sym = { +# 1: ft.partial(saturation_sym, index=1), +# 2: ft.partial(saturation_sym, index=2), +# # 3: ft.partial(saturation_sym, index=2), +# # 4: ft.partial(saturation_sym, index=2), +# # 5: ft.partial(saturation_sym, index=1) +# } +# +# S_pc_sym_prime = { +# 1: ft.partial(saturation_sym_prime, index=1), +# 2: ft.partial(saturation_sym_prime, index=2), +# # 3: ft.partial(saturation_sym_prime, index=2), +# # 4: ft.partial(saturation_sym_prime, index=2), +# # 5: ft.partial(saturation_sym_prime, index=1) +# } +# +# sat_pressure_relationship = { +# 1: ft.partial(saturation, index=1), +# 2: ft.partial(saturation, index=2), +# # 3: ft.partial(saturation, index=2), +# # 4: ft.partial(saturation, index=2), +# # 5: ft.partial(saturation, index=1) +# } + +# exact_solution = { +# 1: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0] + x[1]*x[1])'}, +# 2: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0])'}, +# 3: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0])'}, +# 4: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0])'}, +# 5: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0] + x[1]*x[1])'} +# } +# +# initial_condition = { +# 1: {'wetting': '-(x[0]*x[0] + x[1]*x[1])'}, +# 2: {'wetting': '-x[0]*x[0]'}, +# 3: {'wetting': '-x[0]*x[0]'}, +# 4: {'wetting': '-x[0]*x[0]'}, +# 5: {'wetting': '-(x[0]*x[0] + x[1]*x[1])'} +# } + +############################################# +# Manufacture source expressions with sympy # +############################################# +x, y = sym.symbols('x[0], x[1]') # needed by UFL +t = sym.symbols('t', positive=True) + +p_e_sym = { + 1: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + x*x + y*y)}, + 2: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + x*x)}, + # 3: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + x*x)}, + # 4: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + x*x)}, + # 5: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + x*x + y*y)} } -exact_solution = { - 1: {'wetting': sym.printing.ccode(p1_w), - 'nonwetting': sym.printing.ccode(p1_nw)},# - 2: {'wetting': sym.printing.ccode(p2_w), - 'nonwetting': sym.printing.ccode(p2_nw)} +pc_e_sym = { + 1: -1*p_e_sym[1]['wetting'], + 2: -1*p_e_sym[2]['wetting'], + # 3: -1*p_e_sym[3]['wetting'], + # 4: -1*p_e_sym[4]['wetting'], + # 5: -1*p_e_sym[5]['wetting'] } -# similary to the outer boundary dictionary, if a patch has no outer boundary -# None should be written instead of an expression. This is a bit of a brainfuck: +# #### Manufacture source expressions with sympy +# ############################################################################### +# ## subdomain1 +# x, y = sym.symbols('x[0], x[1]') # needed by UFL +# t = sym.symbols('t', positive=True) +# #f = -sym.diff(u, x, 2) - sym.diff(u, y, 2) # -Laplace(u) +# #f = sym.simplify(f) # simplify f +# p1_w = 1 - (1+t**2)*(1 + x**2 + (y-0.5)**2) +# p1_nw = t*(1-(y-0.5) - x**2)**2 - sym.sqrt(2+t**2)*(1-(y-0.5)) +# +# #dtS1_w = sym.diff(S_pc_rel_sym[1](p1_nw - p1_w), t, 1) +# #dtS1_nw = -sym.diff(S_pc_rel_sym[1](p1_nw - p1_w), t, 1) +# dtS1_w = porosity[1]*sym.diff(sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ), t, 1) +# dtS1_nw = -porosity[1]*sym.diff(sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ), t, 1) +# print("dtS1_w = ", dtS1_w, "\n") +# print("dtS1_nw = ", dtS1_nw, "\n") +# +# #dxdxflux1_w = -sym.diff(relative_permeability[1]['wetting'](S_pc_rel_sym[1](p1_nw - p1_w))*sym.diff(p1_w, x, 1), x, 1) +# #dydyflux1_w = -sym.diff(relative_permeability[1]['wetting'](S_pc_rel_sym[1](p1_nw - p1_w))*sym.diff(p1_w, y, 1), y, 1) +# dxdxflux1_w = -1/viscosity[1]['wetting']*sym.diff(relative_permeability[1]['wetting'](sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ))*sym.diff(p1_w, x, 1), x, 1) +# dydyflux1_w = -1/viscosity[1]['wetting']*sym.diff(relative_permeability[1]['wetting'](sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ))*sym.diff(p1_w, y, 1), y, 1) +# +# rhs1_w = dtS1_w + dxdxflux1_w + dydyflux1_w +# rhs1_w = sym.printing.ccode(rhs1_w) +# print("rhs_w = ", rhs1_w, "\n") +# #rhs_w = sym.expand(rhs_w) +# #print("rhs_w", rhs_w, "\n") +# #rhs_w = sym.collect(rhs_w, x) +# #print("rhs_w", rhs_w, "\n") +# +# #dxdxflux1_nw = -sym.diff(relative_permeability[1]['nonwetting'](S_pc_rel_sym[1](p1_nw - p1_w))*sym.diff(p1_nw, x, 1), x, 1) +# #dydyflux1_nw = -sym.diff(relative_permeability[1]['nonwetting'](S_pc_rel_sym[1](p1_nw - p1_w))*sym.diff(p1_nw, y, 1), y, 1) +# dxdxflux1_nw = -1/viscosity[1]['nonwetting']*sym.diff(relative_permeability[1]['nonwetting'](1-sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ))*sym.diff(p1_nw, x, 1), x, 1) +# dydyflux1_nw = -1/viscosity[1]['nonwetting']*sym.diff(relative_permeability[1]['nonwetting'](1-sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ))*sym.diff(p1_nw, y, 1), y, 1) +# +# rhs1_nw = dtS1_nw + dxdxflux1_nw + dydyflux1_nw +# rhs1_nw = sym.printing.ccode(rhs1_nw) +# print("rhs_nw = ", rhs1_nw, "\n") +# +# ## subdomain2 +# p2_w = 1 - (1+t**2)*(1 + x**2) +# p2_nw = t*(1- x**2)**2 - sym.sqrt(2+t**2)*(1-(y-0.5)) +# +# #dtS2_w = sym.diff(S_pc_rel_sym[2](p2_nw - p2_w), t, 1) +# #dtS2_nw = -sym.diff(S_pc_rel_sym[2](p2_nw - p2_w), t, 1) +# dtS2_w = porosity[2]*sym.diff(sym.Piecewise((sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ), (p2_nw - p2_w) > 0), (1, True) ), t, 1) +# dtS2_nw = -porosity[2]*sym.diff(sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ), t, 1) +# print("dtS2_w = ", dtS2_w, "\n") +# print("dtS2_nw = ", dtS2_nw, "\n") +# +# #dxdxflux2_w = -sym.diff(relative_permeability[2]['wetting'](S_pc_rel_sym[2](p2_nw - p2_w))*sym.diff(p2_w, x, 1), x, 1) +# #dydyflux2_w = -sym.diff(relative_permeability[2]['wetting'](S_pc_rel_sym[2](p2_nw - p2_w))*sym.diff(p2_w, y, 1), y, 1) +# dxdxflux2_w = -1/viscosity[2]['wetting']*sym.diff(relative_permeability[2]['wetting'](sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ))*sym.diff(p2_w, x, 1), x, 1) +# dydyflux2_w = -1/viscosity[2]['wetting']*sym.diff(relative_permeability[2]['wetting'](sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ))*sym.diff(p2_w, y, 1), y, 1) +# +# rhs2_w = dtS2_w + dxdxflux2_w + dydyflux2_w +# rhs2_w = sym.printing.ccode(rhs2_w) +# print("rhs2_w = ", rhs2_w, "\n") +# #rhs_w = sym.expand(rhs_w) +# #print("rhs_w", rhs_w, "\n") +# #rhs_w = sym.collect(rhs_w, x) +# #print("rhs_w", rhs_w, "\n") +# +# #dxdxflux2_nw = -sym.diff(relative_permeability[2]['nonwetting'](S_pc_rel_sym[2](p2_nw - p2_w))*sym.diff(p2_nw, x, 1), x, 1) +# #dydyflux2_nw = -sym.diff(relative_permeability[2]['nonwetting'](S_pc_rel_sym[2](p2_nw - p2_w))*sym.diff(p2_nw, y, 1), y, 1) +# dxdxflux2_nw = -1/viscosity[2]['nonwetting']*sym.diff(relative_permeability[2]['nonwetting'](1-sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ))*sym.diff(p2_nw, x, 1), x, 1) +# dydyflux2_nw = -1/viscosity[2]['nonwetting']*sym.diff(relative_permeability[2]['nonwetting'](1-sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ))*sym.diff(p2_nw, y, 1), y, 1) +# +# rhs2_nw = dtS2_nw + dxdxflux2_nw + dydyflux2_nw +# rhs2_nw = sym.printing.ccode(rhs2_nw) +# print("rhs2_nw = ", rhs2_nw, "\n") +# +# +# ############################################################################### +# +# source_expression = { +# 1: {'wetting': rhs1_w, +# 'nonwetting': rhs1_nw}, +# 2: {'wetting': rhs2_w, +# 'nonwetting': rhs2_nw} +# } +# +# p1_w_00 = p1_w.subs(t, 0) +# p1_nw_00 = p1_nw.subs(t, 0) +# p2_w_00 = p2_w.subs(t, 0) +# p2_nw_00 = p2_nw.subs(t, 0) +# # p1_w_00 = sym.printing.ccode(p1_w_00) +# +# initial_condition = { +# 1: {'wetting': sym.printing.ccode(p1_w_00), +# 'nonwetting': sym.printing.ccode(p1_nw_00)},# +# 2: {'wetting': sym.printing.ccode(p2_w_00), +# 'nonwetting': sym.printing.ccode(p2_nw_00)} +# } +# +# exact_solution = { +# 1: {'wetting': sym.printing.ccode(p1_w), +# 'nonwetting': sym.printing.ccode(p1_nw)},# +# 2: {'wetting': sym.printing.ccode(p2_w), +# 'nonwetting': sym.printing.ccode(p2_nw)} +# } +# +# # similary to the outer boundary dictionary, if a patch has no outer boundary +# # None should be written instead of an expression. This is a bit of a brainfuck: +# # dirichletBC[ind] gives a dictionary of the outer boundaries of subdomain ind. +# # Since a domain patch can have several disjoint outer boundary parts, the expressions +# # need to get an enumaration index which starts at 0. So dirichletBC[ind][j] is +# # the dictionary of outer dirichlet conditions of subdomain ind and boundary part j. +# # finally, dirichletBC[ind][j]['wetting'] and dirichletBC[ind][j]['nonwetting'] return +# # the actual expression needed for the dirichlet condition for both phases if present. +# dirichletBC = { +# #subdomain index: {outer boudary part index: {phase: expression}} +# 1: { 0: {'wetting': sym.printing.ccode(p1_w), +# 'nonwetting': sym.printing.ccode(p1_nw)}}, +# 2: { 0: {'wetting': sym.printing.ccode(p2_w), +# 'nonwetting': sym.printing.ccode(p2_nw)}} +# } + +# turn above symbolic code into exact solution for dolphin and +# construct the rhs that matches the above exact solution. +dtS = dict() +div_flux = dict() +source_expression = dict() +exact_solution = dict() +initial_condition = dict() +for subdomain, isR in isRichards.items(): + dtS.update({subdomain: dict()}) + div_flux.update({subdomain: dict()}) + source_expression.update({subdomain: dict()}) + exact_solution.update({subdomain: dict()}) + initial_condition.update({subdomain: dict()}) + if isR: + subdomain_has_phases = ["wetting"] + else: + subdomain_has_phases = ["wetting", "nonwetting"] + + # conditional for S_pc_prime + pc = pc_e_sym[subdomain] + dtpc = sym.diff(pc, t, 1) + dxpc = sym.diff(pc, x, 1) + dypc = sym.diff(pc, y, 1) + 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)) + for phase in subdomain_has_phases: + # Turn above symbolic expression for exact solution into c code + exact_solution[subdomain].update( + {phase: sym.printing.ccode(p_e_sym[subdomain][phase])} + ) + # save the c code for initial conditions + initial_condition[subdomain].update( + {phase: sym.printing.ccode(p_e_sym[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 = p_e_sym[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 = ka_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 + dydyflux = -1/mu*dka(1-S)*dS*dypc*(dypa - rho*g) \ + + 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 + dydyflux = 1/mu*dka(S)*dS*dypc*(dypa - rho*g) + 1/mu*dydypa*ka(S) + div_flux[subdomain].update({phase: dxdxflux + dydyflux}) + contructed_rhs = dtS[subdomain][phase] - div_flux[subdomain][phase] + source_expression[subdomain].update( + {phase: sym.printing.ccode(contructed_rhs)} + ) + # print(f"source_expression[{subdomain}][{phase}] =", source_expression[subdomain][phase]) + +# Dictionary of dirichlet boundary conditions. +dirichletBC = dict() +# similarly to the outer boundary dictionary, if a patch has no outer boundary +# None should be written instead of an expression. +# This is a bit of a brainfuck: # dirichletBC[ind] gives a dictionary of the outer boundaries of subdomain ind. -# Since a domain patch can have several disjoint outer boundary parts, the expressions -# need to get an enumaration index which starts at 0. So dirichletBC[ind][j] is -# the dictionary of outer dirichlet conditions of subdomain ind and boundary part j. -# finally, dirichletBC[ind][j]['wetting'] and dirichletBC[ind][j]['nonwetting'] return -# the actual expression needed for the dirichlet condition for both phases if present. -dirichletBC = { -#subdomain index: {outer boudary part index: {phase: expression}} - 1: { 0: {'wetting': sym.printing.ccode(p1_w), - 'nonwetting': sym.printing.ccode(p1_nw)}}, - 2: { 0: {'wetting': sym.printing.ccode(p2_w), - 'nonwetting': sym.printing.ccode(p2_nw)}} -} +# Since a domain patch can have several disjoint outer boundary parts, the +# expressions need to get an enumaration index which starts at 0. +# So dirichletBC[ind][j] is the dictionary of outer dirichlet conditions of +# subdomain ind and boundary part j. +# Finally, dirichletBC[ind][j]['wetting'] and dirichletBC[ind][j]['nonwetting'] +# return the actual expression needed for the dirichlet condition for both +# phases if present. + +# subdomain index: {outer boudary part index: {phase: expression}} +for subdomain in isRichards.keys(): + # if subdomain has no outer boundary, outer_boundary_def_points[subdomain] is None + if outer_boundary_def_points[subdomain] is None: + dirichletBC.update({subdomain: None}) + else: + dirichletBC.update({subdomain: dict()}) + # set the dirichlet conditions to be the same code as exact solution on + # the subdomain. + for outer_boundary_ind in outer_boundary_def_points[subdomain].keys(): + dirichletBC[subdomain].update( + {outer_boundary_ind: exact_solution[subdomain]} + ) + # def saturation(pressure, subdomain_index): # # inverse capillary pressure-saturation-relationship diff --git a/TP-two-patch-constant-solution/TP-TP-2-patch-constant-solution.py b/TP-two-patch-constant-solution/TP-TP-2-patch-constant-solution.py new file mode 100755 index 0000000000000000000000000000000000000000..96a0541f644f5463ef53eae7b11fc2f1c89c7937 --- /dev/null +++ b/TP-two-patch-constant-solution/TP-TP-2-patch-constant-solution.py @@ -0,0 +1,652 @@ +#!/usr/bin/python3 +import dolfin as df +import mshr +import numpy as np +import sympy as sym +import typing as tp +import domainPatch as dp +import LDDsimulation as ldd +import functools as ft +#import ufl as ufl + +# init sympy session +sym.init_printing() + +##### Domain and Interface #### +# global simulation domain domain +sub_domain0_vertices = [df.Point(0.0,0.0), # + df.Point(1.0,0.0),# + df.Point(1.0,1.0),# + df.Point(0.0,1.0)] +# interface between subdomain1 and subdomain2 +interface12_vertices = [df.Point(0.0, 0.5), + df.Point(1.0, 0.5) ] +# subdomain1. +sub_domain1_vertices = [interface12_vertices[0], + interface12_vertices[1], + df.Point(1.0,1.0), + df.Point(0.0,1.0) ] + +# vertex coordinates of the outer boundaries. If it can not be specified as a +# polygon, use an entry per boundary polygon. This information is used for defining +# the Dirichlet boundary conditions. If a domain is completely internal, the +# dictionary entry should be 0: None +subdomain1_outer_boundary_verts = { + 0: [interface12_vertices[0], # + df.Point(0.0,1.0), # + df.Point(1.0,1.0), # + interface12_vertices[1]] +} +# subdomain2 +sub_domain2_vertices = [df.Point(0.0,0.0), + df.Point(1.0,0.0), + interface12_vertices[1], + interface12_vertices[0] ] + +subdomain2_outer_boundary_verts = { + 0: [interface12_vertices[1], # + df.Point(1.0,0.0), # + df.Point(0.0,0.0), # + interface12_vertices[0]] +} +# subdomain2_outer_boundary_verts = { +# 0: [interface12_vertices[0], df.Point(0.0,0.0)],# +# 1: [df.Point(0.0,0.0), df.Point(1.0,0.0)], # +# 2: [df.Point(1.0,0.0), interface12_vertices[1]] +# } +# subdomain2_outer_boundary_verts = { +# 0: None +# } + +# list of subdomains given by the boundary polygon vertices. +# Subdomains are given as a list of dolfin points forming +# a closed polygon, such that mshr.Polygon(subdomain_def_points[i]) can be used +# to create the subdomain. subdomain_def_points[0] contains the +# vertices of the global simulation domain and subdomain_def_points[i] contains the +# vertices of the subdomain i. +subdomain_def_points = [sub_domain0_vertices,# + sub_domain1_vertices,# + sub_domain2_vertices] +# in the below list, index 0 corresponds to the 12 interface which has index 1 +interface_def_points = [interface12_vertices] + +# if a subdomain has no outer boundary write None instead, i.e. +# i: None +# if i is the index of the inner subdomain. +outer_boundary_def_points = { + # subdomain number + 1 : subdomain1_outer_boundary_verts, + 2 : subdomain2_outer_boundary_verts +} + +# adjacent_subdomains[i] contains the indices of the subdomains sharing the +# interface i (i.e. given by interface_def_points[i]). +adjacent_subdomains = [[1,2]] +isRichards = { + 1: False, # + 2: False + } + + +############ GRID #######################ΓΌ +mesh_resolution = 40 +timestep_size = 0.01 +number_of_timesteps = 100 +# decide how many timesteps you want analysed. Analysed means, that we write out +# subsequent errors of the L-iteration within the timestep. +number_of_timesteps_to_analyse = 11 +starttime = 0 + +viscosity = {# +# subdom_num : viscosity + 1 : {'wetting' :1, + 'nonwetting': 1}, # + 2 : {'wetting' :1, + 'nonwetting': 1} +} + +densities = { + 1: {'wetting': 1, + 'nonwetting': 1}, + 2: {'wetting': 1, + 'nonwetting': 1}, + # 3: {'wetting': 1}, + # 4: {'wetting': 1} +} + +gravity_acceleration = 9.81 + +porosity = {# +# subdom_num : porosity + 1 : 1,# + 2 : 1 +} + +L = {# +# subdom_num : subdomain L for L-scheme + 1 : {'wetting' :0.25, + 'nonwetting': 0.25},# + 2 : {'wetting' :0.25, + 'nonwetting': 0.25} +} + +l_param = 30 +lambda_param = {# +# subdom_num : lambda parameter for the L-scheme + 1 : {'wetting' :l_param, + 'nonwetting': l_param},# + 2 : {'wetting' :l_param, + 'nonwetting': l_param} +} + +## relative permeabilty functions on subdomain 1 +def rel_perm1w(s): + # relative permeabilty wetting on subdomain1 + return s**2 + +def rel_perm1nw(s): + # relative permeabilty nonwetting on subdomain1 + return (1-s)**2 + +_rel_perm1w = ft.partial(rel_perm1w) +_rel_perm1nw = ft.partial(rel_perm1nw) +subdomain1_rel_perm = { + 'wetting': _rel_perm1w,# + 'nonwetting': _rel_perm1nw +} +## relative permeabilty functions on subdomain 2 +def rel_perm2w(s): + # relative permeabilty wetting on subdomain2 + return s**2 +def rel_perm2nw(s): + # relative permeabilty nonwetting on subdosym.cos(0.8*t - (0.8*x + 1/7*y))main2 + return (1-s)**2 + +_rel_perm2w = ft.partial(rel_perm2w) +_rel_perm2nw = ft.partial(rel_perm2nw) + +subdomain2_rel_perm = { + 'wetting': _rel_perm2w,# + 'nonwetting': _rel_perm2nw +} + +## dictionary of relative permeabilties on all domains. +relative_permeability = {# + 1: subdomain1_rel_perm, + 2: subdomain2_rel_perm +} + + +# definition of the derivatives of the relative permeabilities +# relative permeabilty functions on subdomain 1 +def rel_perm1w_prime(s): + # relative permeabilty on subdomain1 + return 2*s + +def rel_perm1nw_prime(s): + # relative permeabilty on subdomain1 + return 2*(1-s) + +# # definition of the derivatives of the relative permeabilities +# # relative permeabilty functions on subdomain 1 +# def rel_perm2w_prime(s): +# # relative permeabilty on subdomain1 +# return 3*s**2 +# +# def rel_perm2nw_prime(s): +# # relative permeabilty on subdomain1 +# return 2*(l_param_w1-s) + +_rel_perm1w_prime = ft.partial(rel_perm1w_prime) +_rel_perm1nw_prime = ft.partial(rel_perm1nw_prime) +# _rel_perm2w_prime = ft.partial(rel_perm2w_prime) +# _rel_perm2nw_prime = ft.partial(rel_perm2nw_prime) + +subdomain1_rel_perm_prime = { + 'wetting': _rel_perm1w_prime, + 'nonwetting': _rel_perm1nw_prime +} + + +# subdomain2_rel_perm_prime = { +# 'wetting': _rel_perm2w_prime, +# 'nonwetting': _rel_perm2nw_prime +# } + +# dictionary of relative permeabilties on all domains. +ka_prime = { + 1: subdomain1_rel_perm_prime, + 2: subdomain1_rel_perm_prime, +} + +def saturation(pc, n_index, alpha): + # inverse capillary pressure-saturation-relationship + return df.conditional(pc > 0, 1/((1 + (alpha*pc)**n_index)**((n_index - 1)/n_index)), 1) + +# S-pc-relation ship. We use the van Genuchten approach, i.e. pc = 1/alpha*(S^{-1/m} -1)^1/n, where +# we set alpha = 0, assume m = 1-1/n (see Helmig) and assume that residual saturation is Sw +def saturation_sym(pc, n_index, alpha): + # inverse capillary pressure-saturation-relationship + #df.conditional(pc > 0, + return 1/((1 + (alpha*pc)**n_index)**((n_index - 1)/n_index)) + + +# derivative of S-pc relationship with respect to pc. This is needed for the +# construction of a analytic solution. +def saturation_sym_prime(pc, n_index, alpha): + # inverse capillary pressure-saturation-relationship + return -(alpha*(n_index - 1)*(alpha*pc)**(n_index - 1)) / ( (1 + (alpha*pc)**n_index)**((2*n_index - 1)/n_index) ) + +# note that the conditional definition of S-pc in the nonsymbolic part will be +# incorporated in the construction of the exact solution below. +S_pc_sym = { + 1: ft.partial(saturation_sym, n_index=3, alpha=0.001), + 2: ft.partial(saturation_sym, n_index=3, alpha=0.001), + # 3: ft.partial(saturation_sym, n_index=3, alpha=0.001), + # 4: ft.partial(saturation_sym, n_index=3, alpha=0.001), + # 5: ft.partial(saturation_sym, n_index=3, alpha=0.001), + # 6: ft.partial(saturation_sym, n_index=3, alpha=0.001) +} + +S_pc_sym_prime = { + 1: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), + 2: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), + # 3: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), + # 4: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), + # 5: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001), + # 6: ft.partial(saturation_sym_prime, n_index=3, alpha=0.001) +} + +sat_pressure_relationship = { + 1: ft.partial(saturation, n_index=3, alpha=0.001), + 2: ft.partial(saturation, n_index=3, alpha=0.001), + # 3: ft.partial(saturation, n_index=3, alpha=0.001), + # 4: ft.partial(saturation, n_index=3, alpha=0.001), + # 5: ft.partial(saturation, n_index=3, alpha=0.001), + # 6: ft.partial(saturation, n_index=3, alpha=0.001) +} + + +# S-pc-relation ship. We use the van Genuchten approach, i.e. pc = 1/alpha*(S^{-1/m} -1)^1/n, where +# we set alpha = 0, assume m = 1-1/n (see Helmig) and assume that residual saturation is Sw +# def saturation(pc, n_index, alpha): +# # inverse capillary pressure-saturation-relationship +# return df.conditional(pc > 0, 1/((1 + (alpha*pc)**n_index)**((n_index - 1)/n_index)), 1) +# +# # S-pc-relation ship. We use the van Genuchten approach, i.e. pc = 1/alpha*(S^{-1/m} -1)^1/n, where +# # we set alpha = 0, assume m = 1-1/n (see Helmig) and assume that residual saturation is Sw +# def saturation_sym(pc, n_index, alpha): +# # inverse capillary pressure-saturation-relationship +# #df.conditional(capillary_pressure > 0, +# return 1/((1 + (alpha*pc)**n_index)**((n_index - 1)/n_index)) +# +# S_pc_rel = {# +# 1: ft.partial(saturation_sym, n_index = 3, alpha=0.001),# n= 3 stands for non-uniform porous media +# 2: ft.partial(saturation_sym, n_index = 6, alpha=0.001) # n=6 stands for uniform porous media matrix (siehe Helmig) +# } + +# S_pc_rel_sym = {# +# 1: ft.partial(saturation_sym, n_index = sym.Symbol('n'), alpha = sym.Symbol('a')),# n= 3 stands for non-uniform porous media +# 2: ft.partial(saturation_sym, n_index = sym.Symbol('n'), alpha = sym.Symbol('a')) # n=6 stands for uniform porous media matrix (siehe Helmig) +# } + + +# # this function needs to be monotonically decreasing in the capillary_pressure. +# # since in the richards case pc=-pw, this becomes as a function of pw a mono +# # tonically INCREASING function like in our Richards-Richards paper. However +# # since we unify the treatment in the code for Richards and two-phase, we need +# # the same requierment +# # for both cases, two-phase and Richards. +# def saturation(pc, index): +# # inverse capillary pressure-saturation-relationship +# return df.conditional(pc > 0, 1/((1 + pc)**(1/(index + 1))), 1) +# +# +# def saturation_sym(pc, index): +# # inverse capillary pressure-saturation-relationship +# return 1/((1 + pc)**(1/(index + 1))) +# +# +# # derivative of S-pc relationship with respect to pc. This is needed for the +# # construction of a analytic solution. +# def saturation_sym_prime(pc, index): +# # inverse capillary pressure-saturation-relationship +# return -1/((index+1)*(1 + pc)**((index+2)/(index+1))) +# +# +# # note that the conditional definition of S-pc in the nonsymbolic part will be +# # incorporated in the construction of the exact solution below. +# S_pc_sym = { +# 1: ft.partial(saturation_sym, index=1), +# 2: ft.partial(saturation_sym, index=2), +# # 3: ft.partial(saturation_sym, index=2), +# # 4: ft.partial(saturation_sym, index=2), +# # 5: ft.partial(saturation_sym, index=1) +# } +# +# S_pc_sym_prime = { +# 1: ft.partial(saturation_sym_prime, index=1), +# 2: ft.partial(saturation_sym_prime, index=2), +# # 3: ft.partial(saturation_sym_prime, index=2), +# # 4: ft.partial(saturation_sym_prime, index=2), +# # 5: ft.partial(saturation_sym_prime, index=1) +# } +# +# sat_pressure_relationship = { +# 1: ft.partial(saturation, index=1), +# 2: ft.partial(saturation, index=2), +# # 3: ft.partial(saturation, index=2), +# # 4: ft.partial(saturation, index=2), +# # 5: ft.partial(saturation, index=1) +# } + +# exact_solution = { +# 1: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0] + x[1]*x[1])'}, +# 2: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0])'}, +# 3: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0])'}, +# 4: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0])'}, +# 5: {'wetting': '1.0 - (1.0 + t*t)*(1.0 + x[0]*x[0] + x[1]*x[1])'} +# } +# +# initial_condition = { +# 1: {'wetting': '-(x[0]*x[0] + x[1]*x[1])'}, +# 2: {'wetting': '-x[0]*x[0]'}, +# 3: {'wetting': '-x[0]*x[0]'}, +# 4: {'wetting': '-x[0]*x[0]'}, +# 5: {'wetting': '-(x[0]*x[0] + x[1]*x[1])'} +# } + +############################################# +# Manufacture source expressions with sympy # +############################################# +x, y = sym.symbols('x[0], x[1]') # needed by UFL +t = sym.symbols('t', positive=True) + +p_e_sym = { + 1: {'wetting': -3 + 0*t, + 'nonwetting': -1+ 0*t}, + 2: {'wetting': -3+ 0*t, + 'nonwetting': -1+ 0*t}, + # 3: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + x*x)}, + # 4: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + x*x)}, + # 5: {'wetting': 1.0 - (1.0 + t*t)*(1.0 + x*x + y*y)} +} + +# pc_e_sym = { +# 1: -1*p_e_sym[1]['wetting'], +# 2: -1*p_e_sym[2]['wetting'], +# # 3: -1*p_e_sym[3]['wetting'], +# # 4: -1*p_e_sym[4]['wetting'], +# # 5: -1*p_e_sym[5]['wetting'] +# } + +pc_e_sym = { + 1: p_e_sym[1]['nonwetting'] - p_e_sym[1]['wetting'], + 2: p_e_sym[2]['nonwetting'] - p_e_sym[2]['wetting'], + # 3: -1*p_e_sym[3]['wetting'], + # 4: -1*p_e_sym[4]['wetting'], + # 5: -1*p_e_sym[5]['wetting'] +} + + +# #### Manufacture source expressions with sympy +# ############################################################################### +# ## subdomain1 +# x, y = sym.symbols('x[0], x[1]') # needed by UFL +# t = sym.symbols('t', positive=True) +# #f = -sym.diff(u, x, 2) - sym.diff(u, y, 2) # -Laplace(u) +# #f = sym.simplify(f) # simplify f +# p1_w = 1 - (1+t**2)*(1 + x**2 + (y-0.5)**2) +# p1_nw = t*(1-(y-0.5) - x**2)**2 - sym.sqrt(2+t**2)*(1-(y-0.5)) +# +# #dtS1_w = sym.diff(S_pc_rel_sym[1](p1_nw - p1_w), t, 1) +# #dtS1_nw = -sym.diff(S_pc_rel_sym[1](p1_nw - p1_w), t, 1) +# dtS1_w = porosity[1]*sym.diff(sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ), t, 1) +# dtS1_nw = -porosity[1]*sym.diff(sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ), t, 1) +# print("dtS1_w = ", dtS1_w, "\n") +# print("dtS1_nw = ", dtS1_nw, "\n") +# +# #dxdxflux1_w = -sym.diff(relative_permeability[1]['wetting'](S_pc_rel_sym[1](p1_nw - p1_w))*sym.diff(p1_w, x, 1), x, 1) +# #dydyflux1_w = -sym.diff(relative_permeability[1]['wetting'](S_pc_rel_sym[1](p1_nw - p1_w))*sym.diff(p1_w, y, 1), y, 1) +# dxdxflux1_w = -1/viscosity[1]['wetting']*sym.diff(relative_permeability[1]['wetting'](sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ))*sym.diff(p1_w, x, 1), x, 1) +# dydyflux1_w = -1/viscosity[1]['wetting']*sym.diff(relative_permeability[1]['wetting'](sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ))*sym.diff(p1_w, y, 1), y, 1) +# +# rhs1_w = dtS1_w + dxdxflux1_w + dydyflux1_w +# rhs1_w = sym.printing.ccode(rhs1_w) +# print("rhs_w = ", rhs1_w, "\n") +# #rhs_w = sym.expand(rhs_w) +# #print("rhs_w", rhs_w, "\n") +# #rhs_w = sym.collect(rhs_w, x) +# #print("rhs_w", rhs_w, "\n") +# +# #dxdxflux1_nw = -sym.diff(relative_permeability[1]['nonwetting'](S_pc_rel_sym[1](p1_nw - p1_w))*sym.diff(p1_nw, x, 1), x, 1) +# #dydyflux1_nw = -sym.diff(relative_permeability[1]['nonwetting'](S_pc_rel_sym[1](p1_nw - p1_w))*sym.diff(p1_nw, y, 1), y, 1) +# dxdxflux1_nw = -1/viscosity[1]['nonwetting']*sym.diff(relative_permeability[1]['nonwetting'](1-sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ))*sym.diff(p1_nw, x, 1), x, 1) +# dydyflux1_nw = -1/viscosity[1]['nonwetting']*sym.diff(relative_permeability[1]['nonwetting'](1-sym.Piecewise((S_pc_rel[1](p1_nw - p1_w), (p1_nw - p1_w) > 0), (1, True) ))*sym.diff(p1_nw, y, 1), y, 1) +# +# rhs1_nw = dtS1_nw + dxdxflux1_nw + dydyflux1_nw +# rhs1_nw = sym.printing.ccode(rhs1_nw) +# print("rhs_nw = ", rhs1_nw, "\n") +# +# ## subdomain2 +# p2_w = 1 - (1+t**2)*(1 + x**2) +# p2_nw = t*(1- x**2)**2 - sym.sqrt(2+t**2)*(1-(y-0.5)) +# +# #dtS2_w = sym.diff(S_pc_rel_sym[2](p2_nw - p2_w), t, 1) +# #dtS2_nw = -sym.diff(S_pc_rel_sym[2](p2_nw - p2_w), t, 1) +# dtS2_w = porosity[2]*sym.diff(sym.Piecewise((sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ), (p2_nw - p2_w) > 0), (1, True) ), t, 1) +# dtS2_nw = -porosity[2]*sym.diff(sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ), t, 1) +# print("dtS2_w = ", dtS2_w, "\n") +# print("dtS2_nw = ", dtS2_nw, "\n") +# +# #dxdxflux2_w = -sym.diff(relative_permeability[2]['wetting'](S_pc_rel_sym[2](p2_nw - p2_w))*sym.diff(p2_w, x, 1), x, 1) +# #dydyflux2_w = -sym.diff(relative_permeability[2]['wetting'](S_pc_rel_sym[2](p2_nw - p2_w))*sym.diff(p2_w, y, 1), y, 1) +# dxdxflux2_w = -1/viscosity[2]['wetting']*sym.diff(relative_permeability[2]['wetting'](sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ))*sym.diff(p2_w, x, 1), x, 1) +# dydyflux2_w = -1/viscosity[2]['wetting']*sym.diff(relative_permeability[2]['wetting'](sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ))*sym.diff(p2_w, y, 1), y, 1) +# +# rhs2_w = dtS2_w + dxdxflux2_w + dydyflux2_w +# rhs2_w = sym.printing.ccode(rhs2_w) +# print("rhs2_w = ", rhs2_w, "\n") +# #rhs_w = sym.expand(rhs_w) +# #print("rhs_w", rhs_w, "\n") +# #rhs_w = sym.collect(rhs_w, x) +# #print("rhs_w", rhs_w, "\n") +# +# #dxdxflux2_nw = -sym.diff(relative_permeability[2]['nonwetting'](S_pc_rel_sym[2](p2_nw - p2_w))*sym.diff(p2_nw, x, 1), x, 1) +# #dydyflux2_nw = -sym.diff(relative_permeability[2]['nonwetting'](S_pc_rel_sym[2](p2_nw - p2_w))*sym.diff(p2_nw, y, 1), y, 1) +# dxdxflux2_nw = -1/viscosity[2]['nonwetting']*sym.diff(relative_permeability[2]['nonwetting'](1-sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ))*sym.diff(p2_nw, x, 1), x, 1) +# dydyflux2_nw = -1/viscosity[2]['nonwetting']*sym.diff(relative_permeability[2]['nonwetting'](1-sym.Piecewise((S_pc_rel[2](p2_nw - p2_w), (p2_nw - p2_w) > 0), (1, True) ))*sym.diff(p2_nw, y, 1), y, 1) +# +# rhs2_nw = dtS2_nw + dxdxflux2_nw + dydyflux2_nw +# rhs2_nw = sym.printing.ccode(rhs2_nw) +# print("rhs2_nw = ", rhs2_nw, "\n") +# +# +# ############################################################################### +# +# source_expression = { +# 1: {'wetting': rhs1_w, +# 'nonwetting': rhs1_nw}, +# 2: {'wetting': rhs2_w, +# 'nonwetting': rhs2_nw} +# } +# +# p1_w_00 = p1_w.subs(t, 0) +# p1_nw_00 = p1_nw.subs(t, 0) +# p2_w_00 = p2_w.subs(t, 0) +# p2_nw_00 = p2_nw.subs(t, 0) +# # p1_w_00 = sym.printing.ccode(p1_w_00) +# +# initial_condition = { +# 1: {'wetting': sym.printing.ccode(p1_w_00), +# 'nonwetting': sym.printing.ccode(p1_nw_00)},# +# 2: {'wetting': sym.printing.ccode(p2_w_00), +# 'nonwetting': sym.printing.ccode(p2_nw_00)} +# } +# +# exact_solution = { +# 1: {'wetting': sym.printing.ccode(p1_w), +# 'nonwetting': sym.printing.ccode(p1_nw)},# +# 2: {'wetting': sym.printing.ccode(p2_w), +# 'nonwetting': sym.printing.ccode(p2_nw)} +# } +# +# # similary to the outer boundary dictionary, if a patch has no outer boundary +# # None should be written instead of an expression. This is a bit of a brainfuck: +# # dirichletBC[ind] gives a dictionary of the outer boundaries of subdomain ind. +# # Since a domain patch can have several disjoint outer boundary parts, the expressions +# # need to get an enumaration index which starts at 0. So dirichletBC[ind][j] is +# # the dictionary of outer dirichlet conditions of subdomain ind and boundary part j. +# # finally, dirichletBC[ind][j]['wetting'] and dirichletBC[ind][j]['nonwetting'] return +# # the actual expression needed for the dirichlet condition for both phases if present. +# dirichletBC = { +# #subdomain index: {outer boudary part index: {phase: expression}} +# 1: { 0: {'wetting': sym.printing.ccode(p1_w), +# 'nonwetting': sym.printing.ccode(p1_nw)}}, +# 2: { 0: {'wetting': sym.printing.ccode(p2_w), +# 'nonwetting': sym.printing.ccode(p2_nw)}} +# } + +# turn above symbolic code into exact solution for dolphin and +# construct the rhs that matches the above exact solution. +dtS = dict() +div_flux = dict() +source_expression = dict() +exact_solution = dict() +initial_condition = dict() +for subdomain, isR in isRichards.items(): + dtS.update({subdomain: dict()}) + div_flux.update({subdomain: dict()}) + source_expression.update({subdomain: dict()}) + exact_solution.update({subdomain: dict()}) + initial_condition.update({subdomain: dict()}) + if isR: + subdomain_has_phases = ["wetting"] + else: + subdomain_has_phases = ["wetting", "nonwetting"] + + # conditional for S_pc_prime + pc = pc_e_sym[subdomain] + dtpc = sym.diff(pc, t, 1) + dxpc = sym.diff(pc, x, 1) + dypc = sym.diff(pc, y, 1) + 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)) + for phase in subdomain_has_phases: + # Turn above symbolic expression for exact solution into c code + exact_solution[subdomain].update( + {phase: sym.printing.ccode(p_e_sym[subdomain][phase])} + ) + # save the c code for initial conditions + initial_condition[subdomain].update( + {phase: sym.printing.ccode(p_e_sym[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 = p_e_sym[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 = ka_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 + dydyflux = -1/mu*dka(1-S)*dS*dypc*(dypa - rho*g) \ + + 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 + dydyflux = 1/mu*dka(S)*dS*dypc*(dypa - rho*g) + 1/mu*dydypa*ka(S) + div_flux[subdomain].update({phase: dxdxflux + dydyflux}) + contructed_rhs = dtS[subdomain][phase] - div_flux[subdomain][phase] + source_expression[subdomain].update( + {phase: sym.printing.ccode(contructed_rhs)} + ) + # print(f"source_expression[{subdomain}][{phase}] =", source_expression[subdomain][phase]) + +# Dictionary of dirichlet boundary conditions. +dirichletBC = dict() +# similarly to the outer boundary dictionary, if a patch has no outer boundary +# None should be written instead of an expression. +# This is a bit of a brainfuck: +# dirichletBC[ind] gives a dictionary of the outer boundaries of subdomain ind. +# Since a domain patch can have several disjoint outer boundary parts, the +# expressions need to get an enumaration index which starts at 0. +# So dirichletBC[ind][j] is the dictionary of outer dirichlet conditions of +# subdomain ind and boundary part j. +# Finally, dirichletBC[ind][j]['wetting'] and dirichletBC[ind][j]['nonwetting'] +# return the actual expression needed for the dirichlet condition for both +# phases if present. + +# subdomain index: {outer boudary part index: {phase: expression}} +for subdomain in isRichards.keys(): + # if subdomain has no outer boundary, outer_boundary_def_points[subdomain] is None + if outer_boundary_def_points[subdomain] is None: + dirichletBC.update({subdomain: None}) + else: + dirichletBC.update({subdomain: dict()}) + # set the dirichlet conditions to be the same code as exact solution on + # the subdomain. + for outer_boundary_ind in outer_boundary_def_points[subdomain].keys(): + dirichletBC[subdomain].update( + {outer_boundary_ind: exact_solution[subdomain]} + ) + + +# def saturation(pressure, subdomain_index): +# # inverse capillary pressure-saturation-relationship +# return df.conditional(pressure < 0, 1/((1 - pressure)**(1/(subdomain_index + 1))), 1) +# +# sa + +write_to_file = { + 'meshes_and_markers': True, + 'L_iterations': True +} + + +# initialise LDD simulation class +simulation = ldd.LDDsimulation(tol = 1E-14, LDDsolver_tol = 1E-9, debug = False) +simulation.set_parameters(output_dir = "./output/",# + subdomain_def_points = subdomain_def_points,# + isRichards = isRichards,# + interface_def_points = interface_def_points,# + outer_boundary_def_points = outer_boundary_def_points,# + adjacent_subdomains = adjacent_subdomains,# + mesh_resolution = mesh_resolution,# + viscosity = viscosity,# + porosity = porosity,# + L = L,# + lambda_param = lambda_param,# + relative_permeability = relative_permeability,# + saturation = sat_pressure_relationship,# + starttime = starttime,# + number_of_timesteps = number_of_timesteps, + number_of_timesteps_to_analyse = number_of_timesteps_to_analyse, + timestep_size = timestep_size,# + sources = source_expression,# + initial_conditions = initial_condition,# + dirichletBC_expression_strings = dirichletBC,# + exact_solution = exact_solution,# + densities=densities, + include_gravity=True, + write2file = write_to_file,# + ) + +simulation.initialise() +# simulation.write_exact_solution_to_xdmf() +simulation.run()